Skip to content

Commit

Permalink
Fix cubeSize + padding for non-mag1 ad-hoc meshes (#7799)
Browse files Browse the repository at this point in the history
* Fix cubeSize + padding for non-mag1 ad-hoc meshes

* changelog

* reduce cubeSize to 64x64x64 in target-mag

* extract variables
  • Loading branch information
fm3 authored May 14, 2024
1 parent d08a865 commit 3ab7382
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
### Fixed
- Fixed a bug where a toast that was reopened had a flickering effect during the reopening animation. [#7793](https://github.com/scalableminds/webknossos/pull/7793)
- Fixed a bug where some annotation times would be shown double. [#7787](https://github.com/scalableminds/webknossos/pull/7787)
- Fixed a bug where ad-hoc meshes for coarse magnifications would have gaps. [#7799](https://github.com/scalableminds/webknossos/pull/7799)

### Removed

Expand Down
6 changes: 4 additions & 2 deletions frontend/javascripts/admin/admin_rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2302,6 +2302,7 @@ export function computeAdHocMesh(
additionalCoordinates,
cubeSize,
mappingName,
mag,

...rest
} = meshRequest;
Expand All @@ -2317,11 +2318,12 @@ export function computeAdHocMesh(
// The back-end needs a small padding at the border of the
// bounding box to calculate the mesh. This padding
// is added here to the position and bbox size.
position: V3.toArray(V3.sub(position, [1, 1, 1])),
position: V3.toArray(V3.sub(position, mag)), // position is in mag1
additionalCoordinates,
cubeSize: V3.toArray(V3.add(cubeSize, [1, 1, 1])),
cubeSize: V3.toArray(V3.add(cubeSize, [1, 1, 1])), //cubeSize is in target mag
// Name and type of mapping to apply before building mesh (optional)
mapping: mappingName,
mag,
...rest,
},
},
Expand Down
71 changes: 42 additions & 29 deletions frontend/javascripts/oxalis/model/sagas/mesh_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ const MESH_CHUNK_THROTTLE_LIMIT = 50;
// Maps from additional coordinates, layerName and segmentId to a ThreeDMap that stores for each chunk
// (at x, y, z) position whether the mesh chunk was loaded.
const adhocMeshesMapByLayer: Record<string, Record<string, Map<number, ThreeDMap<boolean>>>> = {};
function marchingCubeSizeInMag1(): Vector3 {
return (window as any).__marchingCubeSizeInMag1 != null
? (window as any).__marchingCubeSizeInMag1
: [128, 128, 128];
function marchingCubeSizeInTargetMag(): Vector3 {
return (window as any).__marchingCubeSizeInTargetMag != null
? (window as any).__marchingCubeSizeInTargetMag
: [64, 64, 64];
}
const modifiedCells: Set<number> = new Set();
export function isMeshSTL(buffer: ArrayBuffer): boolean {
Expand Down Expand Up @@ -155,20 +155,25 @@ function removeMapForSegment(
adhocMeshesMapByLayer[additionalCoordinateKey][layerName].delete(segmentId);
}

function getZoomedCubeSize(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 {
// Convert marchingCubeSizeInMag1 to another resolution (zoomStep)
function getCubeSizeInMag1(zoomStep: number, resolutionInfo: ResolutionInfo): Vector3 {
// Convert marchingCubeSizeInTargetMag to mag1 via zoomStep
// Drop the last element of the Vector4;
const [x, y, z] = zoomedAddressToAnotherZoomStepWithInfo(
[...marchingCubeSizeInMag1(), 0],
[...marchingCubeSizeInTargetMag(), zoomStep],
resolutionInfo,
zoomStep,
0,
);
// Drop the last element of the Vector4;
return [x, y, z];
}

function clipPositionToCubeBoundary(position: Vector3): Vector3 {
const currentCube = V3.floor(V3.divide3(position, marchingCubeSizeInMag1()));
const clippedPosition = V3.scale3(currentCube, marchingCubeSizeInMag1());
function clipPositionToCubeBoundary(
position: Vector3,
zoomStep: number,
resolutionInfo: ResolutionInfo,
): Vector3 {
const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo);
const currentCube = V3.floor(V3.divide3(position, cubeSizeInMag1));
const clippedPosition = V3.scale3(currentCube, cubeSizeInMag1);
return clippedPosition;
}

Expand All @@ -182,12 +187,18 @@ const NEIGHBOR_LOOKUP = [
[1, 0, 0],
];

function getNeighborPosition(clippedPosition: Vector3, neighborId: number): Vector3 {
function getNeighborPosition(
clippedPosition: Vector3,
neighborId: number,
zoomStep: number,
resolutionInfo: ResolutionInfo,
): Vector3 {
const neighborMultiplier = NEIGHBOR_LOOKUP[neighborId];
const cubeSizeInMag1 = getCubeSizeInMag1(zoomStep, resolutionInfo);
const neighboringPosition: Vector3 = [
clippedPosition[0] + neighborMultiplier[0] * marchingCubeSizeInMag1()[0],
clippedPosition[1] + neighborMultiplier[1] * marchingCubeSizeInMag1()[1],
clippedPosition[2] + neighborMultiplier[2] * marchingCubeSizeInMag1()[2],
clippedPosition[0] + neighborMultiplier[0] * cubeSizeInMag1[0],
clippedPosition[1] + neighborMultiplier[1] * cubeSizeInMag1[1],
clippedPosition[2] + neighborMultiplier[2] * cubeSizeInMag1[2],
];
return neighboringPosition;
}
Expand Down Expand Up @@ -316,7 +327,7 @@ function* loadFullAdHocMesh(
): Saga<void> {
let isInitialRequest = true;
const { mappingName, mappingType } = meshExtraInfo;
const clippedPosition = clipPositionToCubeBoundary(position);
const clippedPosition = clipPositionToCubeBoundary(position, zoomStep, resolutionInfo);
yield* put(
addAdHocMeshAction(
layer.name,
Expand All @@ -329,7 +340,7 @@ function* loadFullAdHocMesh(
);
yield* put(startedLoadingMeshAction(layer.name, segmentId));

const cubeSize = getZoomedCubeSize(zoomStep, resolutionInfo);
const cubeSize = marchingCubeSizeInTargetMag();
const tracingStoreHost = yield* select((state) => state.tracing.tracingStore.url);
const mag = resolutionInfo.getResolutionByIndexOrThrow(zoomStep);

Expand All @@ -347,12 +358,12 @@ function* loadFullAdHocMesh(

// Segment stats can only be used for volume tracings that have a segment index
// and that don't have editable mappings.
const usePositionsFromSegmentStats =
const usePositionsFromSegmentIndex =
volumeTracing?.hasSegmentIndex &&
!volumeTracing.mappingIsEditable &&
visibleSegmentationLayer?.tracingId != null;
let positionsToRequest = usePositionsFromSegmentStats
? yield* getChunkPositionsFromSegmentStats(
let positionsToRequest = usePositionsFromSegmentIndex
? yield* getChunkPositionsFromSegmentIndex(
tracingStoreHost,
layer,
segmentId,
Expand Down Expand Up @@ -384,13 +395,13 @@ function* loadFullAdHocMesh(
isInitialRequest,
removeExistingMesh && isInitialRequest,
useDataStore,
!usePositionsFromSegmentStats,
!usePositionsFromSegmentIndex,
);
isInitialRequest = false;

// If we are using the positions from the segment index, the backend will
// send an empty neighbors array, as it's not necessary to have them.
if (usePositionsFromSegmentStats && neighbors.length > 0) {
if (usePositionsFromSegmentIndex && neighbors.length > 0) {
throw new Error("Retrieved neighbor positions even though these were not requested.");
}
positionsToRequest = positionsToRequest.concat(neighbors);
Expand All @@ -399,7 +410,7 @@ function* loadFullAdHocMesh(
yield* put(finishedLoadingMeshAction(layer.name, segmentId));
}

function* getChunkPositionsFromSegmentStats(
function* getChunkPositionsFromSegmentIndex(
tracingStoreHost: string,
layer: DataLayer,
segmentId: number,
Expand All @@ -408,7 +419,7 @@ function* getChunkPositionsFromSegmentStats(
clippedPosition: Vector3,
additionalCoordinates: AdditionalCoordinate[] | null | undefined,
) {
const unscaledPositions = yield* call(
const targetMagPositions = yield* call(
getBucketPositionsForAdHocMesh,
tracingStoreHost,
layer.name,
Expand All @@ -417,8 +428,8 @@ function* getChunkPositionsFromSegmentStats(
mag,
additionalCoordinates,
);
const positions = unscaledPositions.map((pos) => V3.scale3(pos, mag));
return sortByDistanceTo(positions, clippedPosition) as Vector3[];
const mag1Positions = targetMagPositions.map((pos) => V3.scale3(pos, mag));
return sortByDistanceTo(mag1Positions, clippedPosition) as Vector3[];
}

function hasMeshChunkExceededThrottleLimit(segmentId: number): boolean {
Expand Down Expand Up @@ -472,7 +483,7 @@ function* maybeLoadMeshChunk(

const { segmentMeshController } = getSceneController();

const cubeSize = getZoomedCubeSize(zoomStep, resolutionInfo);
const cubeSize = marchingCubeSizeInTargetMag();

while (retryCount < MAX_RETRY_COUNT) {
try {
Expand Down Expand Up @@ -505,7 +516,9 @@ function* maybeLoadMeshChunk(
layer.name,
additionalCoordinates,
);
return neighbors.map((neighbor) => getNeighborPosition(clippedPosition, neighbor));
return neighbors.map((neighbor) =>
getNeighborPosition(clippedPosition, neighbor, zoomStep, resolutionInfo),
);
} catch (exception) {
retryCount++;
ErrorHandling.notify(exception as Error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ object DataLayer {
* Defines the length of a bucket per axis. This is the minimal size that can be loaded from a wkw file.
*/
val bucketLength: Int = 32
val bucketSize: Vec3Int = Vec3Int(bucketLength, bucketLength, bucketLength)

implicit object dataLayerFormat extends Format[DataLayer] {
override def reads(json: JsValue): JsResult[DataLayer] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ class VolumeTracingController @Inject()(
fallbackLayer <- tracingService.getFallbackLayer(tracingId)
tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound")
mappingName <- tracingService.baseMappingName(tracing)
_ <- bool2Fox(DataLayer.bucketSize <= request.body.cubeSize) ?~> "cubeSize must be at least one bucket (32³)"
bucketPositionsRaw: ListOfVec3IntProto <- volumeSegmentIndexService
.getSegmentToBucketIndexWithEmptyFallbackWithoutBuffer(
fallbackLayer,
Expand Down

0 comments on commit 3ab7382

Please sign in to comment.