Repository: InsightSoftwareConsortium/ITKIOOMEZarrNGFF (language: python)

Issue:
Support older versions of OME NGFF
We should be permissive and load versions older than `0.4`. Older versions should have identity metadata.


Discussion / hints:
Data is here:
https://github.com/ome/ome-ngff-prototypes
https://idr.github.io/ome-ngff-samples/
A forum question about how to best download it:
https://forum.image.sc/t/download-entire-image-aws-s3-sync-style/76907

Fix patch:
diff --git a/include/itkOMEZarrNGFFImageIO.h b/include/itkOMEZarrNGFFImageIO.h
index 594489a..e001e6f 100644
--- a/include/itkOMEZarrNGFFImageIO.h
+++ b/include/itkOMEZarrNGFFImageIO.h
@@ -128,6 +128,21 @@ class IOOMEZarrNGFF_EXPORT OMEZarrNGFFImageIO : public ImageIOBase
   void
   ReadArrayMetadata(std::string path);
 
+  /** Sets the requested dimension, and initializes spatial metadata to identity. */
+  void
+  InitializeIdentityMetadata(unsigned nDims)
+  {
+    this->SetNumberOfDimensions(nDims);
+
+    // initialize identity transform
+    for (unsigned d = 0; d < this->GetNumberOfDimensions(); ++d)
+    {
+      this->SetSpacing(d, 1.0);
+      this->SetOrigin(d, 0.0);
+      this->SetDirection(d, this->GetDefaultDirection(d));
+    }
+  }
+
   ImageIORegion
   GetLargestRegion()
   {
diff --git a/src/itkOMEZarrNGFFImageIO.cxx b/src/itkOMEZarrNGFFImageIO.cxx
index 00102a0..8cb9f64 100644
--- a/src/itkOMEZarrNGFFImageIO.cxx
+++ b/src/itkOMEZarrNGFFImageIO.cxx
@@ -281,8 +281,16 @@ OMEZarrNGFFImageIO::ReadArrayMetadata(std::string path)
   this->SetComponentType(tensorstoreToITKComponentType(dtype));
 
   std::vector<int64_t> dims(shape_span.rbegin(), shape_span.rend()); // convert KJI into IJK
-  assert(this->GetNumberOfDimensions() == dims.size());
-  // this->SetNumberOfDimensions(dims.size());
+
+  if (this->GetNumberOfDimensions() == 0) // reading version 0.2 or 0.1
+  {
+    this->InitializeIdentityMetadata(dims.size());
+  }
+  else
+  {
+    assert(this->GetNumberOfDimensions() == dims.size());
+  }
+
   for (unsigned d = 0; d < dims.size(); ++d)
   {
     this->SetDimensions(d, dims[d]);
@@ -341,16 +349,26 @@ OMEZarrNGFFImageIO::ReadImageInformation()
   bool status = jsonRead(std::string(this->GetFileName()) + "/.zgroup", json);
   assert(json.at("zarr_format").get<int>() == 2); // only v2 for now
   status = jsonRead(std::string(this->GetFileName()) + "/.zattrs", json);
-  json = json.at("multiscales")[0];    // multiscales must be present in OME-NGFF
-  assert(json.at("version") == "0.4"); // only this is currently supported
-  this->SetNumberOfDimensions(json.at("axes").size());
+  json = json.at("multiscales")[0]; // multiscales must be present in OME-NGFF
+  auto version = json.at("version").get<std::string>();
+  if (version == "0.4" || version == "0.3" || version == "0.2" || version == "0.1")
+  {
+    // these are explicitly supported versions
+  }
+  else
+  {
+    std::string message = "OME-NGFF version " + version + " is not explicitly supported." +
+                          "\nImportant features might be ignored." + "\nSupported versions are 0.1 through 0.4.";
+    OutputWindowDisplayWarningText(message.c_str());
+  }
 
-  // initialize identity transform
-  for (unsigned d = 0; d < this->GetNumberOfDimensions(); ++d)
+  if (json.contains("axes")) // optional before 0.3
+  {
+    this->InitializeIdentityMetadata(json.at("axes").size());
+  }
+  else
   {
-    this->SetSpacing(d, 1.0);
-    this->SetOrigin(d, 0.0);
-    this->SetDirection(d, this->GetDefaultDirection(d));
+    this->SetNumberOfDimensions(0);
   }
 
   if (json.contains("coordinateTransformations")) // optional
@@ -366,7 +384,18 @@ OMEZarrNGFFImageIO::ReadImageInformation()
   }
 
   json = json[this->GetDatasetIndex()];
-  addCoordinateTransformations(this, json.at("coordinateTransformations")); // per-resolution scaling
+  if (json.contains("coordinateTransformations")) // optional for versions prior to 0.4
+  {
+    addCoordinateTransformations(this, json.at("coordinateTransformations")); // per-resolution scaling
+  }
+  else
+  {
+    if (version == "0.4")
+    {
+      itkExceptionMacro(<< "OME-NGFF v0.4 requires `coordinateTransformations` for each resolution level.");
+    }
+  }
+
   path = json.at("path").get<std::string>();
 
   // TODO: parse stuff from "metadata" object into metadata dictionary
@@ -407,7 +436,10 @@ OMEZarrNGFFImageIO::Read(void * buffer)
     ELEMENT_READ(uint64_t)
     ELEMENT_READ(float)
     ELEMENT_READ(double)
-    else { itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType())); }
+    else
+    {
+      itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType()));
+    }
   }
 }
 
