diff --git a/frontend/javascripts/admin/rest_api.ts b/frontend/javascripts/admin/rest_api.ts index 7fc0773631e..e72180a2dbb 100644 --- a/frontend/javascripts/admin/rest_api.ts +++ b/frontend/javascripts/admin/rest_api.ts @@ -80,6 +80,10 @@ import type { DatasourceConfiguration } from "types/schemas/datasource.types"; import type { AnnotationTypeFilterEnum, LOG_LEVELS, Vector3 } from "viewer/constants"; import Constants, { ControlModeEnum, AnnotationStateFilterEnum } from "viewer/constants"; import type BoundingBox from "viewer/model/bucket_data_handling/bounding_box"; +import { + type LayerSourceInfo, + getDataOrTracingStoreUrl, +} from "viewer/model/bucket_data_handling/wkstore_helper"; import { parseProtoAnnotation, parseProtoListOfLong, @@ -855,13 +859,18 @@ export function hasSegmentIndexInDataStore( ); } +export const hasSegmentIndexInDataStoreCached = _.memoize(hasSegmentIndexInDataStore, (...args) => + args.join("::"), +); + export function getSegmentVolumes( - requestUrl: string, + layerSourceInfo: LayerSourceInfo, mag: Vector3, segmentIds: Array, additionalCoordinates: AdditionalCoordinate[] | undefined | null, mappingName: string | null | undefined, ): Promise { + const requestUrl = getDataOrTracingStoreUrl(layerSourceInfo); return doWithToken((token) => Request.sendJSONReceiveJSON(`${requestUrl}/segmentStatistics/volume?token=${token}`, { data: { additionalCoordinates, mag, segmentIds, mappingName }, @@ -871,12 +880,13 @@ export function getSegmentVolumes( } export function getSegmentBoundingBoxes( - requestUrl: string, + layerSourceInfo: LayerSourceInfo, mag: Vector3, segmentIds: Array, additionalCoordinates: AdditionalCoordinate[] | undefined | null, mappingName: string | null | undefined, ): Promise> { + const requestUrl = getDataOrTracingStoreUrl(layerSourceInfo); return doWithToken((token) => Request.sendJSONReceiveJSON(`${requestUrl}/segmentStatistics/boundingBox?token=${token}`, { data: { additionalCoordinates, mag, segmentIds, mappingName }, @@ -1877,12 +1887,13 @@ type MeshRequest = { }; export function computeAdHocMesh( - requestUrl: string, + layerSourceInfo: LayerSourceInfo, meshRequest: MeshRequest, ): Promise<{ buffer: ArrayBuffer; neighbors: Array; }> { + const requestUrl = getDataOrTracingStoreUrl(layerSourceInfo); const { positionWithPadding, additionalCoordinates, @@ -1924,23 +1935,25 @@ export function computeAdHocMesh( } export function getBucketPositionsForAdHocMesh( - tracingStoreUrl: string, - tracingId: string, + layerSourceInfo: LayerSourceInfo, segmentId: number, cubeSize: Vector3, mag: Vector3, additionalCoordinates: AdditionalCoordinate[] | null | undefined, + mappingName: string | null | undefined, ): Promise { + const requestUrl = getDataOrTracingStoreUrl(layerSourceInfo); return doWithToken(async (token) => { const params = new URLSearchParams(); params.set("token", token); const positions = await Request.sendJSONReceiveJSON( - `${tracingStoreUrl}/tracings/volume/${tracingId}/segmentIndex/${segmentId}?${params}`, + `${requestUrl}/segmentIndex/${segmentId}?${params}`, { data: { cubeSize, mag, additionalCoordinates, + mappingName, }, method: "POST", }, diff --git a/frontend/javascripts/viewer/model/bucket_data_handling/wkstore_helper.ts b/frontend/javascripts/viewer/model/bucket_data_handling/wkstore_helper.ts new file mode 100644 index 00000000000..0dc63ac3257 --- /dev/null +++ b/frontend/javascripts/viewer/model/bucket_data_handling/wkstore_helper.ts @@ -0,0 +1,20 @@ +import type { APIDataset } from "types/api_types"; +import type { StoreAnnotation } from "viewer/store"; + +export type LayerSourceInfo = { + dataset: APIDataset; + annotation: StoreAnnotation | null; + tracingId: string | undefined; + segmentationLayerName: string; + useDataStore?: boolean | undefined | null; +}; + +export function getDataOrTracingStoreUrl(layerSourceInfo: LayerSourceInfo) { + const { dataset, annotation, segmentationLayerName, tracingId, useDataStore } = layerSourceInfo; + if (annotation == null || tracingId == null || useDataStore) { + return `${dataset.dataStore.url}/data/datasets/${dataset.id}/layers/${segmentationLayerName}`; + } else { + const tracingStoreHost = annotation?.tracingStore.url; + return `${tracingStoreHost}/tracings/volume/${tracingId}`; + } +} diff --git a/frontend/javascripts/viewer/model/sagas/meshes/ad_hoc_mesh_saga.ts b/frontend/javascripts/viewer/model/sagas/meshes/ad_hoc_mesh_saga.ts index 1a97748c6ca..7b5e558e98e 100644 --- a/frontend/javascripts/viewer/model/sagas/meshes/ad_hoc_mesh_saga.ts +++ b/frontend/javascripts/viewer/model/sagas/meshes/ad_hoc_mesh_saga.ts @@ -1,6 +1,7 @@ import { computeAdHocMesh, getBucketPositionsForAdHocMesh, + hasSegmentIndexInDataStoreCached, sendAnalyticsEvent, } from "admin/rest_api"; import ThreeDMap from "libs/ThreeDMap"; @@ -41,13 +42,14 @@ import { type LoadAdHocMeshAction, loadPrecomputedMeshAction, } from "viewer/model/actions/segmentation_actions"; +import type { LayerSourceInfo } from "viewer/model/bucket_data_handling/wkstore_helper"; import type DataLayer from "viewer/model/data_layer"; import type { MagInfo } from "viewer/model/helpers/mag_info"; import { zoomedAddressToAnotherZoomStepWithInfo } from "viewer/model/helpers/position_converter"; import type { Saga } from "viewer/model/sagas/effect-generators"; import { select } from "viewer/model/sagas/effect-generators"; import { Model } from "viewer/singletons"; -import Store from "viewer/store"; +import Store, { type StoreDataset, type VolumeTracing } from "viewer/store"; import { getAdditionalCoordinatesAsString } from "../../accessors/flycam_accessor"; import { ensureSceneControllerReady, ensureWkReady } from "../ready_sagas"; @@ -293,6 +295,25 @@ function removeMeshWithoutVoxels( } } +function* getUsePositionsFromSegmentIndex( + volumeTracing: VolumeTracing | null | undefined, + dataset: StoreDataset, + layerName: string, + maybeTracingId?: string | null, +): Saga { + if (volumeTracing == null) { + return yield* call( + hasSegmentIndexInDataStoreCached, + dataset.dataStore.url, + dataset.id, + layerName, + ); + } + return ( + volumeTracing?.hasSegmentIndex && !volumeTracing.hasEditableMapping && maybeTracingId != null + ); +} + function* loadFullAdHocMesh( layer: DataLayer, segmentId: number, @@ -320,36 +341,54 @@ function* loadFullAdHocMesh( yield* put(startedLoadingMeshAction(layer.name, segmentId)); const cubeSize = marchingCubeSizeInTargetMag(); - const tracingStoreHost = yield* select((state) => state.annotation.tracingStore.url); + const dataset = yield* select((state) => state.dataset); const mag = magInfo.getMagByIndexOrThrow(zoomStep); const volumeTracing = yield* select((state) => getActiveSegmentationTracing(state)); + const annotation = yield* select((state) => state.annotation); const visibleSegmentationLayer = yield* select((state) => getVisibleSegmentationLayer(state)); + if (visibleSegmentationLayer == null) { + throw new Error( + "Loading the ad-hoc mesh failed because the visible segmentation layer must not be null.", + ); + } // Fetch from datastore if no volumetracing ... - let useDataStore = volumeTracing == null || visibleSegmentationLayer?.tracingId == null; + let forceUsingDataStore = volumeTracing == null || visibleSegmentationLayer.tracingId == null; if (meshExtraInfo.useDataStore != null) { // ... except if the caller specified whether to use the data store ... - useDataStore = meshExtraInfo.useDataStore; + forceUsingDataStore = meshExtraInfo.useDataStore; } else if (volumeTracing?.hasEditableMapping) { // ... or if an editable mapping is active. - useDataStore = false; + forceUsingDataStore = false; } - // Segment stats can only be used for volume tracings that have a segment index + // Segment stats can only be used for segmentation layers that have a segment index // and that don't have editable mappings. - const usePositionsFromSegmentIndex = - volumeTracing?.hasSegmentIndex && - !volumeTracing.hasEditableMapping && - visibleSegmentationLayer?.tracingId != null; + const usePositionsFromSegmentIndex = yield* call( + getUsePositionsFromSegmentIndex, + volumeTracing, + dataset, + layer.name, + visibleSegmentationLayer.tracingId, + ); + + const layerSourceInfo: LayerSourceInfo = { + dataset, + annotation, + tracingId: visibleSegmentationLayer.tracingId, + segmentationLayerName: visibleSegmentationLayer.fallbackLayer ?? visibleSegmentationLayer.name, + useDataStore: forceUsingDataStore, + }; + let positionsToRequest = usePositionsFromSegmentIndex ? yield* getChunkPositionsFromSegmentIndex( - tracingStoreHost, - layer, + layerSourceInfo, segmentId, cubeSize, mag, clippedPosition, additionalCoordinates, + mappingName, ) : [clippedPosition]; @@ -373,7 +412,7 @@ function* loadFullAdHocMesh( magInfo, isInitialRequest, removeExistingMesh && isInitialRequest, - useDataStore, + layerSourceInfo, !usePositionsFromSegmentIndex, ); isInitialRequest = false; @@ -390,22 +429,22 @@ function* loadFullAdHocMesh( } function* getChunkPositionsFromSegmentIndex( - tracingStoreHost: string, - layer: DataLayer, + layerSourceInfo: LayerSourceInfo, segmentId: number, cubeSize: Vector3, mag: Vector3, clippedPosition: Vector3, additionalCoordinates: AdditionalCoordinate[] | null | undefined, + mappingName: string | null | undefined, ) { const targetMagPositions = yield* call( getBucketPositionsForAdHocMesh, - tracingStoreHost, - layer.name, + layerSourceInfo, segmentId, cubeSize, mag, additionalCoordinates, + mappingName, ); const mag1Positions = targetMagPositions.map((pos) => V3.scale3(pos, mag)); return sortByDistanceTo(mag1Positions, clippedPosition) as Vector3[]; @@ -424,7 +463,7 @@ function* maybeLoadMeshChunk( magInfo: MagInfo, isInitialRequest: boolean, removeExistingMesh: boolean, - useDataStore: boolean, + layerSourceInfo: LayerSourceInfo, findNeighbors: boolean, ): Saga { const additionalCoordinates = yield* select((state) => state.flycam.additionalCoordinates); @@ -445,17 +484,10 @@ function* maybeLoadMeshChunk( batchCounterPerSegment[segmentId]++; threeDMap.set(paddedPositionWithinLayer, true); const scaleFactor = yield* select((state) => state.dataset.dataSource.scale.factor); - const dataStoreHost = yield* select((state) => state.dataset.dataStore.url); - const datasetId = yield* select((state) => state.dataset.id); - const tracingStoreHost = yield* select((state) => state.annotation.tracingStore.url); - const dataStoreUrl = `${dataStoreHost}/data/datasets/${datasetId}/layers/${ - layer.fallbackLayer != null ? layer.fallbackLayer : layer.name - }`; - const tracingStoreUrl = `${tracingStoreHost}/tracings/volume/${layer.name}`; if (isInitialRequest) { sendAnalyticsEvent("request_isosurface", { - mode: useDataStore ? "view" : "annotation", + mode: layerSourceInfo.useDataStore ? "view" : "annotation", }); } @@ -472,7 +504,7 @@ function* maybeLoadMeshChunk( context: null, fn: computeAdHocMesh, }, - useDataStore ? dataStoreUrl : tracingStoreUrl, + layerSourceInfo, { positionWithPadding: paddedPositionWithinLayer, additionalCoordinates: additionalCoordinates || undefined, diff --git a/frontend/javascripts/viewer/view/context_menu.tsx b/frontend/javascripts/viewer/view/context_menu.tsx index 149b4351cff..16b8ce280f6 100644 --- a/frontend/javascripts/viewer/view/context_menu.tsx +++ b/frontend/javascripts/viewer/view/context_menu.tsx @@ -146,10 +146,7 @@ import type { import { deleteNodeAsUserAction } from "viewer/model/actions/skeletontracing_actions_with_effects"; import { type MutableNode, type Tree, TreeMap } from "viewer/model/types/tree_types"; import Store from "viewer/store"; -import { - getVolumeRequestUrl, - withMappingActivationConfirmation, -} from "viewer/view/right-border-tabs/segments_tab/segments_view_helper"; +import { withMappingActivationConfirmation } from "viewer/view/right-border-tabs/segments_tab/segments_view_helper"; import { LayoutEvents, layoutEmitter } from "./layouting/layout_persistence"; import { LoadMeshMenuItemLabel } from "./right-border-tabs/segments_tab/load_mesh_menu_item_label"; @@ -1690,26 +1687,26 @@ function ContextMenuInner() { if (visibleSegmentationLayer == null || !isSegmentIndexAvailable) return []; const tracingId = volumeTracing?.tracingId; const additionalCoordinates = flycam.additionalCoordinates; - const requestUrl = getVolumeRequestUrl( + const layerSourceInfo = { dataset, annotation, tracingId, - visibleSegmentationLayer, - ); + segmentationLayerName: visibleSegmentationLayer.name, + }; const magInfo = getMagInfo(visibleSegmentationLayer.mags); const layersFinestMag = magInfo.getFinestMag(); const voxelSize = dataset.dataSource.scale; try { const [segmentSize] = await getSegmentVolumes( - requestUrl, + layerSourceInfo, layersFinestMag, [clickedSegmentOrMeshId], additionalCoordinates, mappingName, ); const [boundingBoxInRequestedMag] = await getSegmentBoundingBoxes( - requestUrl, + layerSourceInfo, layersFinestMag, [clickedSegmentOrMeshId], additionalCoordinates, diff --git a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx index 9084476fc23..dc1177feb22 100644 --- a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx +++ b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segment_statistics_modal.tsx @@ -17,11 +17,7 @@ import { getBoundingBoxInMag1 } from "viewer/model/sagas/volume/helpers"; import { voxelToVolumeInUnit } from "viewer/model/scaleinfo"; import { api } from "viewer/singletons"; import type { Segment } from "viewer/store"; -import { - type SegmentHierarchyGroup, - type SegmentHierarchyNode, - getVolumeRequestUrl, -} from "./segments_view_helper"; +import type { SegmentHierarchyGroup, SegmentHierarchyNode } from "./segments_view_helper"; const MODAL_ERROR_MESSAGE = "Segment statistics could not be fetched. Check the console for more details."; @@ -104,12 +100,12 @@ export function SegmentStatisticsModal({ const voxelSize = dataset.dataSource.scale; // Omit checking that all prerequisites for segment stats (such as a segment index) are // met right here because that should happen before opening the modal. - const requestUrl = getVolumeRequestUrl( + const storeInfoType = { dataset, annotation, - visibleSegmentationLayer.tracingId, - visibleSegmentationLayer, - ); + tracingId: visibleSegmentationLayer.tracingId, + segmentationLayerName: visibleSegmentationLayer.name, + }; const additionalCoordinates = useWkSelector((state) => state.flycam.additionalCoordinates); const hasAdditionalCoords = hasAdditionalCoordinates(additionalCoordinates); const additionalCoordinateStringForModal = getAdditionalCoordinatesAsString( @@ -119,7 +115,6 @@ export function SegmentStatisticsModal({ const segmentStatisticsObjects = useFetch( async () => { await api.tracing.save(); - if (requestUrl == null) return; const maybeVolumeTracing = tracingId != null ? getVolumeTracingById(annotation, tracingId) : null; const maybeGetMappingName = () => { @@ -132,14 +127,14 @@ export function SegmentStatisticsModal({ }; const segmentStatisticsObjects = await Promise.all([ getSegmentVolumes( - requestUrl, + storeInfoType, layersFinestMag, segments.map((segment) => segment.id), additionalCoordinates, maybeGetMappingName(), ), getSegmentBoundingBoxes( - requestUrl, + storeInfoType, layersFinestMag, segments.map((segment) => segment.id), additionalCoordinates, diff --git a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view_helper.tsx b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view_helper.tsx index dcba796a109..0e80ad191f3 100644 --- a/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view_helper.tsx +++ b/frontend/javascripts/viewer/view/right-border-tabs/segments_tab/segments_view_helper.tsx @@ -3,7 +3,7 @@ import { Modal } from "antd"; import type { BasicDataNode } from "antd/es/tree"; import { waitForCondition } from "libs/utils"; import type { MenuClickEventHandler } from "rc-menu/lib/interface"; -import type { APIDataLayer, APIDataset, APISegmentationLayer } from "types/api_types"; +import type { APIDataLayer, APIDataset } from "types/api_types"; import { MappingStatusEnum } from "viewer/constants"; import { getMappingInfo } from "viewer/model/accessors/dataset_accessor"; import { @@ -47,20 +47,6 @@ export function getBaseSegmentationName(segmentationLayer: APIDataLayer) { ); } -export function getVolumeRequestUrl( - dataset: APIDataset, - annotation: StoreAnnotation | null, - tracingId: string | undefined, - visibleSegmentationLayer: APISegmentationLayer | APIDataLayer, -) { - if (annotation == null || tracingId == null) { - return `${dataset.dataStore.url}/data/datasets/${dataset.id}/layers/${visibleSegmentationLayer.name}`; - } else { - const tracingStoreHost = annotation?.tracingStore.url; - return `${tracingStoreHost}/tracings/volume/${tracingId}`; - } -} - export async function hasSegmentIndex( visibleSegmentationLayer: APIDataLayer, dataset: APIDataset, diff --git a/unreleased_changes/8922.md b/unreleased_changes/8922.md new file mode 100644 index 00000000000..192fd0217c7 --- /dev/null +++ b/unreleased_changes/8922.md @@ -0,0 +1,2 @@ +### Added +- If a segmentation layer has a segment index file, it is now used to query ad-hoc meshes. That way, ad-hoc meshes can be correctly fetched also for unconnected segments. diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala index 44b97928a68..5e53fbf3a2d 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala @@ -224,7 +224,7 @@ class BinaryDataController @Inject()( adHocMeshRequest = AdHocMeshRequest( Some(dataSource.id), segmentationLayer, - request.body.cuboid(dataLayer), + request.body.cuboid, request.body.segmentId, request.body.voxelSizeFactorInUnit, tokenContextForRequest(request), diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala index d1fb0d6e537..403a1fe35b9 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/DataRequests.scala @@ -56,7 +56,7 @@ case class WebknossosAdHocMeshRequest( additionalCoordinates: Option[Seq[AdditionalCoordinate]] = None, findNeighbors: Boolean = true ) { - def cuboid(dataLayer: DataLayer): Cuboid = + def cuboid: Cuboid = Cuboid(VoxelPosition(position.x, position.y, position.z, mag), cubeSize.x, cubeSize.y, cubeSize.z) } diff --git a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/DSFullMeshService.scala b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/DSFullMeshService.scala index 25033128191..c537393df65 100644 --- a/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/DSFullMeshService.scala +++ b/webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/DSFullMeshService.scala @@ -13,6 +13,7 @@ import com.scalableminds.webknossos.datastore.services._ import com.typesafe.scalalogging.LazyLogging import com.scalableminds.util.tools.Box.tryo import com.scalableminds.webknossos.datastore.services.mapping.MappingService +import com.scalableminds.webknossos.datastore.services.segmentindex.SegmentIndexFileService import play.api.i18n.MessagesProvider import play.api.libs.json.{Json, OFormat} @@ -40,6 +41,7 @@ class DSFullMeshService @Inject()(meshFileService: MeshFileService, val dsRemoteTracingstoreClient: DSRemoteTracingstoreClient, mappingService: MappingService, config: DataStoreConfig, + segmentIndexFileService: SegmentIndexFileService, adHocMeshServiceHolder: AdHocMeshServiceHolder) extends LazyLogging with FullMeshHelper @@ -66,26 +68,85 @@ class DSFullMeshService @Inject()(meshFileService: MeshFileService, fullMeshRequest: FullMeshRequest)(implicit ec: ExecutionContext, tc: TokenContext): Fox[Array[Byte]] = for { mag <- fullMeshRequest.mag.toFox ?~> "mag.neededForAdHoc" - seedPosition <- fullMeshRequest.seedPosition.toFox ?~> "seedPosition.neededForAdHoc" segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> "dataLayer.mustBeSegmentation" + hasSegmentIndexFile = segmentationLayer.attachments.flatMap(_.segmentIndex).isDefined before = Instant.now - verticesForChunks <- getAllAdHocChunks(dataSource, - segmentationLayer, - fullMeshRequest, - VoxelPosition(seedPosition.x, seedPosition.y, seedPosition.z, mag), - adHocChunkSize) + verticesForChunks <- if (hasSegmentIndexFile) + getAllAdHocChunksWithSegmentIndex(dataSource, segmentationLayer, fullMeshRequest, mag) + else { + for { + seedPosition <- fullMeshRequest.seedPosition.toFox ?~> "seedPosition.neededForAdHocWithoutSegmentIndex" + chunks <- getAllAdHocChunksWithNeighborLogic( + dataSource, + segmentationLayer, + fullMeshRequest, + VoxelPosition(seedPosition.x, seedPosition.y, seedPosition.z, mag), + adHocChunkSize) + } yield chunks + } + encoded = verticesForChunks.map(adHocMeshToStl) array = combineEncodedChunksToStl(encoded) _ = logMeshingDuration(before, "ad-hoc meshing", array.length) } yield array - private def getAllAdHocChunks( + private def getAllAdHocChunksWithSegmentIndex( dataSource: UsableDataSource, segmentationLayer: SegmentationLayer, fullMeshRequest: FullMeshRequest, - topLeft: VoxelPosition, - chunkSize: Vec3Int, - visited: collection.mutable.Set[VoxelPosition] = collection.mutable.Set[VoxelPosition]())( + mag: Vec3Int, + )(implicit ec: ExecutionContext, tc: TokenContext): Fox[List[Array[Float]]] = + for { + segmentIndexFileKey <- segmentIndexFileService.lookUpSegmentIndexFileKey(dataSource.id, segmentationLayer) + segmentIds <- segmentIdsForAgglomerateIdIfNeeded( + dataSource.id, + segmentationLayer, + fullMeshRequest.mappingName, + fullMeshRequest.editableMappingTracingId, + fullMeshRequest.segmentId, + mappingNameForMeshFile = None, + omitMissing = false + ) + topLeftsNested: Seq[Array[Vec3Int]] <- Fox.serialCombined(segmentIds)(sId => + segmentIndexFileService.readSegmentIndex(segmentIndexFileKey, sId)) + topLefts: Array[Vec3Int] = topLeftsNested.toArray.flatten + targetMagPositions = segmentIndexFileService.topLeftsToDistinctTargetMagBucketPositions(topLefts, mag) + vertexChunksWithNeighbors: List[(Array[Float], List[Int])] <- Fox.serialCombined(targetMagPositions) { + targetMagPosition => + val adHocMeshRequest = AdHocMeshRequest( + Some(dataSource.id), + segmentationLayer, + Cuboid( + VoxelPosition( + targetMagPosition.x * mag.x * DataLayer.bucketLength, + targetMagPosition.y * mag.y * DataLayer.bucketLength, + targetMagPosition.z * mag.z * DataLayer.bucketLength, + mag + ), + DataLayer.bucketLength + 1, + DataLayer.bucketLength + 1, + DataLayer.bucketLength + 1 + ), + fullMeshRequest.segmentId, + dataSource.scale.factor, + tc, + fullMeshRequest.mappingName, + fullMeshRequest.mappingType, + fullMeshRequest.additionalCoordinates, + findNeighbors = false, + ) + adHocMeshService.requestAdHocMeshViaActor(adHocMeshRequest) + } + allVertices = vertexChunksWithNeighbors.map(_._1) + } yield allVertices + + private def getAllAdHocChunksWithNeighborLogic(dataSource: UsableDataSource, + segmentationLayer: SegmentationLayer, + fullMeshRequest: FullMeshRequest, + topLeft: VoxelPosition, + chunkSize: Vec3Int, + visited: collection.mutable.Set[VoxelPosition] = + collection.mutable.Set[VoxelPosition]())( implicit ec: ExecutionContext, tc: TokenContext): Fox[List[Array[Float]]] = { val adHocMeshRequest = AdHocMeshRequest( @@ -105,7 +166,7 @@ class DSFullMeshService @Inject()(meshFileService: MeshFileService, nextPositions: List[VoxelPosition] = generateNextTopLeftsFromNeighbors(topLeft, neighbors, chunkSize, visited) _ = visited ++= nextPositions neighborVerticesNested <- Fox.serialCombined(nextPositions) { position: VoxelPosition => - getAllAdHocChunks(dataSource, segmentationLayer, fullMeshRequest, position, chunkSize, visited) + getAllAdHocChunksWithNeighborLogic(dataSource, segmentationLayer, fullMeshRequest, position, chunkSize, visited) } allVertices: List[Array[Float]] = vertices +: neighborVerticesNested.flatten } yield allVertices diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala index eaf50dbf8cb..6ac627d74f4 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/editablemapping/EditableMappingService.scala @@ -401,7 +401,7 @@ class EditableMappingService @Inject()( val adHocMeshRequest = AdHocMeshRequest( dataSourceId = None, dataLayer = editableMappingLayer, - cuboid = request.cuboid(editableMappingLayer), + cuboid = request.cuboid, segmentId = request.segmentId, voxelSizeFactor = request.voxelSizeFactorInUnit, tokenContext = tc, diff --git a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala index 631c809d012..894e9fe90be 100644 --- a/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala +++ b/webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeTracingService.scala @@ -681,7 +681,7 @@ class VolumeTracingService @Inject()( adHocMeshRequest = AdHocMeshRequest( None, volumeLayer, - request.cuboid(volumeLayer), + request.cuboid, request.segmentId, request.voxelSizeFactorInUnit, tc,