Skip to content

Commit

Permalink
non-x-y-subblocks (#63)
Browse files Browse the repository at this point in the history
* initial draft

* something like that?

* formatting

* cosmetic and final touches

* bump version number

* add & fix unittest

* cosmetic

* cosmetic

* fix build error

* fix formatting
  • Loading branch information
ptahmose authored Sep 1, 2023
1 parent 621c519 commit 1d26fe8
Show file tree
Hide file tree
Showing 11 changed files with 804 additions and 77 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.15)
cmake_policy(SET CMP0091 NEW) # enable new "MSVC runtime library selection" (https://cmake.org/cmake/help/latest/variable/CMAKE_MSVC_RUNTIME_LIBRARY.html)

project(libCZI
VERSION 0.50.1
VERSION 0.51.0
HOMEPAGE_URL "https://github.com/ZEISS/libczi"
DESCRIPTION "libCZI is an Open Source Cross-Platform C++ library to read and write CZI")

Expand Down
70 changes: 38 additions & 32 deletions Src/libCZI/CZIReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,21 @@ CCZIReader::~CCZIReader()
{
}

/*virtual */void CCZIReader::Open(std::shared_ptr<libCZI::IStream> stream)
/*virtual */void CCZIReader::Open(const std::shared_ptr<libCZI::IStream>& stream, const ICZIReader::OpenOptions* options)
{
if (this->isOperational == true)
{
throw logic_error("CZIReader is already operational.");
}

if (options == nullptr)
{
auto default_options = OpenOptions{};
return CCZIReader::Open(stream, &default_options);
}

this->hdrSegmentData = CCZIParse::ReadFileHeaderSegmentData(stream.get());
this->subBlkDir = CCZIParse::ReadSubBlockDirectory(stream.get(), this->hdrSegmentData.GetSubBlockDirectoryPosition());
this->subBlkDir = CCZIParse::ReadSubBlockDirectory(stream.get(), this->hdrSegmentData.GetSubBlockDirectoryPosition(), options->lax_subblock_coordinate_checks);
auto attachmentPos = this->hdrSegmentData.GetAttachmentDirectoryPosition();
if (attachmentPos != 0)
{
Expand Down Expand Up @@ -84,13 +90,13 @@ CCZIReader::~CCZIReader()
[&](int index, const CCziSubBlockDirectory::SubBlkEntry& entry)->bool
{
SubBlockInfo info;
info.compressionModeRaw = entry.Compression;
info.pixelType = CziUtils::PixelTypeFromInt(entry.PixelType);
info.coordinate = entry.coordinate;
info.logicalRect = IntRect{ entry.x,entry.y,entry.width,entry.height };
info.physicalSize = IntSize{ (std::uint32_t)entry.storedWidth, (std::uint32_t)entry.storedHeight };
info.mIndex = entry.mIndex;
return funcEnum(index, info);
info.compressionModeRaw = entry.Compression;
info.pixelType = CziUtils::PixelTypeFromInt(entry.PixelType);
info.coordinate = entry.coordinate;
info.logicalRect = IntRect{ entry.x,entry.y,entry.width,entry.height };
info.physicalSize = IntSize{ (std::uint32_t)entry.storedWidth, (std::uint32_t)entry.storedHeight };
info.mIndex = entry.mIndex;
return funcEnum(index, info);
});
}

Expand All @@ -101,14 +107,14 @@ CCZIReader::~CCZIReader()
[&](int index, const CCziSubBlockDirectory::SubBlkEntry& entry)->bool
{
DirectorySubBlockInfo info;
info.compressionModeRaw = entry.Compression;
info.pixelType = CziUtils::PixelTypeFromInt(entry.PixelType);
info.coordinate = entry.coordinate;
info.logicalRect = IntRect{ entry.x,entry.y,entry.width,entry.height };
info.physicalSize = IntSize{ (std::uint32_t)entry.storedWidth, (std::uint32_t)entry.storedHeight };
info.mIndex = entry.mIndex;
info.filePosition = entry.FilePosition;
return funcEnum(index, info);
info.compressionModeRaw = entry.Compression;
info.pixelType = CziUtils::PixelTypeFromInt(entry.PixelType);
info.coordinate = entry.coordinate;
info.logicalRect = IntRect{ entry.x,entry.y,entry.width,entry.height };
info.physicalSize = IntSize{ (std::uint32_t)entry.storedWidth, (std::uint32_t)entry.storedHeight };
info.mIndex = entry.mIndex;
info.filePosition = entry.FilePosition;
return funcEnum(index, info);
});
}

