Now build SimpleITK into static libs and use (auto)cxx.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
+90
-474
@@ -1,485 +1,101 @@
|
||||
mod sys;
|
||||
//! This crate does two things:
|
||||
//! - find an affine transform or translation that transforms one image into the other
|
||||
//! - use bspline or nearest neighbor interpolation to apply a transformation to an image
|
||||
//!
|
||||
//! To do this, [SimpleITK](https://github.com/SimpleITK/SimpleITK), which is written in
|
||||
//! C++, is used. An adapter library is created using [autocxx](https://crates.io/crates/autocxx)
|
||||
//! to expose the required functionality in SimpleITK. Because of this, compilation of this crate
|
||||
//! requires quite some time, several GB of memory, up to 50 GB of hard disk space, as well as
|
||||
//! cmake, a C++ compiler, llvm and git. Use at your own risk!
|
||||
//!
|
||||
//! # Examples
|
||||
//! ## Registration
|
||||
//! ```
|
||||
//! use ndarray::Array2;
|
||||
//! use sitk_registration_sys::registration::{AffineTransform, julia_image};
|
||||
//!
|
||||
//! let j = julia_image(0f32, 0f32).unwrap();
|
||||
//! let shape = j.shape();
|
||||
//! let origin = [
|
||||
//! ((shape[1] - 1) as f64) / 2f64,
|
||||
//! ((shape[0] - 1) as f64) / 2f64,
|
||||
//! ];
|
||||
//! let s = AffineTransform::new([1.2, 0., 0., 1., 5., 7.], origin, [shape[0], shape[1]]);
|
||||
//! let k: Array2<_> = s.transform_image_bspline(j.view()).unwrap().into();
|
||||
//! let t = AffineTransform::register_affine(j.view(), k.view()).unwrap().inverse().unwrap();
|
||||
//! let d = (t.matrix() - s.matrix()).powi(2).sum();
|
||||
//! assert!(d < 0.025, "d: {}, t: {:?}", d, t.parameters);
|
||||
//! ```
|
||||
//!
|
||||
//! ## Interpolation
|
||||
//! ```
|
||||
//! use ndarray::Array2;
|
||||
//! use sitk_registration_sys::registration::{AffineTransform, julia_image};
|
||||
//!
|
||||
//! let j = julia_image(-120f32, 10f32).unwrap();
|
||||
//! let k = julia_image(0f32, 0f32).unwrap();
|
||||
//! let shape = j.shape();
|
||||
//! let origin = [
|
||||
//! ((shape[1] - 1) as f64) / 2f64,
|
||||
//! ((shape[0] - 1) as f64) / 2f64,
|
||||
//! ];
|
||||
//! let transform = AffineTransform::new([1., 0., 0., 1., 120., -10.], origin, [shape[0], shape[1]]);
|
||||
//! let n: Array2<_> = transform.transform_image_bspline(j.view()).unwrap().into();
|
||||
//! 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);
|
||||
//! ```
|
||||
|
||||
use crate::sys::{interp, register};
|
||||
use anyhow::{Result, anyhow};
|
||||
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;
|
||||
extern crate link_cplusplus;
|
||||
pub mod registration;
|
||||
|
||||
/// 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;
|
||||
/// The bindings generated by [autocxx](https://google.github.io/autocxx).
|
||||
/// Everything in here is unsafe.
|
||||
pub use ffi::itk::simple;
|
||||
|
||||
use autocxx::prelude::*;
|
||||
|
||||
include_cpp! {
|
||||
#include "sitkAdditionalProcedures.h"
|
||||
#include "sitkAffineTransform.h"
|
||||
#include "sitkElastixImageFilter.h"
|
||||
#include "sitkInterpolator.h"
|
||||
#include "sitkImage.h"
|
||||
#include "sitkTransform.h"
|
||||
safety!(unsafe)
|
||||
generate!("itk::simple::AffineTransform")
|
||||
generate!("itk::simple::ElastixImageFilter")
|
||||
generate!("itk::simple::InterpolatorEnum")
|
||||
generate!("itk::simple::Image")
|
||||
generate!("itk::simple::Resample")
|
||||
generate!("itk::simple::Transform")
|
||||
opaque!("itk::simple::ElastixImageFilter") // deleted constructor
|
||||
}
|
||||
|
||||
macro_rules! sitk_impl {
|
||||
($($T:ty: $sitk:expr $(,)?)*) => {
|
||||
$(
|
||||
impl PixelType for $T {
|
||||
const PT: u8 = $sitk;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
/// Manually generated bindings for some things that autocxx does not understand.
|
||||
#[cxx::bridge]
|
||||
pub mod ffi_extra {
|
||||
unsafe extern "C++" {
|
||||
include!("ffi_extra.h");
|
||||
|
||||
sitk_impl! {
|
||||
u8: 1,
|
||||
i8: 2,
|
||||
u16: 3,
|
||||
i16: 4,
|
||||
u32: 5,
|
||||
i32: 6,
|
||||
u64: 7,
|
||||
i64: 8,
|
||||
f32: 9,
|
||||
f64: 10,
|
||||
}
|
||||
#[namespace = "itk::simple"]
|
||||
type ElastixImageFilter = crate::ffi::itk::simple::ElastixImageFilter;
|
||||
type ParameterMap;
|
||||
|
||||
#[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);
|
||||
fn new_parameter_map() -> UniquePtr<ParameterMap>;
|
||||
fn insert(self: Pin<&mut ParameterMap>, key: &CxxString, value: &CxxVector<CxxString>);
|
||||
fn keys(self: &ParameterMap) -> UniquePtr<CxxVector<CxxString>>;
|
||||
fn get(self: &ParameterMap, key: &CxxString) -> UniquePtr<CxxVector<CxxString>>;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Transform {
|
||||
pub parameters: [f64; 6],
|
||||
pub dparameters: [f64; 6],
|
||||
pub origin: [f64; 2],
|
||||
pub shape: [usize; 2],
|
||||
}
|
||||
fn get_transform_parameter_map(
|
||||
tfilter: &mut ElastixImageFilter,
|
||||
index: u32,
|
||||
) -> UniquePtr<ParameterMap>;
|
||||
|
||||
impl Mul for Transform {
|
||||
type Output = Transform;
|
||||
fn get_default_parameter_map(kind: &CxxString) -> UniquePtr<ParameterMap>;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn mul(self, other: Transform) -> Transform {
|
||||
let m = self.matrix().dot(&other.matrix());
|
||||
let dm = self.dmatrix().dot(&other.matrix()) + self.matrix().dot(&other.dmatrix());
|
||||
Transform {
|
||||
parameters: [
|
||||
m[[0, 0]],
|
||||
m[[0, 1]],
|
||||
m[[1, 0]],
|
||||
m[[1, 1]],
|
||||
m[[2, 0]],
|
||||
m[[2, 1]],
|
||||
],
|
||||
dparameters: [
|
||||
dm[[0, 0]],
|
||||
dm[[0, 1]],
|
||||
dm[[1, 0]],
|
||||
dm[[1, 1]],
|
||||
dm[[2, 0]],
|
||||
dm[[2, 1]],
|
||||
],
|
||||
origin: self.origin,
|
||||
shape: self.shape,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Self {
|
||||
parameters,
|
||||
dparameters: [0f64; 6],
|
||||
origin,
|
||||
shape,
|
||||
}
|
||||
}
|
||||
|
||||
/// find the affine transform which transforms moving into fixed
|
||||
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,
|
||||
dparameters: [0f64; 6],
|
||||
origin,
|
||||
shape,
|
||||
})
|
||||
}
|
||||
|
||||
/// find the translation which transforms moving into fixed
|
||||
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,
|
||||
dparameters: [0f64; 6],
|
||||
origin,
|
||||
shape,
|
||||
})
|
||||
}
|
||||
|
||||
/// create a transform from a xy translation
|
||||
pub fn from_translation(translation: [f64; 2]) -> Self {
|
||||
Transform {
|
||||
parameters: [1f64, 0f64, 0f64, 1f64, translation[0], translation[1]],
|
||||
dparameters: [0f64; 6],
|
||||
origin: [0f64; 2],
|
||||
shape: [0usize; 2],
|
||||
}
|
||||
}
|
||||
|
||||
/// read a transform from a file
|
||||
pub fn from_file(path: PathBuf) -> Result<Self> {
|
||||
let file = File::open(path)?;
|
||||
Ok(from_reader(file)?)
|
||||
}
|
||||
|
||||
/// write a transform to a file
|
||||
pub fn to_file(&self, path: PathBuf) -> Result<()> {
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
to_writer(&mut file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// true if transform does nothing
|
||||
pub fn is_unity(&self) -> bool {
|
||||
self.parameters == [1f64, 0f64, 0f64, 1f64, 0f64, 0f64]
|
||||
}
|
||||
|
||||
/// transform an image using nearest neighbor interpolation
|
||||
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<'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<'a, A, T>(&self, coordinates: A) -> Result<Array2<f64>>
|
||||
where
|
||||
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"));
|
||||
}
|
||||
let m = self.matrix();
|
||||
let mut res = Array2::zeros([s[0], s[1]]);
|
||||
for i in 0..s[0] {
|
||||
let a = array![
|
||||
coordinates[[i, 0]].clone().into(),
|
||||
coordinates[[i, 1]].clone().into(),
|
||||
1f64
|
||||
]
|
||||
.to_owned();
|
||||
let b = m.dot(&a);
|
||||
res.slice_mut(s![i, ..]).assign(&b.slice(s![..2]));
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// get the matrix defining the transform
|
||||
pub fn matrix(&self) -> Array2<f64> {
|
||||
Array2::from_shape_vec(
|
||||
(3, 3),
|
||||
vec![
|
||||
self.parameters[0],
|
||||
self.parameters[1],
|
||||
self.parameters[4],
|
||||
self.parameters[2],
|
||||
self.parameters[3],
|
||||
self.parameters[5],
|
||||
0f64,
|
||||
0f64,
|
||||
1f64,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// get the matrix describing the error of the transform
|
||||
pub fn dmatrix(&self) -> Array2<f64> {
|
||||
Array2::from_shape_vec(
|
||||
(3, 3),
|
||||
vec![
|
||||
self.dparameters[0],
|
||||
self.dparameters[1],
|
||||
self.dparameters[4],
|
||||
self.dparameters[2],
|
||||
self.dparameters[3],
|
||||
self.dparameters[5],
|
||||
0f64,
|
||||
0f64,
|
||||
1f64,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// get the inverse transform
|
||||
pub fn inverse(&self) -> Result<Transform> {
|
||||
fn det(a: ArrayView2<f64>) -> f64 {
|
||||
(a[[0, 0]] * a[[1, 1]]) - (a[[0, 1]] * a[[1, 0]])
|
||||
}
|
||||
|
||||
let m = self.matrix();
|
||||
let d = det(m.slice(s![..2, ..2]));
|
||||
if d == 0f64 {
|
||||
return Err(anyhow!("transform matrix is not invertible"));
|
||||
}
|
||||
let parameters = [
|
||||
det(m.slice(s![1.., 1..])) / d,
|
||||
-det(m.slice(s![..;2, 1..])) / d,
|
||||
-det(m.slice(s![1.., ..;2])) / d,
|
||||
det(m.slice(s![..;2, ..;2])) / d,
|
||||
det(m.slice(s![..2, 1..])) / d,
|
||||
-det(m.slice(s![..2, ..;2])) / d,
|
||||
];
|
||||
|
||||
Ok(Transform {
|
||||
parameters,
|
||||
dparameters: [0f64; 6],
|
||||
origin: self.origin,
|
||||
shape: self.shape,
|
||||
})
|
||||
}
|
||||
|
||||
/// adapt the transform to a new origin and shape
|
||||
pub fn adapt(&mut self, origin: [f64; 2], shape: [usize; 2]) {
|
||||
self.origin = [
|
||||
origin[0] + (((self.shape[0] - shape[0]) as f64) / 2f64),
|
||||
origin[1] + (((self.shape[1] - shape[1]) as f64) / 2f64),
|
||||
];
|
||||
self.shape = shape;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
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>> {
|
||||
let imgx = 800;
|
||||
let imgy = 600;
|
||||
|
||||
let scalex = 3.0 / imgx as f32;
|
||||
let scaley = 3.0 / imgy as f32;
|
||||
|
||||
let mut im = Array2::<u8>::zeros((imgy, imgx));
|
||||
for x in 0..imgx {
|
||||
for y in 0..imgy {
|
||||
let cy = (y as f32 + shift_y) * scalex - 1.5;
|
||||
let cx = (x as f32 + shift_x) * scaley - 1.5;
|
||||
|
||||
let c = Complex::new(-0.4, 0.6);
|
||||
let mut z = Complex::new(cy, cx);
|
||||
|
||||
let mut i = 0;
|
||||
while i < 255 && z.norm() <= 2.0 {
|
||||
z = z * z + c;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
im[[y, x]] = i as u8;
|
||||
}
|
||||
}
|
||||
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 $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(-120f32, 10f32)?.mapv(|x| x as $t);
|
||||
let k = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
];
|
||||
let transform = Transform::new([1., 0., 0., 1., 120., -10.], origin, [shape[0], shape[1]]);
|
||||
let n = transform.transform_image_bspline(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);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
interp_tests_bspline! {
|
||||
interpbs_u8: u8,
|
||||
interpbs_i8: i8,
|
||||
interpbs_u16: u16,
|
||||
interpbs_i16: i16,
|
||||
interpbs_u32: u32,
|
||||
interpbs_i32: i32,
|
||||
interpbs_u64: u64,
|
||||
interpbs_i64: i64,
|
||||
interpbs_f32: f32,
|
||||
interpbs_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! interp_tests_nearest_neighbor {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(-120f32, 10f32)?.mapv(|x| x as $t);
|
||||
let k = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((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);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
interp_tests_nearest_neighbor! {
|
||||
interpnn_u8: u8,
|
||||
interpnn_i8: i8,
|
||||
interpnn_u16: u16,
|
||||
interpnn_i16: i16,
|
||||
interpnn_u32: u32,
|
||||
interpnn_i32: i32,
|
||||
interpnn_u64: u64,
|
||||
interpnn_i64: i64,
|
||||
interpnn_f32: f32,
|
||||
interpnn_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! registration_tests_translation {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
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;
|
||||
let d = (t.matrix() - m).powi(2).sum();
|
||||
assert!(d < 0.01);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
registration_tests_translation! {
|
||||
registration_translation_u8: u8,
|
||||
registration_translation_i8: i8,
|
||||
registration_translation_u16: u16,
|
||||
registration_translation_i16: i16,
|
||||
registration_translation_u32: u32,
|
||||
registration_translation_i32: i32,
|
||||
registration_translation_u64: u64,
|
||||
registration_translation_i64: i64,
|
||||
registration_translation_f32: f32,
|
||||
registration_translation_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! registration_tests_affine {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
];
|
||||
let s = Transform::new([1.2, 0., 0., 1., 5., 7.], origin, [shape[0], shape[1]]);
|
||||
let k = s.transform_image_bspline(j.view())?;
|
||||
let t = Transform::register_affine(j.view(), k.view())?.inverse()?;
|
||||
let d = (t.matrix() - s.matrix()).powi(2).sum();
|
||||
assert!(d < 0.01);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
registration_tests_affine! {
|
||||
registration_tests_affine_u8: u8,
|
||||
registration_tests_affine_i8: i8,
|
||||
registration_tests_affine_u16: u16,
|
||||
registration_tests_affine_i16: i16,
|
||||
registration_tests_affine_u32: u32,
|
||||
registration_tests_affine_i32: i32,
|
||||
registration_tests_affine_u64: u64,
|
||||
registration_tests_affine_i64: i64,
|
||||
registration_tests_affine_f32: f32,
|
||||
registration_tests_affine_f64: f64,
|
||||
fn set_parameter_map(
|
||||
tfilter: &mut ElastixImageFilter,
|
||||
parameter_map: &UniquePtr<ParameterMap>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
//! Some structs and methods to make working with registration and interpolation methods in
|
||||
//! SimpleITK more Rust friendly.
|
||||
|
||||
use crate::simple;
|
||||
use anyhow::{Result, anyhow};
|
||||
use autocxx::prelude::*;
|
||||
use cxx::{CxxVector, UniquePtr, let_cxx_string};
|
||||
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::marker::PhantomData;
|
||||
use std::ops::{Deref, DerefMut, Mul};
|
||||
use std::path::PathBuf;
|
||||
use num::Complex;
|
||||
use tempfile::tempdir;
|
||||
|
||||
/// 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: simple::PixelIDValueEnum;
|
||||
}
|
||||
|
||||
macro_rules! pixel_type_impl {
|
||||
($($T:ty: $sitk:expr $(,)?)*) => {
|
||||
$(
|
||||
impl PixelType for $T {
|
||||
const PT: simple::PixelIDValueEnum = $sitk;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
pixel_type_impl! {
|
||||
u8: simple::PixelIDValueEnum::sitkUInt8,
|
||||
i8: simple::PixelIDValueEnum::sitkInt8,
|
||||
u16: simple::PixelIDValueEnum::sitkUInt16,
|
||||
i16: simple::PixelIDValueEnum::sitkInt16,
|
||||
u32: simple::PixelIDValueEnum::sitkUInt32,
|
||||
i32: simple::PixelIDValueEnum::sitkInt32,
|
||||
u64: simple::PixelIDValueEnum::sitkUInt64,
|
||||
i64: simple::PixelIDValueEnum::sitkInt64,
|
||||
f32: simple::PixelIDValueEnum::sitkFloat32,
|
||||
f64: simple::PixelIDValueEnum::sitkFloat64,
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pixel_type_impl!(usize: simple::PixelIDValueEnum::sitkUInt64);
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pixel_type_impl!(usize: simple::PixelIDValueEnum::sitkUInt32);
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
pixel_type_impl!(isize: simple::PixelIDValueEnum::sitkInt64);
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
pixel_type_impl!(isize: simple::PixelIDValueEnum::sitkInt32);
|
||||
|
||||
/// Struct holding a pointer to an image
|
||||
pub struct Image<T: PixelType> {
|
||||
image: UniquePtr<simple::Image>,
|
||||
pixel_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: PixelType> Deref for Image<T> {
|
||||
type Target = UniquePtr<simple::Image>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.image
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PixelType> Image<T> {
|
||||
/// encapsulate an itk::simple::Image
|
||||
pub fn new(image: UniquePtr<simple::Image>) -> Self {
|
||||
Self {
|
||||
image,
|
||||
pixel_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// take an ndarray Array2 and turn it into a SimpleITK image
|
||||
pub fn from_array<'a, A>(array: A) -> Self
|
||||
where
|
||||
T: 'a + PixelType,
|
||||
A: AsArray<'a, T, Ix2>,
|
||||
{
|
||||
let array = array.into();
|
||||
let shape = array.shape();
|
||||
let width = (shape[1] as u32).into();
|
||||
let height = (shape[0] as u32).into();
|
||||
let mut image = simple::Image::new3(width, height, T::PT).within_unique_ptr();
|
||||
image.pin_mut().MakeUnique();
|
||||
let buffer = image.pin_mut().GetBufferAsVoid();
|
||||
unsafe { std::ptr::copy(array.as_ptr(), buffer as *mut T, shape[0] * shape[1]) };
|
||||
Self {
|
||||
image,
|
||||
pixel_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// return as an ndarray Array2
|
||||
pub fn as_array(&self) -> Array2<T> {
|
||||
let width = u32::from(self.image.GetWidth()) as usize;
|
||||
let height = u32::from(self.image.GetHeight()) as usize;
|
||||
let mut array = Array2::<T>::uninit((height, width));
|
||||
let buffer = self.image.GetBufferAsVoid1();
|
||||
unsafe {
|
||||
std::ptr::copy(
|
||||
buffer as *const T,
|
||||
array.as_mut_ptr() as *mut T,
|
||||
width * height,
|
||||
);
|
||||
array.assume_init()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A, T> From<A> for Image<T>
|
||||
where
|
||||
T: 'a + PixelType,
|
||||
A: AsArray<'a, T, Ix2>,
|
||||
{
|
||||
fn from(value: A) -> Self {
|
||||
Self::from_array(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Image<T>> for Array2<T>
|
||||
where
|
||||
T: PixelType,
|
||||
{
|
||||
fn from(value: Image<T>) -> Self {
|
||||
value.as_array()
|
||||
}
|
||||
}
|
||||
|
||||
/// a struct describing the transform
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct AffineTransform {
|
||||
/// flattened 2x2 rotation matrix + translation
|
||||
pub parameters: [f64; 6],
|
||||
/// error / significance on parameters
|
||||
pub dparameters: [f64; 6],
|
||||
/// the point about which rotations are performed
|
||||
pub origin: [f64; 2],
|
||||
/// the shape of images for which this transform is meant
|
||||
pub shape: [usize; 2],
|
||||
}
|
||||
|
||||
impl Mul for AffineTransform {
|
||||
type Output = AffineTransform;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn mul(self, other: AffineTransform) -> AffineTransform {
|
||||
let m = self.matrix().dot(&other.matrix());
|
||||
let dm = self.dmatrix().dot(&other.matrix()) + self.matrix().dot(&other.dmatrix());
|
||||
AffineTransform {
|
||||
parameters: [
|
||||
m[[0, 0]],
|
||||
m[[0, 1]],
|
||||
m[[1, 0]],
|
||||
m[[1, 1]],
|
||||
m[[2, 0]],
|
||||
m[[2, 1]],
|
||||
],
|
||||
dparameters: [
|
||||
dm[[0, 0]],
|
||||
dm[[0, 1]],
|
||||
dm[[1, 0]],
|
||||
dm[[1, 1]],
|
||||
dm[[2, 0]],
|
||||
dm[[2, 1]],
|
||||
],
|
||||
origin: self.origin,
|
||||
shape: self.shape,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AffineTransform {}
|
||||
|
||||
impl AffineTransform {
|
||||
/// parameters: flat 2x2 part of matrix, translation; origin: center of rotation
|
||||
pub fn new(parameters: [f64; 6], origin: [f64; 2], shape: [usize; 2]) -> Self {
|
||||
Self {
|
||||
parameters,
|
||||
dparameters: [0f64; 6],
|
||||
origin,
|
||||
shape,
|
||||
}
|
||||
}
|
||||
|
||||
/// find the affine transform which transforms moving into fixed
|
||||
pub fn register_affine<F, M, T>(fixed: F, moving: M) -> Result<AffineTransform>
|
||||
where
|
||||
F: Into<Image<T>>,
|
||||
M: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
Self::register(fixed, moving, true)
|
||||
}
|
||||
|
||||
/// find the translation which transforms moving into fixed
|
||||
pub fn register_translation<F, M, T>(fixed: F, moving: M) -> Result<AffineTransform>
|
||||
where
|
||||
F: Into<Image<T>>,
|
||||
M: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
Self::register(fixed, moving, false)
|
||||
}
|
||||
|
||||
/// find the transform which transforms moving into fixed
|
||||
pub fn register<F, M, T>(fixed: F, moving: M, affine: bool) -> Result<AffineTransform>
|
||||
where
|
||||
F: Into<Image<T>>,
|
||||
M: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
let tmp_folder = tempdir()?;
|
||||
let fixed = fixed.into();
|
||||
let moving = moving.into();
|
||||
let width = u32::from(fixed.GetWidth()) as usize;
|
||||
let height = u32::from(fixed.GetHeight()) as usize;
|
||||
let_cxx_string!(transform_name = if affine { "affine" } else { "translation" });
|
||||
let parameter_map = crate::ffi_extra::get_default_parameter_map(&transform_name);
|
||||
let mut tfilter = simple::ElastixImageFilter::new().within_box();
|
||||
tfilter.as_mut().LogToConsoleOff();
|
||||
tfilter.as_mut().LogToFileOff();
|
||||
tfilter.as_mut().SetLogToFile(false);
|
||||
tfilter.as_mut().SetFixedImage(&fixed);
|
||||
tfilter.as_mut().SetMovingImage(&moving);
|
||||
crate::ffi_extra::set_parameter_map(&mut tfilter, ¶meter_map);
|
||||
tfilter.as_mut().SetParameter("WriteResultImage", "False");
|
||||
tfilter
|
||||
.as_mut()
|
||||
.SetOutputDirectory(tmp_folder.path().display().to_string());
|
||||
let _ = tfilter.as_mut().Execute().within_unique_ptr();
|
||||
let_cxx_string!(tp = "TransformParameters");
|
||||
let p = crate::ffi_extra::get_transform_parameter_map(tfilter.as_mut().deref_mut(), 0)
|
||||
.get(&tp)
|
||||
.iter()
|
||||
.map(|i| i.to_string_lossy().parse::<f64>())
|
||||
.collect::<Result<Vec<f64>, _>>()?;
|
||||
let parameters = if affine {
|
||||
[p[0], p[1], p[2], p[3], p[4], p[5]]
|
||||
} else {
|
||||
[1.0, 0.0, 0.0, 1.0, p[0], p[1]]
|
||||
};
|
||||
let origin = [((height - 1) as f64) / 2.0, ((width - 1) as f64) / 2.0];
|
||||
let shape = [height, width];
|
||||
Ok(AffineTransform::new(parameters, origin, shape))
|
||||
}
|
||||
|
||||
/// create a transform from a xy translation
|
||||
pub fn from_translation(translation: [f64; 2]) -> Self {
|
||||
AffineTransform {
|
||||
parameters: [1f64, 0f64, 0f64, 1f64, translation[0], translation[1]],
|
||||
dparameters: [0f64; 6],
|
||||
origin: [0f64; 2],
|
||||
shape: [0usize; 2],
|
||||
}
|
||||
}
|
||||
|
||||
/// read a transform from a file
|
||||
pub fn from_file(path: PathBuf) -> Result<Self> {
|
||||
let file = File::open(path)?;
|
||||
Ok(from_reader(file)?)
|
||||
}
|
||||
|
||||
/// write a transform to a file
|
||||
pub fn to_file(&self, path: PathBuf) -> Result<()> {
|
||||
let mut file = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.truncate(true)
|
||||
.open(path)?;
|
||||
to_writer(&mut file, self)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// true if transform does nothing
|
||||
pub fn is_unity(&self) -> bool {
|
||||
self.parameters == [1f64, 0f64, 0f64, 1f64, 0f64, 0f64]
|
||||
}
|
||||
|
||||
/// transform an image using nearest neighbor interpolation
|
||||
pub fn transform_image_bspline<I, T>(&self, image: I) -> Result<Image<T>>
|
||||
where
|
||||
I: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
Self::interp(self, image, false)
|
||||
}
|
||||
|
||||
/// transform an image using bspline interpolation
|
||||
pub fn transform_image_nearest_neighbor<I, T>(&self, image: I) -> Result<Image<T>>
|
||||
where
|
||||
I: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
Self::interp(self, image, true)
|
||||
}
|
||||
|
||||
/// transform an image
|
||||
pub fn interp<I, T>(&self, image: I, nearest_neighbor: bool) -> Result<Image<T>>
|
||||
where
|
||||
I: Into<Image<T>>,
|
||||
T: PixelType,
|
||||
{
|
||||
let image = image.into();
|
||||
let width = u32::from(image.GetWidth()) as usize;
|
||||
let height = u32::from(image.GetHeight()) as usize;
|
||||
let origin = [((width - 1) as f64) / 2f64, ((height - 1) as f64) / 2f64];
|
||||
let p = self.parameters;
|
||||
let matrix = cxx_vector([p[0], p[1], p[2], p[3]]);
|
||||
let translation = cxx_vector([p[4], p[5]]);
|
||||
let fixed_center = cxx_vector(origin);
|
||||
let affine_transform =
|
||||
simple::AffineTransform::new3(&matrix, &translation, &fixed_center).within_unique_ptr();
|
||||
let transform = <_ as AsRef<simple::Transform>>::as_ref(affine_transform.as_ref().unwrap());
|
||||
let interpolator = if nearest_neighbor {
|
||||
simple::InterpolatorEnum::sitkBSpline
|
||||
} else {
|
||||
simple::InterpolatorEnum::sitkNearestNeighbor
|
||||
};
|
||||
Ok(Image::<T>::new(
|
||||
simple::Resample(
|
||||
&image,
|
||||
transform,
|
||||
interpolator,
|
||||
0.0,
|
||||
simple::PixelIDValueEnum::sitkUnknown,
|
||||
nearest_neighbor,
|
||||
)
|
||||
.within_unique_ptr(),
|
||||
))
|
||||
}
|
||||
|
||||
/// get coordinates resulting from transforming input coordinates, coordinates must have two
|
||||
/// columns: x & y
|
||||
pub fn transform_coordinates<'a, A, T>(&self, coordinates: A) -> Result<Array2<f64>>
|
||||
where
|
||||
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"));
|
||||
}
|
||||
let m = self.matrix();
|
||||
let mut res = Array2::zeros([s[0], s[1]]);
|
||||
for i in 0..s[0] {
|
||||
let a = array![
|
||||
coordinates[[i, 0]].clone().into(),
|
||||
coordinates[[i, 1]].clone().into(),
|
||||
1f64
|
||||
]
|
||||
.to_owned();
|
||||
let b = m.dot(&a);
|
||||
res.slice_mut(s![i, ..]).assign(&b.slice(s![..2]));
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// get the matrix defining the transform
|
||||
pub fn matrix(&self) -> Array2<f64> {
|
||||
Array2::from_shape_vec(
|
||||
(3, 3),
|
||||
vec![
|
||||
self.parameters[0],
|
||||
self.parameters[1],
|
||||
self.parameters[4],
|
||||
self.parameters[2],
|
||||
self.parameters[3],
|
||||
self.parameters[5],
|
||||
0f64,
|
||||
0f64,
|
||||
1f64,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// get the matrix describing the error of the transform
|
||||
pub fn dmatrix(&self) -> Array2<f64> {
|
||||
Array2::from_shape_vec(
|
||||
(3, 3),
|
||||
vec![
|
||||
self.dparameters[0],
|
||||
self.dparameters[1],
|
||||
self.dparameters[4],
|
||||
self.dparameters[2],
|
||||
self.dparameters[3],
|
||||
self.dparameters[5],
|
||||
0f64,
|
||||
0f64,
|
||||
1f64,
|
||||
],
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// get the inverse transform
|
||||
pub fn inverse(&self) -> Result<AffineTransform> {
|
||||
fn det(a: ArrayView2<f64>) -> f64 {
|
||||
(a[[0, 0]] * a[[1, 1]]) - (a[[0, 1]] * a[[1, 0]])
|
||||
}
|
||||
|
||||
let m = self.matrix();
|
||||
let d = det(m.slice(s![..2, ..2]));
|
||||
if d == 0f64 {
|
||||
return Err(anyhow!("transform matrix is not invertible"));
|
||||
}
|
||||
let parameters = [
|
||||
det(m.slice(s![1.., 1..])) / d,
|
||||
-det(m.slice(s![..;2, 1..])) / d,
|
||||
-det(m.slice(s![1.., ..;2])) / d,
|
||||
det(m.slice(s![..;2, ..;2])) / d,
|
||||
det(m.slice(s![..2, 1..])) / d,
|
||||
-det(m.slice(s![..2, ..;2])) / d,
|
||||
];
|
||||
|
||||
Ok(AffineTransform {
|
||||
parameters,
|
||||
dparameters: [0f64; 6],
|
||||
origin: self.origin,
|
||||
shape: self.shape,
|
||||
})
|
||||
}
|
||||
|
||||
/// adapt the transform to a new origin and shape
|
||||
pub fn adapt(&mut self, origin: [f64; 2], shape: [usize; 2]) {
|
||||
self.origin = [
|
||||
origin[0] + (((self.shape[0] - shape[0]) as f64) / 2f64),
|
||||
origin[1] + (((self.shape[1] - shape[1]) as f64) / 2f64),
|
||||
];
|
||||
self.shape = shape;
|
||||
}
|
||||
}
|
||||
|
||||
/// conveniently collect an iterator into a CxxVector
|
||||
pub fn cxx_vector<T, I>(vec: I) -> UniquePtr<CxxVector<T>>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: cxx::vector::VectorElement + cxx::ExternType<Kind = cxx::kind::Trivial>,
|
||||
{
|
||||
let mut v = CxxVector::new();
|
||||
v.pin_mut().extend(vec);
|
||||
v
|
||||
}
|
||||
|
||||
/// An example of generating julia fractals, for testing purposes.
|
||||
pub fn julia_image(shift_x: f32, shift_y: f32) -> Result<Array2<u8>> {
|
||||
let imgx = 800;
|
||||
let imgy = 600;
|
||||
|
||||
let scalex = 3.0 / imgx as f32;
|
||||
let scaley = 3.0 / imgy as f32;
|
||||
|
||||
let mut im = Array2::<u8>::zeros((imgy, imgx));
|
||||
for x in 0..imgx {
|
||||
for y in 0..imgy {
|
||||
let cy = (y as f32 + shift_y) * scalex - 1.5;
|
||||
let cx = (x as f32 + shift_x) * scaley - 1.5;
|
||||
|
||||
let c = Complex::new(-0.4, 0.6);
|
||||
let mut z = Complex::new(cy, cx);
|
||||
|
||||
let mut i = 0;
|
||||
while i < 255 && z.norm() <= 2.0 {
|
||||
z = z * z + c;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
im[[y, x]] = i as u8;
|
||||
}
|
||||
}
|
||||
Ok(im)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use ndarray::Array2;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_serialization() -> Result<()> {
|
||||
let file = NamedTempFile::new()?;
|
||||
let t = AffineTransform::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 = AffineTransform::from_file(file.path().to_path_buf())?;
|
||||
assert_eq!(s, t);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! interp_tests_bspline {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(-120f32, 10f32)?.mapv(|x| x as $t);
|
||||
let k = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
];
|
||||
let transform = AffineTransform::new([1., 0., 0., 1., 120., -10.], origin, [shape[0], shape[1]]);
|
||||
let n: Array2<_> = transform.transform_image_bspline(j.view())?.into();
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
interp_tests_bspline! {
|
||||
interpbs_u8: u8,
|
||||
interpbs_i8: i8,
|
||||
interpbs_u16: u16,
|
||||
interpbs_i16: i16,
|
||||
interpbs_u32: u32,
|
||||
interpbs_i32: i32,
|
||||
interpbs_u64: u64,
|
||||
interpbs_i64: i64,
|
||||
interpbs_f32: f32,
|
||||
interpbs_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! interp_tests_nearest_neighbor {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(-120f32, 10f32)?.mapv(|x| x as $t);
|
||||
let k = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
];
|
||||
let j0 = j.clone();
|
||||
let k0 = k.clone();
|
||||
let transform = AffineTransform::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: Array2<_> = transform.transform_image_nearest_neighbor(j.view())?.into();
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
interp_tests_nearest_neighbor! {
|
||||
interpnn_u8: u8,
|
||||
interpnn_i8: i8,
|
||||
interpnn_u16: u16,
|
||||
interpnn_i16: i16,
|
||||
interpnn_u32: u32,
|
||||
interpnn_i32: i32,
|
||||
interpnn_u64: u64,
|
||||
interpnn_i64: i64,
|
||||
interpnn_f32: f32,
|
||||
interpnn_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! registration_tests_translation {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
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 = AffineTransform::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;
|
||||
let d = (t.matrix() - m).powi(2).sum();
|
||||
assert!(d < 0.01, "d: {}, t: {:?}", d, t.parameters);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
registration_tests_translation! {
|
||||
registration_translation_u8: u8,
|
||||
registration_translation_i8: i8,
|
||||
registration_translation_u16: u16,
|
||||
registration_translation_i16: i16,
|
||||
registration_translation_u32: u32,
|
||||
registration_translation_i32: i32,
|
||||
registration_translation_u64: u64,
|
||||
registration_translation_i64: i64,
|
||||
registration_translation_f32: f32,
|
||||
registration_translation_f64: f64,
|
||||
}
|
||||
|
||||
macro_rules! registration_tests_affine {
|
||||
($($name:ident: $t:ty $(,)?)*) => {
|
||||
$(
|
||||
#[test]
|
||||
fn $name() -> Result<()> {
|
||||
let j = julia_image(0f32, 0f32)?.mapv(|x| x as $t);
|
||||
let shape = j.shape();
|
||||
let origin = [
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
];
|
||||
let s = AffineTransform::new([1.2, 0., 0., 1., 5., 7.], origin, [shape[0], shape[1]]);
|
||||
let k: Array2<_> = s.transform_image_bspline(j.view())?.into();
|
||||
let t = AffineTransform::register_affine(j.view(), k.view())?.inverse()?;
|
||||
let d = (t.matrix() - s.matrix()).powi(2).sum();
|
||||
assert!(d < 0.025, "d: {}, t: {:?}", d, t.parameters);
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
registration_tests_affine! {
|
||||
registration_tests_affine_u8: u8,
|
||||
registration_tests_affine_i8: i8,
|
||||
registration_tests_affine_u16: u16,
|
||||
registration_tests_affine_i16: i16,
|
||||
registration_tests_affine_u32: u32,
|
||||
registration_tests_affine_i32: i32,
|
||||
registration_tests_affine_u64: u64,
|
||||
registration_tests_affine_i64: i64,
|
||||
registration_tests_affine_f32: f32,
|
||||
registration_tests_affine_f64: f64,
|
||||
}
|
||||
}
|
||||
-362
@@ -1,362 +0,0 @@
|
||||
use crate::PixelType;
|
||||
use anyhow::Result;
|
||||
use libc::{c_double, c_uint};
|
||||
use ndarray::{Array2, AsArray, Ix2};
|
||||
use one_at_a_time_please::one_at_a_time;
|
||||
use std::ptr;
|
||||
|
||||
macro_rules! register_fn {
|
||||
($($name:ident: $T:ty $(,)?)*) => {
|
||||
$(
|
||||
fn $name(
|
||||
width: c_uint,
|
||||
height: c_uint,
|
||||
fixed_arr: *const $T,
|
||||
moving_arr: *const $T,
|
||||
translation_or_affine: bool,
|
||||
transform: &mut *mut c_double,
|
||||
);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! interp_fn {
|
||||
($($name:ident: $T:ty $(,)?)*) => {
|
||||
$(
|
||||
fn $name(
|
||||
width: c_uint,
|
||||
height: c_uint,
|
||||
transform: *const c_double,
|
||||
origin: *const c_double,
|
||||
image: &mut *mut $T,
|
||||
bspline_or_nn: bool,
|
||||
);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
register_fn! {
|
||||
register_u8: u8,
|
||||
register_i8: i8,
|
||||
register_u16: u16,
|
||||
register_i16: i16,
|
||||
register_u32: u32,
|
||||
register_i32: i32,
|
||||
register_u64: u64,
|
||||
register_i64: i64,
|
||||
register_f32: f32,
|
||||
register_f64: f64,
|
||||
}
|
||||
|
||||
interp_fn! {
|
||||
interp_u8: u8,
|
||||
interp_i8: i8,
|
||||
interp_u16: u16,
|
||||
interp_i16: i16,
|
||||
interp_u32: u32,
|
||||
interp_i32: i32,
|
||||
interp_u64: u64,
|
||||
interp_i64: i64,
|
||||
interp_f32: f32,
|
||||
interp_f64: f64,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn interp<'a, A, T>(
|
||||
parameters: [f64; 6],
|
||||
origin: [f64; 2],
|
||||
image: A,
|
||||
bspline_or_nn: bool,
|
||||
) -> 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;
|
||||
let mut im: Vec<_> = image.into_iter().cloned().collect();
|
||||
let im_ptr: *mut T = ptr::from_mut(unsafe { &mut *im.as_mut_ptr() });
|
||||
|
||||
match T::PT {
|
||||
1 => unsafe {
|
||||
interp_u8(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut u8),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
2 => unsafe {
|
||||
interp_i8(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut i8),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
3 => unsafe {
|
||||
interp_u16(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut u16),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
4 => unsafe {
|
||||
interp_i16(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut i16),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
5 => unsafe {
|
||||
interp_u32(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut u32),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
6 => unsafe {
|
||||
interp_i32(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut i32),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
7 => unsafe {
|
||||
interp_u64(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut u64),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
8 => unsafe {
|
||||
interp_i64(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut i64),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
9 => unsafe {
|
||||
interp_f32(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut f32),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
10 => unsafe {
|
||||
interp_f64(
|
||||
width,
|
||||
height,
|
||||
parameters.as_ptr(),
|
||||
origin.as_ptr(),
|
||||
&mut (im_ptr as *mut f64),
|
||||
bspline_or_nn,
|
||||
);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(Array2::from_shape_vec(
|
||||
(shape[0], shape[1]),
|
||||
im.into_iter().collect(),
|
||||
)?)
|
||||
}
|
||||
|
||||
#[one_at_a_time]
|
||||
pub(crate) fn register<'a, A, T>(
|
||||
fixed: A,
|
||||
moving: A,
|
||||
translation_or_affine: bool,
|
||||
) -> 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().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];
|
||||
let mut transform_ptr: *mut c_double = ptr::from_mut(unsafe { &mut *transform.as_mut_ptr() });
|
||||
|
||||
// let ma0 = &mut moving as *mut Vec<T> as usize;
|
||||
// println!("ma0: {:#x}", ma0);
|
||||
|
||||
match T::PT {
|
||||
1 => {
|
||||
unsafe {
|
||||
register_u8(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const u8,
|
||||
moving_ptr as *const u8,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
2 => {
|
||||
unsafe {
|
||||
register_i8(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const i8,
|
||||
moving_ptr as *const i8,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
3 => {
|
||||
unsafe {
|
||||
register_u16(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const u16,
|
||||
moving_ptr as *const u16,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
4 => {
|
||||
unsafe {
|
||||
register_i16(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const i16,
|
||||
moving_ptr as *const i16,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
5 => {
|
||||
unsafe {
|
||||
register_u32(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const u32,
|
||||
moving_ptr as *const u32,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
6 => {
|
||||
unsafe {
|
||||
register_i32(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const i32,
|
||||
moving_ptr as *const i32,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
7 => {
|
||||
unsafe {
|
||||
register_u64(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const u64,
|
||||
moving_ptr as *const u64,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
8 => {
|
||||
unsafe {
|
||||
register_i64(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const i64,
|
||||
moving_ptr as *const i64,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
9 => {
|
||||
unsafe {
|
||||
register_f32(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const f32,
|
||||
moving_ptr as *const f32,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
10 => {
|
||||
unsafe {
|
||||
register_f64(
|
||||
width,
|
||||
height,
|
||||
fixed_ptr as *const f64,
|
||||
moving_ptr as *const f64,
|
||||
translation_or_affine,
|
||||
&mut transform_ptr,
|
||||
)
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// let ma1 = &mut moving as *mut Vec<T> as usize;
|
||||
// println!("ma1: {:#x}", ma1);
|
||||
|
||||
// println!("{}", fixed.len());
|
||||
// println!("{}", moving.len());
|
||||
|
||||
Ok((
|
||||
[
|
||||
transform[0] as f64,
|
||||
transform[1] as f64,
|
||||
transform[2] as f64,
|
||||
transform[3] as f64,
|
||||
transform[4] as f64,
|
||||
transform[5] as f64,
|
||||
],
|
||||
[
|
||||
((shape[0] - 1) as f64) / 2f64,
|
||||
((shape[1] - 1) as f64) / 2f64,
|
||||
],
|
||||
[shape[0], shape[1]],
|
||||
))
|
||||
}
|
||||
Reference in New Issue
Block a user