- first commit

This commit is contained in:
Wim Pomp
2025-08-10 17:34:35 +02:00
commit 3e98e7e86f
11 changed files with 2813 additions and 0 deletions

75
.gitignore vendored Normal file
View File

@@ -0,0 +1,75 @@
/target
/Cargo.lock
# Byte-compiled / optimized / DLL files
__pycache__/
.pytest_cache/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
.venv/
env/
bin/
build/
develop-eggs/
dist/
eggs/
lib/
lib64/
parts/
sdist/
var/
include/
man/
venv/
*.egg-info/
.installed.cfg
*.egg
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
pip-selfcheck.json
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Rope
.ropeproject
# Django stuff:
*.log
*.pot
.DS_Store
# Sphinx documentation
docs/_build/
# PyCharm
.idea/
# VSCode
.vscode/
# Pyenv
.python-version
/tests/files/*

22
Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "libczirw-sys"
version = "0.1.0"
edition = "2024"
rust-version = "1.85.1"
authors = ["Wim Pomp <w.pomp@nki.nl>"]
license = "MIT"
description = "Wrapper around libCZIAPI"
homepage = "https://github.com/wimpomp/libczirw-sys"
repository = "https://github.com/wimpomp/libczirw-sys"
readme = "README.md"
keywords = ["czi", "zeiss", "libczi"]
categories = ["multimedia::images", "science"]
[dependencies]
anyhow = "1.0.98"
[build-dependencies]
anyhow = "1.0.98"
bindgen = "0.72.0"
cmake = "0.1.54"
git2 = "0.20.2"

19
LICENSE Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2025 Wim Pomp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# libCZIrw-sys
Crate linking to [libCZIAPI](https://github.com/ZEISS/libczi).
This crate attempts to provide save wrappers to objects and functions in libCZIAPI.
Direct often unsafe access using pointer is available through the sys module.
This code is licensed with an MIT license, but Zeiss' libCZI has a LGPL license.

90
build.rs Normal file
View File

@@ -0,0 +1,90 @@
use anyhow::{Error, Result};
use std::env;
use std::fs::OpenOptions;
use std::io::{Read, Seek, Write};
use std::path::PathBuf;
fn main() -> Result<()> {
if env::var("DOCS_RS").is_err() {
let out_dir = PathBuf::from(env::var("OUT_DIR")?).canonicalize()?;
let libczi_dir = out_dir.join("libczirw");
let rep = if !libczi_dir.exists() {
git2::Repository::clone("https://github.com/ZEISS/libczi.git", &libczi_dir)
.expect("unable to clone libczirw")
} else {
git2::Repository::open(&libczi_dir)?
};
let (object, _) = rep.revparse_ext("494ac62f853de6ab86458f167fd85a03ee6d4f7e")?;
rep.checkout_tree(&object, None)?;
let dst = cmake::Config::new(&libczi_dir)
.define("LIBCZI_BUILD_UNITTESTS", "OFF")
.define("LIBCZI_BUILD_CZICMD", "OFF")
.define("LIBCZI_BUILD_DYNLIB", "OFF")
.define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_EIGEN3", "OFF")
.define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_ZSTD", "OFF")
.define("LIBCZI_BUILD_CURL_BASED_STREAM", "OFF")
.define("LIBCZI_BUILD_PREFER_EXTERNAL_PACKAGE_LIBCURL", "OFF")
.define("LIBCZI_BUILD_AZURESDK_BASED_STREAM", "OFF")
.define("LIBCZI_BUILD_PREFER_EXTERNALPACKAGE_RAPIDJSON", "OFF")
.define("LIBCZI_BUILD_LIBCZIAPI", "ON")
.build();
let libczi_src = libczi_dir.join("Src/libCZI");
let libcziapi_inc = libczi_dir.join("Src/libCZIAPI/inc");
let libcziapi_src = libczi_dir.join("Src/libCZIAPI/src");
let libczi_h = libcziapi_inc.join("libCZIApi.h");
let import_export = libczi_dir.join("Src/libCZIAPI/inc/importexport.h");
{
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(&import_export)
.expect("Could not open file");
let mut data = String::new();
file.read_to_string(&mut data).expect("Could not read file");
let data = data.replace(" __declspec(dllexport)", "");
let bytes = data.as_bytes();
(&file).rewind().expect("Could not rewind");
(&file).write_all(bytes).expect("Could not write file");
file.set_len(bytes.len() as u64)
.expect("Could not truncate");
};
let bindings = bindgen::Builder::default()
.clang_args([
"-x",
"c++",
"-std=c++14",
"-I",
libcziapi_inc
.to_str()
.ok_or(Error::msg("cannot into string"))?,
"-I",
libcziapi_src
.to_str()
.ok_or(Error::msg("cannot into string"))?,
"-I",
libczi_src
.to_str()
.ok_or(Error::msg("cannot into string"))?,
])
.header(libczi_h.to_str().ok_or(Error::msg("cannot into string"))?)
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join("lib_czi_api.rs"))
.expect("Couldn't write bindings!");
println!(
"cargo:rustc-link-search=native={}",
dst.join("build/Src/libCZIAPI").display()
);
println!("cargo:rustc-link-lib=libCZIAPI");
}
println!("cargo::rerun-if-changed=build.rs");
Ok(())
}

1158
src/functions.rs Normal file

File diff suppressed because it is too large Load Diff

110
src/handle.rs Normal file
View File

@@ -0,0 +1,110 @@
use crate::misc::Ptr;
use crate::sys::*;
use std::mem::MaybeUninit;
use std::ops::Deref;
/// CZI-reader object.
#[derive(Clone, Debug)]
pub struct CziReader(pub (crate) CziReaderObjectHandle);
/// sub-block object.
#[derive(Clone, Debug)]
pub struct SubBlock(pub (crate) SubBlockObjectHandle);
/// input stream object.
#[derive(Clone, Debug)]
pub struct InputStream(pub (crate) InputStreamObjectHandle);
/// output stream object.
#[derive(Clone, Debug)]
pub struct OutputStream(pub (crate) OutputStreamObjectHandle);
/// memory allocation object - which is a pointer to a memory block, which must be
/// freed with 'libCZI_Free'.
/// TODO(JBL): this is not really used so far, should be removed I guess.
#[derive(Clone, Debug)]
pub struct MemoryAllocation(pub (crate) MemoryAllocationObjectHandle);
/// bitmap object.
#[derive(Clone, Debug)]
pub struct Bitmap(pub (crate) BitmapObjectHandle);
/// metadata segment object.
#[derive(Clone, Debug)]
pub struct MetadataSegment(pub (crate) MetadataSegmentObjectHandle);
/// attachment object.
#[derive(Clone, Debug)]
pub struct Attachment(pub (crate) AttachmentObjectHandle);
/// writer object.
#[derive(Clone, Debug)]
pub struct CziWriter(pub (crate) CziWriterObjectHandle);
/// single-channel-scaling-tile-accessor.
#[derive(Clone, Debug)]
pub struct SingleChannelScalingTileAccessor(pub (crate) SingleChannelScalingTileAccessorObjectHandle);
/// document info object.
#[derive(Clone, Debug)]
pub struct CziDocumentInfo(pub (crate) CziDocumentInfoHandle);
/// display settings object.
#[derive(Clone, Debug)]
pub struct DisplaySettings(pub (crate) DisplaySettingsHandle);
/// channel display settings object.
#[derive(Clone, Debug)]
pub struct ChannelDisplaySettings(pub (crate) ChannelDisplaySettingsHandle);
macro_rules! impl_struct {
($($n:ident: $t:ty: $s:ty $(,)?)*) => {
$(
impl $t {
#[allow(dead_code)]
pub (crate) fn handle(&self) -> ObjectHandle { self.0 }
}
impl Ptr for $t {
type Pointer = $s;
unsafe fn assume_init(ptr: MaybeUninit<Self::Pointer>) -> Self {
Self(unsafe { ptr.assume_init() })
}
fn as_mut_ptr(&self) -> *mut Self::Pointer {
&self.0 as *const _ as *mut _
}
fn as_ptr(&self) -> *const Self::Pointer {
&self.0 as *const _ as *const _
}
}
impl Deref for $t {
type Target = ObjectHandle;
fn deref(&self) -> &Self::Target {
&self.0
}
}
)*
};
}
impl_struct! {
CziReader: CziReader: CziReaderObjectHandle,
SubBlock: SubBlock: SubBlockObjectHandle,
InputStream: InputStream: InputStreamObjectHandle,
OutputStream: OutputStream: OutputStreamObjectHandle,
MemoryAllocation: MemoryAllocation: MemoryAllocationObjectHandle,
Bitmap: Bitmap: BitmapObjectHandle,
MetadataSegment: MetadataSegment: MetadataSegmentObjectHandle,
Attachment: Attachment: AttachmentObjectHandle,
CziWriter: CziWriter: CziWriterObjectHandle,
SingleChannelScalingTileAccessor: SingleChannelScalingTileAccessor: SingleChannelScalingTileAccessorObjectHandle,
CziDocumentInfo: CziDocumentInfo: CziDocumentInfoHandle,
DisplaySettings: DisplaySettings: DisplaySettingsHandle,
ChannelDisplaySettings: ChannelDisplaySettings: ChannelDisplaySettingsHandle,
}

1125
src/interop.rs Normal file

File diff suppressed because it is too large Load Diff

83
src/lib.rs Normal file
View File

@@ -0,0 +1,83 @@
mod functions;
mod handle;
mod interop;
mod misc;
pub mod sys;
pub use functions::*;
pub use handle::*;
pub use interop::*;
pub use misc::{LibCZIApiError, RawDataType, PixelType};
#[cfg(test)]
mod tests {
use crate::handle::{CziReader, InputStream};
use crate::interop::{LibCZIBuildInformation, ReaderOpenInfo};
use anyhow::{Error, Result};
use std::env;
#[test]
fn test_libczi_xml() -> Result<()> {
let path = env::home_dir()
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists());
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(
path.to_str().ok_or(Error::msg("cannot into str"))?,
)?;
let open_info = ReaderOpenInfo::new(&stream);
czi.open(open_info)?;
let metadata_segment = czi.get_metadata_segment()?;
let xml = metadata_segment.get_metadata_as_xml()?;
let s = String::try_from(&xml)?;
println!("xml: {}", &s[..s.len().min(100)]);
Ok(())
}
#[test]
fn test_libczi_pyramid_statistics() -> Result<()> {
let path = env::home_dir()
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists());
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(
path.to_str().ok_or(Error::msg("cannot into str"))?,
)?;
let open_info = ReaderOpenInfo::new(&stream);
czi.open(open_info)?;
let s = czi.get_pyramid_statistics()?;
println!("xml: {}", &s[..s.len().min(100)]);
Ok(())
}
#[test]
fn test_libczi_document_info() -> Result<()> {
let path = env::home_dir()
.unwrap()
.join("code/rust/ndbioimage/tests/files/Experiment-2029.czi");
assert!(path.exists());
let czi = CziReader::create()?;
let stream = InputStream::create_from_file_utf8(
path.to_str().ok_or(Error::msg("cannot into str"))?,
)?;
let open_info = ReaderOpenInfo::new(&stream);
czi.open(open_info)?;
let metadata_segment = czi.get_metadata_segment()?;
let document_info = metadata_segment.get_czi_document_info()?;
let general_document_info = document_info.get_general_document_info()?;
println!("xml: {}", &general_document_info[..general_document_info.len().min(100)]);
Ok(())
}
#[test]
fn test_lib_czi_build_information() -> Result<()> {
let build_info = LibCZIBuildInformation::get()?;
println!("compiler information: {:?}", build_info.get_compiler_information());
println!("repository url: {:?}", build_info.get_repository_url());
println!("repository branch: {:?}", build_info.get_repository_branch());
println!("repository tag: {:?}", build_info.get_repository_tag());
Ok(())
}
}

115
src/misc.rs Normal file
View File

@@ -0,0 +1,115 @@
use anyhow::{anyhow, Error, Result};
use std::fmt;
use std::mem::MaybeUninit;
use std::os::raw::c_int;
/// the error type for libCZIAPI
#[derive(Clone, Debug)]
pub enum LibCZIApiError {
OK,
InvalidArgument,
InvalidHandle,
OutOfMemory,
IndexOutOfRange,
LockUnlockSemanticViolated,
UnspecifiedError,
}
impl std::error::Error for LibCZIApiError {}
impl TryFrom<c_int> for LibCZIApiError {
type Error = Error;
fn try_from(code: c_int) -> Result<Self> {
match code {
0 => Ok(LibCZIApiError::OK),
1 => Err(Error::from(LibCZIApiError::InvalidArgument)),
2 => Err(Error::from(LibCZIApiError::InvalidHandle)),
3 => Err(Error::from(LibCZIApiError::OutOfMemory)),
4 => Err(Error::from(LibCZIApiError::IndexOutOfRange)),
20 => Err(Error::from(LibCZIApiError::LockUnlockSemanticViolated)),
50 => Err(Error::from(LibCZIApiError::UnspecifiedError)),
_ => Err(anyhow!("Unknown error code {}", code)),
}
}
}
impl fmt::Display for LibCZIApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "LibCZIApi {self:?}")
}
}
/// enum for SubBlock.get_raw_data
pub enum RawDataType {
Data,
Metadata,
}
/// pixel type
pub enum PixelType {
Gray8,
Gray16,
Gray32Float,
Bgr24,
Bgr48,
Bgr96Float,
Bgra32,
Gray64ComplexFloat,
Bgr192ComplexFloat,
Gray32,
Gray64Float,
}
impl TryFrom<i32> for PixelType {
type Error = Error;
fn try_from(pixel_type: i32) -> Result<Self> {
match pixel_type {
0 => Ok(PixelType::Gray8),
1 => Ok(PixelType::Gray16),
2 => Ok(PixelType::Gray32Float),
3 => Ok(PixelType::Bgr24),
4 => Ok(PixelType::Bgr48),
8 => Ok(PixelType::Bgr96Float),
9 => Ok(PixelType::Bgra32),
10 => Ok(PixelType::Gray64ComplexFloat),
11 => Ok(PixelType::Bgr192ComplexFloat),
12 => Ok(PixelType::Gray32),
13 => Ok(PixelType::Gray64Float),
_ => Err(anyhow!("Unknown pixel type {}", pixel_type)),
}
}
}
impl From<PixelType> for i32 {
fn from(pixel_type: PixelType) -> Self {
match pixel_type {
PixelType::Gray8 => 0,
PixelType::Gray16 => 1,
PixelType::Gray32Float => 2,
PixelType::Bgr24 => 3,
PixelType::Bgr48 => 4,
PixelType::Bgr96Float => 8,
PixelType::Bgra32 => 9,
PixelType::Gray64ComplexFloat => 10,
PixelType::Bgr192ComplexFloat => 11,
PixelType::Gray32 => 12,
PixelType::Gray64Float => 13,
}
}
}
pub trait Ptr {
type Pointer;
unsafe fn assume_init(ptr: MaybeUninit<Self::Pointer>) -> Self;
fn as_mut_ptr(&self) -> *mut Self::Pointer
where
Self: Sized;
fn as_ptr(&self) -> *const Self::Pointer
where
Self: Sized;
}

9
src/sys.rs Normal file
View File

@@ -0,0 +1,9 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unsafe_op_in_unsafe_fn)]
#![allow(dead_code)]
#![allow(rustdoc::broken_intra_doc_links)]
#![allow(rustdoc::invalid_html_tags)]
include!(concat!(env!("OUT_DIR"), "/lib_czi_api.rs"));