Expand Down Expand Up @@ -137,7 +143,7 @@ CCZIReader::~CCZIReader()
}
}

return true;
return true;
});
}

Expand Down Expand Up @@ -169,8 +175,8 @@ CCZIReader::~CCZIReader()
[&](int index, const SubBlockInfo& sbinfo)->bool
{
info = sbinfo;
foundASubBlock = true;
return false;
foundASubBlock = true;
return false;
});
}
else
Expand All @@ -179,14 +185,14 @@ CCZIReader::~CCZIReader()
[&](int index, const SubBlockInfo& sbinfo)->bool
{
int c;
if (sbinfo.coordinate.TryGetPosition(DimensionIndex::C, &c) == true && c == channelIndex)
{
info = sbinfo;
foundASubBlock = true;
return false;
}
if (sbinfo.coordinate.TryGetPosition(DimensionIndex::C, &c) == true && c == channelIndex)
{
info = sbinfo;
foundASubBlock = true;
return false;
}

return true;
return true;
});
}

Expand Down Expand Up @@ -236,10 +242,10 @@ CCZIReader::~CCZIReader()
[&](int index, const CCziAttachmentsDirectory::AttachmentEntry& ae)
{
ai.contentGuid = ae.ContentGuid;
memcpy(ai.contentFileType, ae.ContentFileType, sizeof(ae.ContentFileType));
ai.name = ae.Name;
bool b = funcEnum(index, ai);
return b;
memcpy(ai.contentFileType, ae.ContentFileType, sizeof(ae.ContentFileType));
ai.name = ae.Name;
bool b = funcEnum(index, ai);
return b;
});
}

Expand All @@ -263,7 +269,7 @@ CCZIReader::~CCZIReader()
}
}

return true;
return true;
});
}

Expand Down
2 changes: 1 addition & 1 deletion Src/libCZI/CZIReader.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CCZIReader : public libCZI::ICZIReader, public std::enable_shared_from_thi
void EnumerateSubBlocksEx(const std::function<bool(int index, const libCZI::DirectorySubBlockInfo& info)>& funcEnum) override;

// interface ICZIReader
void Open(std::shared_ptr<libCZI::IStream> stream) override;
void Open(const std::shared_ptr<libCZI::IStream>& stream, const ICZIReader::OpenOptions* options) override;
libCZI::FileHeaderInfo GetFileHeaderInfo() override;
std::shared_ptr<libCZI::IMetadataSegment> ReadMetadataSegment() override;
std::shared_ptr<libCZI::IAccessor> CreateAccessor(libCZI::AccessorType accessorType) override;
Expand Down
86 changes: 61 additions & 25 deletions Src/libCZI/CziParse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,15 @@ using namespace libCZI;
return fileHdr;
}

/*static*/CCziSubBlockDirectory CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset)
/*static*/CCziSubBlockDirectory CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, bool lax_subblock_coordinate_checks)
{
CCziSubBlockDirectory subBlkDir;
CCZIParse::ReadSubBlockDirectory(str, offset, subBlkDir);
CCZIParse::ReadSubBlockDirectory(str, offset, subBlkDir, lax_subblock_coordinate_checks);
subBlkDir.AddingFinished();
return subBlkDir;
}

/*static*/void CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, SegmentSizes* segmentSizes /*= nullptr*/)
/*static*/void CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, bool lax_subblock_coordinate_checks, SegmentSizes* segmentSizes /*= nullptr*/)
{
SubBlockDirectorySegment subBlckDirSegment;
std::uint64_t bytesRead;
Expand Down Expand Up @@ -152,20 +152,20 @@ using namespace libCZI;
}
else if (subBlkDirDV != nullptr)
{
CCZIParse::AddEntryToSubBlockDirectory(subBlkDirDV, addFunc);
CCZIParse::AddEntryToSubBlockDirectory(subBlkDirDV, addFunc, lax_subblock_coordinate_checks);
}
});
}

