libCZI
Reading and Writing CZI documents made easy
Loading...
Searching...
No Matches
using libCZI

Opening a CZI-file

The first thing you want to do with a CZI-file is to open it. This is done with the CZIReader-object. An instance of a CZIReader is created with the function

std::shared_ptr<ICZIReader> CreateCZIReader()

The CZIReader-object has the following methods

virtual void Open(std::shared_ptr<IStream> stream);
virtual std::shared_ptr<IMetadataSegment> ReadMetadataSegment();
virtual std::shared_ptr<IAccessor> CreateAccessor(AccessorType accessorType);
virtual void Close();
// ... some omitted
virtual SubBlockStatistics GetStatistics();

With the Open-method, the caller has to pass in an object which implements the interface "IStream". This object is used by the CZIReader in order to access the data in a CZI-file. It is just the abstraction of a random-access stream. libCZI includes a (simple) implementation for reading a file which is constructed by passing in a filename:

std::shared_ptr<IStream> CreateStreamFromFile(const wchar_t* szFilename)

When the Open-method has executed, the sub-block directory has been read and parsed, and the remaing methods can be called. This code can be used to open a CZI-file:

auto stream = libCZI::CreateStreamFromFile(LR"(D:\CZI\sample.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
LIBCZI_API std::shared_ptr< ICZIReader > CreateCZIReader()
LIBCZI_API std::shared_ptr< IStream > CreateStreamFromFile(const wchar_t *szFilename)

The SubBlockStatistics gives information about the sub-blocks in the CZI-file. The coordinates of each sub-block are examined, and the mininum and maximum are determined and are available in the struct returned by the method ISubBlockRepository::GetStatistics(). It is usually important to examine the dimBounds member in order to determine which dimensions are used in the CZI-file.

Reading sub-blocks in a CZI-file

All the sub-blocks in the CZI-file can be enumerated by the method ISubBlockRepository::EnumerateSubBlocks:

virtual void EnumerateSubBlocks(std::function<bool(int index, const SubBlockInfo& info)> funcEnum)

This code will print some of the information for all sub-blocks contained in a CZI-file:

auto stream = libCZI::CreateStreamFromFile(LR"(W:\Data\TestData\DCV_30MB.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
cziReader->EnumerateSubBlocks(
[](int idx, const libCZI::SubBlockInfo& info)
{
cout << "Index " << idx << ": " << libCZI::Utils::DimCoordinateToString(&info.coordinate) << " Rect=" << info.logicalRect << endl;
return true;
});
static std::string DimCoordinateToString(const libCZI::IDimCoordinate *coord)
Information about a sub-block.
Definition libCZI.h:235
libCZI::IntRect logicalRect
The rectangle where the bitmap (in this sub-block) is located.
Definition libCZI.h:248
libCZI::CDimCoordinate coordinate
The coordinate of the sub-block.
Definition libCZI.h:245

It will print out something like:

...
Index 4: Z4C0T0 Rect=(0,0,512,512)
Index 5: Z5C0T0 Rect=(0,0,512,512)
Index 6: Z6C0T0 Rect=(0,0,512,512)
Index 7: Z7C0T0 Rect=(0,0,512,512)
Index 8: Z8C0T0 Rect=(0,0,512,512)
Index 9: Z9C0T0 Rect=(0,0,512,512)
Index 10: Z10C0T0 Rect=(0,0,512,512)
Index 11: Z11C0T0 Rect=(0,0,512,512)
Index 12: Z12C0T0 Rect=(0,0,512,512)
Index 13: Z13C0T0 Rect=(0,0,512,512)
Index 14: Z14C0T0 Rect=(0,0,512,512)
...

The index argument we got here is used to identify a sub-block and can be used with the method ISubBlockRepository::ReadSubBlock. This code will enumerate all sub-blocks found in a CZI-file, read them and save them to a PNG-file:

auto stream = libCZI::CreateStreamFromFile(LR"(W:\Data\TestData\DCV_30MB.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
cziReader->EnumerateSubBlocks(
[&](int idx, const libCZI::SubBlockInfo& info)
{
auto sbBlk = cziReader->ReadSubBlock(idx);
auto bitmap = sbBlk->CreateBitmap();
wstring filename(L"SubBlock#");
filename += to_wstring(idx);
filename += L".PNG";
SaveAsPng(filename, bitmap);
return true;
});

Note that the function SaveAsPng is not part of libCZI. It is also worth noting that the ISubBlock::CreateBitmap method will transparently decode the bitmap (in case we have a compressed bitmap in the sub-block).

creating multi-tile composites

This piece of code will extract a small rectangular region from a huge multi-tile document:

auto stream = libCZI::CreateStreamFromFile(LR"(D:\PICTURES\NaCZIrTestData\Example_TMA1_Zeb1_SPRR2_Ck19_S100-1-1-1-1.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
auto statistics = cziReader->GetStatistics();
auto accessor = cziReader->CreateSingleChannelTileAccessor();
libCZI::CDimCoordinate planeCoord{ { libCZI::DimensionIndex::C,1 } }; // the document only contains C-dimension
auto multiTileComposit = accessor->Get(
libCZI::IntRect{ statistics.boundingBox.x + 26152, statistics.boundingBox.y + 32215 ,3000,2200 },
&planeCoord,
nullptr); // use default options
SaveAsPng(LR"(D:\TileComposite.png)", multiTileComposit);
Implementation of a class representing a coordinate (and implementing the IDimCoordinate-interface).
Definition libCZI_DimCoordinate.h:149
@ C
The C-dimension ("channel").
A rectangle (with integer coordinates).
Definition libCZI_Pixels.h:17
int x
The x-coordinate of the upper-left point of the rectangle.
Definition libCZI_Pixels.h:18

Note that we are using the SubBlockStatistics in order to specify a ROI with coordinates relative to the upper left corner of the bounding box. The result is depicted here:

Here is an example which leverages the SingleChannelScalingTileAccessor:

auto stream = libCZI::CreateStreamFromFile(LR"(D:\PICTURES\NaCZIrTestData\Example_TMA1_Zeb1_SPRR2_Ck19_S100-1-1-1-1.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
auto statistics = cziReader->GetStatistics();
auto accessor = cziReader->CreateSingleChannelScalingTileAccessor();
libCZI::CDimCoordinate planeCoord{ { libCZI::DimensionIndex::C,1 } }; // the document only contains C-dimension, we choose channel#1
auto multiTileComposit = accessor->Get(
statistics.boundingBox.x + statistics.boundingBox.w / 4,
statistics.boundingBox.y + statistics.boundingBox.h / 4 ,
(statistics.boundingBox.w / 8) * 5,
(statistics.boundingBox.h / 8) * 5 },
&planeCoord,
0.1f,
nullptr);
SaveAsPng(LR"(D:\ScalingTileComposite.png)", multiTileComposit);

The ROI is now x= \(\frac{width}{4}\), y= \(\frac{height}{4}\), w= \(\frac{5}{8}\cdot width\), h= \(\frac{5}{8}\cdot height\) - where \(width\) and \(height\) refer to the width and height of the bounding box of the document.
The zoom is given as 0.1 - so the resulting document will have width= \(0.1\times\frac{5}{8}\cdot width\) and height=width= \(0.1\times\frac{5}{8}\cdot height\).

This operaton is depicted here:

creating a multi-channel composite

In order to create a colorful picture from a bunch of channels (usually grayscale), we need to apply a color to it - that's refered to as "tinting". Furthermore, we want to apply a gradation curve. All the required parameters for this are refered to as "display settings". In a CZI-file we can find display settings in the metadata.
The following sample is reading the display settings from the metadata; then we get a (scaled) multi-tile composite for each of the channels (more exactly: only for those channels which are marked 'active' in the display settings). Those bitmaps are then fed into a function which will produce the multi-channel-composite (according to the display settings).

auto stream = libCZI::CreateStreamFromFile(LR"(D:\PICTURES\NaCZIrTestData\Example_TMA1_Zeb1_SPRR2_Ck19_S100-1-1-1-1.czi)");
auto cziReader = libCZI::CreateCZIReader();
cziReader->Open(stream);
auto statistics = cziReader->GetStatistics();
// get the display-setting from the document's metadata
auto mds = cziReader->ReadMetadataSegment();
auto md = mds->CreateMetaFromMetadataSegment();
auto docInfo = md->GetDocumentInfo();
auto dsplSettings = docInfo->GetDisplaySettings();
statistics.boundingBox.x + statistics.boundingBox.w / 4,
statistics.boundingBox.y + statistics.boundingBox.h / 4 ,
(statistics.boundingBox.w / 8) * 5, (statistics.boundingBox.h / 8) * 5 };
// get the tile-composite for all channels (which are marked 'active' in the display-settings)
std::vector<shared_ptr<libCZI::IBitmapData>> actvChBms;
int index = 0; // index counting only the active channels
std::map<int, int> activeChNoToChIdx; // we need to keep track which 'active channels" corresponds to which channel index
auto accessor = cziReader->CreateSingleChannelScalingTileAccessor();
libCZI::CDisplaySettingsHelper::EnumEnabledChannels(dsplSettings.get(),
[&](int chIdx)->bool
{
libCZI::CDimCoordinate planeCoord{ { libCZI::DimensionIndex::C, chIdx } };
actvChBms.emplace_back(accessor->Get(roi, &planeCoord, 0.05f, nullptr));
activeChNoToChIdx[chIdx] = index++;
return true;
});
// initialize the helper with the display-settings and provide the pixeltypes
// (for each active channel)
libCZI::CDisplaySettingsHelper dsplHlp;
dsplHlp.Initialize(dsplSettings.get(),
[&](int chIdx)->libCZI::PixelType { return actvChBms[activeChNoToChIdx[chIdx]]->GetPixelType(); });
// pass the tile-composites we just created (and the display-settings for the those active
// channels) into the multi-channel-composor-function
dsplHlp.GetActiveChannelsCount(),
std::begin(actvChBms),
dsplHlp.GetChannelInfosArray());
SaveAsPng(LR"(D:\ScalingTileComposite_MultiChannelComposite.png)", mcComposite);
static void ComposeMultiChannel_Bgr24(libCZI::IBitmapData *dest, int channelCount, libCZI::IBitmapData *const *srcBitmaps, const ChannelInfo *channelInfos)
PixelType
An enum representing a pixel-type.
Definition libCZI_Pixels.h:144

In this sample we used the same document and ROI as before. The CZI-file in this case contained 5 channels (and all being 'active'), so we had to get 5 tile-composites (using the SingleChannelScalingTileAccessor), which are then all put into the ComposeMultiChannel_Bgr24 function (alongside with the corresponding display-settings). The function will produce a new bitmap (always of pixeltype Bgr24) which then contains the multi-channel-composite image. In this process we leveraged a utility CDisplaySettingsHelper which hides some of the book-keeping (among other things, it helps sorting out the 'active' channels and it converts the display-settings we got from the document's metadata into the form expected by the ComposeMultiChannel_Bgr24 function).

The complete operation is depicted here:

stream objects

All input/output operations in libCZI are done through stream objects. Stream objects are used by the CZIReader to access the data in a CZI-file. The stream object is an abstraction of a random-access stream.
libCZI defines three different stream objects - read-only streams, write-only streams and read-write streams. The respective interfaces are: IStream, IOutputStream and IInputOutputStream. libCZI provides implementations for reading from a file and for writing to a file in the file-system.
In addition, there is an experimental implementation for reading from an http(s)-server. This implementation is based on libcurl and allows reading from a CZI-file which is located on a web-server.

For creating a stream object for reading, a class factory is provided (in the file libCZI_StreamsLib.h).