diff --git a/src/itkOMEZarrNGFFImageIO.cxx b/src/itkOMEZarrNGFFImageIO.cxx index a8317a3..e4c6dbc 100644 --- a/src/itkOMEZarrNGFFImageIO.cxx +++ b/src/itkOMEZarrNGFFImageIO.cxx @@ -32,10 +32,29 @@ #include +// Evaluate tensorstore future (statement) and error-check the result. +#define TS_EVAL_CHECK(statement) \ + { \ + auto result = statement.result(); \ + if (!result.ok()) /* error */ \ + { \ + itkGenericExceptionMacro("tensorstore error: " << result.status()); \ + } \ + } \ + ITK_NOOP_STATEMENT + + namespace itk { namespace { +template +struct TypeList +{}; + +constexpr TypeList + supportedPixelTypes{}; + IOComponentEnum tensorstoreToITKComponentType(const tensorstore::DataType dtype) { @@ -147,6 +166,26 @@ itkToTensorstoreComponentType(const IOComponentEnum itkComponentType) } } +// Returns TensorStore KvStore driver name appropriate for this path. +// Options are file, zip. TODO: http, gcs (GoogleCouldStorage), etc. +std::string +getKVstoreDriver(std::string path) +{ + if (path.size() < 4) + { + return "file"; + } + if (path.substr(0, 4) == "http") + { // http or https + return "http"; + } + if (path.substr(path.size() - 4) == ".zip" || path.substr(path.size() - 7) == ".memory") + { + return "zip_memory"; + } + return "file"; +} + template void ReadFromStore(const tensorstore::TensorStore<> & store, const ImageIORegion & storeIORegion, TPixel * buffer) @@ -209,7 +248,8 @@ ReadFromStoreIfTypesMatch(const IOComponentEnum componentType, // Tries to read from the specified store, trying any of the specified pixel types. template bool -TryToReadFromStore(const IOComponentEnum componentType, +TryToReadFromStore(TypeList, + const IOComponentEnum componentType, const tensorstore::TensorStore<> & store, const ImageIORegion & storeIORegion, void * buffer) @@ -217,6 +257,92 @@ TryToReadFromStore(const IOComponentEnum componentType, return (ReadFromStoreIfTypesMatch(componentType, store, storeIORegion, buffer) || ...); } +// Writes to the store if the specified pixel type and the ITK component type match. +template +bool +WriteToStoreIfTypesMatch(const IOComponentEnum componentType, + tensorstore::TensorStore<> & store, + tensorstore::Context & tsContext, + const std::string & fileName, + const std::string & path, + const std::vector & shape, + const void * const buffer) +{ + if (tensorstoreToITKComponentType(tensorstore::dtype_v) == componentType) + { + std::string dtype; + // we prefer to write using our own endianness, so no conversion is necessary + if (ByteSwapper::SystemIsBigEndian()) + { + dtype = ">"; + } + else + { + dtype = "<"; + } + + if (sizeof(TPixel) == 1) + { + dtype = "|"; + } + if (std::numeric_limits::is_integer) + { + if (std::numeric_limits::is_signed) + { + dtype += 'i'; + } + else + { + dtype += 'u'; + } + } + else + { + dtype += 'f'; + } + dtype += std::to_string(sizeof(TPixel)); + + auto openFuture = tensorstore::Open( + { + { "driver", "zarr" }, + { "kvstore", { { "driver", getKVstoreDriver(fileName) }, { "path", fileName + "/" + path } } }, + { "metadata", + { + { "compressor", { { "id", "blosc" } } }, + { "dtype", dtype }, + { "shape", shape }, + } }, + }, + tsContext, + tensorstore::OpenMode::create | tensorstore::OpenMode::delete_existing, + tensorstore::ReadWriteMode::read_write); + TS_EVAL_CHECK(openFuture); + + auto writeStore = openFuture.value(); + auto * p = static_cast(buffer); + auto arr = tensorstore::Array(p, shape, tensorstore::c_order); + auto writeFuture = tensorstore::Write(tensorstore::UnownedToShared(arr), writeStore); + TS_EVAL_CHECK(writeFuture); + return true; + } + return false; +} + +// Tries to write to the specified store, trying any of the specified pixel types. +template +bool +TryToWriteToStore(TypeList, + const IOComponentEnum componentType, + tensorstore::TensorStore<> & store, + tensorstore::Context & tsContext, + const std::string & fileName, + const std::string & path, + const std::vector & shape, + const void * const buffer) +{ + return (WriteToStoreIfTypesMatch(componentType, store, tsContext, fileName, path, shape, buffer) || ...); +} + // Update an existing "read" specification for an "http" driver to retrieve remote files. // Note that an "http" driver specification may operate on an HTTP or HTTPS connection. void @@ -273,26 +399,6 @@ OMEZarrNGFFImageIO::PrintSelf(std::ostream & os, Indent indent) const os << indent << "ChannelIndex: " << m_ChannelIndex << std::endl; } -// Returns TensorStore KvStore driver name appropriate for this path. -// Options are file, zip. TODO: http, gcs (GoogleCouldStorage), etc. -std::string -getKVstoreDriver(std::string path) -{ - if (path.size() < 4) - { - return "file"; - } - if (path.substr(0, 4) == "http") - { // http or https - return "http"; - } - if (path.substr(path.size() - 4) == ".zip" || path.substr(path.size() - 7) == ".memory") - { - return "zip_memory"; - } - return "file"; -} - // JSON file path, e.g. "C:/Dev/ITKIOOMEZarrNGFF/v0.4/cyx.ome.zarr/.zgroup" void writeJson(nlohmann::json json, std::string path, std::string driver) @@ -379,18 +485,6 @@ OMEZarrNGFFImageIO::CanReadFile(const char * filename) // return this->HasSupportedWriteExtension(filename, true); } -// Evaluate tensorstore future (statement) and error-check the result. -#define TS_EVAL_CHECK(statement) \ - { \ - auto result = statement.result(); \ - if (!result.ok()) /* error */ \ - { \ - itkExceptionMacro("tensorstore error: " << result.status()); \ - } \ - } \ - ITK_NOOP_STATEMENT - - thread_local tensorstore::TensorStore<> store; // initialized by ReadImageInformation/ReadArrayMetadata void @@ -654,8 +748,7 @@ OMEZarrNGFFImageIO::Read(void * buffer) } if (const IOComponentEnum componentType{ this->GetComponentType() }; - !TryToReadFromStore( - componentType, store, storeIORegion, buffer)) + !TryToReadFromStore(supportedPixelTypes, componentType, store, storeIORegion, buffer)) { itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(componentType)); } @@ -714,54 +807,6 @@ OMEZarrNGFFImageIO::WriteImageInformation() } -// We need to specify dtype for opening. As dtype is dependent on component type, this macro is long. -#define ELEMENT_WRITE(typeName) \ - else if (tensorstoreToITKComponentType(tensorstore::dtype_v) == this->GetComponentType()) \ - { \ - if (sizeof(typeName) == 1) \ - { \ - dtype = "|"; \ - } \ - if (std::numeric_limits::is_integer) \ - { \ - if (std::numeric_limits::is_signed) \ - { \ - dtype += 'i'; \ - } \ - else \ - { \ - dtype += 'u'; \ - } \ - } \ - else \ - { \ - dtype += 'f'; \ - } \ - dtype += std::to_string(sizeof(typeName)); \ - \ - auto openFuture = tensorstore::Open( \ - { \ - { "driver", "zarr" }, \ - { "kvstore", { { "driver", driver }, { "path", this->m_FileName + "/" + path } } }, \ - { "metadata", \ - { \ - { "compressor", { { "id", "blosc" } } }, \ - { "dtype", dtype }, \ - { "shape", shape }, \ - } }, \ - }, \ - tsContext, \ - tensorstore::OpenMode::create | tensorstore::OpenMode::delete_existing, \ - tensorstore::ReadWriteMode::read_write); \ - TS_EVAL_CHECK(openFuture); \ - \ - auto writeStore = openFuture.value(); \ - auto * p = reinterpret_cast(buffer); \ - auto arr = tensorstore::Array(p, shape, tensorstore::c_order); \ - auto writeFuture = tensorstore::Write(tensorstore::UnownedToShared(arr), writeStore); \ - TS_EVAL_CHECK(writeFuture); \ - } - void OMEZarrNGFFImageIO::Write(const void * buffer) { @@ -771,9 +816,11 @@ OMEZarrNGFFImageIO::Write(const void * buffer) } this->WriteImageInformation(); - if (itkToTensorstoreComponentType(this->GetComponentType()) == tensorstore::dtype_v) + const IOComponentEnum componentType{ this->GetComponentType() }; + + if (itkToTensorstoreComponentType(componentType) == tensorstore::dtype_v) { - itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType())); + itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(componentType)); } std::vector shape(this->GetNumberOfDimensions()); @@ -788,35 +835,9 @@ OMEZarrNGFFImageIO::Write(const void * buffer) shape[shape.size() - 1 - d] = dSize; // convert IJK into KJI } - std::string dtype; - // we prefer to write using our own endianness, so no conversion is necessary - if (ByteSwapper::SystemIsBigEndian()) - { - dtype = ">"; - } - else - { - dtype = "<"; - } - - std::string driver = getKVstoreDriver(this->GetFileName()); - - if (false) // start with a plain "if" - { - } // so element statements can all be "else if" - ELEMENT_WRITE(int8_t) - ELEMENT_WRITE(uint8_t) - ELEMENT_WRITE(int16_t) - ELEMENT_WRITE(uint16_t) - ELEMENT_WRITE(int32_t) - ELEMENT_WRITE(uint32_t) - ELEMENT_WRITE(int64_t) - ELEMENT_WRITE(uint64_t) - ELEMENT_WRITE(float) - ELEMENT_WRITE(double) - else + if (!TryToWriteToStore(supportedPixelTypes, componentType, store, tsContext, m_FileName, path, shape, buffer)) { - itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType())); + itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(componentType)); } if (m_FileName.substr(m_FileName.size() - 4) == ".zip" || m_FileName.substr(m_FileName.size() - 7) == ".memory")