/*static*/void CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, CCziSubBlockDirectory& subBlkDir)
/*static*/void CCZIParse::ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, CCziSubBlockDirectory& subBlkDir, bool lax_subblock_coordinate_checks)
{
CCZIParse::ReadSubBlockDirectory(str, offset, [&](const CCziSubBlockDirectoryBase::SubBlkEntry& e)->void {subBlkDir.AddSubBlock(e); });
CCZIParse::ReadSubBlockDirectory(str, offset, [&](const CCziSubBlockDirectoryBase::SubBlkEntry& e)->void {subBlkDir.AddSubBlock(e); }, lax_subblock_coordinate_checks, nullptr);
}

/*static*/CCziAttachmentsDirectory CCZIParse::ReadAttachmentsDirectory(libCZI::IStream* str, std::uint64_t offset)
{
CCziAttachmentsDirectory attDir;
CCZIParse::ReadAttachmentsDirectory(str, offset, [&](const CCziAttachmentsDirectoryBase::AttachmentEntry& ae)->void {attDir.AddAttachmentEntry(ae); });
CCZIParse::ReadAttachmentsDirectory(str, offset, [&](const CCziAttachmentsDirectoryBase::AttachmentEntry& ae)->void {attDir.AddAttachmentEntry(ae); }, nullptr);
return attDir;
}

Expand Down Expand Up @@ -518,40 +518,76 @@ using namespace libCZI;
throw std::logic_error("not (yet) implemented");
}

