Skip to content

Commit 8361f14

Browse files
authored
On dataset upload, attempt exploring also as zarr3 (#9015)
### Steps to test: - Upload zarr3 data without datasource-properties.json - Should be explored and become viewable ### Issues: - fixes #9014 ------ - [x] Added changelog entry (create a `$PR_NUMBER.md` file in `unreleased_changes` or use `./tools/create-changelog-entry.py`) - [x] Considered [common edge cases](../blob/master/.github/common_edge_cases.md) - [x] Needs datastore update after deployment
1 parent e96e381 commit 8361f14

File tree

3 files changed

+40
-19
lines changed

3 files changed

+40
-19
lines changed

unreleased_changes/9015.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
### Fixed
2+
- Fixed upload of zarr3 datasets with no prior webknossos metadata (datasource-properties.json)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ object StorageTransformerSpecification {
165165

166166
object Zarr3ArrayHeader extends JsonImplicits {
167167

168-
def FILENAME_ZARR_JSON = "zarr.json"
168+
val FILENAME_ZARR_JSON = "zarr.json"
169169
implicit object Zarr3ArrayHeaderFormat extends Format[Zarr3ArrayHeader] {
170170
override def reads(json: JsValue): JsResult[Zarr3ArrayHeader] =
171171
for {

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/uploading/UploadService.scala

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.scalableminds.webknossos.datastore.datareaders.n5.{N5Header, N5Metada
1414
import com.scalableminds.webknossos.datastore.datareaders.precomputed.PrecomputedHeader.FILENAME_INFO
1515
import com.scalableminds.webknossos.datastore.datareaders.zarr.NgffMetadata.FILENAME_DOT_ZATTRS
1616
import com.scalableminds.webknossos.datastore.datareaders.zarr.ZarrHeader.FILENAME_DOT_ZARRAY
17+
import com.scalableminds.webknossos.datastore.datareaders.zarr3.Zarr3ArrayHeader.FILENAME_ZARR_JSON
1718
import com.scalableminds.webknossos.datastore.datavault.S3DataVault
1819
import com.scalableminds.webknossos.datastore.explore.ExploreLocalLayerService
1920
import com.scalableminds.webknossos.datastore.helpers.{DatasetDeleter, DirectoryConstants, UPath}
@@ -481,13 +482,14 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
481482
_ <- Fox.successful(())
482483
uploadedDataSourceType = guessTypeOfUploadedDataSource(unpackToDir)
483484
_ <- uploadedDataSourceType match {
484-
case UploadedDataSourceType.ZARR | UploadedDataSourceType.NEUROGLANCER_PRECOMPUTED |
485-
UploadedDataSourceType.N5_MULTISCALES | UploadedDataSourceType.N5_ARRAY =>
485+
case UploadedDataSourceType.ZARR | UploadedDataSourceType.ZARR3 |
486+
UploadedDataSourceType.NEUROGLANCER_PRECOMPUTED | UploadedDataSourceType.N5_MULTISCALES |
487+
UploadedDataSourceType.N5_ARRAY =>
486488
exploreLocalDatasource(unpackToDir, dataSourceId, uploadedDataSourceType)
487489
case UploadedDataSourceType.EXPLORED =>
488490
checkPathsInUploadedDatasourcePropertiesJson(unpackToDir, dataSourceId.organizationId)
489-
case UploadedDataSourceType.ZARR_MULTILAYER | UploadedDataSourceType.NEUROGLANCER_MULTILAYER |
490-
UploadedDataSourceType.N5_MULTILAYER =>
491+
case UploadedDataSourceType.ZARR_MULTILAYER | UploadedDataSourceType.ZARR3_MULTILAYER |
492+
UploadedDataSourceType.NEUROGLANCER_MULTILAYER | UploadedDataSourceType.N5_MULTILAYER =>
491493
tryExploringMultipleLayers(unpackToDir, dataSourceId, uploadedDataSourceType)
492494
case UploadedDataSourceType.WKW => addLayerAndMagDirIfMissing(unpackToDir).toFox
493495
}
@@ -509,6 +511,7 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
509511
typ: UploadedDataSourceType.Value): Fox[Unit] =
510512
for {
511513
_ <- Fox.runIf(typ == UploadedDataSourceType.ZARR)(addLayerAndMagDirIfMissing(path, FILENAME_DOT_ZARRAY).toFox)
514+
_ <- Fox.runIf(typ == UploadedDataSourceType.ZARR3)(addLayerAndMagDirIfMissing(path, FILENAME_ZARR_JSON).toFox)
512515
explored <- exploreLocalLayerService.exploreLocal(path, dataSourceId)
513516
_ <- exploreLocalLayerService.writeLocalDatasourceProperties(explored, path)
514517
} yield ()
@@ -518,7 +521,8 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
518521
typ: UploadedDataSourceType.Value): Fox[Option[Path]] =
519522
for {
520523
layerDirs <- typ match {
521-
case UploadedDataSourceType.ZARR_MULTILAYER => getZarrLayerDirectories(path).toFox
524+
case UploadedDataSourceType.ZARR_MULTILAYER => getZarrLayerDirectories(path).toFox
525+
case UploadedDataSourceType.ZARR3_MULTILAYER => getZarr3LayerDirectories(path).toFox
522526
case UploadedDataSourceType.NEUROGLANCER_MULTILAYER | UploadedDataSourceType.N5_MULTILAYER =>
523527
PathUtils.listDirectories(path, silent = false).toFox
524528
}
@@ -680,6 +684,10 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
680684
UploadedDataSourceType.ZARR
681685
} else if (looksLikeZarrArray(dataSourceDir, maxDepth = 3).getOrElse(false)) {
682686
UploadedDataSourceType.ZARR_MULTILAYER
687+
} else if (looksLikeZarr3Array(dataSourceDir, maxDepth = 2).getOrElse(false)) {
688+
UploadedDataSourceType.ZARR3
689+
} else if (looksLikeZarr3Array(dataSourceDir, maxDepth = 3).getOrElse(false)) {
690+
UploadedDataSourceType.ZARR3_MULTILAYER
683691
} else if (looksLikeNeuroglancerPrecomputed(dataSourceDir, 1).getOrElse(false)) {
684692
UploadedDataSourceType.NEUROGLANCER_PRECOMPUTED
685693
} else if (looksLikeNeuroglancerPrecomputed(dataSourceDir, 2).getOrElse(false)) {
@@ -705,6 +713,9 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
705713
private def looksLikeZarrArray(dataSourceDir: Path, maxDepth: Int): Box[Boolean] =
706714
containsMatchingFile(List(FILENAME_DOT_ZARRAY, FILENAME_DOT_ZATTRS), dataSourceDir, maxDepth)
707715

716+
private def looksLikeZarr3Array(dataSourceDir: Path, maxDepth: Int): Box[Boolean] =
717+
containsMatchingFile(List(FILENAME_ZARR_JSON), dataSourceDir, maxDepth)
718+
708719
private def looksLikeNeuroglancerPrecomputed(dataSourceDir: Path, maxDepth: Int): Box[Boolean] =
709720
containsMatchingFile(List(FILENAME_INFO), dataSourceDir, maxDepth)
710721

@@ -754,14 +765,20 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
754765
private def getZarrLayerDirectories(dataSourceDir: Path): Box[Seq[Path]] =
755766
for {
756767
potentialLayers <- PathUtils.listDirectories(dataSourceDir, silent = false)
757-
layerDirs = potentialLayers.filter(p => looksLikeZarrArray(p, maxDepth = 2).isDefined)
768+
layerDirs = potentialLayers.filter(p => looksLikeZarrArray(p, maxDepth = 2).getOrElse(false))
769+
} yield layerDirs
770+
771+
private def getZarr3LayerDirectories(dataSourceDir: Path): Box[Seq[Path]] =
772+
for {
773+
potentialLayers <- PathUtils.listDirectories(dataSourceDir, silent = false)
774+
layerDirs = potentialLayers.filter(p => looksLikeZarr3Array(p, maxDepth = 2).getOrElse(false))
758775
} yield layerDirs
759776

760777
private def addLayerAndMagDirIfMissing(dataSourceDir: Path, headerFile: String = FILENAME_HEADER_WKW): Box[Unit] =
761778
if (Files.exists(dataSourceDir)) {
762779
for {
763780
listing: Seq[Path] <- PathUtils.listFilesRecursive(dataSourceDir,
764-
maxDepth = 2,
781+
maxDepth = 3,
765782
silent = false,
766783
filters = p => p.getFileName.toString == headerFile)
767784
listingRelative = listing.map(dataSourceDir.normalize().relativize(_))
@@ -777,17 +794,19 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
777794
} yield ()
778795
} else Full(())
779796

780-
private def looksLikeMagDir(headerWkwPaths: Seq[Path]): Boolean =
781-
headerWkwPaths.headOption.exists { oneHeaderWkwPath =>
782-
pathDepth(oneHeaderWkwPath) == 0
783-
}
797+
private def looksLikeMagDir(headerFilePaths: Seq[Path]): Boolean =
798+
pathExistsWithDepth(0, headerFilePaths) && !pathExistsWithDepth(1, headerFilePaths) && !pathExistsWithDepth(
799+
2,
800+
headerFilePaths)
784801

785-
private def pathDepth(path: Path) = path.toString.count(_ == '/')
802+
private def looksLikeLayerDir(headerFilePaths: Seq[Path]): Boolean =
803+
pathExistsWithDepth(1, headerFilePaths) && !pathExistsWithDepth(2, headerFilePaths)
786804

787-
private def looksLikeLayerDir(headerWkwPaths: Seq[Path]): Boolean =
788-
headerWkwPaths.headOption.exists { oneHeaderWkwPath =>
789-
pathDepth(oneHeaderWkwPath) == 1
790-
}
805+
private def pathExistsWithDepth(pathDepth: Int, paths: Seq[Path]) =
806+
paths.exists(getPathDepth(_) == pathDepth)
807+
808+
private def getPathDepth(path: Path) =
809+
path.toString.count(_ == '/')
791810

792811
private def unpackDataset(uploadDir: Path, unpackToDir: Path, datasetId: ObjectId): Fox[Unit] =
793812
for {
@@ -897,6 +916,6 @@ class UploadService @Inject()(dataSourceService: DataSourceService,
897916
}
898917

899918
object UploadedDataSourceType extends Enumeration {
900-
val ZARR, EXPLORED, ZARR_MULTILAYER, WKW, NEUROGLANCER_PRECOMPUTED, NEUROGLANCER_MULTILAYER, N5_MULTISCALES,
901-
N5_MULTILAYER, N5_ARRAY = Value
919+
val ZARR, ZARR3, ZARR3_MULTILAYER, EXPLORED, ZARR_MULTILAYER, WKW, NEUROGLANCER_PRECOMPUTED, NEUROGLANCER_MULTILAYER,
920+
N5_MULTISCALES, N5_MULTILAYER, N5_ARRAY = Value
902921
}

0 commit comments

Comments
 (0)