Skip to content

Commit 96b7158

Browse files
Read Zarr Mesh Files (#8682)
- Similar to #8633 we now support reading mesh files in zarr format. Note that there are also neuroglancerPrecomputed meshes, so this time there are three services the meshFileService delegates to. - The frontend no longer needs to specially handle neuroglancerPrecomputed meshes, since the lookUpMeshFileKey function abstracts from that. Because of that, I also simplified the frontend types. - Note that zarr meshfiles must be registered in the datasource-properties.json, they are not explored. - Also fixed a small error in job result status reporting - Also fixed a small error in mag selection for animation job. ### Steps to test: - Load some precomputed meshes (both zarr, hdf5, neuroglancerPrecomputed; talk to me for example datasets), should show meshes correctly - Create animations via wk worker, should also work correctly (I tested this locally) - Note that neuroglancerPrecomputed meshes now need to be registered as attachments in the datasource-properties.json. You may need to re-explore the relevant datasets or edit the json. ### TODOs: - [x] introduce and look up MeshFileKey - [x] delegate to zarr, hdf5 or neuroglancer meshfile services - [x] explore remote neuroglancer meshes - [x] parse new meshfile attributes zarr group format - [x] do we still need the null vs none meshfile-mapping attribute? - [x] test with frontend (zarr,hdf5,neuro) - [x] adapt frontend to no longer need path + fileType for meshfiles - [x] clear caches - [x] unify fullMeshStl route - [x] unify spelling (meshfile vs meshFile) - [x] When exploring remote neuroglancer meshes, add credentialId, and use it? (or create follow-up issue) - [x] re-add singletons to module after merge of zarr-agglomerate’s latest changes - [x] unify function names with zarr-agglomerate’s latest changes ### Issues: - contributes to #8618 - contributes to #8567 ------ - [x] Updated [changelog](../blob/master/CHANGELOG.unreleased.md#unreleased) - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [x] Needs datastore update after deployment --------- Co-authored-by: MichaelBuessemeyer <39529669+MichaelBuessemeyer@users.noreply.github.com>
1 parent d1fddd2 commit 96b7158

38 files changed

+859
-557
lines changed

app/controllers/WKRemoteWorkerController.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,16 +79,18 @@ class WKRemoteWorkerController @Inject()(jobDAO: JobDAO,
7979
for {
8080
_ <- workerDAO.findOneByKey(key) ?~> "job.worker.notFound"
8181
jobBeforeChange <- jobDAO.findOne(id)(GlobalAccessContext)
82-
_ <- jobDAO.updateStatus(id, request.body)
82+
_ <- jobDAO.updateStatus(id, request.body) ?~> "job.updateStatus.failed"
8383
jobAfterChange <- jobDAO.findOne(id)(GlobalAccessContext) ?~> "job.notFound"
8484
_ = jobService.trackStatusChange(jobBeforeChange, jobAfterChange)
8585
_ <- jobService.cleanUpIfFailed(jobAfterChange) ?~> "job.cleanup.failed"
8686
_ <- Fox.runIf(request.body.state == JobState.SUCCESS) {
87-
creditTransactionService.completeTransactionOfJob(jobAfterChange._id)(GlobalAccessContext)
87+
creditTransactionService
88+
.completeTransactionOfJob(jobAfterChange._id)(GlobalAccessContext) ?~> "job.creditTransaction.failed"
8889
}
8990
_ <- Fox.runIf(
9091
jobAfterChange.state != request.body.state && (request.body.state == JobState.FAILURE || request.body.state == JobState.CANCELLED)) {
91-
creditTransactionService.refundTransactionForJob(jobAfterChange._id)(GlobalAccessContext)
92+
creditTransactionService
93+
.refundTransactionForJob(jobAfterChange._id)(GlobalAccessContext) ?~> "job.creditTransaction.refund.failed"
9294
}
9395
} yield Ok
9496
}

app/models/organization/CreditTransactionService.scala

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package models.organization
22

33
import com.scalableminds.util.accesscontext.DBAccessContext
44
import com.scalableminds.util.objectid.ObjectId
5-
import com.scalableminds.util.tools.{Fox, FoxImplicits}
5+
import com.scalableminds.util.tools.{Empty, Failure, Fox, FoxImplicits, Full}
66
import com.typesafe.scalalogging.LazyLogging
77
import play.api.libs.json.{JsObject, Json}
88

@@ -42,15 +42,30 @@ class CreditTransactionService @Inject()(creditTransactionDAO: CreditTransaction
4242

4343
def completeTransactionOfJob(jobId: ObjectId)(implicit ctx: DBAccessContext): Fox[Unit] =
4444
for {
45-
transaction <- creditTransactionDAO.findTransactionForJob(jobId)
46-
_ <- organizationService.assertOrganizationHasPaidPlan(transaction._organization)
47-
_ <- creditTransactionDAO.commitTransaction(transaction._id)
45+
transactionBox <- creditTransactionDAO.findTransactionForJob(jobId).shiftBox
46+
_ <- transactionBox match {
47+
case Full(transaction) =>
48+
for {
49+
_ <- organizationService.assertOrganizationHasPaidPlan(transaction._organization)
50+
_ <- creditTransactionDAO.commitTransaction(transaction._id)
51+
} yield ()
52+
case Empty => Fox.successful(()) // Assume transaction-less Job
53+
case f: Failure => f.toFox
54+
}
55+
4856
} yield ()
4957

5058
def refundTransactionForJob(jobId: ObjectId)(implicit ctx: DBAccessContext): Fox[Unit] =
5159
for {
52-
transaction <- creditTransactionDAO.findTransactionForJob(jobId)
53-
_ <- refundTransaction(transaction)
60+
transactionBox <- creditTransactionDAO.findTransactionForJob(jobId).shiftBox
61+
_ <- transactionBox match {
62+
case Full(transaction) =>
63+
for {
64+
_ <- refundTransaction(transaction)
65+
} yield ()
66+
case Empty => Fox.successful(()) // Assume transaction-less Job
67+
case f: Failure => f.toFox
68+
}
5469
} yield ()
5570

5671
private def refundTransaction(creditTransaction: CreditTransaction)(implicit ctx: DBAccessContext): Fox[Unit] =
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
START TRANSACTION;
2+
3+
do $$ begin ASSERT (select schemaVersion from webknossos.releaseInformation) = 134, 'Previous schema version mismatch'; end; $$ LANGUAGE plpgsql;
4+
5+
ALTER TYPE webknossos.LAYER_ATTACHMENT_DATAFORMAT ADD VALUE 'neuroglancerPrecomputed';
6+
7+
UPDATE webknossos.releaseInformation SET schemaVersion = 135;
8+
9+
COMMIT TRANSACTION;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- Removing enum types directly is not possible so no reversion is available for this.

conf/messages

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,10 @@ mesh.file.listChunks.failed=Failed to load chunk list for segment {0} from mesh
265265
mesh.file.loadChunk.failed=Failed to load mesh chunk for segment
266266
mesh.file.open.failed=Failed to open mesh file for reading
267267
mesh.file.readEncoding.failed=Failed to read encoding from mesh file
268+
mesh.file.lookup.failed=Failed to look up mesh file “{0}”
269+
mesh.file.readVersion.failed=Failed to read format version from file “{0}”
270+
mesh.file.readMappingName.failed=Failed to read mapping name from mesh file “{0}”
271+
mesh.meshFileName.required=Trying to load mesh from mesh file, but mesh file name was not supplied.
268272

269273
task.create.noTasks=Zero tasks were requested
270274
task.create.failed=Failed to create Task
@@ -347,7 +351,9 @@ job.trainModel.notAllowed.organization = Training AI models is only allowed for
347351
job.runInference.notAllowed.organization = Running inference is only allowed for datasets of your own organization.
348352
job.paidJob.notAllowed.noPaidPlan = You are not allowed to run this job because your organization does not have a paid plan.
349353
job.notEnoughCredits = Your organization does not have enough WEBKNOSSOS credits to run this job.
350-
creditTransaction.notPaidPlan = Your organization does not have a paid plan.
354+
job.updateStatus.failed = Failed to update long-running job’s status
355+
job.creditTransaction.failed = Failed to perform credit transaction
356+
job.creditTransaction.refund.failed = Failed to perform credit transaction refund
351357

352358
voxelytics.disabled = Voxelytics workflow reporting and logging are not enabled for this WEBKNOSSOS instance.
353359
voxelytics.runNotFound = Workflow runs not found
@@ -368,10 +374,10 @@ folder.notFound=Could not find the requested folder
368374
folder.delete.root=Cannot delete the organization’s root folder
369375
folder.move.root=Cannot move the organization’s root folder
370376
folder.update.notAllowed=No write access on this folder
377+
folder.noWriteAccess=No write access in this folder
371378
folder.update.name.failed=Failed to update the folder’s name
372379
folder.update.teams.failed=Failed to update the folder’s allowed teams
373380
folder.create.failed.teams.failed=Failed to create folder in this location
374-
folder.noWriteAccess=No write access in this folder
375381
folder.nameMustNotContainSlash=Folder names cannot contain forward slashes
376382

377383
segmentAnything.notEnabled=AI based quick select is not enabled for this WEBKNOSSOS instance.

frontend/javascripts/admin/api/mesh.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type MeshSegmentInfo = {
2222
};
2323

2424
type ListMeshChunksRequest = {
25-
meshFile: APIMeshFileInfo;
25+
meshFileName: string;
2626
segmentId: number;
2727
};
2828

@@ -52,7 +52,7 @@ export function getMeshfileChunksForSegment(
5252
params.append("editableMappingTracingId", editableMappingTracingId);
5353
}
5454
const payload: ListMeshChunksRequest = {
55-
meshFile,
55+
meshFileName: meshFile.name,
5656
segmentId,
5757
};
5858
return Request.sendJSONReceiveJSON(
@@ -72,7 +72,7 @@ type MeshChunkDataRequest = {
7272
};
7373

7474
type MeshChunkDataRequestList = {
75-
meshFile: APIMeshFileInfo;
75+
meshFileName: string;
7676
requests: MeshChunkDataRequest[];
7777
};
7878

frontend/javascripts/types/api_types.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1005,12 +1005,10 @@ export type ServerEditableMapping = {
10051005

10061006
export type APIMeshFileInfo = {
10071007
name: string;
1008-
path: string | null | undefined;
1009-
fileType: string | null | undefined;
10101008
mappingName?: string | null | undefined;
1011-
// 0 - is the first mesh file version
1012-
// 1-2 - the format should behave as v0 (refer to voxelytics for actual differences)
1013-
// 3 - is the newer version with draco encoding.
1009+
// 0 - unsupported (is the first mesh file version)
1010+
// 1-2 - unsupported (the format should behave as v0; refer to voxelytics for actual differences)
1011+
// 3+ - is the newer version with draco encoding.
10141012
formatVersion: number;
10151013
};
10161014
export type APIConnectomeFile = {

frontend/javascripts/viewer/model/sagas/meshes/precomputed_mesh_saga.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ function* loadPrecomputedMeshesInChunksForLod(
365365
dataset,
366366
getBaseSegmentationName(segmentationLayer),
367367
{
368-
meshFile,
368+
meshFileName: meshFile.name,
369369
// Only extract the relevant properties
370370
requests: chunks.map(({ byteOffset, byteSize }) => ({
371371
byteOffset,

frontend/javascripts/viewer/view/action-bar/create_animation_modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ function CreateAnimationModal(props: Props) {
258258
const adhocMagIndex = getMagInfo(layer.resolutions).getClosestExistingIndex(
259259
preferredQualityForMeshAdHocComputation,
260260
);
261-
const adhocMag = layer.resolutions[adhocMagIndex];
261+
const adhocMag = getMagInfo(layer.resolutions).getMagByIndexOrThrow(adhocMagIndex);
262262

263263
return Object.values(meshInfos)
264264
.filter((meshInfo: MeshInformation) => meshInfo.isVisible)

tools/postgres/schema.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ CREATE TABLE webknossos.releaseInformation (
2121
schemaVersion BIGINT NOT NULL
2222
);
2323

24-
INSERT INTO webknossos.releaseInformation(schemaVersion) values(134);
24+
INSERT INTO webknossos.releaseInformation(schemaVersion) values(135);
2525
COMMIT TRANSACTION;
2626

2727

@@ -163,7 +163,7 @@ CREATE TABLE webknossos.dataset_layer_additionalAxes(
163163
);
164164

165165
CREATE TYPE webknossos.LAYER_ATTACHMENT_TYPE AS ENUM ('agglomerate', 'connectome', 'segmentIndex', 'mesh', 'cumsum');
166-
CREATE TYPE webknossos.LAYER_ATTACHMENT_DATAFORMAT AS ENUM ('hdf5', 'zarr3', 'json');
166+
CREATE TYPE webknossos.LAYER_ATTACHMENT_DATAFORMAT AS ENUM ('hdf5', 'zarr3', 'json', 'neuroglancerPrecomputed');
167167
CREATE TABLE webknossos.dataset_layer_attachments(
168168
_dataset TEXT CONSTRAINT _dataset_objectId CHECK (_dataset ~ '^[0-9a-f]{24}$') NOT NULL,
169169
layerName TEXT NOT NULL,

0 commit comments

Comments
 (0)