From d876f752d101760148075672416837d6f9fcb35f Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Fri, 3 Oct 2025 11:30:48 +0100 Subject: [PATCH 1/2] Bump dependencies --- pom.xml | 39 ++++------- src/loci/formats/S3FileSystemStore.java | 64 ++++++++++--------- .../formats/utests/JZarrServiceImplTest.java | 2 +- test/loci/formats/utests/ZarrReaderMock.java | 2 +- test/loci/formats/utests/ZarrReaderTest.java | 2 +- 5 files changed, 49 insertions(+), 60 deletions(-) diff --git a/pom.xml b/pom.xml index 5090caa..2d6cbbe 100644 --- a/pom.xml +++ b/pom.xml @@ -54,8 +54,9 @@ ome formats-api - 7.3.1 + 8.3.0 + dev.zarr jzarr @@ -80,6 +81,12 @@ + + dev.zarr + zarr-java + 0.0.5-SNAPSHOT + + org.mockito mockito-inline @@ -94,36 +101,16 @@ test - - - xalan - serializer - 2.7.3 - runtime - - - - xalan - xalan - 2.7.3 - runtime - - - com.amazonaws - aws-java-sdk-s3 - 1.12.659 - - - commons-logging - commons-logging - - + software.amazon.awssdk + s3 + 2.34.6 + commons-logging commons-logging - 1.2 + 1.3.5 diff --git a/src/loci/formats/S3FileSystemStore.java b/src/loci/formats/S3FileSystemStore.java index 2d5a40f..ed06b67 100644 --- a/src/loci/formats/S3FileSystemStore.java +++ b/src/loci/formats/S3FileSystemStore.java @@ -37,6 +37,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URI; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.LinkOption; @@ -51,21 +52,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.ListObjectsRequest; -import com.amazonaws.services.s3.model.ObjectListing; -import com.amazonaws.services.s3.model.S3Object; -import com.amazonaws.services.s3.model.S3ObjectInputStream; -import com.amazonaws.services.s3.model.S3ObjectSummary; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.AnonymousAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.core.ResponseInputStream; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectResponse; +import software.amazon.awssdk.services.s3.model.ListObjectsRequest; +import software.amazon.awssdk.services.s3.model.ListObjectsResponse; +import software.amazon.awssdk.services.s3.model.S3Object; public class S3FileSystemStore implements Store { private Path root; - AmazonS3 client; + S3Client client; public static final String ENDPOINT_PROTOCOL= "https://"; protected static final Logger LOGGER = LoggerFactory.getLogger(S3FileSystemStore.class); @@ -91,10 +90,9 @@ private void setupClient() { String[] pathSplit = root.toString().split(File.separator); String endpoint = ENDPOINT_PROTOCOL + pathSplit[1] + File.separator; try { - client = AmazonS3ClientBuilder.standard() - .withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, "auto")) - .withPathStyleAccessEnabled(true) - .withCredentials(new AWSStaticCredentialsProvider(new AnonymousAWSCredentials())).build(); + client = S3Client.builder() + .endpointOverride(new URI(endpoint)) + .credentialsProvider(AnonymousCredentialsProvider.create()).build(); } catch (Exception e) { LOGGER.info("Exception caught while constructing S3 client", e); } @@ -103,7 +101,7 @@ private void setupClient() { public void close() { if (client != null) { - client.shutdown(); + client.close(); } } @@ -122,9 +120,9 @@ public InputStream getInputStream(String key) throws IOException { String key2 = root.toString().substring(root.toString().indexOf(pathSplit[3]), root.toString().length()) + File.separator + key; try { - S3Object o = client.getObject(bucketName, key2); - S3ObjectInputStream responseStream = o.getObjectContent(); - return responseStream; + ResponseInputStream o = client.getObject(GetObjectRequest.builder().bucket(bucketName).key(key2) + .build()); + return o; } catch (Exception e) { LOGGER.info( "Unable to locate or access key: " + key2, e); } @@ -207,29 +205,33 @@ private TreeSet getKeysFor(String suffix) throws IOException { // Append the desired key onto the remaining prefix String key2 = root.toString().substring(root.toString().indexOf(pathSplit[3]), root.toString().length()); - ListObjectsRequest listObjectsRequest = new ListObjectsRequest() - .withBucketName(bucketName) - .withPrefix(key2) + ListObjectsRequest listObjectsRequest = ListObjectsRequest.builder() + .bucket(bucketName) + .prefix(key2) + .build() ; - ObjectListing listObjectsResponse = null; + ListObjectsResponse listObjectsResponse = null; String lastKey = null; do { if ( listObjectsResponse != null ) { - listObjectsRequest = listObjectsRequest - .withMarker(lastKey) - ; + listObjectsRequest = ListObjectsRequest.builder() + .bucket(bucketName) + .prefix(key2) + .marker(lastKey) + .build(); } + + listObjectsResponse = client.listObjects(listObjectsRequest); - listObjectsResponse = client.listObjects(listObjectsRequest); - List objects = listObjectsResponse.getObjectSummaries(); + List objects = listObjectsResponse.contents(); // Iterate over results - ListIterator iterVals = objects.listIterator(); + ListIterator iterVals = objects.listIterator(); while (iterVals.hasNext()) { - S3ObjectSummary object = (S3ObjectSummary) iterVals.next(); - String k = object.getKey(); + S3Object object = (S3Object) iterVals.next(); + String k = object.key(); if (k.contains(suffix)) { String key = k.substring(k.indexOf(key2) + key2.length() + 1, k.indexOf(suffix)); if (!key.isEmpty()) { diff --git a/test/loci/formats/utests/JZarrServiceImplTest.java b/test/loci/formats/utests/JZarrServiceImplTest.java index 220b223..0b617b3 100644 --- a/test/loci/formats/utests/JZarrServiceImplTest.java +++ b/test/loci/formats/utests/JZarrServiceImplTest.java @@ -1,4 +1,4 @@ -package test.loci.formats.utests; +package loci.formats.utests; /*- * #%L diff --git a/test/loci/formats/utests/ZarrReaderMock.java b/test/loci/formats/utests/ZarrReaderMock.java index bc0cd23..7bd38f8 100644 --- a/test/loci/formats/utests/ZarrReaderMock.java +++ b/test/loci/formats/utests/ZarrReaderMock.java @@ -1,4 +1,4 @@ -package test.loci.formats.utests; +package loci.formats.utests; /*- * #%L diff --git a/test/loci/formats/utests/ZarrReaderTest.java b/test/loci/formats/utests/ZarrReaderTest.java index 7c45fe1..7b3ac76 100644 --- a/test/loci/formats/utests/ZarrReaderTest.java +++ b/test/loci/formats/utests/ZarrReaderTest.java @@ -1,4 +1,4 @@ -package test.loci.formats.utests; +package loci.formats.utests; /*- * #%L From c31b12bcdd9cbb4a739c72426e1656b0b7fba038 Mon Sep 17 00:00:00 2001 From: Dominik Lindner Date: Wed, 5 Nov 2025 12:07:52 +0000 Subject: [PATCH 2/2] Remove jzarr, use zarr-java for v2 and v3 --- pom.xml | 63 ++-- src/loci/formats/S3FileSystemStore.java | 247 -------------- src/loci/formats/in/ZarrReader.java | 216 +++++------- .../formats/services/JZarrServiceImpl.java | 320 ++++++------------ src/loci/formats/services/ZarrLocation.java | 289 ++++++++++++++++ src/loci/formats/services/ZarrService.java | 35 +- .../formats/utests/JZarrServiceImplTest.java | 281 --------------- test/loci/formats/utests/ZarrReaderMock.java | 50 --- test/loci/formats/utests/ZarrReaderTest.java | 289 ---------------- 9 files changed, 484 insertions(+), 1306 deletions(-) delete mode 100644 src/loci/formats/S3FileSystemStore.java create mode 100644 src/loci/formats/services/ZarrLocation.java delete mode 100644 test/loci/formats/utests/JZarrServiceImplTest.java delete mode 100644 test/loci/formats/utests/ZarrReaderMock.java delete mode 100644 test/loci/formats/utests/ZarrReaderTest.java diff --git a/pom.xml b/pom.xml index 2d6cbbe..f6cec41 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,17 @@ + + ${basedir}/../.. + 1.8 + 1.8 + UTF-8 + + 0.0.5-SNAPSHOT + 2.20.0 + 2.34.6 + + ome @@ -57,54 +68,25 @@ 8.3.0 - - dev.zarr - jzarr - 0.4.2 - - - com.fasterxml.jackson.core - jackson-databind - - - com.fasterxml.jackson.core - jackson-core - - - com.fasterxml.jackson.core - jackson-annotations - - - org.apache.httpcomponents - httpcore - - - - dev.zarr zarr-java - 0.0.5-SNAPSHOT + ${zarr-java.version} - - org.mockito - mockito-inline - 3.7.7 - test + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} - - org.testng - testng - 6.10 - test + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + ${jackson.version} - software.amazon.awssdk s3 - 2.34.6 + ${aws.version} @@ -114,13 +96,6 @@ - - ${basedir}/../.. - 1.8 - 1.8 - UTF-8 - - ${project.basedir}/src ${project.basedir}/test diff --git a/src/loci/formats/S3FileSystemStore.java b/src/loci/formats/S3FileSystemStore.java deleted file mode 100644 index ed06b67..0000000 --- a/src/loci/formats/S3FileSystemStore.java +++ /dev/null @@ -1,247 +0,0 @@ -package loci.formats; - -/*- - * #%L - * Implementation of Bio-Formats readers for the next-generation file formats - * %% - * Copyright (C) 2020 - 2022 Open Microscopy Environment - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -import com.bc.zarr.ZarrConstants; -import com.bc.zarr.ZarrUtils; -import com.bc.zarr.storage.Store; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.LinkOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.ListIterator; -import java.util.TreeSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; -import software.amazon.awssdk.core.ResponseInputStream; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetObjectRequest; -import software.amazon.awssdk.services.s3.model.GetObjectResponse; -import software.amazon.awssdk.services.s3.model.ListObjectsRequest; -import software.amazon.awssdk.services.s3.model.ListObjectsResponse; -import software.amazon.awssdk.services.s3.model.S3Object; - -public class S3FileSystemStore implements Store { - - private Path root; - S3Client client; - public static final String ENDPOINT_PROTOCOL= "https://"; - protected static final Logger LOGGER = - LoggerFactory.getLogger(S3FileSystemStore.class); - - public S3FileSystemStore(String path, FileSystem fileSystem) { - if (fileSystem == null) { - root = Paths.get(path); - } else { - root = fileSystem.getPath(path); - } - setupClient(); - } - - public void updateRoot(String path) { - root = Paths.get(path); - } - - public String getRoot() { - return root.toString(); - } - - private void setupClient() { - String[] pathSplit = root.toString().split(File.separator); - String endpoint = ENDPOINT_PROTOCOL + pathSplit[1] + File.separator; - try { - client = S3Client.builder() - .endpointOverride(new URI(endpoint)) - .credentialsProvider(AnonymousCredentialsProvider.create()).build(); - } catch (Exception e) { - LOGGER.info("Exception caught while constructing S3 client", e); - } - - } - - public void close() { - if (client != null) { - client.close(); - } - } - - public S3FileSystemStore(Path rootPath) { - root = rootPath; - setupClient(); - } - - @Override - public InputStream getInputStream(String key) throws IOException { - // Get the base bucket name from splitting the root path and removing the prefixed protocol and end-point - String[] pathSplit = root.toString().split(File.separator); - String bucketName = pathSplit[2]; - - // Append the desired key onto the remaining prefix - String key2 = root.toString().substring(root.toString().indexOf(pathSplit[3]), root.toString().length()) + File.separator + key; - - try { - ResponseInputStream o = client.getObject(GetObjectRequest.builder().bucket(bucketName).key(key2) - .build()); - return o; - } catch (Exception e) { - LOGGER.info( "Unable to locate or access key: " + key2, e); - } - - return null; - } - - @Override - public OutputStream getOutputStream(String key) throws IOException { - final Path filePath = root.resolve(key); - final Path dir = filePath.getParent(); - Files.createDirectories(dir); - return Files.newOutputStream(filePath); - } - - @Override - public void delete(String key) throws IOException { - final Path toBeDeleted = root.resolve(key); - if (Files.isDirectory(toBeDeleted)) { - ZarrUtils.deleteDirectoryTreeRecursively(toBeDeleted); - } - if (Files.exists(toBeDeleted)){ - Files.delete(toBeDeleted); - } - if (Files.exists(toBeDeleted)|| Files.isDirectory(toBeDeleted)) { - throw new IOException("Unable to initialize " + toBeDeleted.toAbsolutePath().toString()); - } - } - - @Override - public TreeSet getArrayKeys() throws IOException { - return getKeysFor(ZarrConstants.FILENAME_DOT_ZARRAY); - } - - @Override - public TreeSet getGroupKeys() throws IOException { - return getKeysFor(ZarrConstants.FILENAME_DOT_ZGROUP); - } - - /** - * Copied from {@com.bc.zarr.storage.FileSystemStorage#getKeysEndingWith(String). - * - * @param suffix - * @return - * @throws IOException - */ - public TreeSet getKeysEndingWith(String suffix) throws IOException { - return (TreeSet)Files.walk(this.root).filter((path) -> { - return path.toString().endsWith(suffix); - }).map((path) -> { - return this.root.relativize(path).toString(); - }).collect(Collectors.toCollection(TreeSet::new)); - } - - /** - * Copied from {@com.bc.zarr.storage.FileSystemStorage#getRelativeLeafKeys(String). - * - * @param key - * @return - * @throws IOException - */ - public Stream getRelativeLeafKeys(String key) throws IOException { - Path walkingRoot = this.root.resolve(key); - return Files.walk(walkingRoot).filter((path) -> { - return !Files.isDirectory(path, new LinkOption[0]); - }).map((path) -> { - return walkingRoot.relativize(path).toString(); - }).map(ZarrUtils::normalizeStoragePath).filter((s) -> { - return s.trim().length() > 0; - }); - } - - private TreeSet getKeysFor(String suffix) throws IOException { - TreeSet keys = new TreeSet(); - - // Get the base bucket name from splitting the root path and removing the prefixed protocol and end-point - String[] pathSplit = root.toString().split(File.separator); - String bucketName = pathSplit[2]; - - // Append the desired key onto the remaining prefix - String key2 = root.toString().substring(root.toString().indexOf(pathSplit[3]), root.toString().length()); - - ListObjectsRequest listObjectsRequest = ListObjectsRequest.builder() - .bucket(bucketName) - .prefix(key2) - .build() - ; - - ListObjectsResponse listObjectsResponse = null; - String lastKey = null; - - do { - if ( listObjectsResponse != null ) { - listObjectsRequest = ListObjectsRequest.builder() - .bucket(bucketName) - .prefix(key2) - .marker(lastKey) - .build(); - } - - listObjectsResponse = client.listObjects(listObjectsRequest); - - List objects = listObjectsResponse.contents(); - - // Iterate over results - ListIterator iterVals = objects.listIterator(); - while (iterVals.hasNext()) { - S3Object object = (S3Object) iterVals.next(); - String k = object.key(); - if (k.contains(suffix)) { - String key = k.substring(k.indexOf(key2) + key2.length() + 1, k.indexOf(suffix)); - if (!key.isEmpty()) { - keys.add(key.substring(0, key.length()-1)); - } - } - lastKey = k; - } - } while ( listObjectsResponse.isTruncated() ); - - return keys; - } -} \ No newline at end of file diff --git a/src/loci/formats/in/ZarrReader.java b/src/loci/formats/in/ZarrReader.java index bf8822f..b48ac75 100644 --- a/src/loci/formats/in/ZarrReader.java +++ b/src/loci/formats/in/ZarrReader.java @@ -31,9 +31,11 @@ import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.ByteBuffer; import java.nio.file.FileVisitOption; import java.util.ArrayList; import java.util.Collections; @@ -51,9 +53,7 @@ import org.w3c.dom.Document; import org.xml.sax.SAXException; -import com.bc.zarr.JZarrException; -import com.bc.zarr.ZarrUtils; - +import dev.zarr.zarrjava.store.StoreHandle; import loci.common.DataTools; import loci.common.Location; import loci.common.RandomAccessInputStream; @@ -78,6 +78,7 @@ import ome.xml.model.primitives.PositiveInteger; import ome.xml.model.primitives.Timestamp; import loci.formats.services.OMEXMLService; +import loci.formats.services.ZarrLocation; import loci.formats.services.ZarrService; @@ -112,6 +113,8 @@ public class ZarrReader extends FormatReader { private boolean hasSPW = false; private transient int currentOpenZarr = -1; + private ZarrLocation zarr; + public ZarrReader() { super("Zarr", "zarr"); suffixSufficient = false; @@ -129,11 +132,12 @@ public int getRequiredDirectories(String[] files) /* @see loci.formats.IFormatReader#isThisType(String, boolean) */ @Override public boolean isThisType(String name, boolean open) { - Location zarrFolder = new Location(name); - if (zarrFolder != null && zarrFolder.getAbsolutePath().toLowerCase().indexOf(".zarr") > 0) { + try { + new ZarrLocation(name); return true; + } catch (URISyntaxException e) { + return false; } - return super.isThisType(name, open); } /* @see loci.formats.IFormatReader#close() */ @@ -179,96 +183,62 @@ protected void initFile(String id) throws FormatException, IOException { super.initFile(id); LOGGER.debug("ZarrReader attempting to initialize file: {}", id); final MetadataStore store = makeFilterMetadata(); - Location zarrFolder = new Location(id); - String zarrPath = zarrFolder.getAbsolutePath(); - String zarrRootPath = zarrPath.substring(0, zarrPath.indexOf(".zarr") + 5); - String name = zarrRootPath.substring(zarrRootPath.lastIndexOf(File.separator)+1, zarrRootPath.length() - 5); - Location omeMetaFile = new Location( zarrRootPath + File.separator + "OME", "METADATA.ome.xml" ); - String canonicalPath = new Location(zarrRootPath).getCanonicalPath(); - - initializeZarrService(); - reloadOptionsFile(zarrRootPath); + + try { + this.zarr = new ZarrLocation(id); + } catch (IllegalArgumentException | URISyntaxException e) { + throw new FormatException("Failed to initialize ZarrLocation", e); + } + + StoreHandle omeMetaFile = zarr.getStoreHandle().resolve("OME", "METADATA.ome.xml" ); + + initializeZarrService(zarr); + reloadOptionsFile(zarr.getPath()); ArrayList omeSeriesOrder = new ArrayList(); if(omeMetaFile.exists()) { LOGGER.debug("ZarrReader parsing existing OME-XML"); parseOMEXML(omeMetaFile, store, omeSeriesOrder); } + // Parse base level attributes - Map attr = zarrService.getGroupAttr(canonicalPath); + Map attr = zarr.metadataFromGroup("0"); + if (attr.containsKey("ome")) { + attr = (Map) attr.get("ome"); + } int attrIndex = 0; if (attr != null && !attr.isEmpty()) { - parseResolutionCount(zarrRootPath, "", attr); + parseResolutionCount("0", attr); parseOmeroMetadata(attr); - if (saveAnnotations()) { - String jsonAttr; - try { - jsonAttr = ZarrUtils.toJson(attr, true); - store.setXMLAnnotationValue(jsonAttr, attrIndex); - String xml_id = MetadataTools.createLSID("Annotation", attrIndex); - store.setXMLAnnotationID(xml_id, attrIndex); - } catch (JZarrException e) { - LOGGER.warn("Failed to convert attributes to JSON"); - e.printStackTrace(); - } - } } - generateGroupKeys(attr, canonicalPath); - // Parse group attributes - if (groupKeys.isEmpty()) { - LOGGER.debug("ZarrReader adding group keys from ZarrService"); - groupKeys.addAll(zarrService.getGroupKeys(canonicalPath)); - } + if (attr.containsKey("plate")) { + generateGroupKeys(attr); - List orderedGroupKeys = reorderGroupKeys(groupKeys, omeSeriesOrder); - for (String key: orderedGroupKeys) { - Map attributes = zarrService.getGroupAttr(canonicalPath+File.separator+key); - if (attributes != null && !attributes.isEmpty()) { - parseResolutionCount(zarrRootPath, key, attributes); - parseLabels(zarrRootPath, attributes); - parseImageLabels(zarrRootPath, attributes); - attrIndex++; - if (saveAnnotations()) { - String jsonAttr; - try { - jsonAttr = ZarrUtils.toJson(attributes, true); - store.setXMLAnnotationValue(jsonAttr, attrIndex); - String xml_id = MetadataTools.createLSID("Annotation", attrIndex); - store.setXMLAnnotationID(xml_id, attrIndex); - } catch (JZarrException e) { - LOGGER.warn("Failed to convert attributes to JSON"); - e.printStackTrace(); - } - } + // Parse group attributes + if (groupKeys.isEmpty()) { + LOGGER.debug("ZarrReader adding group keys from ZarrService"); + groupKeys.addAll(zarr.metadataFromGroup("0").keySet()); } - } - - // Parse array attributes - generateArrayKeys(attr, canonicalPath); - if (arrayPaths.isEmpty()) { - LOGGER.debug("ZarrReader adding Array Keys from ZarrService"); - arrayPaths.addAll(zarrService.getArrayKeys(canonicalPath)); - } - orderArrayPaths(zarrRootPath); - if (saveAnnotations()) { - for (String key: arrayPaths) { - Map attributes = zarrService.getArrayAttr(zarrRootPath+File.separator+key); + List orderedGroupKeys = reorderGroupKeys(groupKeys, omeSeriesOrder); + for (String key : orderedGroupKeys) { + Map attributes = zarr.metadataFromGroup(key); if (attributes != null && !attributes.isEmpty()) { + parseResolutionCount(key, attributes); + parseLabels(attributes); + parseImageLabels(attributes); attrIndex++; - String jsonAttr; - try { - jsonAttr = ZarrUtils.toJson(attributes, true); - store.setXMLAnnotationValue(jsonAttr, attrIndex); - String xml_id = MetadataTools.createLSID("Annotation", attrIndex); - store.setXMLAnnotationID(xml_id, attrIndex); - } catch (JZarrException e) { - LOGGER.warn("Failed to convert attributes to JSON"); - e.printStackTrace(); - } } } + + // Parse array attributes + generateArrayKeys(attr); + if (arrayPaths.isEmpty()) { + LOGGER.debug("ZarrReader adding Array Keys from ZarrService"); + arrayPaths.addAll(zarr.metadataFromArray("0").keySet()); + } + orderArrayPaths(); } core.clear(); @@ -352,8 +322,8 @@ protected void initFile(String id) throws FormatException, IOException { store.setImageName(arrayPaths.get(seriesToCoreIndex(i)), i); store.setImageID(MetadataTools.createLSID("Image", i), i); } - parsePlate(attr, zarrRootPath, "", store); setSeries(0); + parsePlate(attr, "", store); LOGGER.debug("ZarrReader initialization complete"); } @@ -456,16 +426,16 @@ private static int[] getOriginalShape(int [] shape5D, int size) { @Override public void reopenFile() throws IOException { try { - String canonicalPath = new Location(currentId).getCanonicalPath(); - initializeZarrService(); + ZarrLocation zarr = new ZarrLocation(currentId); + initializeZarrService(zarr); } - catch (FormatException e) { + catch (Exception e) { throw new IOException(e); } } - protected void initializeZarrService() throws IOException, FormatException { - zarrService = new JZarrServiceImpl(altStore()); + protected void initializeZarrService(ZarrLocation zarr) throws IOException, FormatException { + zarrService = new JZarrServiceImpl(zarr); openZarr(); } @@ -567,18 +537,14 @@ public void setResolution(int no, boolean openZarr) { private void openZarr() { try { if (currentId != null && zarrService != null) { - String zarrRootPath = currentId.substring(0, currentId.indexOf(".zarr")+5); - String newZarrPath = zarrRootPath; if (arrayPaths != null && !arrayPaths.isEmpty()) { int seriesIndex = seriesToCoreIndex(series); if (!hasFlattenedResolutions()) { seriesIndex += resolution; } if (seriesIndex != currentOpenZarr) { - newZarrPath += File.separator + arrayPaths.get(seriesIndex); - String canonicalPath = new Location(newZarrPath).getCanonicalPath(); - LOGGER.debug("Opening zarr for series {} at path: {}", seriesIndex, canonicalPath); - zarrService.open(canonicalPath); + LOGGER.debug("Opening zarr for series {} at path: {}", seriesIndex, arrayPaths.get(seriesIndex)); + zarrService.open(arrayPaths.get(seriesIndex)); currentOpenZarr = seriesIndex; } } @@ -588,7 +554,7 @@ private void openZarr() { } } - private void orderArrayPaths(String root) { + private void orderArrayPaths() { for (int i = 0; i < resSeries.size(); i++) { for (String arrayPath: resSeries.get(i)) { arrayPaths.remove(arrayPath); @@ -601,7 +567,7 @@ private void orderArrayPaths(String root) { } } - private void parseResolutionCount(String root, String key, Map attr) throws IOException, FormatException { + private void parseResolutionCount(String key, Map attr) throws IOException, FormatException { ArrayList multiscales = (ArrayList) attr.get("multiscales"); if (multiscales != null) { for (int x = 0; x < multiscales.size(); x++) { @@ -642,14 +608,15 @@ else if (multiscaleAxes.get(i) instanceof HashMap) { String scalePath = (String) multiScale.get("path"); int numRes = multiscalePaths.size(); if (i == 0) { - resCounts.put(key.isEmpty() ? scalePath : key + File.separator + scalePath, numRes); + resCounts.put(key.isEmpty() ? scalePath : key + "/" + scalePath, numRes); uniqueResCounts.add(numRes); } - resIndexes.put(key.isEmpty() ? scalePath : key + File.separator + scalePath, i); + resIndexes.put(key.isEmpty() ? scalePath : key + "/" + scalePath, i); ArrayList list = resSeries.get(resCounts.size() - 1); - list.add(key.isEmpty() ? scalePath : key + File.separator + scalePath); + list.add(key.isEmpty() ? scalePath : key + "/" + scalePath); resSeries.put(resCounts.size() - 1, list); - pathArrayDimensions.put(key.isEmpty() ? scalePath : key + File.separator + scalePath, pathDimensions); + pathArrayDimensions.put(key.isEmpty() ? scalePath : key + "/" + scalePath, pathDimensions); + arrayPaths.add(key.isEmpty() ? scalePath : key + "/" + scalePath); } List coordinateTransformations = (List)datasets.get("coordinateTransformations"); if (coordinateTransformations != null) { @@ -667,7 +634,7 @@ else if (multiscaleAxes.get(i) instanceof HashMap) { } } - private void generateArrayKeys(Map attr, String canonicalPath) { + private void generateArrayKeys(Map attr) { if (uniqueResCounts.size() != 1) { LOGGER.debug("Cannout automatically generate ArrayKeys as resolution counts differ"); } @@ -683,13 +650,8 @@ private void generateArrayKeys(Map attr, String canonicalPath) { for (int i = 0; i < fieldCount; i++) { int resolutionCount = (Integer)(uniqueResCounts.toArray())[0]; for (int j = 0; j < resolutionCount; j++) { - String key = rowName + File.separator + columnName + File.separator + i + File.separator + j; - if (Files.isDirectory(Paths.get(canonicalPath+File.separator+key))) { - arrayPaths.add(rowName + File.separator + columnName + File.separator + i + File.separator + j); - } - else { - LOGGER.debug("Skipping array path as sparse data: {}", key); - } + String key = rowName + "/" + columnName + "/" + i + "/" + j; + arrayPaths.add(key); } } } @@ -697,7 +659,7 @@ private void generateArrayKeys(Map attr, String canonicalPath) { } } - private void generateGroupKeys(Map attr, String canonicalPath) { + private void generateGroupKeys(Map attr) { Map plates = (Map) attr.get("plate"); if (plates != null) { ArrayList columns = (ArrayList)plates.get("columns"); @@ -706,36 +668,21 @@ private void generateGroupKeys(Map attr, String canonicalPath) { for (Object row: rows) { String rowName = ((Map) row).get("name"); - if (Files.isDirectory(Paths.get(canonicalPath+File.separator+rowName))) { - groupKeys.add(rowName); - } - else { - LOGGER.debug("Skipping group key as sparse data: {}", rowName); - } + groupKeys.add(rowName); for (Object column: columns) { String columnName = ((Map) column).get("name"); - String columnKey = rowName + File.separator + columnName; - if (Files.isDirectory(Paths.get(canonicalPath+File.separator+columnKey))) { - groupKeys.add(columnKey); - } - else { - LOGGER.debug("Skipping group key as sparse data: {}", columnKey); - } + String columnKey = rowName + "/" + columnName; + groupKeys.add(columnKey); for (int i = 0; i < fieldCount; i++) { - String key = rowName + File.separator + columnName + File.separator + i; - if (Files.isDirectory(Paths.get(canonicalPath+File.separator+key))) { - groupKeys.add(key); - } - else { - LOGGER.debug("Skipping group key as sparse data: {}", key); - } + String key = rowName + "/" + columnName + "/" + i; + groupKeys.add(key); } } } } } - private void parsePlate(Map attr, String root, String key, MetadataStore store) throws IOException, FormatException { + private void parsePlate(Map attr, String key, MetadataStore store) throws IOException, FormatException { Map plates = (Map) attr.get("plate"); if (plates != null) { ArrayList columns = (ArrayList)plates.get("columns"); @@ -828,16 +775,14 @@ private void parsePlate(Map attr, String root, String key, Metad } int wellIndex = (wellRowIndex * columns.size()) + wellColIndex; store.setWellExternalIdentifier(wellPath, 0, wellIndex); - parseWells(root, wellPath, store, 0, wellIndex, acqIdsIndexMap); + parseWells(wellPath, store, 0, wellIndex, acqIdsIndexMap); } } } - private void parseWells(String root, String key, MetadataStore store, int plateIndex, int wellIndex, + private void parseWells(String key, MetadataStore store, int plateIndex, int wellIndex, HashMap acqIdsIndexMap) throws IOException, FormatException { - String path = key.isEmpty() ? root : root + File.separator + key; - String canonicalPath = new Location(path).getCanonicalPath(); - Map attr = zarrService.getGroupAttr(canonicalPath); + Map attr = zarr.metadataFromGroup(key); Map wells = (Map) attr.get("well"); if (wells != null) { ArrayList images = (ArrayList)wells.get("images"); @@ -868,7 +813,7 @@ private void parseWells(String root, String key, MetadataStore store, int plateI } } - private void parseLabels(String root, Map attr) throws IOException, FormatException { + private void parseLabels(Map attr) throws IOException, FormatException { ArrayList labels = (ArrayList) attr.get("labels"); if (labels != null) { for (int l = 0; l < labels.size(); l++) { @@ -877,7 +822,7 @@ private void parseLabels(String root, Map attr) throws IOExcepti } } - private void parseImageLabels(String root, Map attr) throws IOException, FormatException { + private void parseImageLabels(Map attr) throws IOException, FormatException { Map imageLabel = (Map) attr.get("image-label"); if (imageLabel != null) { String version = (String) imageLabel.get("version"); @@ -946,11 +891,11 @@ public void parseOmeroMetadata(Map attr) throws IOException, For } } - private void parseOMEXML(Location omeMetaFile, MetadataStore store, ArrayList origSeries) throws IOException, FormatException { + private void parseOMEXML(StoreHandle omeMetaFile, MetadataStore store, ArrayList origSeries) throws IOException, FormatException { Document omeDocument = null; - try (RandomAccessInputStream measurement = - new RandomAccessInputStream(omeMetaFile.getAbsolutePath())) { try { + ByteBuffer buf = omeMetaFile.read(); + RandomAccessInputStream measurement = new RandomAccessInputStream(buf.array()); omeDocument = XMLTools.parseDOM(measurement); } catch (ParserConfigurationException e) { @@ -959,7 +904,6 @@ private void parseOMEXML(Location omeMetaFile, MetadataStore store, ArrayList attr; + /** * Default constructor. */ - public JZarrServiceImpl(String root) { - checkClassDependency(com.bc.zarr.ZarrArray.class); - if (root != null && (root.toLowerCase().contains("s3:") || root.toLowerCase().contains("s3."))) { - String[] pathSplit = root.toString().split(File.separator); - if (S3FileSystemStore.ENDPOINT_PROTOCOL.contains(pathSplit[0].toLowerCase())) { - s3fs = new S3FileSystemStore(Paths.get(root)); - } - else { - LOGGER.warn("Zarr Reader is not using S3FileSystemStore as this is currently for use with S3 configured with a https endpoint"); - } - } + public JZarrServiceImpl(ZarrLocation zarr) { + checkClassDependency(Array.class); + this.zarr = zarr; } @Override - public void open(String file) throws IOException, FormatException { - currentId = file; - zarrArray = getArray(file); - } - - public void open(String id, ZarrArray array) { - currentId = id; - zarrArray = array; - } - - public Map getGroupAttr(String path) throws IOException, FormatException { - return getGroup(path).getAttributes(); - } - - public Map getArrayAttr(String path) throws IOException, FormatException { - return getArray(path).getAttributes(); + public void open(String path) throws IOException, FormatException { + array = zarr.getArray(path); + attr = zarr.metadataFromArray(path); } - public Set getGroupKeys(String path) throws IOException, FormatException { - return getGroup(path).getGroupKeys(); - } - - public Set getArrayKeys(String path) throws IOException, FormatException { - return getGroup(path).getArrayKeys(); + private boolean isV2() { + return array instanceof dev.zarr.zarrjava.v2.Array; } public DataType getZarrPixelType(int pixType) { DataType pixelType = null; switch(pixType) { case FormatTools.INT8: - pixelType = DataType.i1; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.INT8 : dev.zarr.zarrjava.v3.DataType.INT8; break; case FormatTools.INT16: - pixelType = DataType.i2; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.INT16 : dev.zarr.zarrjava.v3.DataType.INT16; break; case FormatTools.INT32: - pixelType = DataType.i4; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.INT32 : dev.zarr.zarrjava.v3.DataType.INT32; break; case FormatTools.UINT8: - pixelType = DataType.u1; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.UINT8 : dev.zarr.zarrjava.v3.DataType.UINT8; break; case FormatTools.UINT16: - pixelType = DataType.u2; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.UINT16 : dev.zarr.zarrjava.v3.DataType.UINT16; break; case FormatTools.UINT32: - pixelType = DataType.u4; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.UINT32 : dev.zarr.zarrjava.v3.DataType.UINT32; break; case FormatTools.FLOAT: - pixelType = DataType.f4; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.FLOAT32 : dev.zarr.zarrjava.v3.DataType.FLOAT32; break; case FormatTools.DOUBLE: - pixelType = DataType.f8; + pixelType = isV2() ? dev.zarr.zarrjava.v2.DataType.FLOAT64 : dev.zarr.zarrjava.v3.DataType.FLOAT64; break; } return(pixelType); } public int getOMEPixelType(DataType pixType) { - int pixelType = -1; - switch(pixType) { - case i1: + if (isV2()) { + dev.zarr.zarrjava.v2.DataType dt = (dev.zarr.zarrjava.v2.DataType)pixType; + switch(dt) { + case INT8: pixelType = FormatTools.INT8; break; - case i2: + case INT16: pixelType = FormatTools.INT16; break; - case i4: + case INT32: pixelType = FormatTools.INT32; break; - case u1: + case UINT8: pixelType = FormatTools.UINT8; break; - case u2: + case UINT16: pixelType = FormatTools.UINT16; break; - case u4: + case UINT32: pixelType = FormatTools.UINT32; break; - case f4: + case FLOAT32: pixelType = FormatTools.FLOAT; break; - case f8: + case FLOAT64: pixelType = FormatTools.DOUBLE; break; - case i8: + default: + break; + } + } + else { + dev.zarr.zarrjava.v3.DataType dt = (dev.zarr.zarrjava.v3.DataType)pixType; + switch(dt) { + case INT8: + pixelType = FormatTools.INT8; + break; + case INT16: + pixelType = FormatTools.INT16; + break; + case INT32: + pixelType = FormatTools.INT32; + break; + case UINT8: + pixelType = FormatTools.UINT8; + break; + case UINT16: + pixelType = FormatTools.UINT16; + break; + case UINT32: + pixelType = FormatTools.UINT32; + break; + case FLOAT32: + pixelType = FormatTools.FLOAT; + break; + case FLOAT64: pixelType = FormatTools.DOUBLE; break; default: break; } + } return(pixelType); } @@ -193,179 +180,62 @@ public String getNoZarrMsg() { @Override public int[] getShape() { - if (zarrArray != null) return zarrArray.getShape(); + if (attr != null && attr.containsKey("shape")) { + long[] shape = (long[]) attr.get("shape"); + int[] res = new int[shape.length]; + for (int i = 0; i < shape.length; i++) { + res[i] = (int) shape[i]; + } + return res; + } return null; } @Override public int[] getChunkSize() { - if (zarrArray != null) return zarrArray.getChunks(); + if (attr != null && attr.containsKey("chunkShape")) + return (int[]) attr.get("chunkShape"); return null; } @Override public int getPixelType() { - if (zarrArray != null) return getOMEPixelType(zarrArray.getDataType()); + if (attr != null && attr.containsKey("dataType")) + return getOMEPixelType((DataType) attr.get("dataType")); return 0; } @Override public boolean isLittleEndian() { - if (zarrArray != null) return (zarrArray.getByteOrder().equals(ByteOrder.LITTLE_ENDIAN)); + if (attr != null && attr.containsKey("littleEndian")) + return (boolean) attr.get("littleEndian"); return false; } @Override public void close() throws IOException { - zarrArray = null; - currentId = null; - if (s3fs != null) { - s3fs.close(); - } + array = null; + attr = null; } @Override public boolean isOpen() { - return (zarrArray != null && currentId != null); + return (array != null); } - - @Override - public String getID() { - return currentId; - } - + @Override public Object readBytes(int[] shape, int[] offset) throws FormatException, IOException { - if (zarrArray != null) { + if (array != null) { try { - return zarrArray.read(shape, offset); - } catch (InvalidRangeException e) { - throw new FormatException(e); - } - } - else throw new IOException("No Zarr file opened"); - } - - @Override - public void saveBytes(Object data, int[] shape, int[] offset) throws FormatException, IOException { - if (zarrArray != null) { - try { - zarrArray.write(data, shape, offset); - } catch (InvalidRangeException e) { + long[] offsetLong = new long[offset.length]; + for (int i = 0; i < offset.length; i++) { + offsetLong[i] = offset[i]; + } + return array.read(offsetLong, shape).getDataAsByteBuffer().array(); + } catch (ZarrException e) { throw new FormatException(e); } } else throw new IOException("No Zarr file opened"); } - - @Override - public void create(String file, MetadataRetrieve meta, int[] chunks, Compression compression) throws IOException { - int seriesCount = meta.getImageCount(); - int resolutionCount = 1; - - ArrayParams params = new ArrayParams(); - params.chunks(chunks); - params.compressor(nullComp); - - boolean isLittleEndian = !meta.getPixelsBigEndian(0); - if (isLittleEndian) { - params.byteOrder(ByteOrder.LITTLE_ENDIAN); - } - - int x = meta.getPixelsSizeX(0).getValue().intValue(); - int y = meta.getPixelsSizeY(0).getValue().intValue(); - int z = meta.getPixelsSizeZ(0).getValue().intValue(); - int c = meta.getPixelsSizeC(0).getValue().intValue(); - int t = meta.getPixelsSizeT(0).getValue().intValue(); - // c /= meta.getChannelSamplesPerPixel(0, 0).getValue().intValue(); - int [] shape = {x, y, z, c, t}; - params.shape(shape); - - int pixelType = FormatTools.pixelTypeFromString(meta.getPixelsType(0).toString()); - DataType zarrPixelType = getZarrPixelType(pixelType); - int bytes = FormatTools.getBytesPerPixel(pixelType); - params.dataType(zarrPixelType); - - if (seriesCount > 1) { - ZarrGroup root = ZarrGroup.create(file); - ZarrGroup currentGroup = root; - for (int i = 0; i < seriesCount; i++) { - x = meta.getPixelsSizeX(i).getValue().intValue(); - y = meta.getPixelsSizeY(i).getValue().intValue(); - z = meta.getPixelsSizeZ(i).getValue().intValue(); - c = meta.getPixelsSizeC(i).getValue().intValue(); - t = meta.getPixelsSizeT(i).getValue().intValue(); - // c /= meta.getChannelSamplesPerPixel(i, 0).getValue().intValue(); - shape = new int[]{x, y, z, c, t}; - params.shape(shape); - - pixelType = FormatTools.pixelTypeFromString(meta.getPixelsType(i).toString()); - zarrPixelType = getZarrPixelType(pixelType); - params.dataType(zarrPixelType); - - isLittleEndian = !meta.getPixelsBigEndian(i); - if (isLittleEndian) { - params.byteOrder(ByteOrder.LITTLE_ENDIAN); - } - - if (meta instanceof IPyramidStore) { - resolutionCount = ((IPyramidStore) meta).getResolutionCount(i); - } - if (resolutionCount > 1) { - currentGroup = root.createSubGroup("Series"+i); - for (int j = 0; j < resolutionCount; j++) { - zarrArray = currentGroup.createArray("Resolution"+j, params); - } - } - else { - zarrArray = currentGroup.createArray("Series"+i, params); - } - } - } - else { - zarrArray = ZarrArray.create(file, params); - } - currentId = file; - } - - @Override - public void create(String id, MetadataRetrieve meta, int[] chunks) throws IOException { - create(id, meta, chunks, Compression.NONE); - } - - private String stripZarrRoot(String path) { - return path.substring(path.indexOf(".zarr")+5); - } - - private String getZarrRoot(String path) { - return path.substring(0, path.indexOf(".zarr")+5); - } - - private ZarrGroup getGroup(String path) throws IOException { - ZarrGroup group = null; - if (s3fs == null) { - group = ZarrGroup.open(path); - } - else { - s3fs.updateRoot(getZarrRoot(s3fs.getRoot()) + stripZarrRoot(path)); - group = ZarrGroup.open(s3fs); - } - return group; - } - - private ZarrArray getArray(String path) throws IOException { - ZarrArray array = null; - if (s3fs == null) { - array = ZarrArray.open(path); - } - else { - s3fs.updateRoot(getZarrRoot(s3fs.getRoot()) + stripZarrRoot(path)); - array = ZarrArray.open(s3fs); - } - return array; - } - - public boolean usingS3FileSystemStore() { - return s3fs != null; - } } diff --git a/src/loci/formats/services/ZarrLocation.java b/src/loci/formats/services/ZarrLocation.java new file mode 100644 index 0000000..adefa4b --- /dev/null +++ b/src/loci/formats/services/ZarrLocation.java @@ -0,0 +1,289 @@ +package loci.formats.services; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import dev.zarr.zarrjava.core.Array; +import dev.zarr.zarrjava.core.ArrayMetadata; +import dev.zarr.zarrjava.core.Group; +import dev.zarr.zarrjava.core.Node; +import dev.zarr.zarrjava.store.FilesystemStore; +import dev.zarr.zarrjava.store.HttpStore; +import dev.zarr.zarrjava.store.S3Store; +import dev.zarr.zarrjava.store.StoreHandle; +import dev.zarr.zarrjava.v2.Endianness; +import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3ClientBuilder; +import software.amazon.awssdk.services.s3.S3Configuration; + +/** + *

+ * This class provides a unified interface for accessing Zarr stores from different + * storage backends including local filesystem, HTTP/HTTPS endpoints, and S3-compatible + * object storage. It offers convenience methods for accessing arrays and groups, + * as well as their metadata. + *

+ *

+ * Supported URI schemes: + *

    + *
  • file:// or no scheme - Local filesystem storage
  • + *
  • http:// or https:// - HTTP-based storage
  • + *
  • s3:// - S3-compatible object storage (with authentication via URL parameters)
  • + *
+ *

+ */ +public class ZarrLocation { + + /** The underlying store handle for accessing Zarr data */ + StoreHandle store; + + /** The full path to the Zarr store including the .zarr extension */ + String path; + + /** The name of the Zarr (typically the .zarr directory name) */ + String name; + + /** + * Constructs a ZarrLocation from the given path. + *

+ * The path must contain a .zarr extension. The constructor automatically detects + * the storage backend based on the URI scheme and initializes the appropriate store. + *

+ *

+ * For S3 URIs, authentication parameters can be provided as query parameters: + *

    + *
  • anonymous - Use anonymous access
  • + *
  • accessKeyId and secretAccessKey - Use basic credentials
  • + *
  • region - Specify AWS region (defaults to US_EAST_1)
  • + *
+ * Example: s3://host/bucket/path/data.zarr?anonymous=true + *

+ * + * @param orgPath the path to the Zarr store, must contain .zarr extension + * @throws URISyntaxException if the path is not a valid URI + * @throws IllegalArgumentException if the path does not contain .zarr or uses an unsupported URI scheme + */ + public ZarrLocation(final String orgPath) throws URISyntaxException, IllegalArgumentException { + int i = orgPath.lastIndexOf(".zarr"); + if (i > 0) { + this.path = orgPath.substring(0, i+5); + } else { + throw new IllegalArgumentException("Path is not a .zarr"); + } + + URI uri = new URI(path); + if (uri.getScheme() == null || uri.getScheme().equals("file")) { + int sep = path.lastIndexOf(File.separator); + name = path.substring(sep + 1); + String storePath = path.substring(0, sep); + store = new FilesystemStore(storePath).resolve(name); + } else if (uri.getScheme().startsWith("http")) { + int sep = path.lastIndexOf("/"); + name = path.substring(sep + 1); + String storePath = path.substring(0, sep); + store = new HttpStore(storePath).resolve(name); + } else if (uri.getScheme().startsWith("s3")) { + String[] tmp = path.replaceFirst("s3://", "").split("/"); + String host = tmp[0]; + String bucket = tmp[1]; + name = tmp[tmp.length - 1]; + String[] rest = Arrays.copyOfRange(tmp, 2, tmp.length); + + // Extract URL parameters for authentication + Map params = new HashMap<>(); + String query = (new URI(orgPath)).getQuery(); + if (query != null) { + String[] pairs = query.split("&"); + for (String pair : pairs) { + int idx = pair.indexOf("="); + if (idx > 0) { + params.put(pair.substring(0, idx), pair.substring(idx + 1)); + } + } + } + + URI endpoint = new URI("https://" + host); + S3ClientBuilder clientBuilder = S3Client.builder() + .endpointOverride(endpoint) + .region(Region.US_EAST_1); // Default region required even for non-AWS + + S3Configuration s3Config = S3Configuration.builder() + .pathStyleAccessEnabled(true) + .build(); + clientBuilder.serviceConfiguration(s3Config); + + if (params.containsKey("anonymous")) { + clientBuilder.credentialsProvider(AnonymousCredentialsProvider.create()); + } else if (params.containsKey("accessKeyId") && params.containsKey("secretAccessKey")) { + AwsBasicCredentials credentials = AwsBasicCredentials.create( + params.get("accessKeyId"), + params.get("secretAccessKey")); + clientBuilder.credentialsProvider(StaticCredentialsProvider.create(credentials)); + } + if (params.containsKey("region")) { + clientBuilder.region(Region.of(params.get("region"))); + } + + S3Client client = clientBuilder.build(); + store = new S3Store(client, bucket, null).resolve(rest); + } else { + throw new IllegalArgumentException("Unsupported URI scheme: " + uri.getScheme()); + } + } + + /** + * Opens and returns a Zarr group at the specified path. + * + * @param path the relative path to the group within the store (empty string for root group) + * @return the opened Group object + * @throws IOException if the group cannot be opened or does not exist + */ + public Group getGroup(String path) throws IOException { + try { + if (path.isEmpty()) { + return (Group) Node.open(store); + } else { + return (Group) Node.open(store.resolve(path.split("/"))); + } + } catch (Exception e) { + throw new IOException("Failed to open group at path: "+store+" / " + path, e); + } + } + + /** + * Opens and returns a Zarr array at the specified path. + * + * @param path the relative path to the array within the store (empty string for root array) + * @return the opened Array object + * @throws IOException if the array cannot be opened or does not exist + */ + public Array getArray(String path) throws IOException { + try { + if (path.isEmpty()) { + return (Array) Node.open(store); + } else { + return (Array) Node.open(store.resolve(path.split("/"))); + } + } catch (Exception e) { + throw new IOException("Failed to open array at path: "+store+" / " + path, e); + } + } + + /** + * Retrieves metadata from a Zarr array at the specified path. + * + * @param path the relative path to the array within the store + * @return a map containing metadata including shape, chunkShape, dataType, and littleEndian + * @throws RuntimeException if the array cannot be accessed or metadata cannot be retrieved + */ + public Map metadataFromArray(String path) { + try { + Array array = getArray(path); + return metadataFromArray(array); + } catch (IOException e) { + throw new RuntimeException("Failed to get array metadata from path: "+store+" / " + path, e); + } + } + + /** + * Extracts metadata from a Zarr array object. + *

+ * The returned map contains: + *

    + *
  • shape - The dimensions of the array
  • + *
  • chunkShape - The chunk dimensions
  • + *
  • dataType - The data type of array elements
  • + *
  • littleEndian - Byte order (Todo: currently always true for v3 arrays)
  • + *
+ *

+ * + * @param array the Array object to extract metadata from + * @return a map containing the array metadata + */ + public Map metadataFromArray(Array array) { + Map res = new HashMap<>(); + ArrayMetadata m = array.metadata(); + if (array instanceof dev.zarr.zarrjava.v3.Array) { + // TODO: Implement!! + res.put("littleEndian", true); + } else { + dev.zarr.zarrjava.v2.ArrayMetadata m2 = (dev.zarr.zarrjava.v2.ArrayMetadata) m; + res.put("littleEndian", m2.endianness.equals(Endianness.LITTLE)); + } + res.put("shape", m.shape); + res.put("chunkShape", m.chunkShape()); + res.put("dataType", m.dataType()); + return res; + } + + /** + * Retrieves metadata from a Zarr group at the specified path. + * + * @param path the relative path to the group within the store + * @return a map containing the group's attributes + * @throws RuntimeException if the group cannot be accessed or metadata cannot be retrieved + */ + public Map metadataFromGroup(String path) { + try { + Group group = getGroup(path); + return metadataFromGroup(group); + } catch (IOException e) { + throw new RuntimeException("Failed to get group metadata from path: "+store+" / " + path, e); + } + } + + /** + * Extracts metadata attributes from a Zarr group object. + *

+ * This method handles both Zarr v2 and v3 group formats. + *

+ * + * @param group the Group object to extract metadata from + * @return a map containing the group's attributes + */ + public Map metadataFromGroup(Group group) { + if (group instanceof dev.zarr.zarrjava.v2.Group) { + dev.zarr.zarrjava.v2.GroupMetadata m = ((dev.zarr.zarrjava.v2.Group) group).metadata; + return m.attributes; + } else { + dev.zarr.zarrjava.v3.GroupMetadata m = ((dev.zarr.zarrjava.v3.Group) group).metadata; + return m.attributes; + } + } + + /** + * Returns the underlying store handle. + * + * @return the StoreHandle for this Zarr location + */ + public StoreHandle getStoreHandle() { + return store; + } + + /** + * Returns the name of the Zarr. + * + * @return the name (typically the .zarr directory name) + */ + public String getName() { + return name; + } + + /** + * Returns the full path to the Zarr. + * + * @return the complete path including the .zarr extension + */ + public String getPath() { + return path; + } +} diff --git a/src/loci/formats/services/ZarrService.java b/src/loci/formats/services/ZarrService.java index 13b6c3a..a29a511 100644 --- a/src/loci/formats/services/ZarrService.java +++ b/src/loci/formats/services/ZarrService.java @@ -30,21 +30,10 @@ */ import java.io.IOException; -import java.util.Map; -import java.util.Set; - import loci.common.services.Service; import loci.formats.FormatException; -import loci.formats.meta.MetadataRetrieve; -import loci.formats.services.ZarrService.Compression; public interface ZarrService extends Service { - - enum Compression { - NONE, - ZLIB - } - /** * Gets the text string for when Zarr implementation has not been found. @@ -82,31 +71,9 @@ enum Compression { */ public Object readBytes(int [] shape, int [] offset) throws FormatException, IOException; - /** - * Writes values to the Zarr Array - * @param buf values to be written in a one dimensional array - * @param shape int array representing the shape of each dimension - * @param x the offset for each dimension - */ - void saveBytes(Object data, int[] shape, int[] offset) throws FormatException, IOException; - - public void open(String file) throws IOException, FormatException; + public void open(String path) throws IOException, FormatException; boolean isLittleEndian(); boolean isOpen() throws IOException; - - String getID() throws IOException; - - public void create(String id, MetadataRetrieve meta, int[] chunks) throws IOException; - - void create(String id, MetadataRetrieve meta, int[] chunks, Compression compression) throws IOException; - - public Map getGroupAttr(String path) throws IOException, FormatException; - - public Map getArrayAttr(String path) throws IOException, FormatException; - - public Set getGroupKeys(String path) throws IOException, FormatException; - - public Set getArrayKeys(String path) throws IOException, FormatException; } diff --git a/test/loci/formats/utests/JZarrServiceImplTest.java b/test/loci/formats/utests/JZarrServiceImplTest.java deleted file mode 100644 index 0b617b3..0000000 --- a/test/loci/formats/utests/JZarrServiceImplTest.java +++ /dev/null @@ -1,281 +0,0 @@ -package loci.formats.utests; - -/*- - * #%L - * Implementation of Bio-Formats readers for the next-generation file formats - * %% - * Copyright (C) 2020 - 2022 Open Microscopy Environment - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -import static org.junit.Assert.*; - -import java.io.IOException; -import java.nio.ByteOrder; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import com.bc.zarr.DataType; -import com.bc.zarr.ZarrArray; -import com.bc.zarr.ZarrGroup; - -import loci.formats.FormatException; -import loci.formats.FormatTools; -import loci.formats.services.JZarrServiceImpl; -import ucar.ma2.InvalidRangeException; - -public class JZarrServiceImplTest { - - JZarrServiceImpl jzarrService; - String testID = "testID"; - private static MockedStatic zarrGroupStatic; - private static MockedStatic zarrArrayStatic; - - @Mock - ZarrArray zarrArray; - - @Mock - ZarrGroup zarrGroup; - - @BeforeMethod - public void setup() { - jzarrService = new JZarrServiceImpl(null); - zarrArray = Mockito.mock(ZarrArray.class); - zarrGroup = Mockito.mock(ZarrGroup.class); - jzarrService.open(testID, zarrArray); - - zarrGroupStatic = Mockito.mockStatic(ZarrGroup.class); - zarrGroupStatic.when(() -> ZarrGroup.open("path")).thenReturn(zarrGroup); - - zarrArrayStatic = Mockito.mockStatic(ZarrArray.class); - zarrArrayStatic.when(() -> ZarrArray.open("path")).thenReturn(zarrArray); - } - - @AfterMethod - public void teardown() { - try { - jzarrService.close(); - zarrGroupStatic.close(); - zarrArrayStatic.close(); - } catch (IOException e) { - fail("IOException thrown while closing JZarrServiceImpl"); - } - } - - @DataProvider(name = "pixelTypes") - public Object[][] createPixelTypes() { - return new Object[][] { - {FormatTools.INT8, DataType.i1}, - {FormatTools.INT16, DataType.i2}, - {FormatTools.INT32, DataType.i4}, - {FormatTools.UINT8, DataType.u1}, - {FormatTools.UINT16, DataType.u2}, - {FormatTools.UINT32, DataType.u4}, - {FormatTools.FLOAT, DataType.f4}, - {FormatTools.DOUBLE, DataType.f8}, - }; - } - - @Test(dataProvider = "pixelTypes") - public void testZarrPixelType(int omePixelType, DataType jzarrPixelType) { - assertEquals(jzarrPixelType, jzarrService.getZarrPixelType(omePixelType)); - } - - @Test(dataProvider = "pixelTypes") - public void testOMEPixelType(int omePixelType, DataType jzarrPixelType) { - assertEquals(omePixelType, jzarrService.getOMEPixelType(jzarrPixelType)); - } - - @Test(dataProvider = "pixelTypes") - public void testPixelType(int omePixelType, DataType jzarrPixelType) { - when(zarrArray.getDataType()).thenReturn(jzarrPixelType); - assertEquals(omePixelType, jzarrService.getPixelType()); - } - - @Test - public void testIsLittleEndian() { - when(zarrArray.getByteOrder()).thenReturn(ByteOrder.BIG_ENDIAN); - assertEquals(false, jzarrService.isLittleEndian()); - when(zarrArray.getByteOrder()).thenReturn(ByteOrder.LITTLE_ENDIAN); - assertEquals(true, jzarrService.isLittleEndian()); - } - - @Test - public void testGetNoZarrMessage() { - assertEquals(JZarrServiceImpl.NO_ZARR_MSG, jzarrService.getNoZarrMsg()); - } - - @Test - public void testGetShape() { - int[] expectedShape = {1024, 1024, 32, 32, 32}; - when(zarrArray.getShape()).thenReturn(expectedShape); - assertEquals(expectedShape, jzarrService.getShape()); - } - - @Test - public void testGetChunkSize() { - int[] expectedChunks = {256, 256, 8, 8, 8}; - when(zarrArray.getChunks()).thenReturn(expectedChunks); - assertEquals(expectedChunks, jzarrService.getChunkSize()); - } - - @Test - public void testGetGroupAttributes() { - Map emptyAttributes = new HashMap(); - Map attributes = new HashMap(); - attributes.put("AttributeKey1", "AttributeValue1"); - attributes.put("AttributeKey2", "AttributeValue2"); - - try { - assertEquals(jzarrService.getGroupAttr("path"), emptyAttributes); - when(zarrGroup.getAttributes()).thenReturn(attributes); - assertEquals(jzarrService.getGroupAttr("path"), attributes); - } catch (IOException | FormatException e) { - fail("Unexpected exception thrown while retrieving group attributes"); - e.printStackTrace(); - } - } - - @Test - public void testGetGroupKeys() { - Set emptyKeys = new HashSet(); - Set keys = new HashSet(); - keys.add("Key1"); - keys.add("Key2"); - - try { - assertEquals(jzarrService.getGroupKeys("path"), emptyKeys); - when(zarrGroup.getGroupKeys()).thenReturn(keys); - assertEquals(jzarrService.getGroupKeys("path"), keys); - } catch (IOException | FormatException e) { - fail("Unexpected exception thrown while retrieving group keys"); - e.printStackTrace(); - } - } - - @Test - public void testGetArrayAttributes() { - Map emptyAttributes = new HashMap(); - Map attributes = new HashMap(); - attributes.put("AttributeKey1", "AttributeValue1"); - attributes.put("AttributeKey2", "AttributeValue2"); - - try { - assertEquals(jzarrService.getArrayAttr("path"), emptyAttributes); - when(zarrArray.getAttributes()).thenReturn(attributes); - assertEquals(jzarrService.getArrayAttr("path"), attributes); - } catch (IOException | FormatException e) { - fail("Unexpected exception thrown while retrieving array attributes"); - e.printStackTrace(); - } - } - - @Test - public void testGetArrayKeys() { - Set emptyKeys = new HashSet(); - Set keys = new HashSet(); - keys.add("Key1"); - keys.add("Key2"); - - try { - assertEquals(jzarrService.getArrayKeys("path"), emptyKeys); - when(zarrGroup.getArrayKeys()).thenReturn(keys); - assertEquals(jzarrService.getArrayKeys("path"), keys); - } catch (IOException | FormatException e) { - fail("Unexpected exception thrown while retrieving array keys"); - e.printStackTrace(); - } - } - - @Test - public void testGetID() { - assertEquals(testID, jzarrService.getID()); - JZarrServiceImpl nullJzarrService = new JZarrServiceImpl(null); - assertEquals(null, nullJzarrService.getID()); - nullJzarrService.open("TestGetID", zarrArray); - assertEquals("TestGetID", nullJzarrService.getID()); - } - - @Test - public void testIsOpen() { - assertEquals(true, jzarrService.isOpen()); - JZarrServiceImpl nullJzarrService = new JZarrServiceImpl(null); - assertEquals(false, nullJzarrService.isOpen()); - nullJzarrService.open("isOpenTestID", zarrArray); - assertEquals(true, nullJzarrService.isOpen()); - } - - @Test - public void testReadBytes() { - int[] expectedBytes = {256, 256, 8, 8, 8}; - int[] shape = {1024, 1024, 32, 32, 32}; - int[] offset = {0, 0, 0, 0, 0}; - try { - when(zarrArray.read(shape, offset)).thenReturn(expectedBytes); - assertEquals(expectedBytes, jzarrService.readBytes(shape, offset)); - } catch (IOException e) { - fail("Unexpected exception on JZarrServiceImpl readBytes"); - e.printStackTrace(); - } catch (InvalidRangeException e) { - fail("Unexpected InvalidRangeException on ZarrArray read"); - e.printStackTrace(); - } catch (FormatException e) { - fail("Unexpected FormatException on JZarrServiceImpl readBytes"); - e.printStackTrace(); - } - } - - @Test - public void testSaveBytes() { - int[] data = {256, 256, 8, 8, 8}; - int[] shape = {1024, 1024, 32, 32, 32}; - int[] offset = {0, 0, 0, 0, 0}; - try { - jzarrService.saveBytes(data, shape, offset); - verify(zarrArray).write(data, shape, offset); - } catch (IOException e) { - fail("Unexpected IOException on JZarrServiceImpl saveBytes"); - e.printStackTrace(); - } catch (InvalidRangeException e) { - fail("Unexpected InvalidRangeException on ZarrArray write"); - e.printStackTrace(); - } catch (FormatException e) { - fail("Unexpected FormatException on JZarrServiceImpl saveBytes"); - e.printStackTrace(); - } - } -} diff --git a/test/loci/formats/utests/ZarrReaderMock.java b/test/loci/formats/utests/ZarrReaderMock.java deleted file mode 100644 index 7bd38f8..0000000 --- a/test/loci/formats/utests/ZarrReaderMock.java +++ /dev/null @@ -1,50 +0,0 @@ -package loci.formats.utests; - -/*- - * #%L - * Implementation of Bio-Formats readers for the next-generation file formats - * %% - * Copyright (C) 2020 - 2022 Open Microscopy Environment - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -import java.io.IOException; - -import loci.formats.FormatException; -import loci.formats.in.ZarrReader; -import loci.formats.services.ZarrService; - -public class ZarrReaderMock extends ZarrReader { - - private ZarrService mockService; - - public ZarrReaderMock(ZarrService zarrService) { - mockService = zarrService; - } - - @Override - protected void initializeZarrService() throws IOException, FormatException { - zarrService = mockService; - } -} diff --git a/test/loci/formats/utests/ZarrReaderTest.java b/test/loci/formats/utests/ZarrReaderTest.java deleted file mode 100644 index 7b3ac76..0000000 --- a/test/loci/formats/utests/ZarrReaderTest.java +++ /dev/null @@ -1,289 +0,0 @@ -package loci.formats.utests; - -/*- - * #%L - * Implementation of Bio-Formats readers for the next-generation file formats - * %% - * Copyright (C) 2020 - 2022 Open Microscopy Environment - * %% - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * #L% - */ - -import static org.junit.Assert.*; -import static org.testng.AssertJUnit.assertEquals; -import static org.testng.AssertJUnit.assertTrue; -import static org.testng.AssertJUnit.assertFalse; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; - -import static org.mockito.Mockito.when; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import loci.common.DataTools; -import loci.common.Location; -import loci.formats.FormatException; -import loci.formats.FormatTools; -import loci.formats.in.ZarrReader; -import loci.formats.services.ZarrService; - - -/** - * Tests the functionality of ZarrReader - */ -public class ZarrReaderTest { - - @Mock - private ZarrService zarrService; - - @InjectMocks - private ZarrReaderMock reader; - - private File file; - private int[] shape = {8, 16, 32, 512, 1024}; - - @BeforeClass - public void setUp() throws Exception { - zarrService = Mockito.mock(ZarrService.class); - reader = new ZarrReaderMock(zarrService); - file = File.createTempFile("tileTest", ".zarr"); - String rootPath = file.getAbsolutePath(); - String canonicalPath = new Location(rootPath).getCanonicalPath(); - - Map topLevelAttributes = new HashMap(); - ArrayList multiscales = new ArrayList(); - Map datasets = new HashMap(); - ArrayList multiscalePaths = new ArrayList(); - Map multiScale1 = new HashMap(); - Map multiScale2 = new HashMap(); - Map multiScale3 = new HashMap(); - multiScale1.put("path", "0"); - multiScale2.put("path", "1"); - multiScale3.put("path", "2"); - multiscalePaths.add(multiScale1); - multiscalePaths.add(multiScale2); - multiscalePaths.add(multiScale3); - datasets.put("datasets", multiscalePaths); - datasets.put("axes", Arrays.asList("t", "c", "z", "y", "x")); - multiscales.add(datasets); - topLevelAttributes.put("multiscales", multiscales); - - when(zarrService.getGroupAttr(canonicalPath)).thenReturn(topLevelAttributes); - when(zarrService.getShape()).thenReturn(shape); - when(zarrService.getPixelType()).thenReturn(0); - reader.setId(file.getAbsolutePath()); - } - - @AfterClass - public void tearDown() throws Exception { - reader.close(); - file.delete(); - } - - @Test - public void testIsThisType() { - assertTrue(reader.isThisType("test/path/data.zarr", true)); - assertTrue(reader.isThisType("test/path/data.ZARR", true)); - assertTrue(reader.isThisType("test/path/data.zarr/0/0", true)); - assertTrue(reader.isThisType("test/path/data.zarr/0/0/0.0.0.0.0", true)); - assertTrue(reader.isThisType("test/path/data.zarr/0/0/.zattrs", true)); - assertFalse(reader.isThisType("test/path/data.zar", true)); - } - - @Test - public void testGetOptimalTileHeight() { - int [] expectedChunks = {8, 8, 8, 256, 128}; - when(zarrService.getChunkSize()).thenReturn(expectedChunks); - assertEquals(expectedChunks[3], reader.getOptimalTileHeight()); - } - - @Test - public void testGetOptimalTileWidth() { - int [] expectedChunks = {8, 8, 8, 256, 128}; - when(zarrService.getChunkSize()).thenReturn(expectedChunks); - assertEquals(expectedChunks[4], reader.getOptimalTileWidth()); - } - - @Test - public void testDimensions() { - assertEquals(shape[4], reader.getSizeX()); - assertEquals(shape[3], reader.getSizeY()); - assertEquals(shape[2], reader.getSizeZ()); - assertEquals(shape[1], reader.getSizeC()); - assertEquals(shape[0], reader.getSizeT()); - } - - @Test - public void testOpenBytes() { - int[] readerShape = {1, 1, 1, shape[3], shape[4]}; - int[] readerOffset = {0, 0, 0, 0, 0}; - int[] expectedPixelValues = new int[1024*512]; - for (int i = 0; i < expectedPixelValues.length; i++) { - expectedPixelValues[i] = i; - } - byte[] buf = new byte[1024*512*4]; - byte[] expectedBuf = DataTools.intsToBytes(expectedPixelValues, false); - try { - when(zarrService.readBytes(readerShape, readerOffset)).thenReturn(expectedPixelValues); - when(zarrService.getPixelType()).thenReturn(4); - buf = reader.openBytes(0, buf); - assertEquals(expectedBuf, buf); - - when(zarrService.readBytes(readerShape, readerOffset)).thenReturn(expectedBuf); - when(zarrService.getPixelType()).thenReturn(0); - buf = new byte[1024*512*4]; - buf = reader.openBytes(0, buf); - assertEquals(expectedBuf, buf); - } catch (FormatException | IOException e) { - fail("Unexpected exception thrown while reading bytes"); - } - } - - @Test - public void testGetDomains() { - assertEquals(FormatTools.NON_SPECIAL_DOMAINS, reader.getDomains()); - } - - @Test - public void testSetSeries() { - assertEquals(0, reader.getSeries()); - reader.setSeries(1); - assertEquals(1, reader.getSeries()); - } - - @Test - public void testResolutionCount() { - try { - reader.close(); - reader.setFlattenedResolutions(false); - reader.setId(file.getAbsolutePath()); - } catch (IOException | FormatException e) { - fail("Unexpected exception while setting flattenedResolutions"); - } - assertEquals(3, reader.getResolutionCount()); - assertEquals(1, reader.getSeriesCount()); - try { - reader.close(); - reader.setFlattenedResolutions(true); - reader.setId(file.getAbsolutePath()); - } catch (IOException | FormatException e) { - fail("Unexpected exception while setting flattenedResolutions"); - } - assertEquals(1, reader.getResolutionCount()); - assertEquals(3, reader.getSeriesCount()); - } - - @Test - public void testParseOmeroMetadataWithIntegerValues() { - Map omeroMetadata = new HashMap<>(); - omeroMetadata.put("id", 1); - omeroMetadata.put("name", "Test Image"); - omeroMetadata.put("version", "0.1"); - - ArrayList channels = new ArrayList<>(); - Map channel = new HashMap<>(); - channel.put("active", true); - channel.put("coefficient", 1); - channel.put("color", "FFFFFF"); - channel.put("family", "linear"); - channel.put("inverted", false); - channel.put("label", "Channel 1"); - - Map window = new HashMap<>(); - window.put("start", 0); - window.put("end", 255); - window.put("min", 0); - window.put("max", 255); - channel.put("window", window); - - channels.add(channel); - omeroMetadata.put("channels", channels); - - Map rdefs = new HashMap<>(); - rdefs.put("defaultT", 0); - rdefs.put("defaultZ", 0); - rdefs.put("model", "color"); - omeroMetadata.put("rdefs", rdefs); - - Map test = new HashMap<>(); - test.put("omero", omeroMetadata); - try { - reader.parseOmeroMetadata(test); - } catch (IOException | FormatException e) { - fail("Unexpected exception while parsing Omero metadata with Integer values"); - } - } - - @Test - public void testParseOmeroMetadataWithDoubleValues() { - Map omeroMetadata = new HashMap<>(); - omeroMetadata.put("id", 1); - omeroMetadata.put("name", "Test Image"); - omeroMetadata.put("version", "0.1"); - - ArrayList channels = new ArrayList<>(); - Map channel = new HashMap<>(); - channel.put("active", true); - channel.put("coefficient", 1.0); - channel.put("color", "FFFFFF"); - channel.put("family", "linear"); - channel.put("inverted", false); - channel.put("label", "Channel 1"); - - Map window = new HashMap<>(); - window.put("start", 0.0); - window.put("end", 255.0); - window.put("min", 0.0); - window.put("max", 255.0); - channel.put("window", window); - - channels.add(channel); - omeroMetadata.put("channels", channels); - - Map rdefs = new HashMap<>(); - rdefs.put("defaultT", 0); - rdefs.put("defaultZ", 0); - rdefs.put("model", "color"); - omeroMetadata.put("rdefs", rdefs); - - Map test = new HashMap<>(); - test.put("omero", omeroMetadata); - try { - reader.parseOmeroMetadata(test); - } catch (IOException | FormatException e) { - fail("Unexpected exception while parsing Omero metadata with Double values"); - } - } -}