/*static*/void CCZIParse::AddEntryToSubBlockDirectory(const SubBlockDirectoryEntryDV* subBlkDirDE, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc)
/*static*/void CCZIParse::AddEntryToSubBlockDirectory(const SubBlockDirectoryEntryDV* subBlkDirDV, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, bool lax_subblock_coordinate_checks)
{
CCziSubBlockDirectory::SubBlkEntry entry;
entry.Invalidate();

// TODO: - add consistency checks like dimension appears twice, X and Y are not present ...
for (int i = 0; i < subBlkDirDE->DimensionCount; ++i)
bool x_was_given = false;
bool y_was_given = false;
for (int i = 0; i < subBlkDirDV->DimensionCount; ++i)
{
if (CCZIParse::IsXDimension(subBlkDirDE->DimensionEntries[i].Dimension, 4))
if (CCZIParse::IsXDimension(subBlkDirDV->DimensionEntries[i].Dimension, 4))
{
entry.x = subBlkDirDE->DimensionEntries[i].Start;
entry.width = subBlkDirDE->DimensionEntries[i].Size;
entry.storedWidth = subBlkDirDE->DimensionEntries[i].StoredSize;
entry.x = subBlkDirDV->DimensionEntries[i].Start;
entry.width = subBlkDirDV->DimensionEntries[i].Size;
entry.storedWidth = subBlkDirDV->DimensionEntries[i].StoredSize;
x_was_given = true;
}
else if (CCZIParse::IsYDimension(subBlkDirDE->DimensionEntries[i].Dimension, 4))
else if (CCZIParse::IsYDimension(subBlkDirDV->DimensionEntries[i].Dimension, 4))
{
entry.y = subBlkDirDE->DimensionEntries[i].Start;
entry.height = subBlkDirDE->DimensionEntries[i].Size;
entry.storedHeight = subBlkDirDE->DimensionEntries[i].StoredSize;
entry.y = subBlkDirDV->DimensionEntries[i].Start;
entry.height = subBlkDirDV->DimensionEntries[i].Size;
entry.storedHeight = subBlkDirDV->DimensionEntries[i].StoredSize;
y_was_given = true;
}
else if (CCZIParse::IsMDimension(subBlkDirDE->DimensionEntries[i].Dimension, 4))
else if (CCZIParse::IsMDimension(subBlkDirDV->DimensionEntries[i].Dimension, 4))
{
entry.mIndex = subBlkDirDE->DimensionEntries[i].Start;
entry.mIndex = subBlkDirDV->DimensionEntries[i].Start;
if (!lax_subblock_coordinate_checks && subBlkDirDV->DimensionEntries[i].Size != 1)
{
stringstream string_stream;
string_stream << "Size for dimension 'M' is expected to be 1, but found " << subBlkDirDV->DimensionEntries[i].Size << " (file-offset:" << subBlkDirDV->FilePosition << ").";
throw LibCZICZIParseException(string_stream.str().c_str(), LibCZICZIParseException::ErrorCode::NonConformingSubBlockDimensionEntry);
}
}
else
{
libCZI::DimensionIndex dim = CCZIParse::DimensionCharToDimensionIndex(subBlkDirDE->DimensionEntries[i].Dimension, 4);
entry.coordinate.Set(dim, subBlkDirDE->DimensionEntries[i].Start);
libCZI::DimensionIndex dim = CCZIParse::DimensionCharToDimensionIndex(subBlkDirDV->DimensionEntries[i].Dimension, 4);
entry.coordinate.Set(dim, subBlkDirDV->DimensionEntries[i].Start);
if (!lax_subblock_coordinate_checks && subBlkDirDV->DimensionEntries[i].Size != 1)
{
stringstream string_stream;
string_stream << "Size for dimension '" << Utils::DimensionToChar(dim) << "' is expected to be 1, but found " << subBlkDirDV->DimensionEntries[i].Size << " (file-offset:" << subBlkDirDV->FilePosition << ").";
throw LibCZICZIParseException(string_stream.str().c_str(), LibCZICZIParseException::ErrorCode::NonConformingSubBlockDimensionEntry);
}
}
}

entry.FilePosition = subBlkDirDE->FilePosition;
entry.PixelType = subBlkDirDE->PixelType;
entry.Compression = subBlkDirDE->Compression;
if (!lax_subblock_coordinate_checks && (!x_was_given || !y_was_given))
{
stringstream string_stream;
string_stream << "No coordinate/size given for ";
if (!x_was_given && y_was_given)
{
string_stream << "'X'";
}
else if (!y_was_given && x_was_given)
{
string_stream << "'Y'";
}
else
{
string_stream << "'X' and 'Y'";
}

string_stream << " (file-offset:" << subBlkDirDV->FilePosition << ").";
throw LibCZICZIParseException(string_stream.str().c_str(), LibCZICZIParseException::ErrorCode::NonConformingSubBlockDimensionEntry);
}

entry.FilePosition = subBlkDirDV->FilePosition;
entry.PixelType = subBlkDirDV->PixelType;
entry.Compression = subBlkDirDV->Compression;

addFunc(entry);
}
Expand Down
22 changes: 17 additions & 5 deletions Src/libCZI/CziParse.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,25 @@ class CCZIParse
static FileHeaderSegmentData ReadFileHeaderSegment(libCZI::IStream* str);
static CFileHeaderSegmentData ReadFileHeaderSegmentData(libCZI::IStream* str);

static CCziSubBlockDirectory ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset);
/// Parse the subblock-directory from the specified stream at the specified offset.
/// Historically, libCZI did not check whether the elements in the dimensions-entry-list had a size other than
/// "1" given (for all dimensions other than X and Y). We refer to this as "lax parsing". If the argument
/// lax_subblock_coordinate_checks is true, then we check for those sizes to be as expected and otherwise
/// throw an exception.
///
/// \param [in,out] str The stream to read from.
/// \param offset The offset in the stream.
/// \param lax_subblock_coordinate_checks True to do "lax subblock coordinate checking".
///
/// \returns An in-memory representation of the subblock-directory.
static CCziSubBlockDirectory ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, bool lax_subblock_coordinate_checks);

static CCziAttachmentsDirectory ReadAttachmentsDirectory(libCZI::IStream* str, std::uint64_t offset);
static void ReadAttachmentsDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziAttachmentsDirectoryBase::AttachmentEntry&)>& addFunc, SegmentSizes* segmentSizes = nullptr);
static void ReadAttachmentsDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziAttachmentsDirectoryBase::AttachmentEntry&)>& addFunc, SegmentSizes* segmentSizes);

