Skip to content

Commit 6fa3d30

Browse files
In Data Loading, Clip to Layer BoundingBox (Redo #8551) (#8573)
* Revert "Revert "In Data Loading, Clip to Layer BoundingBox (#8551)" (#8572)" This reverts commit d30f8d8. * null check for request.dataSource * data requests: use Option[DataSourceId] rather than nullable DataSource * format * implement pr feedback --------- Co-authored-by: Florian M <[email protected]>
1 parent 2da75a5 commit 6fa3d30

File tree

20 files changed

+141
-78
lines changed

20 files changed

+141
-78
lines changed

CHANGELOG.unreleased.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
1818
- Remove `data.maybe` dependency and replaced with regular Typescript types. [#8563](https://github.com/scalableminds/webknossos/pull/8563)
1919
- Updated `View Modes` documentation page with links for mouse and keyboard shortcuts. [#8582](https://github.com/scalableminds/webknossos/pull/8582)
2020
- Renamed the button to view the compound annotation of all tasks of a tasktype to be more descriptive. [#8565](https://github.com/scalableminds/webknossos/pull/8565)
21-
- Replaced fixed threshold of 40 meshes by a dynamic limit based on the number of triangles in the mesh for the "Create Animation" job. [#8588](https://github.com/scalableminds/webknossos/pull/8588)
21+
- Replaced fixed threshold of 40 meshes by a dynamic limit based on the number of triangles in the mesh for the "Create Animation" job. [#8588](https://github.com/scalableminds/webknossos/pull/8588)
2222
- Replaced Redux selector `useSelector((state: OxalisState) => ...)` with a typed `useWkSelector(state => ...)` shorthand. [#8591](https://github.com/scalableminds/webknossos/pull/8591)
2323
- Renamed `OxalisState`, `OxalisApplication`, and `OxalisApi` to their respective `Webknossos{State, API, Application}` equivalent [#8591](https://github.com/scalableminds/webknossos/pull/8591)
2424
- Renamed `frontend/javascripts/oxalis` to `frontend/javascripts/viewer`. [#8601](https://github.com/scalableminds/webknossos/pull/8601)
25+
- When loading data from a data layer that has data stored beyond the bounding box specified in the datasource-properties.json, data outside of the bounding box is now zeroed. (the layer is “clipped”). [#8551](https://github.com/scalableminds/webknossos/pull/8551)
2526

2627
### Fixed
2728
- Fixed that layer bounding boxes were sometimes colored green even though this should only happen for tasks. [#8535](https://github.com/scalableminds/webknossos/pull/8535)

util/src/main/scala/com/scalableminds/util/geometry/BoundingBox.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import play.api.libs.json.{JsObject, Json}
66

77
case class BoundingBox(topLeft: Vec3Int, width: Int, height: Int, depth: Int) {
88

9-
val bottomRight: Vec3Int = topLeft.move(width, height, depth)
9+
lazy val bottomRight: Vec3Int = topLeft.move(width, height, depth)
1010

1111
def intersects(other: BoundingBox): Boolean =
1212
math.max(topLeft.x, other.topLeft.x) < math.min(bottomRight.x, other.bottomRight.x) &&
@@ -45,6 +45,9 @@ case class BoundingBox(topLeft: Vec3Int, width: Int, height: Int, depth: Int) {
4545
BoundingBox(Vec3Int(x, y, z), w, h, d)
4646
}
4747

48+
def isFullyContainedIn(other: BoundingBox): Boolean =
49+
this.intersection(other).contains(this)
50+
4851
def isEmpty: Boolean =
4952
width <= 0 || height <= 0 || depth <= 0
5053

@@ -61,6 +64,9 @@ case class BoundingBox(topLeft: Vec3Int, width: Int, height: Int, depth: Int) {
6164
// Since floorDiv is used for topLeft, ceilDiv is used for the size to avoid voxels being lost at the border
6265
BoundingBox(topLeft / that, ceilDiv(width, that.x), ceilDiv(height, that.y), ceilDiv(depth, that.z))
6366

67+
def move(delta: Vec3Int): BoundingBox =
68+
this.copy(topLeft = this.topLeft + delta)
69+
6470
def toSql: List[Int] =
6571
List(topLeft.x, topLeft.y, topLeft.z, width, height, depth)
6672

util/src/main/scala/com/scalableminds/util/geometry/Vec3Int.scala

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ case class Vec3Int(x: Int, y: Int, z: Int) {
2323
def /(that: Vec3Int): Vec3Int =
2424
Vec3Int(x / that.x, y / that.y, z / that.z)
2525

26+
def unary_- : Vec3Int =
27+
Vec3Int(-x, -y, -z)
28+
2629
def scale(s: Float): Vec3Int =
2730
Vec3Int((x * s).toInt, (y * s).toInt, (z * s).toInt)
2831

@@ -53,8 +56,6 @@ case class Vec3Int(x: Int, y: Int, z: Int) {
5356
def move(other: Vec3Int): Vec3Int =
5457
move(other.x, other.y, other.z)
5558

56-
def negate: Vec3Int = Vec3Int(-x, -y, -z)
57-
5859
def to(bottomRight: Vec3Int): Seq[Vec3Int] =
5960
range(bottomRight, _ to _)
6061

webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/BinaryDataController.scala

+12-12
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class BinaryDataController @Inject()(
6363
(dataSource, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId,
6464
datasetDirectoryName,
6565
dataLayerName) ~> NOT_FOUND
66-
(data, indices) <- requestData(dataSource, dataLayer, request.body)
66+
(data, indices) <- requestData(dataSource.id, dataLayer, request.body)
6767
duration = Instant.since(t)
6868
_ = if (duration > (10 seconds))
6969
logger.info(
@@ -111,7 +111,7 @@ class BinaryDataController @Inject()(
111111
depth,
112112
DataServiceRequestSettings(halfByte = halfByte, appliedAgglomerate = mappingName)
113113
)
114-
(data, indices) <- requestData(dataSource, dataLayer, dataRequest)
114+
(data, indices) <- requestData(dataSource.id, dataLayer, dataRequest)
115115
} yield Ok(data).withHeaders(createMissingBucketsHeaders(indices): _*)
116116
}
117117
}
@@ -127,7 +127,7 @@ class BinaryDataController @Inject()(
127127
(dataSource, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId,
128128
datasetDirectoryName,
129129
dataLayerName) ~> NOT_FOUND
130-
(data, indices) <- requestData(dataSource, dataLayer, request.body)
130+
(data, indices) <- requestData(dataSource.id, dataLayer, request.body)
131131
} yield Ok(data).withHeaders(createMissingBucketsHeaders(indices): _*)
132132
}
133133
}
@@ -155,7 +155,7 @@ class BinaryDataController @Inject()(
155155
cubeSize,
156156
cubeSize
157157
)
158-
(data, indices) <- requestData(dataSource, dataLayer, dataRequest)
158+
(data, indices) <- requestData(dataSource.id, dataLayer, dataRequest)
159159
} yield Ok(data).withHeaders(createMissingBucketsHeaders(indices): _*)
160160
}
161161
}
@@ -189,7 +189,7 @@ class BinaryDataController @Inject()(
189189
depth = 1,
190190
DataServiceRequestSettings(appliedAgglomerate = mappingName)
191191
)
192-
(data, _) <- requestData(dataSource, dataLayer, dataRequest)
192+
(data, _) <- requestData(dataSource.id, dataLayer, dataRequest)
193193
intensityRange: Option[(Double, Double)] = intensityMin.flatMap(min => intensityMax.map(max => (min, max)))
194194
layerColor = color.flatMap(Color.fromHTML)
195195
params = ImageCreatorParameters(
@@ -228,7 +228,7 @@ class BinaryDataController @Inject()(
228228
datasetDirectoryName,
229229
dataLayerName) ~> NOT_FOUND
230230
segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> Messages("dataLayer.notFound")
231-
mappingRequest = DataServiceMappingRequest(dataSource, segmentationLayer, mappingName)
231+
mappingRequest = DataServiceMappingRequest(Some(dataSource.id), segmentationLayer, mappingName)
232232
result <- mappingService.handleMappingRequest(mappingRequest)
233233
} yield Ok(result)
234234
}
@@ -249,7 +249,7 @@ class BinaryDataController @Inject()(
249249
dataLayerName) ~> NOT_FOUND
250250
segmentationLayer <- tryo(dataLayer.asInstanceOf[SegmentationLayer]).toFox ?~> "dataLayer.mustBeSegmentation"
251251
adHocMeshRequest = AdHocMeshRequest(
252-
Some(dataSource),
252+
Some(dataSource.id),
253253
segmentationLayer,
254254
request.body.cuboid(dataLayer),
255255
request.body.segmentId,
@@ -287,7 +287,7 @@ class BinaryDataController @Inject()(
287287
(dataSource, dataLayer) <- dataSourceRepository.getDataSourceAndDataLayer(organizationId,
288288
datasetDirectoryName,
289289
dataLayerName) ~> NOT_FOUND
290-
positionAndMagOpt <- findDataService.findPositionWithData(dataSource, dataLayer)
290+
positionAndMagOpt <- findDataService.findPositionWithData(dataSource.id, dataLayer)
291291
} yield Ok(Json.obj("position" -> positionAndMagOpt.map(_._1), "mag" -> positionAndMagOpt.map(_._2)))
292292
}
293293
}
@@ -301,19 +301,19 @@ class BinaryDataController @Inject()(
301301
datasetDirectoryName,
302302
dataLayerName) ?~> Messages(
303303
"dataSource.notFound") ~> NOT_FOUND ?~> Messages("histogram.layerMissing", dataLayerName)
304-
listOfHistograms <- findDataService.createHistogram(dataSource, dataLayer) ?~> Messages("histogram.failed",
305-
dataLayerName)
304+
listOfHistograms <- findDataService.createHistogram(dataSource.id, dataLayer) ?~> Messages("histogram.failed",
305+
dataLayerName)
306306
} yield Ok(Json.toJson(listOfHistograms))
307307
}
308308
}
309309

310310
private def requestData(
311-
dataSource: DataSource,
311+
dataSourceId: DataSourceId,
312312
dataLayer: DataLayer,
313313
dataRequests: DataRequestCollection
314314
)(implicit tc: TokenContext): Fox[(Array[Byte], List[Int])] = {
315315
val requests =
316-
dataRequests.map(r => DataServiceDataRequest(dataSource, dataLayer, r.cuboid(dataLayer), r.settings))
316+
dataRequests.map(r => DataServiceDataRequest(Some(dataSourceId), dataLayer, r.cuboid(dataLayer), r.settings))
317317
binaryDataService.handleDataRequests(requests)
318318
}
319319

webknossos-datastore/app/com/scalableminds/webknossos/datastore/controllers/ZarrStreamingController.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ class ZarrStreamingController @Inject()(
276276
_ <- Fox.fromBool(dataLayer.containsMag(magParsed)) ?~> Messages("dataLayer.wrongMag", dataLayerName, mag) ~> NOT_FOUND
277277
cubeSize = DataLayer.bucketLength
278278
request = DataServiceDataRequest(
279-
dataSource,
279+
Some(dataSource.id),
280280
dataLayer,
281281
Cuboid(
282282
topLeft = VoxelPosition(x * cubeSize * magParsed.x,

webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/DatasetArrayBucketProvider.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class DatasetArrayBucketProvider(dataLayer: DataLayer,
7373
case Some(remoteSourceDescriptorService: RemoteSourceDescriptorService) =>
7474
for {
7575
magPath: VaultPath <- remoteSourceDescriptorService.vaultPathFor(readInstruction.baseDir,
76-
readInstruction.dataSource.id,
76+
readInstruction.dataSourceId,
7777
readInstruction.dataLayer.name,
7878
magLocator)
7979
chunkContentsCache <- sharedChunkContentsCacheOpt.toFox

webknossos-datastore/app/com/scalableminds/webknossos/datastore/dataformats/MappingProvider.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ class MappingProvider(layer: SegmentationLayer) {
1212

1313
def load(readInstruction: MappingReadInstruction): Box[Array[Byte]] = {
1414
val mappingFile = readInstruction.baseDir
15-
.resolve(readInstruction.dataSource.id.organizationId)
16-
.resolve(readInstruction.dataSource.id.directoryName)
15+
.resolve(readInstruction.dataSourceId.organizationId)
16+
.resolve(readInstruction.dataSourceId.directoryName)
1717
.resolve(layer.name)
1818
.resolve(MappingProvider.mappingsDir)
1919
.resolve(s"${readInstruction.mapping}.${MappingProvider.mappingFileExtension}")

webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/datasource/DataSource.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import play.api.libs.json._
88
package object datasource {
99

1010
case class DataSourceId(directoryName: String, organizationId: String) {
11-
override def toString: String = s"DataSourceId($organizationId/$directoryName)"
11+
override def toString: String = s"$organizationId/$directoryName"
1212
}
1313

1414
object DataSourceId {

webknossos-datastore/app/com/scalableminds/webknossos/datastore/models/requests/Cuboid.scala

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.scalableminds.webknossos.datastore.models.requests
22

3-
import com.scalableminds.util.geometry.Vec3Int
3+
import com.scalableminds.util.geometry.{BoundingBox, Vec3Int}
44
import com.scalableminds.webknossos.datastore.models.{BucketPosition, VoxelPosition}
55

66
/**
@@ -51,4 +51,10 @@ case class Cuboid(topLeft: VoxelPosition, width: Int, height: Int, depth: Int) {
5151
height * mag.y,
5252
depth * mag.z
5353
)
54+
55+
def toBoundingBoxInMag: BoundingBox =
56+
BoundingBox(Vec3Int(topLeft.voxelXInMag, topLeft.voxelYInMag, topLeft.voxelZInMag), width, height, depth)
57+
58+
def toMag1BoundingBox: BoundingBox =
59+
toMag1.toBoundingBoxInMag
5460
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package com.scalableminds.webknossos.datastore.models.requests
22

3+
import com.scalableminds.util.geometry.Vec3Int
34
import com.scalableminds.webknossos.datastore.models.{AdditionalCoordinate, BucketPosition}
4-
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, DataSource, SegmentationLayer}
5+
import com.scalableminds.webknossos.datastore.models.datasource.{DataLayer, DataSourceId, SegmentationLayer}
56

67
import java.nio.file.Path
78

@@ -15,32 +16,41 @@ object DataServiceRequestSettings {
1516
}
1617

1718
case class DataServiceDataRequest(
18-
dataSource: DataSource, // null in VolumeTracings
19+
dataSourceId: Option[DataSourceId], // None in case of volume tracings
1920
dataLayer: DataLayer,
2021
cuboid: Cuboid,
2122
settings: DataServiceRequestSettings
2223
) {
2324
def isSingleBucket: Boolean = cuboid.isSingleBucket(DataLayer.bucketLength)
25+
def mag: Vec3Int = cuboid.mag
26+
27+
// dataSource is None and unused for volume tracings. Insert dummy DataSourceId
28+
// (also unused in that case, except for logging and bucket provider cache key)
29+
def dataSourceIdOrVolumeDummy: DataSourceId = dataSourceId.getOrElse(DataSourceId("VolumeTracing", dataLayer.name))
2430
}
2531

2632
case class DataReadInstruction(
2733
baseDir: Path,
28-
dataSource: DataSource,
34+
dataSourceId: DataSourceId, // Dummy value in case of volume tracings
2935
dataLayer: DataLayer,
3036
bucket: BucketPosition,
3137
version: Option[Long] = None
3238
) {
33-
def layerSummary: String = f"${dataSource.id.organizationId}/${dataSource.id.directoryName}/${dataLayer.name}"
39+
def layerSummary: String = f"$dataSourceId/${dataLayer.name}"
3440
}
3541

3642
case class DataServiceMappingRequest(
37-
dataSource: DataSource,
43+
dataSourceId: Option[DataSourceId], // None in case of volume tracings
3844
dataLayer: SegmentationLayer,
3945
mapping: String
40-
)
46+
) {
47+
// dataSource is None and unused for volume tracings. Insert dummy DataSourceId
48+
// (also unused in that case, except for logging and bucket provider cache key)
49+
def dataSourceIdOrVolumeDummy: DataSourceId = dataSourceId.getOrElse(DataSourceId("VolumeTracing", dataLayer.name))
50+
}
4151

4252
case class MappingReadInstruction(
4353
baseDir: Path,
44-
dataSource: DataSource,
54+
dataSourceId: DataSourceId, // Dummy value in case of volume tracings
4555
mapping: String
4656
)

0 commit comments

Comments
 (0)