@@ -555,7 +587,10 @@ OMEZarrNGFFImageIO::Write(const void * buffer)
   ELEMENT_WRITE(uint64_t)
   ELEMENT_WRITE(float)
   ELEMENT_WRITE(double)
-  else { itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType())); }
+  else
+  {
+    itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType()));
+  }
 }
 
 } // end namespace itk


Test patch:
diff --git a/test/itkOMEZarrNGFFImageIOTest.cxx b/test/itkOMEZarrNGFFImageIOTest.cxx
index a99c071..b0a69be 100644
--- a/test/itkOMEZarrNGFFImageIOTest.cxx
+++ b/test/itkOMEZarrNGFFImageIOTest.cxx
@@ -84,6 +84,8 @@ doTest(const char * inputFileName, const char * outputFileName, unsigned dimensi
       return doTest<PixelType, 3>(inputFileName, outputFileName);
     case 4:
       return doTest<PixelType, 4>(inputFileName, outputFileName);
+    case 5:
+      return doTest<PixelType, 5>(inputFileName, outputFileName);
     default:
       itkGenericExceptionMacro("Unsupported image dimension: " << dimension);
       break;
@@ -112,11 +114,11 @@ itkOMEZarrNGFFImageIOTest(int argc, char * argv[])
   {
     imageReader->UpdateOutputInformation();
   }
-  catch (itk::ExceptionObject& exc)
+  catch (itk::ExceptionObject & exc)
   {
     // maybe pointing to a directory without zarr-compatible extension
     imageReader->SetImageIO(itk::OMEZarrNGFFImageIO::New()); // explicitly request zarr IO
-    imageReader->UpdateOutputInformation(); // retry
+    imageReader->UpdateOutputInformation();                  // retry
   }
 
   unsigned dim = imageReader->GetImageIO()->GetNumberOfDimensions();
@@ -139,6 +141,9 @@ itkOMEZarrNGFFImageIOTest(int argc, char * argv[])
     case itk::ImageIOBase::IOComponentEnum::FLOAT:
       return doTest<float>(inputFileName, outputFileName, dim);
       break;
+    case itk::ImageIOBase::IOComponentEnum::DOUBLE:
+      return doTest<double>(inputFileName, outputFileName, dim);
+      break;
     default:
       std::cerr << "Unsupported input image pixel component type: ";
       std::cerr << itk::ImageIOBase::GetComponentTypeAsString(componentType);