Skip to content

Commit 4766276

Browse files
committed
feat: introduce ZARR3 StorageFormat, repurpose ZARR3
ZARR storage previously referred to zarr 2. given the update to support zarr 3, ZARR3 was introduced. However, ZARR should still exists as a "generic zarr" format that prefers the newest zarr version. The behavior of ZARR is as follows For readers in order: - return a Zarr3 reader - else return a Zarr2 reader For Writers, in order of attempt: - if the location contains a zarr3 container, try to open a zarr3 writer - else if the location contains a zarr2 container, try to open a zarr2 writer - else if there is no existing zarr container, attempt to create a Zarr3 container. - if a Zarr3 container could not be created, create a zarr2 container. Signed-off-by: Caleb Hulbert <cmhulbert@gmail.com>
1 parent 4a80f73 commit 4766276

2 files changed

Lines changed: 60 additions & 6 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/universe/N5Factory.java

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ public N5Reader openFSReader(final String path) {
244244
@Deprecated
245245
public N5Reader openZarrReader(final String path) {
246246

247-
return openN5ContainerWithStorageFormat(StorageFormat.ZARR2, path, this::openReader);
247+
return openN5ContainerWithStorageFormat(StorageFormat.ZARR, path, this::openReader);
248248
}
249249

250250
/**
@@ -430,17 +430,37 @@ public N5Reader openReader(@Nullable final StorageFormat storage, @Nullable fina
430430
switch (storage) {
431431
case N5:
432432
return new N5KeyValueReader(access, containerPath, gsonBuilder, cacheAttributes);
433-
case ZARR:
433+
case ZARR3:
434434
return new ZarrV3KeyValueReader(access,containerPath, gsonBuilder, cacheAttributes);
435435
case ZARR2:
436436
return new ZarrKeyValueReader(access, containerPath, gsonBuilder, zarrMapN5DatasetAttributes, zarrMergeAttributes, cacheAttributes);
437+
case ZARR:
438+
return newGenericZarrReader(access, location);
437439
case HDF5:
438440
return new N5HDF5Reader(containerPath, hdf5OverrideBlockSize, gsonBuilder, hdf5DefaultBlockSize);
439441
}
440442
return null;
441443
}
442444
}
443445

446+
/**
447+
* Open a zarr as N5Reader at the given {@code access} and {@code location}.
448+
* Will prefer returning the newest version of zarr that is found at the location.
449+
*
450+
* @param access to the key-value access backend
451+
* @param location of the zarr container
452+
* @return an N5Reader
453+
*/
454+
private N5Reader newGenericZarrReader(final KeyValueAccess access, final URI location) {
455+
for (StorageFormat zarrFormat : List.of(ZARR3, ZARR2)) {
456+
try {
457+
return openReader(zarrFormat, access, location);
458+
} catch (Exception ignored) {
459+
}
460+
}
461+
throw new N5Exception("Unable to open Zarr reader at " + location.toString() + " as N5Reader");
462+
}
463+
444464
/**
445465
* Open an {@link N5Writer} for N5 Container.
446466
* <p>
@@ -473,7 +493,7 @@ public N5Writer openFSWriter(final String path) {
473493
@Deprecated
474494
public N5Writer openZarrWriter(final String path) {
475495

476-
return openN5ContainerWithStorageFormat(StorageFormat.ZARR2, path, this::openWriter);
496+
return openN5ContainerWithStorageFormat(StorageFormat.ZARR, path, this::openWriter);
477497
}
478498

479499
/**
@@ -636,12 +656,14 @@ public N5Writer openWriter(@Nullable final StorageFormat storage, @Nullable fina
636656

637657
final String containerLocation = location.toString();
638658
switch (storage) {
639-
case ZARR:
659+
case ZARR3:
640660
final ZarrV3KeyValueWriter writer = new ZarrV3KeyValueWriter(access, containerLocation, gsonBuilder, cacheAttributes);
641661
writer.setDimensionSeparator(zarrDimensionSeparator);
642662
return writer;
643663
case ZARR2:
644664
return new ZarrKeyValueWriter(access, containerLocation, gsonBuilder, zarrMapN5DatasetAttributes, zarrMergeAttributes, zarrDimensionSeparator, cacheAttributes);
665+
case ZARR:
666+
return newGenericZarrWriter(access, location);
645667
case N5:
646668
return new N5KeyValueWriter(access, containerLocation, gsonBuilder, cacheAttributes);
647669
case HDF5:
@@ -651,6 +673,37 @@ public N5Writer openWriter(@Nullable final StorageFormat storage, @Nullable fina
651673
return null;
652674
}
653675

676+
/**
677+
* Try to get a zarr writer at the given location and access.
678+
* If a container exists at the location, load that version as a writer if possible.
679+
* If no container exists, create a new writer with the newest zarr version.
680+
*
681+
* @param access to the key-value backend
682+
* @param location of the zarr writer
683+
* @return the zarr writer
684+
*/
685+
private N5Writer newGenericZarrWriter(final KeyValueAccess access, final URI location) {
686+
List<StorageFormat> zarrFormats = List.of(ZARR3, ZARR2);
687+
for (StorageFormat zarrFormat : zarrFormats) {
688+
/* we dont care about the read, but we do want to prefer a writer over a container that
689+
* exists, rather than creating a new writer; the only way to check is to see if
690+
* we can get a valid reader. If we can, try the writer. */
691+
try (N5Reader ignore = openReader(zarrFormat, access, location)) {
692+
return openWriter(zarrFormat, access, location);
693+
} catch (Exception ignored) {
694+
}
695+
}
696+
/* However, if we have no valid readers, then try and return the first valid (created) writer */
697+
for (StorageFormat zarrFormat : zarrFormats) {
698+
try {
699+
return openWriter(zarrFormat, access, location);
700+
} catch (Exception ignored) {
701+
}
702+
}
703+
704+
throw new N5Exception("Unable to open Zarr writer at " + location.toString() + " as N5Reader");
705+
}
706+
654707
private <T extends N5Reader> T openN5ContainerWithStorageFormat(
655708
final StorageFormat format,
656709
final String uri,

src/main/java/org/janelia/saalfeldlab/n5/universe/StorageFormat.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
import java.util.regex.Pattern;
1919

2020
public enum StorageFormat {
21-
ZARR(Pattern.compile("zarr(3)?", Pattern.CASE_INSENSITIVE), uri -> Pattern.compile("\\.zarr$", Pattern.CASE_INSENSITIVE).matcher(new File(uri.getPath()).toString()).find()),
21+
ZARR(Pattern.compile("zarr", Pattern.CASE_INSENSITIVE), uri -> Pattern.compile("\\.zarr$", Pattern.CASE_INSENSITIVE).matcher(new File(uri.getPath()).toString()).find()),
22+
ZARR3(Pattern.compile("zarr3", Pattern.CASE_INSENSITIVE), uri -> Pattern.compile("\\.zarr$", Pattern.CASE_INSENSITIVE).matcher(new File(uri.getPath()).toString()).find()),
2223
ZARR2(Pattern.compile("zarr2", Pattern.CASE_INSENSITIVE), uri -> Pattern.compile("\\.zarr$", Pattern.CASE_INSENSITIVE).matcher(new File(uri.getPath()).toString()).find()),
2324
N5(Pattern.compile("n5", Pattern.CASE_INSENSITIVE), uri -> Pattern.compile("\\.n5$", Pattern.CASE_INSENSITIVE).matcher(new File(uri.getPath()).toString()).find()),
2425
HDF5(Pattern.compile("h(df)?5", Pattern.CASE_INSENSITIVE), uri -> {
@@ -93,7 +94,7 @@ public static Pair<StorageFormat, String> getStorageFromNestedScheme(String uri)
9394
return StorageFormat.HDF5;
9495
} catch (final Exception ignore) {}
9596
if (kva.exists(kva.compose(uri, ZARR3_ATTRIBUTES)))
96-
return StorageFormat.ZARR;
97+
return StorageFormat.ZARR3;
9798
if (Arrays.stream(ZARR2_KEYS).anyMatch(it -> kva.exists(kva.compose(uri, it))))
9899
return StorageFormat.ZARR2;
99100
if (kva.exists(kva.compose(uri, N5_ATTRIBUTES)))

0 commit comments

Comments
 (0)