- put PixelType trait in lib

- use AsArray to make functions more flexible
- less image copying when doing re
This commit is contained in:
Wim Pomp
2025-03-06 22:49:49 +01:00
parent 934f038ea1
commit 0c6a38e4fa
4 changed files with 205 additions and 148 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "sitk-registration-sys"
version = "2025.3.1"
version = "2025.3.2"
edition = "2024"
license = "MIT OR Apache-2.0"
description = "register and interpolate images"
@@ -14,11 +14,11 @@ keywords = ["registration", "affine", "bspline", "transform"]
categories = ["multimedia::images", "science"]
[lib]
name = "sitk_regsitration_sys"
name = "sitk_registration_sys"
crate-type = ["cdylib", "rlib"]
[dependencies]
anyhow = "1.0.96"
anyhow = "1.0.97"
libc = "0.2.170"
ndarray = "0.16.1"
num = "0.4.3"
@@ -29,3 +29,6 @@ serde_yaml = "0.9.33"
[build-dependencies]
cmake = "0.1.54"
git2 = "0.20.0"
[dev-dependencies]
tempfile = "3.18.0"

View File

@@ -286,8 +286,8 @@ reg(
try {
string kind = (t_or_a == false) ? "translation" : "affine";
// std::filesystem::path output_path = std::filesystem::temp_directory_path() / gen_random(12);
std::filesystem::path output_path = std::filesystem::temp_directory_path();
// std::filesystem::create_directory(output_path);
std::filesystem::path output_path = std::filesystem::temp_directory_path();
sitk::ElastixImageFilter tfilter = sitk::ElastixImageFilter();
tfilter.LogToConsoleOff();
@@ -298,11 +298,7 @@ reg(
tfilter.SetParameterMap(sitk::GetDefaultParameterMap(kind));
tfilter.SetParameter("WriteResultImage", "false");
tfilter.SetOutputDirectory(output_path);
// cout << "output_path: " << output_path << endl;
// tfilter.PrintParameterMap();
// cout << "r6 " << std::flush;
tfilter.Execute();
// cout << "r7 " << std::flush;
sitk::ElastixImageFilter::ParameterMapType parameter_map = tfilter.GetTransformParameterMap(0);
for (sitk::ElastixImageFilter::ParameterMapType::iterator parameter = parameter_map.begin(); parameter != parameter_map.end(); ++parameter) {
if (parameter->first == "TransformParameters") {
@@ -323,7 +319,6 @@ reg(
break;
}
}
// cout << "r8 " << std::flush;
} catch (const std::exception &exc) {
cerr << exc.what();
}
@@ -334,14 +329,14 @@ extern "C" void
register_u8(
unsigned int width,
unsigned int height,
uint8_t* fixed_arr,
uint8_t* moving_arr,
uint8_t** fixed_arr,
uint8_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkUInt8;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -349,14 +344,14 @@ extern "C" void
register_i8(
unsigned int width,
unsigned int height,
int8_t* fixed_arr,
int8_t* moving_arr,
int8_t** fixed_arr,
int8_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkInt8;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -364,14 +359,14 @@ extern "C" void
register_u16(
unsigned int width,
unsigned int height,
uint16_t* fixed_arr,
uint16_t* moving_arr,
uint16_t** fixed_arr,
uint16_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkUInt16;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -379,14 +374,14 @@ extern "C" void
register_i16(
unsigned int width,
unsigned int height,
int16_t* fixed_arr,
int16_t* moving_arr,
int16_t** fixed_arr,
int16_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkInt16;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -394,14 +389,14 @@ extern "C" void
register_u32(
unsigned int width,
unsigned int height,
uint32_t* fixed_arr,
uint32_t* moving_arr,
uint32_t** fixed_arr,
uint32_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkUInt32;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -409,14 +404,14 @@ extern "C" void
register_i32(
unsigned int width,
unsigned int height,
int32_t* fixed_arr,
int32_t* moving_arr,
int32_t** fixed_arr,
int32_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkInt32;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -424,14 +419,14 @@ extern "C" void
register_u64(
unsigned int width,
unsigned int height,
uint64_t* fixed_arr,
uint64_t* moving_arr,
uint64_t** fixed_arr,
uint64_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkUInt64;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -439,14 +434,14 @@ extern "C" void
register_i64(
unsigned int width,
unsigned int height,
int64_t* fixed_arr,
int64_t* moving_arr,
int64_t** fixed_arr,
int64_t** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkInt64;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -454,14 +449,14 @@ extern "C" void
register_f32(
unsigned int width,
unsigned int height,
float* fixed_arr,
float* moving_arr,
float** fixed_arr,
float** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkFloat32;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}
@@ -469,13 +464,13 @@ extern "C" void
register_f64(
unsigned int width,
unsigned int height,
double* fixed_arr,
double* moving_arr,
double** fixed_arr,
double** moving_arr,
bool t_or_a,
double** transform
) {
sitk::PixelIDValueEnum id = sitk::PixelIDValueEnum::sitkFloat64;
sitk::Image fixed = make_image(width, height, fixed_arr, id);
sitk::Image moving = make_image(width, height, moving_arr, id);
sitk::Image fixed = make_image(width, height, *fixed_arr, id);
sitk::Image moving = make_image(width, height, *moving_arr, id);
reg(fixed, moving, t_or_a, transform);
}

View File

@@ -1,14 +1,52 @@
mod sys;
use crate::sys::{PixelType, interp, register};
use crate::sys::{interp, register};
use anyhow::{Result, anyhow};
use ndarray::{Array2, ArrayView2, array, s};
use ndarray::{Array2, ArrayView2, AsArray, Ix2, array, s};
use serde::{Deserialize, Serialize};
use serde_yaml::{from_reader, to_writer};
use std::fs::File;
use std::ops::Mul;
use std::path::PathBuf;
/// a trait marking number types that can be used in sitk:
/// (u/i)(8/16/32/64), (u/i)size, f(32/64)
pub trait PixelType: Clone {
const PT: u8;
}
macro_rules! sitk_impl {
($($T:ty: $sitk:expr $(,)?)*) => {
$(
impl PixelType for $T {
const PT: u8 = $sitk;
}
)*
};
}
sitk_impl! {
u8: 1,
i8: 2,
u16: 3,
i16: 4,
u32: 5,
i32: 6,
u64: 7,
i64: 8,
f32: 9,
f64: 10,
}
#[cfg(target_pointer_width = "64")]
sitk_impl!(usize: 7);
#[cfg(target_pointer_width = "32")]
sitk_impl!(usize: 5);
#[cfg(target_pointer_width = "64")]
sitk_impl!(isize: 8);
#[cfg(target_pointer_width = "32")]
sitk_impl!(isize: 6);
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Transform {
pub parameters: [f64; 6],
@@ -47,6 +85,17 @@ impl Mul for Transform {
}
}
impl PartialEq<Self> for Transform {
fn eq(&self, other: &Self) -> bool {
self.parameters == other.parameters
&& self.dparameters == other.dparameters
&& self.origin == other.origin
&& self.shape == other.shape
}
}
impl Eq for Transform {}
impl Transform {
/// parameters: flat 2x2 part of matrix, translation; origin: center of rotation
pub fn new(parameters: [f64; 6], origin: [f64; 2], shape: [usize; 2]) -> Self {
@@ -59,10 +108,11 @@ impl Transform {
}
/// find the affine transform which transforms moving into fixed
pub fn register_affine<T: PixelType>(
fixed: ArrayView2<T>,
moving: ArrayView2<T>,
) -> Result<Transform> {
pub fn register_affine<'a, A, T>(fixed: A, moving: A) -> Result<Transform>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
let (parameters, origin, shape) = register(fixed, moving, true)?;
Ok(Transform {
parameters,
@@ -73,10 +123,11 @@ impl Transform {
}
/// find the translation which transforms moving into fixed
pub fn register_translation<T: PixelType>(
fixed: ArrayView2<T>,
moving: ArrayView2<T>,
) -> Result<Transform> {
pub fn register_translation<'a, A, T>(fixed: A, moving: A) -> Result<Transform>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
let (parameters, origin, shape) = register(fixed, moving, false)?;
Ok(Transform {
parameters,
@@ -104,7 +155,11 @@ impl Transform {
/// write a transform to a file
pub fn to_file(&self, path: PathBuf) -> Result<()> {
let mut file = File::open(path)?;
let mut file = std::fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(path)?;
to_writer(&mut file, self)?;
Ok(())
}
@@ -115,23 +170,30 @@ impl Transform {
}
/// transform an image using nearest neighbor interpolation
pub fn transform_image_bspline<T: PixelType>(&self, image: ArrayView2<T>) -> Result<Array2<T>> {
pub fn transform_image_bspline<'a, A, T>(&self, image: A) -> Result<Array2<T>>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
interp(self.parameters, self.origin, image, false)
}
/// transform an image using bspline interpolation
pub fn transform_image_nearest_neighbor<T: PixelType>(
&self,
image: ArrayView2<T>,
) -> Result<Array2<T>> {
pub fn transform_image_nearest_neighbor<'a, A, T>(&self, image: A) -> Result<Array2<T>>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
interp(self.parameters, self.origin, image, true)
}
/// get coordinates resulting from transforming input coordinates
pub fn transform_coordinates<T>(&self, coordinates: ArrayView2<T>) -> Result<Array2<f64>>
pub fn transform_coordinates<'a, A, T>(&self, coordinates: A) -> Result<Array2<f64>>
where
T: Clone + Into<f64>,
T: 'a + Clone + Into<f64>,
A: AsArray<'a, T, Ix2>,
{
let coordinates = coordinates.into();
let s = coordinates.shape();
if s[1] != 2 {
return Err(anyhow!("coordinates must have two columns"));
@@ -233,6 +295,7 @@ mod tests {
use anyhow::Result;
use ndarray::Array2;
use num::Complex;
use tempfile::NamedTempFile;
/// An example of generating julia fractals.
fn julia_image(shift_x: f32, shift_y: f32) -> Result<Array2<u8>> {
@@ -263,6 +326,16 @@ mod tests {
Ok(im)
}
#[test]
fn test_serialization() -> Result<()> {
let file = NamedTempFile::new()?;
let t = Transform::new([1.2, 0.3, -0.4, 0.9, 10.2, -9.5], [59.5, 49.5], [120, 100]);
t.to_file(file.path().to_path_buf())?;
let s = Transform::from_file(file.path().to_path_buf())?;
assert_eq!(s, t);
Ok(())
}
macro_rules! interp_tests_bspline {
($($name:ident: $t:ty $(,)?)*) => {
$(
@@ -310,7 +383,12 @@ mod tests {
((shape[1] - 1) as f64) / 2f64,
((shape[0] - 1) as f64) / 2f64,
];
let j0 = j.clone();
let k0 = k.clone();
let transform = Transform::new([1., 0., 0., 1., 120., -10.], origin, [shape[0], shape[1]]);
// make sure j & k weren't mutated
assert!(j.iter().zip(j0.iter()).map(|(a, b)| a == b).all(|x| x));
assert!(k.iter().zip(k0.iter()).map(|(a, b)| a == b).all(|x| x));
let n = transform.transform_image_nearest_neighbor(j.view())?;
let d = (k.mapv(|x| x as f64) - n.mapv(|x| x as f64)).powi(2).sum();
assert!(d <= (shape[0] * shape[1]) as f64);
@@ -340,7 +418,12 @@ mod tests {
fn $name() -> Result<()> {
let j = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
let k = julia_image(10f32, 20f32)?.mapv(|x| x as $t);
let j0 = j.clone();
let k0 = k.clone();
let t = Transform::register_translation(j.view(), k.view())?;
// make sure j & k weren't mutated
assert!(j.iter().zip(j0.iter()).map(|(a, b)| a == b).all(|x| x));
assert!(k.iter().zip(k0.iter()).map(|(a, b)| a == b).all(|x| x));
let mut m = Array2::eye(3);
m[[0, 2]] = -10f64;
m[[1, 2]] = -20f64;

View File

@@ -1,6 +1,7 @@
use crate::PixelType;
use anyhow::Result;
use libc::{c_double, c_uint};
use ndarray::{Array2, ArrayView2};
use ndarray::{Array2, AsArray, Ix2};
use one_at_a_time_please::one_at_a_time;
use std::ptr;
@@ -62,48 +63,17 @@ unsafe extern "C" {
}
}
pub trait PixelType: Clone {
const PT: u8;
}
macro_rules! sitk_impl {
($($T:ty: $sitk:expr $(,)?)*) => {
$(
impl PixelType for $T {
const PT: u8 = $sitk;
}
)*
};
}
sitk_impl! {
u8: 1,
i8: 2,
u16: 3,
i16: 4,
u32: 5,
i32: 6,
u64: 7,
i64: 8,
f32: 9,
f64: 10,
}
#[cfg(target_pointer_width = "64")]
sitk_impl!(usize: 7);
#[cfg(target_pointer_width = "32")]
sitk_impl!(usize: 5);
#[cfg(target_pointer_width = "64")]
sitk_impl!(isize: 8);
#[cfg(target_pointer_width = "32")]
sitk_impl!(isize: 6);
pub(crate) fn interp<T: PixelType>(
pub(crate) fn interp<'a, A, T>(
parameters: [f64; 6],
origin: [f64; 2],
image: ArrayView2<T>,
image: A,
bspline_or_nn: bool,
) -> Result<Array2<T>> {
) -> Result<Array2<T>>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
let image = image.into();
let shape: Vec<usize> = image.shape().to_vec();
let width = shape[1] as c_uint;
let height = shape[0] as c_uint;
@@ -220,16 +190,22 @@ pub(crate) fn interp<T: PixelType>(
}
#[one_at_a_time]
pub(crate) fn register<T: PixelType>(
fixed: ArrayView2<T>,
moving: ArrayView2<T>,
pub(crate) fn register<'a, A, T>(
fixed: A,
moving: A,
translation_or_affine: bool,
) -> Result<([f64; 6], [f64; 2], [usize; 2])> {
) -> Result<([f64; 6], [f64; 2], [usize; 2])>
where
T: 'a + PixelType,
A: AsArray<'a, T, Ix2>,
{
let fixed = fixed.into();
let moving = moving.into();
let shape: Vec<usize> = fixed.shape().to_vec();
let width = shape[1] as c_uint;
let height = shape[0] as c_uint;
let fixed: Vec<_> = fixed.into_iter().cloned().collect();
let moving: Vec<_> = moving.into_iter().cloned().collect();
let fixed: Vec<_> = fixed.into_iter().collect();
let moving: Vec<_> = moving.into_iter().collect();
let fixed_ptr = fixed.as_ptr();
let moving_ptr = moving.as_ptr();
let mut transform: Vec<c_double> = vec![0.0; 6];