- first commit
This commit is contained in:
75
.gitignore
vendored
Normal file
75
.gitignore
vendored
Normal 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
22
Cargo.toml
Normal 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
19
LICENSE
Normal 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
7
README.md
Normal 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
90
build.rs
Normal 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
1158
src/functions.rs
Normal file
File diff suppressed because it is too large
Load Diff
110
src/handle.rs
Normal file
110
src/handle.rs
Normal 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
1125
src/interop.rs
Normal file
File diff suppressed because it is too large
Load Diff
83
src/lib.rs
Normal file
83
src/lib.rs
Normal 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
115
src/misc.rs
Normal 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
9
src/sys.rs
Normal 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"));
|
||||
Reference in New Issue
Block a user