static void ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, CCziSubBlockDirectory& subBlkDir);
static void ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, CCziSubBlockDirectory& subBlkDir, bool lax_subblock_coordinate_checks);

static void ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, SegmentSizes* segmentSizes = nullptr);
static void ReadSubBlockDirectory(libCZI::IStream* str, std::uint64_t offset, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, bool lax_subblock_coordinate_checks, SegmentSizes* segmentSizes);

struct SubBlockStorageAllocate
{
Expand Down Expand Up @@ -98,7 +110,7 @@ class CCZIParse
static void ParseThroughDirectoryEntries(int count, const std::function<void(int, void*)>& funcRead, const std::function<void(const SubBlockDirectoryEntryDE*, const SubBlockDirectoryEntryDV*)>& funcAddEntry);

static void AddEntryToSubBlockDirectory(const SubBlockDirectoryEntryDE* subBlkDirDE, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc);
static void AddEntryToSubBlockDirectory(const SubBlockDirectoryEntryDV* subBlkDirDE, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc);
static void AddEntryToSubBlockDirectory(const SubBlockDirectoryEntryDV* subBlkDirDV, const std::function<void(const CCziSubBlockDirectoryBase::SubBlkEntry&)>& addFunc, bool lax_subblock_coordinate_checks);

static libCZI::DimensionIndex DimensionCharToDimensionIndex(const char* ptr, size_t size);
static bool IsMDimension(const char* ptr, size_t size);
Expand Down
1 change: 1 addition & 0 deletions Src/libCZI/CziReaderWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ void CCziReaderWriter::ReadCziStructure()
{
this->sbBlkDirectory.AddSubBlock(e);
},
true,
&sbBlkDirSegmentSize);

this->sbBlkDirectory.SetModified(false);
Expand Down
35 changes: 28 additions & 7 deletions Src/libCZI/libCZI.h
Original file line number Diff line number Diff line change
Expand Up @@ -618,17 +618,38 @@ namespace libCZI
};

/// This interface is used to represent the CZI-file.
class ICZIReader : public ISubBlockRepository, public ISubBlockRepositoryEx, public IAttachmentRepository
class LIBCZI_API ICZIReader : public ISubBlockRepository, public ISubBlockRepositoryEx, public IAttachmentRepository
{
public:
/// Opens the specified stream and reads the global information from the CZI-document.
/// The stream passed in will have its refcount incremented, a reference is held until Close
/// is called (or the instance is destroyed).
/// This structure gathers the settings for controlling the 'Open' operation of the CZIReader-class.
struct LIBCZI_API OpenOptions
{
/// This option controls whether the lax parameter validation when parsing the dimension-entry of a subblock is to be used.
/// Previous versions of libCZI did not check whether certain values in the file have the expected value. If those values
/// are different than expected, this meant that libCZI would not be able to deal with the document properly.
/// If lax checking of this is disabled, then Open will fail with a corresponding exception.
/// The default is to enable lax checking (for compatibility with previous libCZI-versions), but users are encouraged to
/// disable this for new code.
bool lax_subblock_coordinate_checks{ true };

/// Sets the the default.
void SetDefault()
{
this->lax_subblock_coordinate_checks = true;
}
};

/// Opens the specified stream and reads the global information from the CZI-document. The stream
/// passed in will have its refcount incremented, a reference is held until Close is called (or
/// the instance is destroyed).
///
/// \remark
/// If this method is called twice, then an exception of type std::logic_error is thrown.
/// If this method is called twice (assuming successful return), then an exception of type std::logic_error is
/// thrown.
///
/// \param stream The stream object.
virtual void Open(std::shared_ptr<IStream> stream) = 0;
/// \param stream The stream object.
/// \param options (Optional) Options for controlling the operation. If nullptr is given here, then the default settings are used.
virtual void Open(const std::shared_ptr<IStream>& stream, const OpenOptions* options = nullptr) = 0;

/// Gets the file header information.
/// \return The file header information.
Expand Down
Loading

0 comments on commit 1d26fe8

Please sign in to comment.