Skip to content
Draft
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
28 changes: 28 additions & 0 deletions app/controllers/DatasetController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,34 @@ class DatasetController @Inject()(userService: UserService,
} yield Ok(Json.toJson(usersJs))
}

// Note that dataSource is also included in the full publicWrites. This
def readDataSource(datasetId: ObjectId,
// Optional sharing token allowing access to datasets your team does not normally have access to.")
sharingToken: Option[String]): Action[AnyContent] =
sil.UserAwareAction.async { implicit request =>
log() {
val ctx = URLSharing.fallbackTokenAccessContext(sharingToken)
for {
dataset <- datasetDAO.findOne(datasetId)(ctx) ?~> notFoundMessage(datasetId.toString) ~> NOT_FOUND
organization <- organizationDAO.findOne(dataset._organization)(GlobalAccessContext) ~> NOT_FOUND
_ <- Fox.runOptional(request.identity)(user =>
datasetLastUsedTimesDAO.updateForDatasetAndUser(dataset._id, user._id))
// Access checked above via dataset. In case of shared dataset/annotation, show datastore even if not otherwise accessible
dataStore <- datasetService.dataStoreFor(dataset)(GlobalAccessContext)
js <- datasetService.publicWrites(dataset, request.identity, Some(organization), Some(dataStore))
_ = request.identity.map { user =>
analyticsService.track(OpenDatasetEvent(user, dataset))
if (dataset.isPublic) {
mailchimpClient.tagUser(user, MailchimpTag.HasViewedPublishedDataset)
}
userDAO.updateLastActivity(user._id)
}
} yield {
Ok(Json.toJson(js))
}
}
}

def read(datasetId: ObjectId,
// Optional sharing token allowing access to datasets your team does not normally have access to.")
sharingToken: Option[String]): Action[AnyContent] =
Expand Down
3 changes: 1 addition & 2 deletions app/controllers/WKRemoteDataStoreController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,10 @@ class WKRemoteDataStoreController @Inject()(
Action.async { implicit request =>
dataStoreService.validateAccess(name, key) { _ =>
for {
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext)
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> "dataset.notFound" ~> NOT_FOUND
dataSource <- datasetService.dataSourceFor(dataset)
} yield Ok(Json.toJson(dataSource))
}

}

def updateDataSource(name: String, key: String, datasetId: ObjectId): Action[DataSource] =
Expand Down
10 changes: 10 additions & 0 deletions app/controllers/WKRemoteTracingStoreController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,16 @@ class WKRemoteTracingStoreController @Inject()(tracingStoreService: TracingStore
}
}

def getDataSource(name: String, key: String, datasetId: ObjectId): Action[AnyContent] =
Action.async { implicit request =>
tracingStoreService.validateAccess(name, key) { _ =>
for {
dataset <- datasetDAO.findOne(datasetId)(GlobalAccessContext) ?~> "dataset.notFound" ~> NOT_FOUND
dataSource <- datasetService.dataSourceFor(dataset)
} yield Ok(Json.toJson(dataSource))
}
}

