Skip to content

Commit 6d14096

Browse files
authored
Fixes for reading attachments: fill value; hdf5 path (#8727)
Two fixes included in #8717 as a separate PR to get them into master sooner. To distinguish between Box states Failure and Empty for shard loading, we need to make sure to use `?=>` instead of `?~>` (which only attaches an error message in Failure case, and passes through Empty) ### Steps to test: - In a dataset with both registered and scanned agglomerate files, both should be listed and usable. - If a zarr array has no shard files, fill_value should be read (according to shape from zarr.json) ------ - [x] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [x] Needs datastore update after deployment
1 parent d6c24bd commit 6d14096

File tree

7 files changed

+34
-13
lines changed

7 files changed

+34
-13
lines changed

unreleased_changes/8727.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- Fixed reading chunks from arrays where the whole shard is missing (should return fill_value)

webknossos-datastore/app/com/scalableminds/webknossos/datastore/datareaders/ChunkReader.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,22 @@ class ChunkReader(header: DatasetHeader) extends FoxImplicits {
2525
typed <- chunkBytesAndShapeBox.map(_._1) match {
2626
case Full(chunkBytes) if useSkipTypingShortcut =>
2727
shortcutChunkTyper.wrapAndType(chunkBytes, chunkShape).toFox ?~> "chunk.shortcutWrapAndType.failed"
28-
case Empty if useSkipTypingShortcut =>
29-
shortcutChunkTyper.createFromFillValueCached(chunkShape) ?~> "chunk.shortcutCreateFromFillValue.failed"
3028
case Full(chunkBytes) =>
3129
chunkTyper.wrapAndType(chunkBytes, chunkShape).toFox ?~> "chunk.wrapAndType.failed"
3230
case Empty =>
33-
chunkTyper.createFromFillValueCached(chunkShape) ?~> "chunk.createFromFillValue.failed"
31+
createFromFillValue(chunkShape, useSkipTypingShortcut)
3432
case f: Failure =>
3533
f.toFox ?~> s"Reading chunk at $path failed"
3634
}
3735
} yield typed
3836

37+
def createFromFillValue(chunkShape: Array[Int], useSkipTypingShortcut: Boolean)(
38+
implicit ec: ExecutionContext): Fox[MultiArray] =
39+
if (useSkipTypingShortcut)
40+
shortcutChunkTyper.createFromFillValueCached(chunkShape) ?~> "chunk.shortcutCreateFromFillValue.failed"
41+
else
42+
chunkTyper.createFromFillValueCached(chunkShape) ?~> "chunk.createFromFillValue.failed"
43+
3944
// Returns bytes (optional, Fox.empty may later be replaced with fill value)
4045
// and chunk shape (optional, only for data formats where each chunk reports its own shape, e.g. N5)
4146
protected def readChunkBytesAndShape(path: VaultPath, range: Option[NumericRange[Long]])(

webknossos-datastore/app/com/scalableminds/webknossos/datastore/datareaders/DatasetArray.scala

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.scalableminds.webknossos.datastore.datareaders
33
import com.scalableminds.util.accesscontext.TokenContext
44
import com.scalableminds.util.cache.AlfuCache
55
import com.scalableminds.util.geometry.Vec3Int
6-
import com.scalableminds.util.tools.{Fox, FoxImplicits}
6+
import com.scalableminds.util.tools.{Empty, Failure, Fox, FoxImplicits, Full}
77
import com.scalableminds.webknossos.datastore.datavault.VaultPath
88
import com.scalableminds.webknossos.datastore.models.datasource.DataSourceId
99
import com.scalableminds.webknossos.datastore.models.AdditionalCoordinate
@@ -248,10 +248,15 @@ class DatasetArray(vaultPath: VaultPath,
248248
implicit ec: ExecutionContext,
249249
tc: TokenContext): Fox[MultiArray] =
250250
if (header.isSharded) {
251+
val chunkShape = chunkShapeAtIndex(chunkIndex)
251252
for {
252-
(shardPath, chunkRange) <- getShardedChunkPathAndRange(chunkIndex) ?~> "chunk.getShardedPathAndRange.failed"
253-
chunkShape = chunkShapeAtIndex(chunkIndex)
254-
multiArray <- chunkReader.read(shardPath, chunkShape, Some(chunkRange), useSkipTypingShortcut)
253+
shardPathAndChunkRangeBox <- getShardedChunkPathAndRange(chunkIndex).shiftBox
254+
multiArray <- shardPathAndChunkRangeBox match {
255+
case Full((shardPath, chunkRange)) =>
256+
chunkReader.read(shardPath, chunkShape, Some(chunkRange), useSkipTypingShortcut)
257+
case Empty => chunkReader.createFromFillValue(chunkShape, useSkipTypingShortcut)
258+
case f: Failure => f.toFox
259+
}
255260
} yield multiArray
256261
} else {
257262
val chunkPath = vaultPath / getChunkFilename(chunkIndex)

webknossos-datastore/app/com/scalableminds/webknossos/datastore/datareaders/precomputed/PrecomputedArray.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class PrecomputedArray(vaultPath: VaultPath,
8787
val shardPath = shardingSpecification.getPathForShard(vaultPath, minishardInfo._1)
8888
for {
8989
_ <- Fox.fromBool(minishardInfo._2 <= Int.MaxValue) ?~> "Minishard number is too large"
90-
minishardIndex <- getMinishardIndex(shardPath, minishardInfo._2.toInt) ?~> f"Could not get minishard index for chunkIndex ${chunkIndex
90+
minishardIndex <- getMinishardIndex(shardPath, minishardInfo._2.toInt) ?=> f"Could not get minishard index for chunkIndex ${chunkIndex
9191
.mkString(",")}"
9292
chunkRange: NumericRange.Exclusive[Long] <- getChunkRange(chunkIdentifier, minishardIndex) ?~> s"Could not get chunk range for chunkIndex ${chunkIndex
9393
.mkString(",")} with chunkIdentifier $chunkIdentifier in minishard index."

webknossos-datastore/app/com/scalableminds/webknossos/datastore/datareaders/zarr3/Zarr3Array.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class Zarr3Array(vaultPath: VaultPath,
120120
private def readAndParseShardIndex(shardPath: VaultPath)(implicit ec: ExecutionContext,
121121
tc: TokenContext): Fox[Array[(Long, Long)]] =
122122
for {
123-
shardIndexRaw <- readShardIndex(shardPath) ?~> "zarr.readShardIndex.failed"
123+
shardIndexRaw <- readShardIndex(shardPath) ?=> "zarr.readShardIndex.failed"
124124
parsed = parseShardIndex(shardIndexRaw)
125125
} yield parsed
126126

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/AgglomerateService.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ class AgglomerateService @Inject()(config: DataStoreConfig,
9898
})
9999
localFallbackAttachment = LayerAttachment(
100100
mappingName,
101-
localDatasetDir.resolve(dataLayer.name).resolve(agglomerateDir).toUri,
101+
localDatasetDir
102+
.resolve(dataLayer.name)
103+
.resolve(agglomerateDir)
104+
.resolve(mappingName + "." + hdf5AgglomerateFileExtension)
105+
.toUri,
102106
LayerAttachmentDataformat.hdf5
103107
)
104108
selectedAttachment = registeredAttachmentNormalized.getOrElse(localFallbackAttachment)

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/mesh/MeshFileService.scala

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,10 @@ class MeshFileService @Inject()(config: DataStoreConfig,
7474

7575
private val dataBaseDir = Paths.get(config.Datastore.baseDirectory)
7676
private val meshesDir = "meshes"
77+
private val hdf5MeshFileExtension = "hdf5"
7778

7879
private val meshFileKeyCache
79-
: AlfuCache[(DataSourceId, String, String), MeshFileKey] = AlfuCache() // dataSourceId, layerName, mappingName → MeshFileKey
80+
: AlfuCache[(DataSourceId, String, String), MeshFileKey] = AlfuCache() // dataSourceId, layerName, meshFileName → MeshFileKey
8081

8182
def lookUpMeshFileKey(dataSourceId: DataSourceId, dataLayer: DataLayer, meshFileName: String)(
8283
implicit ec: ExecutionContext): Fox[MeshFileKey] =
@@ -99,7 +100,11 @@ class MeshFileService @Inject()(config: DataStoreConfig,
99100
})
100101
localFallbackAttachment = LayerAttachment(
101102
meshFileName,
102-
localDatasetDir.resolve(dataLayer.name).resolve(meshesDir).toUri,
103+
localDatasetDir
104+
.resolve(dataLayer.name)
105+
.resolve(meshesDir)
106+
.resolve(meshFileName + "." + hdf5MeshFileExtension)
107+
.toUri,
103108
LayerAttachmentDataformat.hdf5
104109
)
105110
selectedAttachment = registeredAttachmentNormalized.getOrElse(localFallbackAttachment)
@@ -119,7 +124,7 @@ class MeshFileService @Inject()(config: DataStoreConfig,
119124
val layerDir =
120125
dataBaseDir.resolve(dataSourceId.organizationId).resolve(dataSourceId.directoryName).resolve(dataLayer.name)
121126
val scannedMeshFileNames = PathUtils
122-
.listFiles(layerDir.resolve(meshesDir), silent = true, PathUtils.fileExtensionFilter(hdf5FileExtension))
127+
.listFiles(layerDir.resolve(meshesDir), silent = true, PathUtils.fileExtensionFilter(hdf5MeshFileExtension))
123128
.map { paths =>
124129
paths.map(path => FilenameUtils.removeExtension(path.getFileName.toString))
125130
}

0 commit comments

Comments
 (0)