88package com .glencoesoftware .pyramid ;
99
1010import java .io .IOException ;
11+ import java .nio .ByteBuffer ;
1112import java .nio .ByteOrder ;
1213import java .nio .file .Files ;
1314import java .nio .file .Path ;
6970import org .perf4j .StopWatch ;
7071import org .perf4j .slf4j .Slf4JStopWatch ;
7172
72- import com .bc .zarr .DataType ;
73- import com .bc .zarr .ZarrArray ;
74- import com .bc .zarr .ZarrGroup ;
7573import com .glencoesoftware .bioformats2raw .IProgressListener ;
7674import com .glencoesoftware .bioformats2raw .NoOpProgressListener ;
7775import com .glencoesoftware .bioformats2raw .ProgressBarListener ;
8179import picocli .CommandLine .Option ;
8280import picocli .CommandLine .Parameters ;
8381
82+ // Zarr v2
83+
84+ import com .bc .zarr .DataType ;
85+ import com .bc .zarr .ZarrArray ;
86+ import com .bc .zarr .ZarrGroup ;
87+
88+ // Zarr v3
89+
90+ import dev .zarr .zarrjava .ZarrException ;
91+ import dev .zarr .zarrjava .store .FilesystemStore ;
92+ import dev .zarr .zarrjava .utils .Utils ;
93+ import dev .zarr .zarrjava .v3 .Array ;
94+ import dev .zarr .zarrjava .v3 .Group ;
95+
8496/**
8597 * Writes a pyramid OME-TIFF file or Bio-Formats 5.9.x "Faas" TIFF file.
8698 * Image tiles are read from files within a specific folder structure:
@@ -142,8 +154,13 @@ public class PyramidFromDirectoryWriter implements Callable<Void> {
142154
143155 String imageFile = null ;
144156
157+ // used for reading v2 data
145158 private ZarrGroup reader = null ;
146159
160+ // used for reading v3 data
161+ private Group v3Reader = null ;
162+ private FilesystemStore v3Store = null ;
163+
147164 /** Writer metadata. */
148165 OMEPyramidStore metadata ;
149166 OMEPyramidStore binaryOnly ;
@@ -672,6 +689,38 @@ private byte[] getInputTileBytes(PyramidSeries s, int resolution,
672689 y * descriptor .tileSizeY , x * descriptor .tileSizeX );
673690 int [] shape = s .getArray (1 , 1 , 1 , realHeight , realWidth );
674691
692+ if (isV3 ()) {
693+ return readV3Tile (s , descriptor , pos , shape , gridPosition );
694+ }
695+ return readV2Tile (s , descriptor , pos , shape , gridPosition );
696+ }
697+
698+ private byte [] readV3Tile (PyramidSeries s , ResolutionDescriptor descriptor ,
699+ int [] pos , int [] shape , int [] gridPosition )
700+ throws FormatException , IOException
701+ {
702+ Array block = getZarrV3Array (descriptor .path );
703+ if (block == null ) {
704+ throw new FormatException ("Could not find block = " + descriptor .path +
705+ ", position = [" + pos [0 ] + ", " + pos [1 ] + ", " + pos [2 ] + "]" );
706+ }
707+ try {
708+ ucar .ma2 .Array tile = block .read (Utils .toLongArray (gridPosition ), shape );
709+ ByteBuffer buf = tile .getDataAsByteBuffer (
710+ s .littleEndian ? ByteOrder .LITTLE_ENDIAN : ByteOrder .BIG_ENDIAN );
711+ byte [] bytes = new byte [buf .remaining ()];
712+ buf .get (bytes );
713+ return bytes ;
714+ }
715+ catch (ZarrException e ) {
716+ throw new FormatException (e );
717+ }
718+ }
719+
720+ private byte [] readV2Tile (PyramidSeries s , ResolutionDescriptor descriptor ,
721+ int [] pos , int [] shape , int [] gridPosition )
722+ throws FormatException , IOException
723+ {
675724 ZarrArray block = reader .openArray (descriptor .path );
676725
677726 if (block == null ) {
@@ -769,10 +818,18 @@ private void populateMetadata() throws IOException {
769818 metadata .setWellColumn (new NonNegativeInteger (colIndex ), 0 , i );
770819 metadata .setWellRow (new NonNegativeInteger (rowIndex ), 0 , i );
771820
772- ZarrGroup wellGroup = getZarrGroup (well );
821+ Map <String , Object > wellGroupAttrs = null ;
822+ if (isV3 ()) {
823+ Group v3WellGroup = getZarrGroupV3 (well );
824+ wellGroupAttrs = v3WellGroup .metadata .attributes ;
825+ }
826+ else {
827+ ZarrGroup wellGroup = getZarrGroup (well );
828+ wellGroupAttrs = wellGroup .getAttributes ();
829+ }
773830
774831 Map <String , Object > wellAttr =
775- (Map <String , Object >) wellGroup . getAttributes () .get ("well" );
832+ (Map <String , Object >) wellGroupAttrs .get ("well" );
776833 List <Map <String , Object >> images =
777834 (List <Map <String , Object >>) wellAttr .get ("images" );
778835 for (int img =0 ; img <images .size (); img ++) {
@@ -864,6 +921,31 @@ private PixelType getPixelType(DataType type) {
864921 }
865922 }
866923
924+ private PixelType getV3PixelType (dev .zarr .zarrjava .v3 .DataType type ) {
925+ switch (type ) {
926+ case BOOL :
927+ return PixelType .BIT ;
928+ case INT8 :
929+ return PixelType .INT8 ;
930+ case INT16 :
931+ return PixelType .INT16 ;
932+ case INT32 :
933+ return PixelType .INT32 ;
934+ case UINT8 :
935+ return PixelType .UINT8 ;
936+ case UINT16 :
937+ return PixelType .UINT16 ;
938+ case UINT32 :
939+ return PixelType .UINT32 ;
940+ case FLOAT32 :
941+ return PixelType .FLOAT ;
942+ case FLOAT64 :
943+ return PixelType .DOUBLE ;
944+ default :
945+ throw new IllegalArgumentException ("Unsupported pixel type: " + type );
946+ }
947+ }
948+
867949 private ZarrGroup getZarrGroup (String path ) throws IOException {
868950 return ZarrGroup .open (inputDirectory .resolve (path ).toString ());
869951 }
@@ -872,12 +954,33 @@ private int getSubgroupCount(String path) throws IOException {
872954 return getZarrGroup (path ).getGroupKeys ().size ();
873955 }
874956
957+ private Group getZarrGroupV3 (String ... path ) throws IOException {
958+ return Group .open (v3Store .resolve (path ));
959+ }
960+
961+ private Array getZarrV3Array (String ... path )
962+ throws FormatException , IOException
963+ {
964+ try {
965+ return Array .open (v3Store .resolve (path ));
966+ }
967+ catch (ZarrException e ) {
968+ throw new FormatException (e );
969+ }
970+ }
971+
875972 /**
876973 * Calculate the number of series.
877974 *
878975 * @return number of series
879976 */
880977 private int getSeriesCount () throws IOException {
978+ if (isV3 ()) {
979+ // TODO: handle plate data
980+ return (int ) v3Reader .storeHandle .list ()
981+ .filter (key -> !key .equals ("OME" ) && !key .equals ("zarr.json" ))
982+ .count ();
983+ }
881984 Set <String > groupKeys = reader .getGroupKeys ();
882985 groupKeys .remove ("OME" );
883986 int groupKeyCount = groupKeys .size ();
@@ -904,18 +1007,35 @@ private int getSeriesCount() throws IOException {
9041007 * @param s current series
9051008 */
9061009 private void findNumberOfResolutions (PyramidSeries s ) throws IOException {
907- ZarrGroup seriesGroup = getZarrGroup (s .path );
908- if (seriesGroup == null ) {
909- throw new IOException ("Expected series " + s .index + " not found" );
1010+ int arrayKeys = 0 ;
1011+ List <Map <String , Object >> multiscales = null ;
1012+
1013+ if (isV3 ()) {
1014+ Group v3Series = getZarrGroupV3 (s .path );
1015+ if (v3Series == null ) {
1016+ throw new IOException ("Expected series " + s .index + " not found" );
1017+ }
1018+
1019+ Map <String , Object > ome =
1020+ (Map <String , Object >) v3Series .metadata .attributes .get ("ome" );
1021+ multiscales = (List <Map <String , Object >>) ome .get ("multiscales" );
1022+ }
1023+ else {
1024+ ZarrGroup seriesGroup = getZarrGroup (s .path );
1025+ if (seriesGroup == null ) {
1026+ throw new IOException ("Expected series " + s .index + " not found" );
1027+ }
1028+ arrayKeys = seriesGroup .getArrayKeys ().size ();
1029+
1030+ Map <String , Object > seriesAttributes = seriesGroup .getAttributes ();
1031+ multiscales =
1032+ (List <Map <String , Object >>) seriesAttributes .get ("multiscales" );
9101033 }
9111034
9121035 // use multiscales metadata if it exists, to distinguish between
9131036 // resolutions and labels
9141037 // if no multiscales metadata (older dataset?), assume no labels
9151038 // and just use the path listing length
916- Map <String , Object > seriesAttributes = seriesGroup .getAttributes ();
917- List <Map <String , Object >> multiscales =
918- (List <Map <String , Object >>) seriesAttributes .get ("multiscales" );
9191039 if (multiscales != null && multiscales .size () > 0 ) {
9201040 List <Map <String , Object >> datasets =
9211041 (List <Map <String , Object >>) multiscales .get (0 ).get ("datasets" );
@@ -925,7 +1045,7 @@ private void findNumberOfResolutions(PyramidSeries s) throws IOException {
9251045 }
9261046
9271047 if (s .numberOfResolutions == 0 ) {
928- s .numberOfResolutions = seriesGroup . getArrayKeys (). size () ;
1048+ s .numberOfResolutions = arrayKeys ;
9291049 }
9301050 }
9311051
@@ -938,11 +1058,18 @@ public void initialize()
9381058 {
9391059 createReader ();
9401060
941- if (reader == null ) {
1061+ if (reader == null && v3Store == null ) {
9421062 throw new FormatException ("Could not create a reader" );
9431063 }
9441064
945- Map <String , Object > attributes = reader .getAttributes ();
1065+ Map <String , Object > attributes = null ;
1066+ if (isV3 ()) {
1067+ attributes = v3Reader .metadata .attributes ;
1068+ attributes = (Map <String , Object >) attributes .get ("ome" );
1069+ }
1070+ else {
1071+ attributes = reader .getAttributes ();
1072+ }
9461073 Integer layoutVersion = (Integer ) attributes .get ("bioformats2raw.layout" );
9471074 if (layoutVersion == null ) {
9481075 LOG .warn ("Layout version not recorded; may be unsupported" );
@@ -1095,12 +1222,30 @@ else if (imageFile != null && !imageFile.isEmpty()) {
10951222 s .dimensionLengths [s .dimensionOrder .indexOf ("C" ) - 2 ] = s .c ;
10961223
10971224 // make sure that OME-XML and first resolution array have same dimensions
1098- ZarrGroup imgGroup = getZarrGroup (s .path );
1099- ZarrArray imgArray = imgGroup .openArray ("0" );
1100- int [] dims = imgArray .getShape ();
1225+ Map <String , Object > firstResAttrs = null ;
1226+ int [] dims = null ;
1227+ boolean bigEndian = false ;
1228+ PixelType type = null ;
1229+
1230+ if (isV3 ()) {
1231+ Group v3ImgGroup = getZarrGroupV3 (s .path );
1232+ firstResAttrs =
1233+ (Map <String , Object >) v3ImgGroup .metadata .attributes .get ("ome" );
1234+ Array v3Array = getZarrV3Array (s .path , "0" );
1235+ dims = Utils .toIntArray (v3Array .metadata .shape );
1236+ type = getV3PixelType (v3Array .metadata .dataType );
1237+ }
1238+ else {
1239+ ZarrGroup imgGroup = getZarrGroup (s .path );
1240+ ZarrArray imgArray = imgGroup .openArray ("0" );
1241+ dims = imgArray .getShape ();
1242+ firstResAttrs = imgGroup .getAttributes ();
1243+ bigEndian = imgArray .getByteOrder () == ByteOrder .BIG_ENDIAN ;
1244+ type = getPixelType (imgArray .getDataType ());
1245+ }
11011246
11021247 List <Map <String , Object >> imgMultiscales =
1103- (List <Map <String , Object >>) imgGroup . getAttributes () .get ("multiscales" );
1248+ (List <Map <String , Object >>) firstResAttrs .get ("multiscales" );
11041249 List <Map <String , Object >> imgAxes = null ;
11051250 int channelIndex = -1 ;
11061251 if (imgMultiscales != null ) {
@@ -1146,15 +1291,13 @@ else if (s.c < dims[channelIndex]) {
11461291 s .planeCount = s .z * s .t ;
11471292
11481293 // Zarr format allows both little and big endian order
1149- boolean bigEndian = imgArray .getByteOrder () == ByteOrder .BIG_ENDIAN ;
11501294 s .littleEndian = !bigEndian ;
11511295 if (bigEndian != metadata .getPixelsBigEndian (seriesIndex )) {
11521296 LOG .debug ("Setting BigEndian={} for series {}" , bigEndian , seriesIndex );
11531297 metadata .setPixelsBigEndian (bigEndian , seriesIndex );
11541298 }
11551299
11561300 // make sure pixel types are consistent between Zarr and OME metadata
1157- PixelType type = getPixelType (imgArray .getDataType ());
11581301 s .pixelType = FormatTools .pixelTypeFromString (type .getValue ());
11591302 if (type != metadata .getPixelsType (seriesIndex )) {
11601303 LOG .debug ("Setting PixelsType = {} for series {}" , type , seriesIndex );
@@ -1208,7 +1351,12 @@ else if (rgb) {
12081351 }
12091352
12101353 s .planeCount *= effectiveChannels ;
1211- s .describePyramid (reader , metadata );
1354+ if (isV3 ()) {
1355+ s .describePyramidV3 (v3Store , metadata );
1356+ }
1357+ else {
1358+ s .describePyramid (reader , metadata );
1359+ }
12121360
12131361 metadata .setTiffDataIFD (new NonNegativeInteger (totalPlanes ), s .index , 0 );
12141362
@@ -1917,10 +2065,26 @@ private void createReader() throws IOException {
19172065 LOG .debug ("attempting to open {}" , zarr );
19182066 if (Files .exists (zarr )) {
19192067 LOG .debug (" zarr directory exists" );
1920- reader = ZarrGroup .open (zarr .toString ());
2068+ try {
2069+ reader = ZarrGroup .open (zarr .toString ());
2070+ }
2071+ catch (IOException e ) {
2072+ LOG .debug ("Could not open as v2" , e );
2073+ }
2074+
2075+ // couldn't open or no attributes implies we should try
2076+ // reading as v3 instead of v2
2077+ if (reader == null || reader .getAttributes ().size () == 0 ) {
2078+ v3Store = new FilesystemStore (zarr );
2079+ v3Reader = Group .open (v3Store .resolve ());
2080+ }
19212081 }
19222082 }
19232083
2084+ private boolean isV3 () {
2085+ return v3Store != null ;
2086+ }
2087+
19242088 /**
19252089 * Set up the root logger, turning on debug logging if appropriate.
19262090 */
0 commit comments