def createTracing(name: String,
key: String,
annotationId: ObjectId,
Expand Down
1 change: 1 addition & 0 deletions conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ mesh.file.lookup.failed=Failed to look up mesh file “{0}”
mesh.file.readVersion.failed=Failed to read format version from file “{0}”
mesh.file.readMappingName.failed=Failed to read mapping name from mesh file “{0}”
mesh.meshFileName.required=Trying to load mesh from mesh file, but mesh file name was not supplied.
mesh.loadFull.failed=Failed to load full segment mesh.

segmentIndexFile.notFound=Could not find requested segment index file

Expand Down
3 changes: 2 additions & 1 deletion conf/webknossos.latest.routes
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ GET /datasets/:datasetId/layers/:layer/thumbnail
POST /datasets/:datasetId/layers/:layer/segmentAnythingMask controllers.DatasetController.segmentAnythingMask(datasetId: ObjectId, layer: String, intensityMin: Option[Float], intensityMax: Option[Float])
PUT /datasets/:datasetId/clearThumbnailCache controllers.DatasetController.removeFromThumbnailCache(datasetId: ObjectId)
GET /datasets/:datasetName/isValidNewName controllers.DatasetController.isValidNewName(datasetName: String)
GET /datasets/:datasetId controllers.DatasetController.read(datasetId: ObjectId, sharingToken: Option[String])
DELETE /datasets/:datasetId/deleteOnDisk controllers.DatasetController.deleteOnDisk(datasetId: ObjectId)
POST /datasets/:datasetId/reserveAttachmentUploadToPath controllers.DatasetController.reserveAttachmentUploadToPath(datasetId: ObjectId)
POST /datasets/:datasetId/finishAttachmentUploadToPath controllers.DatasetController.finishAttachmentUploadToPath(datasetId: ObjectId)
POST /datasets/:datasetId/reserveUploadToPathsForPreliminary controllers.DatasetController.reserveUploadToPathsForPreliminary(datasetId: ObjectId)
POST /datasets/:datasetId/finishUploadToPaths controllers.DatasetController.finishUploadToPaths(datasetId: ObjectId)
GET /datasets/:datasetId controllers.DatasetController.read(datasetId: ObjectId, sharingToken: Option[String])
POST /datasets/compose controllers.DatasetController.compose()
POST /datasets/reserveUploadToPaths controllers.DatasetController.reserveUploadToPaths()

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

# User access tokens for datastore authentication
POST /userToken/generate controllers.UserTokenController.generateTokenForDataStore()
Expand Down
42 changes: 42 additions & 0 deletions frontend/javascripts/admin/rest_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,48 @@ export function getSegmentVolumes(
);
}

type SegmentStatisticsParametersMeshBased = {
mag: Vector3;
segmentIds: number[];
mappingName?: string | null;
additionalCoordinates?: AdditionalCoordinate[] | null;
meshFileName?: string | null;
lod?: number;
seedPosition?: Vector3 | null;
};

export function getSegmentSurfaceArea(
// tracings/volume/:tracingId/...
// data/datasets/:datasetId/layers/:dataLayerName/...
requestUrl: string,
mag: Vector3,
lod: number | undefined,
seedPosition: Vector3 | undefined | null,
meshFileName: string | undefined | null,
segmentIds: Array<number>,
additionalCoordinates: AdditionalCoordinate[] | undefined | null,
mappingName: string | null | undefined,
): Promise<number[]> {
return doWithToken((token) => {
const data: SegmentStatisticsParametersMeshBased = {
mag,
segmentIds,
mappingName,
additionalCoordinates,
lod,
meshFileName,
seedPosition,
};
return Request.sendJSONReceiveJSON(
`${requestUrl}/segmentStatistics/surfaceArea?token=${token}`,
{
data,
method: "POST",
},
);
});
}

