Skip to content

Commit

Permalink
Merge pull request #69 from N-Dekker/Replace-ELEMENT_WRITE-macro
Browse files Browse the repository at this point in the history
  • Loading branch information
dzenanz authored Aug 26, 2024
2 parents ccf414f + 679a165 commit c2ddc46
Showing 1 changed file with 134 additions and 113 deletions.
247 changes: 134 additions & 113 deletions src/itkOMEZarrNGFFImageIO.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,29 @@

#include <nlohmann/json.hpp>

// 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 <typename...>
struct TypeList
{};

constexpr TypeList<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double>
supportedPixelTypes{};

IOComponentEnum
tensorstoreToITKComponentType(const tensorstore::DataType dtype)
{
Expand Down Expand Up @@ -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 <typename TPixel>
void
ReadFromStore(const tensorstore::TensorStore<> & store, const ImageIORegion & storeIORegion, TPixel * buffer)
Expand Down Expand Up @@ -209,14 +248,101 @@ ReadFromStoreIfTypesMatch(const IOComponentEnum componentType,
// Tries to read from the specified store, trying any of the specified pixel types.
template <typename... TPixel>
bool
TryToReadFromStore(const IOComponentEnum componentType,
TryToReadFromStore(TypeList<TPixel...>,
const IOComponentEnum componentType,
const tensorstore::TensorStore<> & store,
const ImageIORegion & storeIORegion,
void * buffer)
{
return (ReadFromStoreIfTypesMatch<TPixel>(componentType, store, storeIORegion, buffer) || ...);
}

// Writes to the store if the specified pixel type and the ITK component type match.
template <typename TPixel>
bool
WriteToStoreIfTypesMatch(const IOComponentEnum componentType,
tensorstore::TensorStore<> & store,
tensorstore::Context & tsContext,
const std::string & fileName,
const std::string & path,
const std::vector<int64_t> & shape,
const void * const buffer)
{
if (tensorstoreToITKComponentType(tensorstore::dtype_v<TPixel>) == componentType)
{
std::string dtype;
// we prefer to write using our own endianness, so no conversion is necessary
if (ByteSwapper<int>::SystemIsBigEndian())
{
dtype = ">";
}
else
{
dtype = "<";
}

if (sizeof(TPixel) == 1)
{
dtype = "|";
}
if (std::numeric_limits<TPixel>::is_integer)
{
if (std::numeric_limits<TPixel>::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<TPixel const *>(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 <typename... TPixel>
bool
TryToWriteToStore(TypeList<TPixel...>,
const IOComponentEnum componentType,
tensorstore::TensorStore<> & store,
tensorstore::Context & tsContext,
const std::string & fileName,
const std::string & path,
const std::vector<int64_t> & shape,
const void * const buffer)
{
return (WriteToStoreIfTypesMatch<TPixel>(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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -654,8 +748,7 @@ OMEZarrNGFFImageIO::Read(void * buffer)
}

if (const IOComponentEnum componentType{ this->GetComponentType() };
!TryToReadFromStore<int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t, int64_t, uint64_t, float, double>(
componentType, store, storeIORegion, buffer))
!TryToReadFromStore(supportedPixelTypes, componentType, store, storeIORegion, buffer))
{
itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(componentType));
}
Expand Down Expand Up @@ -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<typeName>) == this->GetComponentType()) \
{ \
if (sizeof(typeName) == 1) \
{ \
dtype = "|"; \
} \
if (std::numeric_limits<typeName>::is_integer) \
{ \
if (std::numeric_limits<typeName>::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<typeName const *>(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)
{
Expand All @@ -771,9 +816,11 @@ OMEZarrNGFFImageIO::Write(const void * buffer)
}
this->WriteImageInformation();

if (itkToTensorstoreComponentType(this->GetComponentType()) == tensorstore::dtype_v<void>)
const IOComponentEnum componentType{ this->GetComponentType() };

if (itkToTensorstoreComponentType(componentType) == tensorstore::dtype_v<void>)
{
itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(this->GetComponentType()));
itkExceptionMacro("Unsupported component type: " << GetComponentTypeAsString(componentType));
}

std::vector<int64_t> shape(this->GetNumberOfDimensions());
Expand All @@ -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<int>::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")
Expand Down

0 comments on commit c2ddc46

Please sign in to comment.