Skip to content

Commit

Permalink
Ome ngff volume annotation (#6242)
Browse files Browse the repository at this point in the history
* add routes for zattrs for volume annotations

* get data source for tracing to extract scale

* add changelog entry

* reformat
  • Loading branch information
leowe authored May 26, 2022
1 parent 77775e4 commit 7453c97
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 20 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Adding a New Volume Layer via the left border tab now gives the option to restrict resolutions for the new layer. [#6229](https://github.com/scalableminds/webknossos/pull/6229)
- Added support for segment interpolation with depths > 2. Also, the feature was changed to work on an explicit trigger (either via the button in the toolbar or via the shortcut V). When triggering the interpolation, the current segment id is interpolated between the current slice and the least-recently annotated slice. [#6235](https://github.com/scalableminds/webknossos/pull/6235)
- Added Route to get OME-NGFF Headers for a data layer (.zattrs file) following the corresponding [spec](https://ngff.openmicroscopy.org/latest/). [#6226](https://github.com/scalableminds/webknossos/pull/6226)
- Added Route to get OME-NGFF Headers for Volume annotation. [#6242](https://github.com/scalableminds/webknossos/pull/6242)

### Changed
- When creating a new annotation with a volume layer (without fallback) for a dataset which has an existing segmentation layer, the original segmentation layer is still listed (and viewable) in the left sidebar. Earlier versions simply hid the original segmentation layer. [#6186](https://github.com/scalableminds/webknossos/pull/6186)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ class ZarrStreamingController @Inject()(
"dataSource.notFound") ~> 404
existingMags = dataLayer.resolutions

omeNgffHeader = OmeNgffHeader.createFromDataLayerName(dataLayerName,
dataSourceScale = dataSource.scale,
mags = existingMags)
omeNgffHeader = OmeNgffHeader.fromDataLayerName(dataLayerName,
dataSourceScale = dataSource.scale,
mags = existingMags)
} yield Ok(Json.toJson(omeNgffHeader))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ object OmeNgffOneHeader {
case class OmeNgffHeader(multiscales: List[OmeNgffOneHeader])

object OmeNgffHeader {
def createFromDataLayerName(dataLayerName: String,
dataSourceScale: Vec3Double,
mags: List[Vec3Int]): OmeNgffHeader = {
def fromDataLayerName(dataLayerName: String, dataSourceScale: Vec3Double, mags: List[Vec3Int]): OmeNgffHeader = {
val datasets = mags.map(
mag =>
OmeNgffDataset(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import java.io.File
import java.nio.{ByteBuffer, ByteOrder}
import akka.stream.scaladsl.Source
import com.google.inject.Inject
import com.scalableminds.util.geometry.{BoundingBox, Vec3Int}
import com.scalableminds.util.geometry.{BoundingBox, Vec3Double, Vec3Int}
import com.scalableminds.util.tools.ExtendedTypes.ExtendedString
import com.scalableminds.util.tools.Fox
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, ElementClass}
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, DataSourceId, ElementClass}
import com.scalableminds.webknossos.datastore.models.{WebKnossosDataRequest, WebKnossosIsosurfaceRequest}
import com.scalableminds.webknossos.datastore.services.UserAccessRequest
import com.scalableminds.webknossos.datastore.VolumeTracing.{VolumeTracing, VolumeTracingOpt, VolumeTracings}
import com.scalableminds.webknossos.datastore.dataformats.zarr.ZarrCoordinatesParser
import com.scalableminds.webknossos.datastore.helpers.ProtoGeometryImplicits
import com.scalableminds.webknossos.datastore.jzarr.{ArrayOrder, OmeNgffHeader, ZarrHeader}
import com.scalableminds.webknossos.datastore.rpc.RPC
import com.scalableminds.webknossos.tracingstore.slacknotification.TSSlackNotificationService
import com.scalableminds.webknossos.tracingstore.tracings.volume.{ResolutionRestrictions, VolumeTracingService}
Expand Down Expand Up @@ -270,24 +271,36 @@ class VolumeTracingController @Inject()(
(channels, dtype) = ElementClass.toChannelAndZarrString(tracing.elementClass)
// data request method always decompresses before sending
compressor = None

shape = Array(
channels,
// Zarr can't handle data sets that don't start at 0, so we extend shape to include "true" coords
(tracing.boundingBox.width + tracing.boundingBox.topLeft.x) / magParsed.x,
(tracing.boundingBox.height + tracing.boundingBox.topLeft.y) / magParsed.y,
(tracing.boundingBox.depth + tracing.boundingBox.topLeft.z) / magParsed.z
)

chunks = Array(channels, cubeLength, cubeLength, cubeLength)

zarrHeader = ZarrHeader(zarr_format = 2,
shape = shape,
chunks = chunks,
compressor = compressor,
dtype = dtype,
order = ArrayOrder.F)
} yield
Ok(
// Json.toJson doesn't work on zarrHeader at the moment, because it doesn't write None values in Options
Json.obj(
"dtype" -> dtype,
"dtype" -> zarrHeader.dtype,
"fill_value" -> 0,
"zarr_format" -> 2,
"order" -> "F",
"chunks" -> List(channels, cubeLength, cubeLength, cubeLength),
"zarr_format" -> zarrHeader.zarr_format,
"order" -> zarrHeader.order,
"chunks" -> zarrHeader.chunks,
"compressor" -> compressor,
"filters" -> None,
"shape" -> List(
channels,
// Zarr can't handle data sets that don't start at 0, so we extend shape to include "true" coords
(tracing.boundingBox.width + tracing.boundingBox.topLeft.x) / magParsed.x,
(tracing.boundingBox.height + tracing.boundingBox.topLeft.y) / magParsed.y,
(tracing.boundingBox.depth + tracing.boundingBox.topLeft.z) / magParsed.z
),
"dimension_seperator" -> "."
"shape" -> zarrHeader.shape,
"dimension_seperator" -> zarrHeader.dimension_separator
))
}
}
Expand All @@ -298,6 +311,29 @@ class VolumeTracingController @Inject()(
}
}

/**
* Handles a request for .zattrs file for a Volume Tracing via a HTTP GET.
* Uses the OME-NGFF standard (see https://ngff.openmicroscopy.org/latest/)
* Used by zarr-streaming.
*/
def zAttrs(
token: Option[String],
tracingId: String,
): Action[AnyContent] = Action.async { implicit request =>
accessTokenService.validateAccess(UserAccessRequest.readTracing(tracingId), urlOrHeaderToken(token, request)) {
for {
tracing <- tracingService.find(tracingId) ?~> Messages("tracing.notFound")

existingMags = tracing.resolutions.map(vec3IntFromProto)
dataSource <- remoteWebKnossosClient.getDataSource(tracing.organizationName, tracing.dataSetName)

omeNgffHeader = OmeNgffHeader.fromDataLayerName(tracingId,
dataSourceScale = dataSource.scale,
mags = existingMags.toList)
} yield Ok(Json.toJson(omeNgffHeader))
}
}

def rawZarrCube(token: Option[String], tracingId: String, mag: String, cxyz: String): Action[AnyContent] =
Action.async { implicit request =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ POST /volume/mergedFromContents @com.scalablemin
# Zarr endpoints for volume annotations
GET /volume/zarr/:tracingId @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.volumeTracingFolderContent(token: Option[String], tracingId: String)
GET /volume/zarr/:tracingId/.zgroup @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.zGroup(token: Option[String], tracingId: String)
GET /volume/zarr/:tracingId/.zattrs @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.zAttrs(token: Option[String], tracingId: String)
GET /volume/zarr/:tracingId/:mag @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.volumeTracingMagFolderContent(token: Option[String], tracingId: String, mag: String)
GET /volume/zarr/:tracingId/:mag/.zarray @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.zArray(token: Option[String], tracingId: String, mag: String)
GET /volume/zarr/:tracingId/:mag/:cxyz @com.scalableminds.webknossos.tracingstore.controllers.VolumeTracingController.rawZarrCube(token: Option[String], tracingId: String, mag: String, cxyz: String)
Expand Down

0 comments on commit 7453c97

Please sign in to comment.