- update README

- fix tests
- crt-static in build.rs
This commit is contained in:
w.pomp
2026-08-07 11:30:02 +02:00
parent a4de4a359f
commit 76d7d6a8ad
3 changed files with 222 additions and 30 deletions
+215 -5
View File
@@ -1,9 +1,219 @@
# libCZIrw-sys # libCZIrw-sys
Crate linking to [libCZIAPI](https://github.com/ZEISS/libczi). Crate linking to [libCZIAPI](https://github.com/ZEISS/libczi). This crate attempts to provide safe wrappers to objects
This crate attempts to provide save wrappers to objects and functions in libCZIAPI. and functions in libCZIAPI. Direct often unsafe access using pointer is available through the `sys` module.
Direct often unsafe access using pointer is available through the sys module.
By default, libCZIAPI will be statically linked. The feature 'dynamic' will switch it to dynamic linking. By default, libCZIAPI will be statically linked. The feature `dynamic` will switch it to dynamic linking.
This code is licensed with an MIT or APACHE 2 license, but Zeiss' libCZI which is included as a submodule has a LGPL license. This code is licensed with an MIT or APACHE 2 license, but Zeiss' libCZI which is included as a submodule has a LGPL
license.
### Reading a CZI file
The typical pattern is: create a reader, open it with an input stream, and query statistics or sub-blocks:
```rust
use libczirw_sys::{CziReader, Dimension, InputStream, ReaderOpenInfo};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// create the reader and a stream for the file
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8("path/to/file.czi")?;
czi.open(ReaderOpenInfo::new(&stream))?;
// get the (simple) statistics about the sub-blocks
let statistics = czi.get_statistics_simple()?;
println!("number of sub-blocks: {}", statistics.get_sub_block_count());
// iterate over the dimensions that are present (Z, C, T, ...)
let dim_bounds = statistics.get_dim_bounds();
let dimensions = Dimension::vec_from_bitflags(dim_bounds.get_dimensions_valid());
for (i, dim) in dimensions.iter().enumerate() {
println!("{:?}: {}", dim, dim_bounds.get_size()[i]);
}
// the bounding box of all sub-blocks
let bbox = statistics.get_bounding_box();
println!(
"overall extent: {} x {} (offset {}, {})",
bbox.get_w(),
bbox.get_h(),
bbox.get_x(),
bbox.get_y()
);
Ok(())
}
```
### Reading pixel data
Each sub-block can be decoded into a bitmap. Locking the bitmap gives access to the raw pixels:
```rust
use libczirw_sys::{CziReader, InputStream, ReaderOpenInfo};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8("path/to/file.czi")?;
czi.open(ReaderOpenInfo::new(&stream))?;
// read the first sub-block and turn it into a bitmap
let sub_block = czi.read_sub_block(0)?;
let bitmap = sub_block.create_bitmap()?;
let info = bitmap.get_info()?;
println!(
"width: {}, height: {}, pixel type: {:?}",
info.get_width(),
info.get_height(),
info.get_pixel_type()?
);
// locking gives access to the raw pixel data
let locked = bitmap.lock()?;
let stride = locked.lock_info.get_stride();
let pixels = locked.lock_info.get_data_roi(); // copies the pixel bytes
// the bitmap is automatically unlocked when `locked` goes out of scope
Ok(())
}
```
### Reading the metadata
CZI files store metadata as an XML document, which can be retrieved as a string:
```rust
use libczirw_sys::{CziReader, InputStream, ReaderOpenInfo};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8("path/to/file.czi")?;
czi.open(ReaderOpenInfo::new(&stream))?;
let metadata_segment = czi.get_metadata_segment()?;
let xml = metadata_segment.get_metadata_as_xml()?;
let xml: String = (&xml).try_into()?;
println!("{}", xml);
// document-level information (title, user, creation date, ...) as JSON
let doc_info = metadata_segment.get_czi_document_info()?;
let general_info = doc_info.get_general_document_info()?;
println!("{}", general_info);
Ok(())
}
```
### Reading attachments
Attachments hold additional data (e.g. microscope setup or experiment info) and are decoded based on their content type:
```rust
use libczirw_sys::{AttachmentData, CziReader, InputStream, ReaderOpenInfo};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8("path/to/file.czi")?;
czi.open(ReaderOpenInfo::new(&stream))?;
for index in 0..czi.get_attachment_count()? {
let info = czi.get_attachment_info_from_directory(index)?;
println!(
"attachment '{}' of type {}",
info.get_name()?,
info.get_content_file_type()?
);
let attachment = czi.read_attachment(index)?;
match attachment.get_data()? {
AttachmentData::Float(values) => println!(" floats: {:?}", values),
AttachmentData::Xml(xml) => println!(" xml: {}", &xml[..xml.len().min(100)]),
AttachmentData::Unknown(bytes) => println!(" raw bytes: {}", bytes.len()),
}
}
Ok(())
}
```
### Writing a CZI file
Create a writer, add sub-blocks and metadata, and close the file to finalize it:
```rust
use libczirw_sys::{
AddSubBlockInfo, Coordinate, CziWriter, OutputStream, PixelType, WriteMetadataInfo,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let writer = CziWriter::create(r#"{"allow_duplicate_subblocks": true}"#)?;
let stream = OutputStream::create_for_file_utf8("output.czi", true)?;
writer.init(&stream, r#"{"minimum_m_index": 0, "maximum_m_index": 0}"#)?;
// a single gray-8-bit sub-block of 100 x 100 pixels
let width = 100;
let height = 100;
let pixels: Vec<u8> = (0..(width * height)).map(|i| i as u8).collect();
// bit 0 => dimension Z is valid (Z is the first dimension, value 1);
// the Z coordinate of this sub-block is 0
let coordinate = Coordinate::new(1 << 0, [0, 0, 0, 0, 0, 0, 0, 0, 0]);
let sub_block = AddSubBlockInfo::new(
coordinate,
0, // m_index_valid
0, // m_index
0, // x
0, // y
width, // logical width
height, // logical height
width, // physical width
height, // physical height
PixelType::Gray8,
0, // compression mode (none)
&pixels, // raw pixel data
b"", // sub-block metadata
b"", // attachment data
);
writer.add_sub_block(sub_block)?;
let xml_metadata = br#"<?xml version="1.0" encoding="utf-8"?>
<ImageDocument>
<Metadata>
<Information>
<Title>My document</Title>
</Information>
</Metadata>
</ImageDocument>"#;
writer.write_metadata(WriteMetadataInfo::new(xml_metadata))?;
// finalize the file - required to produce a valid CZI
writer.close()?;
Ok(())
}
```
### Version and build information
```rust
use libczirw_sys::{LibCZIBuildInformation, LibCZIVersionInfo};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let version = LibCZIVersionInfo::get_lib_czi_version_info()?;
println!(
"libCZI version {}.{}.{}",
version.get_major(),
version.get_minor(),
version.get_patch()
);
let build_info = LibCZIBuildInformation::get()?;
println!("compiler: {}", build_info.get_compiler_information());
println!("repository: {}", build_info.get_repository_url());
Ok(())
}
```
## Error handling
All fallible operations return a `Result` with the crate's `Error` type, which maps libCZIAPI error codes onto
descriptive variants (see `src/error.rs`). The `?` operator can be used directly; conversions from UTF-8 and
null-termination errors are provided via `From` implementations on the error type.
+3 -14
View File
@@ -236,13 +236,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let _importexport_guard = patch_importexport(&libcziapi_inc)?; let _importexport_guard = patch_importexport(&libcziapi_inc)?;
let dst = cmake_config.build(); let dst = cmake_config.build();
#[cfg(not(feature = "dynamic"))] let bindings = bindgen::Builder::default()
let bindings = bindgen::Builder::default();
#[cfg(feature = "dynamic")]
let bindings = bindgen::Builder::default();
let bindings = bindings
.merge_extern_blocks(true) .merge_extern_blocks(true)
.clang_args([ .clang_args([
"-fms-extensions", "-fms-extensions",
@@ -268,11 +262,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.ok_or(Error::msg("cannot into string"))?, .ok_or(Error::msg("cannot into string"))?,
) )
.generate()?; .generate()?;
bindings.write_to_file(dst.join("lib_czi_api.rs"))?; bindings.write_to_file(dst.join("lib_czi_api.rs"))?;
#[cfg(not(feature = "dynamic"))] if cfg!(any(not(feature = "dynamic"), target_feature = "crt-static")) {
{
let libcziapi_dir = dst.join("build/Src/libCZIAPI"); let libcziapi_dir = dst.join("build/Src/libCZIAPI");
let libcziapi_dir2 = dst.join("Src/libCZIAPI"); let libcziapi_dir2 = dst.join("Src/libCZIAPI");
let libczi_dir = dst.join("build/Src/libCZI"); let libczi_dir = dst.join("build/Src/libCZI");
@@ -367,10 +359,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo::rustc-link-lib=shell32"); println!("cargo::rustc-link-lib=shell32");
println!("cargo::rustc-link-lib=Windowscodecs"); println!("cargo::rustc-link-lib=Windowscodecs");
} }
} } else {
#[cfg(feature = "dynamic")]
{
println!( println!(
"cargo::rustc-link-search=native={}", "cargo::rustc-link-search=native={}",
dst.join("build/Src/libCZIAPI").display() dst.join("build/Src/libCZIAPI").display()
+4 -11
View File
@@ -17,14 +17,11 @@ mod tests {
use crate::handle::{CziReader, InputStream}; use crate::handle::{CziReader, InputStream};
use crate::interop::{LibCZIBuildInformation, ReaderOpenInfo}; use crate::interop::{LibCZIBuildInformation, ReaderOpenInfo};
use crate::misc::Dimension; use crate::misc::Dimension;
use std::env; use std::path::{Path, PathBuf};
use std::path::PathBuf;
#[test] #[test]
fn test_read_shape() -> Result<(), Box<dyn std::error::Error>> { fn test_read_shape() -> Result<(), Box<dyn std::error::Error>> {
let path = env::home_dir() let path = Path::new("test-files/Experiment-2029.czi");
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists()); assert!(path.exists());
let czi = CziReader::create()?; let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?; let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?;
@@ -48,9 +45,7 @@ mod tests {
#[test] #[test]
fn test_read_bytes() -> Result<(), Box<dyn std::error::Error>> { fn test_read_bytes() -> Result<(), Box<dyn std::error::Error>> {
let path = env::home_dir() let path = Path::new("test-files/Experiment-2029.czi");
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists()); assert!(path.exists());
let czi = CziReader::create()?; let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?; let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?;
@@ -72,9 +67,7 @@ mod tests {
#[test] #[test]
fn test_libczi_xml() -> Result<(), Box<dyn std::error::Error>> { fn test_libczi_xml() -> Result<(), Box<dyn std::error::Error>> {
let path = env::home_dir() let path = Path::new("test-files/Experiment-2029.czi");
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists()); assert!(path.exists());
let czi = CziReader::create()?; let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?; let stream = InputStream::create_from_file_utf8(path.to_string_lossy().as_ref())?;