Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions frontend/javascripts/admin/rest_api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import dayjs from "dayjs";
import { V3 } from "libs/mjs";
import type { RequestOptions, RequestOptionsWithData } from "libs/request";
import Request from "libs/request";
import type { Message } from "libs/toast";
Expand Down Expand Up @@ -1965,7 +1964,7 @@ export function computeAdHocMesh(
// is added here to the position and bbox size.
position: positionWithPadding, // position is in mag1
additionalCoordinates,
cubeSize: V3.toArray(V3.add(cubeSize, [1, 1, 1])), //cubeSize is in target mag
cubeSize, // cubeSize is in target mag
// Name and type of mapping to apply before building mesh (optional)
mapping: mappingName,
voxelSizeFactorInUnit: scaleFactor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ class BoundingBox {
};
}

clipPositionIntoBoundingBox(position: Vector3): Vector3 {
return V3.toArray(V3.max(this.min, V3.min(position, this.max)));
}

extend(other: BoundingBox): BoundingBox {
const newMin = V3.min(this.min, other.min);
const newMax = V3.max(this.max, other.max);
Expand Down
52 changes: 43 additions & 9 deletions frontend/javascripts/viewer/model/sagas/meshes/ad_hoc_mesh_saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ function* loadAdHocMeshFromAction(action: LoadAdHocMeshAction): Saga<void> {
);
} catch (exc) {
Toast.error(`The mesh for segment ${action.segmentId} could not be loaded. Please try again.`);
console.log("Exception when loading ad-hoc mesh for segment", action.segmentId, ":", exc);
ErrorHandling.notify(exc as any);
}
}
Expand Down Expand Up @@ -469,11 +470,23 @@ function* maybeLoadMeshChunk(
const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates);
const threeDMap = getOrAddMapForSegment(layer.name, segmentId, additionalCoordinates);
const mag = magInfo.getMagByIndexOrThrow(zoomStep);
const paddedPosition = V3.toArray(V3.sub(clippedPosition, mag));
const paddedPositionWithinLayer =
layer.cube.boundingBox.clipPositionIntoBoundingBox(paddedPosition);

if (threeDMap.get(paddedPositionWithinLayer)) {
/*
Both cube position and cubeSize are padded. This is to achieve two effects:
(1) An overlap of 1vx (target mag) is added between cubes to fill visible gaps in the
mesh chunk grid.
(2) Cubes directly at dataset layer borders should actually extend by 1vx (target mag)
*beyond* the layer border so that marchingCubes can add closing surfaces for segments
that touch the layer borders.
To achieve both, all positions are moved by 1vx towards topleft and all sizes increased by 1.
Additionally, cubes that touch a lower layer border are increased by 1 again in that direction.
Note that this process can result in negative positions at the topleft layer border.
This is expected and the backend will handle it, adding the closing surface.
*/
const paddedPosition = V3.sub(clippedPosition, mag);
const paddedCubeSize = getPaddedCubeSizeInTargetMag(paddedPosition, mag, layer);

if (threeDMap.get(paddedPosition)) {
return [];
}

Expand All @@ -482,7 +495,7 @@ function* maybeLoadMeshChunk(
}

batchCounterPerSegment[segmentId]++;
threeDMap.set(paddedPositionWithinLayer, true);
threeDMap.set(paddedPosition, true);
const scaleFactor = yield* select((state) => state.dataset.dataSource.scale.factor);

if (isInitialRequest) {
Expand All @@ -495,8 +508,6 @@ function* maybeLoadMeshChunk(

const { segmentMeshController } = getSceneController();

const cubeSize = marchingCubeSizeInTargetMag();

while (retryCount < MAX_RETRY_COUNT) {
try {
const { buffer: responseBuffer, neighbors } = yield* call(
Expand All @@ -506,11 +517,11 @@ function* maybeLoadMeshChunk(
},
layerSourceInfo,
{
positionWithPadding: paddedPositionWithinLayer,
positionWithPadding: paddedPosition,
additionalCoordinates: additionalCoordinates || undefined,
mag,
segmentId,
cubeSize,
cubeSize: paddedCubeSize,
scaleFactor,
findNeighbors,
...meshExtraInfo,
Expand Down Expand Up @@ -550,6 +561,29 @@ function* maybeLoadMeshChunk(
return [];
}

function getPaddedCubeSizeInTargetMag(
paddedPosition: Vector3,
mag: Vector3,
layer: DataLayer,
): Vector3 {
let cubeSize = marchingCubeSizeInTargetMag();

// Always increase cubeSize by 1,1,1 to fill grid gaps
cubeSize = V3.add(cubeSize, [1, 1, 1]);

// If a cube precisely touches a lower layer border, increase its size in that direction
// by 1 again, to provide a closing surface on that layer border.
// Note that the paddedPosition already takes care of the upper layer borders
const cubeBottomRight = V3.add(paddedPosition, V3.scale3(cubeSize, mag));
const layerBottomRight = layer.cube.boundingBox.max;
for (let dimension = 0; dimension < 3; dimension++) {
if (cubeBottomRight[dimension] === layerBottomRight[dimension]) {
cubeSize[dimension] += 1;
}
}
return cubeSize;
}

function* markEditedCellAsDirty(): Saga<void> {
const volumeTracing = yield* select((state) => getActiveSegmentationTracing(state));

Expand Down
2 changes: 2 additions & 0 deletions unreleased_changes/9143.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
### Fixed
- Fixed that some ad-hoc meshes that touch the dataset layer edges wouldn’t have closing edges there.
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,13 @@ class BinaryDataService(val dataBaseDir: Path,

rs.reverse.foreach {
case (bucket, data) =>
val xMin = math.max(cuboid.topLeft.voxelXInMag, bucket.topLeft.voxelXInMag)
val yMin = math.max(cuboid.topLeft.voxelYInMag, bucket.topLeft.voxelYInMag)
val zMin = math.max(cuboid.topLeft.voxelZInMag, bucket.topLeft.voxelZInMag)
val xMin = math.max(0, math.max(cuboid.topLeft.voxelXInMag, bucket.topLeft.voxelXInMag))
val yMin = math.max(0, math.max(cuboid.topLeft.voxelYInMag, bucket.topLeft.voxelYInMag))
val zMin = math.max(0, math.max(cuboid.topLeft.voxelZInMag, bucket.topLeft.voxelZInMag))

val xMax = math.min(cuboid.bottomRight.voxelXInMag, bucket.topLeft.voxelXInMag + bucketLength)
val yMax = math.min(cuboid.bottomRight.voxelYInMag, bucket.topLeft.voxelYInMag + bucketLength)
val zMax = math.min(cuboid.bottomRight.voxelZInMag, bucket.topLeft.voxelZInMag + bucketLength)
val xMax = math.max(0, math.min(cuboid.bottomRight.voxelXInMag, bucket.topLeft.voxelXInMag + bucketLength))
val yMax = math.max(0, math.min(cuboid.bottomRight.voxelYInMag, bucket.topLeft.voxelYInMag + bucketLength))
val zMax = math.max(0, math.min(cuboid.bottomRight.voxelZInMag, bucket.topLeft.voxelZInMag + bucketLength))

for {
z <- zMin until zMax
Expand Down
Loading