Skip to content

Commit ccf1c28

Browse files
fm3philippottoMichaelBuessemeyer
authored
Calculate surface area of segments (#8928)
- Adds surface area segment stat, based on mesh triangle surfaces - Fixes fullMesh loading with ad-hoc meshes, also used in render_animation worker jobs ### URL of deployed dev instance (used for testing): - https://meshsurfacearea.webknossos.xyz ### Steps to test: - For dataset with segment index file, and also for volume annotation: - Context menu should now have explicitly triggered loading of segment statistics - Surface area should be included ### TODOs: - [x] Fix Infinity/NaN - [x] Validate math/output value against meshlab - [x] move to segment statistics route - [x] also allow for adhoc/volume/proofreading? - [x] Frontend - [x] Add surface area segment statistic - [x] Move segment statistic out of toplevel rightclick menu to save computation - [x] Select correct combination of meshing parameters for the new route - [x] Wait for #8922 to enable static layer ad-hoc without seedPosition - [x] Re-Test proofreading ad-hoc, do we need to set editableMappingTracingId or is the layer doing this for us already? ### Issues: - fixes #8610 ------ - [x] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [x] Removed dev-only changes like prints and application.conf edits - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [x] Needs datastore update after deployment --------- Co-authored-by: Philipp Otto <[email protected]> Co-authored-by: Michael Büßemeyer <[email protected]>
1 parent 05299c4 commit ccf1c28

File tree

19 files changed

+365
-139
lines changed

19 files changed

+365
-139
lines changed

app/controllers/WKRemoteDataStoreController.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ class WKRemoteDataStoreController @Inject()(
246246
Action.async { implicit request =>
247247
dataStoreService.validateAccess(name, key) { _ =>
248248
for {
249-
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ~> NOT_FOUND
249+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
250250
layers <- datasetLayerDAO.findAllForDataset(dataset._id)
251251
magsAndLinkedMags <- Fox.serialCombined(layers)(l => datasetService.getPathsForDataLayer(dataset._id, l.name))
252252
magLinkInfos = magsAndLinkedMags.map(_.map { case (mag, linkedMags) => MagLinkInfo(mag, linkedMags) })
@@ -261,18 +261,17 @@ class WKRemoteDataStoreController @Inject()(
261261
Action.async { implicit request =>
262262
dataStoreService.validateAccess(name, key) { _ =>
263263
for {
264-
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext)
264+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
265265
dataSource <- datasetService.dataSourceFor(dataset)
266266
} yield Ok(Json.toJson(dataSource))
267267
}
268-
269268
}
270269

271270
def updateDataSource(name: String, key: String, datasetId: ObjectId): Action[DataSource] =
272271
Action.async(validateJson[DataSource]) { implicit request =>
273272
dataStoreService.validateAccess(name, key) { _ =>
274273
for {
275-
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ~> NOT_FOUND
274+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
276275
_ <- Fox.runIf(!dataset.isVirtual)(
277276
datasetDAO.updateDataSource(datasetId,
278277
name,

app/controllers/WKRemoteTracingStoreController.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import models.annotation._
1717
import models.dataset.{DatasetDAO, DatasetService}
1818
import models.user.UserDAO
1919
import models.user.time.TimeSpanService
20+
import play.api.i18n.Messages
2021
import play.api.libs.json.Json
2122
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
2223
import scalapb.GeneratedMessage
@@ -155,12 +156,22 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
155156
tracingStoreService.validateAccess(name, key) { _ =>
156157
implicit val ctx: DBAccessContext = GlobalAccessContext
157158
for {
158-
dataset <- datasetDAO.findOne(datasetId) ?~> "dataset.notFound" ~> NOT_FOUND
159+
dataset <- datasetDAO.findOne(datasetId) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
159160
dataStore <- datasetService.dataStoreFor(dataset)
160161
} yield Ok(Json.toJson(dataStore.url))
161162
}
162163
}
163164

165+
def getDataSource(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
166+
Action.async { implicit request =>
167+
tracingStoreService.validateAccess(name, key) { _ =>
168+
for {
169+
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> Messages("dataset.notFound", datasetId) ~> NOT_FOUND
170+
dataSource <- datasetService.dataSourceFor(dataset)
171+
} yield Ok(Json.toJson(dataSource))
172+
}
173+
}
174+
164175
def createTracing(name: String,
165176
key: String,
166177
annotationId: ObjectId,

conf/messages

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ mesh.file.lookup.failed=Failed to look up mesh file “{0}”
276276
mesh.file.readVersion.failed=Failed to read format version from file “{0}”
277277
mesh.file.readMappingName.failed=Failed to read mapping name from mesh file “{0}”
278278
mesh.meshFileName.required=Trying to load mesh from mesh file, but mesh file name was not supplied.
279+
mesh.loadFull.failed=Failed to load full segment mesh.
279280

280281
segmentIndexFile.notFound=Could not find requested segment index file
281282

conf/webknossos.latest.routes

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,12 @@ GET /datasets/:datasetId/layers/:layer/thumbnail
103103
POST /datasets/:datasetId/layers/:layer/segmentAnythingMask controllers.DatasetController.segmentAnythingMask(datasetId: ObjectId, layer: String, intensityMin: Option[Float], intensityMax: Option[Float])
104104
PUT /datasets/:datasetId/clearThumbnailCache controllers.DatasetController.removeFromThumbnailCache(datasetId: ObjectId)
105105
GET /datasets/:datasetName/isValidNewName controllers.DatasetController.isValidNewName(datasetName: String)
106-
GET /datasets/:datasetId controllers.DatasetController.read(datasetId: ObjectId, sharingToken: Option[String])
107106
DELETE /datasets/:datasetId/deleteOnDisk controllers.DatasetController.deleteOnDisk(datasetId: ObjectId)
108107
POST /datasets/:datasetId/reserveAttachmentUploadToPath controllers.DatasetController.reserveAttachmentUploadToPath(datasetId: ObjectId)
109108
POST /datasets/:datasetId/finishAttachmentUploadToPath controllers.DatasetController.finishAttachmentUploadToPath(datasetId: ObjectId)
110109
POST /datasets/:datasetId/reserveUploadToPathsForPreliminary controllers.DatasetController.reserveUploadToPathsForPreliminary(datasetId: ObjectId)
111110
POST /datasets/:datasetId/finishUploadToPaths controllers.DatasetController.finishUploadToPaths(datasetId: ObjectId)
111+
GET /datasets/:datasetId controllers.DatasetController.read(datasetId: ObjectId, sharingToken: Option[String])
112112
POST /datasets/compose controllers.DatasetController.compose()
113113
POST /datasets/reserveUploadToPaths controllers.DatasetController.reserveUploadToPaths()
114114

@@ -152,6 +152,7 @@ GET /tracingstores/:name/datasetId
152152
GET /tracingstores/:name/annotationId controllers.WKRemoteTracingStoreController.annotationIdForTracing(name: String, key: String, tracingId: String)
153153
GET /tracingstores/:name/dataStoreUri/:datasetId controllers.WKRemoteTracingStoreController.dataStoreUriForDataset(name: String, key: String, datasetId: ObjectId)
154154
POST /tracingstores/:name/createTracing controllers.WKRemoteTracingStoreController.createTracing(name: String, key: String, annotationId: ObjectId, previousVersion: Long)
155+
GET /tracingstores/:name/datasources/:datasetId controllers.WKRemoteTracingStoreController.getDataSource(name: String, key: String, datasetId: ObjectId)
155156

156157
# User access tokens for datastore authentication
157158
POST /userToken/generate controllers.UserTokenController.generateTokenForDataStore()

frontend/javascripts/admin/rest_api.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,41 @@ export function getSegmentVolumes(
879879
);
880880
}
881881

882+
type SegmentStatisticsParametersMeshBased = {
883+
mag: Vector3;
884+
segmentIds: number[];
885+
mappingName?: string | null;
886+
additionalCoordinates?: AdditionalCoordinate[] | null;
887+
meshFileName?: string | null;
888+
};
889+
890+
export function getSegmentSurfaceArea(
891+
layerSourceInfo: LayerSourceInfo,
892+
mag: Vector3,
893+
meshFileName: string | undefined | null,
894+
segmentIds: Array<number>,
895+
additionalCoordinates: AdditionalCoordinate[] | undefined | null,
896+
mappingName: string | null | undefined,
897+
): Promise<number[]> {
898+
const requestUrl = getDataOrTracingStoreUrl(layerSourceInfo);
899+
return doWithToken((token) => {
900+
const data: SegmentStatisticsParametersMeshBased = {
901+
mag,
902+
segmentIds,
903+
mappingName,
904+
additionalCoordinates,
905+
meshFileName,
906+
};
907+
return Request.sendJSONReceiveJSON(
908+
`${requestUrl}/segmentStatistics/surfaceArea?token=${token}`,
909+
{
910+
data,
911+
method: "POST",
912+
},
913+
);
914+
});
915+
}
916+
882917
export function getSegmentBoundingBoxes(
883918
layerSourceInfo: LayerSourceInfo,
884919
mag: Vector3,

frontend/javascripts/libs/format_utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export const nmFactorToUnit3D = new Map([
293293
[1e99, "Ym³"],
294294
]);
295295

296-
// Accepts an volume that is interpreted in the given unit and returns a string
296+
// Accepts a volume that is interpreted in the given unit and returns a string
297297
// that uses a readable unit to represent the volume.
298298
// E.g. formatNumberToVolume(0.003, Unit.m) == "3000.0 cm³"
299299
export function formatNumberToVolume(

frontend/javascripts/viewer/model/accessors/volumetracing_accessor.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,3 +947,14 @@ export function getReadableNameOfVolumeLayer(
947947
? getReadableNameByVolumeTracingId(tracing, layer.tracingId)
948948
: null;
949949
}
950+
951+
export function getCurrentMappingName(state: WebknossosState) {
952+
const visibleSegmentationLayer = getVisibleSegmentationLayer(state);
953+
const volumeTracing = getActiveSegmentationTracing(state);
954+
if (volumeTracing?.mappingName != null) return volumeTracing?.mappingName;
955+
const mappingInfo = getMappingInfo(
956+
state.temporaryConfiguration.activeMappingByLayer,
957+
visibleSegmentationLayer?.name,
958+
);
959+
return mappingInfo.mappingName;
960+
}

0 commit comments

Comments
 (0)