export function getSegmentBoundingBoxes(
requestUrl: string,
mag: Vector3,
Expand Down
2 changes: 1 addition & 1 deletion frontend/javascripts/libs/format_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ export const nmFactorToUnit3D = new Map([
[1e99, "Ym³"],
]);

// Accepts an volume that is interpreted in the given unit and returns a string
// Accepts a volume that is interpreted in the given unit and returns a string
// that uses a readable unit to represent the volume.
// E.g. formatNumberToVolume(0.003, Unit.m) == "3000.0 cm³"
export function formatNumberToVolume(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -947,3 +947,14 @@ export function getReadableNameOfVolumeLayer(
? getReadableNameByVolumeTracingId(tracing, layer.tracingId)
: null;
}

export function getCurrentMappingName(state: WebknossosState) {
const visibleSegmentationLayer = getVisibleSegmentationLayer(state);
const volumeTracing = getActiveSegmentationTracing(state);
if (volumeTracing?.mappingName != null) return volumeTracing?.mappingName;
const mappingInfo = getMappingInfo(
state.temporaryConfiguration.activeMappingByLayer,
visibleSegmentationLayer?.name,
);
return mappingInfo.mappingName;
}
60 changes: 41 additions & 19 deletions frontend/javascripts/viewer/view/context_menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CopyOutlined, PushpinOutlined, ReloadOutlined, WarningOutlined } from "@ant-design/icons";
import { getSegmentBoundingBoxes, getSegmentVolumes } from "admin/rest_api";
import { getSegmentBoundingBoxes, getSegmentSurfaceArea, getSegmentVolumes } from "admin/rest_api";
import {
ConfigProvider,
Dropdown,
Expand All @@ -18,7 +18,12 @@ import type {
} from "antd/es/menu/interface";
import { AsyncIconButton } from "components/async_clickables";
import FastTooltip from "components/fast_tooltip";
import { formatLengthAsVx, formatNumberToLength, formatNumberToVolume } from "libs/format_utils";
import {
formatLengthAsVx,
formatNumberToArea,
formatNumberToLength,
formatNumberToVolume,
} from "libs/format_utils";
import { V3 } from "libs/mjs";
import { useFetch } from "libs/react_helpers";
import { useWkSelector } from "libs/react_hooks";
Expand Down Expand Up @@ -75,6 +80,7 @@ import { maybeGetSomeTracing } from "viewer/model/accessors/tracing_accessor";
import {
getActiveCellId,
getActiveSegmentationTracing,
getCurrentMappingName,
getSegmentsForLayer,
hasAgglomerateMapping,
hasConnectomeFile,
Expand Down Expand Up @@ -1396,7 +1402,7 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
? [
// Segment 0 cannot/shouldn't be made active (as this
// would be an eraser effectively).
segmentIdAtPosition > 0 && !disabledVolumeInfo.PICK_CELL.isDisabled
segmentIdAtPosition !== 0 && !disabledVolumeInfo.PICK_CELL.isDisabled
? {
key: "select-cell",
onClick: () => {
Expand All @@ -1415,9 +1421,9 @@ function getNoNodeContextMenuOptions(props: NoNodeContextMenuProps): ItemType[]
),
}
: null,
segmentIdAtPosition > 0 ? onlyShowThisSegmentItem : null,
segmentIdAtPosition > 0 ? toggleSegmentVisibilityItem : null,
segmentIdAtPosition > 0 ? showAllSegmentsItem : null,
segmentIdAtPosition !== 0 ? onlyShowThisSegmentItem : null,
segmentIdAtPosition !== 0 ? toggleSegmentVisibilityItem : null,
segmentIdAtPosition !== 0 ? showAllSegmentsItem : null,
focusInSegmentListItem,
loadPrecomputedMeshItem,
computeMeshAdHocItem,
Expand Down Expand Up @@ -1672,17 +1678,10 @@ function ContextMenuInner() {
dataset,
visibleSegmentationLayer?.name,
);
const mappingName: string | null | undefined = useWkSelector((state) => {
if (volumeTracing?.mappingName != null) return volumeTracing?.mappingName;
const mappingInfo = getMappingInfo(
state.temporaryConfiguration.activeMappingByLayer,
visibleSegmentationLayer?.name,
);
return mappingInfo.mappingName;
});
const mappingName: string | null | undefined = useWkSelector(getCurrentMappingName);
const isLoadingMessage = "loading";
const isLoadingVolumeAndBB = [isLoadingMessage, isLoadingMessage];
const [segmentVolumeLabel, boundingBoxInfoLabel] = useFetch(
const [segmentVolumeLabel, boundingBoxInfoLabel, segmentSurfaceAreaLabel] = useFetch(
async () => {
const { annotation, flycam } = Store.getState();
// The value that is returned if the context menu is closed is shown if it's still loading
Expand Down Expand Up @@ -1715,13 +1714,25 @@ function ContextMenuInner() {
additionalCoordinates,
mappingName,
);
const lod = 0; // todop
const [surfaceArea] = await getSegmentSurfaceArea(
requestUrl,
layersFinestMag,
lod,
globalPosition,
currentMeshFile?.name,
[clickedSegmentOrMeshId],
additionalCoordinates,
mappingName,
);
const boundingBoxInMag1 = getBoundingBoxInMag1(boundingBoxInRequestedMag, layersFinestMag);
const boundingBoxTopLeftString = `(${boundingBoxInMag1.topLeft[0]}, ${boundingBoxInMag1.topLeft[1]}, ${boundingBoxInMag1.topLeft[2]})`;
const boundingBoxSizeString = `(${boundingBoxInMag1.width}, ${boundingBoxInMag1.height}, ${boundingBoxInMag1.depth})`;
const volumeInUnit3 = voxelToVolumeInUnit(voxelSize, layersFinestMag, segmentSize);
return [
formatNumberToVolume(volumeInUnit3, LongUnitToShortUnitMap[voxelSize.unit]),
`${boundingBoxTopLeftString}, ${boundingBoxSizeString}`,
formatNumberToArea(surfaceArea, LongUnitToShortUnitMap[voxelSize.unit]),
];
} catch (_error) {
const notFetchedMessage = "could not be fetched";
Expand Down Expand Up @@ -1797,7 +1808,7 @@ function ContextMenuInner() {
getInfoMenuItem(
"positionInfo",
<>
<PushpinOutlined style={{ transform: "rotate(-45deg)" }} /> Position:{" "}
<PushpinOutlined style={{ transform: "rotate(-45deg)", marginInlineEnd: 5 }} /> Position:{" "}
{nodePositionAsString}
{copyIconWithTooltip(nodePositionAsString, "Copy node position")}
</>,
Expand All @@ -1810,7 +1821,8 @@ function ContextMenuInner() {
getInfoMenuItem(
"positionInfo",
<>
<PushpinOutlined style={{ transform: "rotate(-45deg)" }} /> Position: {positionAsString}
<PushpinOutlined style={{ transform: "rotate(-45deg)", marginInlineEnd: 5 }} /> Position:{" "}
{positionAsString}
{copyIconWithTooltip(positionAsString, "Copy position")}
</>,
),
Expand Down Expand Up @@ -1847,9 +1859,19 @@ function ContextMenuInner() {
</>,
),
);
}

if (areSegmentStatisticsAvailable) {
infoRows.push(
getInfoMenuItem(
"volumeInfo",
<>
<i className="fas fa-expand-alt segment-context-icon" />
Surface Area: {segmentSurfaceAreaLabel}
{copyIconWithTooltip(segmentSurfaceAreaLabel as string, "Copy surface area")}
{refreshButton}
</>,
),
);

infoRows.push(
getInfoMenuItem(
"boundingBoxPositionInfo",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getSegmentBoundingBoxes, getSegmentVolumes } from "admin/rest_api";
import { getSegmentBoundingBoxes, getSegmentSurfaceArea, getSegmentVolumes } from "admin/rest_api";
import { Alert, Modal, Spin, Table } from "antd";
import { formatNumberToVolume } from "libs/format_utils";
import { useFetch } from "libs/react_helpers";
Expand All @@ -11,7 +11,10 @@ import {
getAdditionalCoordinatesAsString,
hasAdditionalCoordinates,
} from "viewer/model/accessors/flycam_accessor";
import { getVolumeTracingById } from "viewer/model/accessors/volumetracing_accessor";
import {
getCurrentMappingName,
getVolumeTracingById,
} from "viewer/model/accessors/volumetracing_accessor";
import { saveAsCSV, transformToCSVRow } from "viewer/model/helpers/csv_helpers";
import { getBoundingBoxInMag1 } from "viewer/model/sagas/volume/helpers";
import { voxelToVolumeInUnit } from "viewer/model/scaleinfo";
Expand Down Expand Up @@ -116,6 +119,13 @@ export function SegmentStatisticsModal({
additionalCoordinates,
", ",
);
const currentMeshFile = useWkSelector((state) =>
visibleSegmentationLayer != null
? state.localSegmentationData[visibleSegmentationLayer.name].currentMeshFile
: null,
);
const mappingName: string | null | undefined = useWkSelector(getCurrentMappingName);

const segmentStatisticsObjects = useFetch(
async () => {
await api.tracing.save();
Expand All @@ -130,21 +140,33 @@ export function SegmentStatisticsModal({
);
return mappingInfo.mappingName;
};
const lod = 0; // todop;
const segmentIds = segments.map((segment) => segment.id);
const segmentStatisticsObjects = await Promise.all([
getSegmentVolumes(
requestUrl,
layersFinestMag,
segments.map((segment) => segment.id),
segmentIds,
additionalCoordinates,
maybeGetMappingName(),
),
getSegmentBoundingBoxes(
requestUrl,
layersFinestMag,
segments.map((segment) => segment.id),
segmentIds,
additionalCoordinates,
maybeGetMappingName(),
),
getSegmentSurfaceArea(
requestUrl,
layersFinestMag,
lod,
null,
currentMeshFile?.name,
segmentIds,
additionalCoordinates,
mappingName,
),
]).then(
(response) => {
const segmentSizes = response[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,5 @@ class DSMeshController @Inject()(
} yield Ok(data)
}
}

}
Loading