Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ome ngff volume annotation #6242

Merged
merged 4 commits into from
May 26, 2022
Merged
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
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