use snafu::Snafu;
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Rescale {
pub slope: f64,
pub intercept: f64,
}
impl Rescale {
#[inline]
pub fn new(slope: f64, intercept: f64) -> Self {
Rescale { slope, intercept }
}
#[inline]
pub fn apply(&self, value: f64) -> f64 {
self.slope * value + self.intercept
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum VoiLutFunction {
Linear,
LinearExact,
Sigmoid,
}
#[derive(Debug, Copy, Clone, PartialEq, Snafu)]
pub struct FromVoiLutFunctionError {
_private: (),
}
impl std::convert::TryFrom<&str> for VoiLutFunction {
type Error = FromVoiLutFunctionError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"LINEAR" => Ok(Self::Linear),
"LINEAR_EXACT" => Ok(Self::LinearExact),
"SIGMOID" => Ok(Self::Sigmoid),
_ => Err(FromVoiLutFunctionError { _private: () }),
}
}
}
impl Default for VoiLutFunction {
fn default() -> Self {
VoiLutFunction::Linear
}
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct WindowLevel {
pub width: f64,
pub center: f64,
}
#[derive(Debug, PartialEq)]
pub struct WindowLevelTransform {
voi_lut_function: VoiLutFunction,
window_level: WindowLevel,
}
impl WindowLevelTransform {
#[inline]
pub fn new(voi_lut_function: VoiLutFunction, window_level: WindowLevel) -> Self {
WindowLevelTransform {
voi_lut_function,
window_level: WindowLevel {
center: window_level.center,
width: match voi_lut_function {
VoiLutFunction::LinearExact => window_level.width.max(0.),
VoiLutFunction::Linear | VoiLutFunction::Sigmoid => window_level.width.max(1.),
},
},
}
}
#[inline]
pub fn linear(window_level: WindowLevel) -> Self {
Self::new(VoiLutFunction::Linear, window_level)
}
pub fn apply(&self, value: f64, y_max: f64) -> f64 {
match self.voi_lut_function {
VoiLutFunction::Linear => window_level_linear(
value,
self.window_level.width,
self.window_level.center,
y_max,
),
VoiLutFunction::LinearExact => window_level_linear_exact(
value,
self.window_level.width,
self.window_level.center,
y_max,
),
VoiLutFunction::Sigmoid => window_level_sigmoid(
value,
self.window_level.width,
self.window_level.center,
y_max,
),
}
}
}
fn window_level_linear(value: f64, window_width: f64, window_center: f64, y_max: f64) -> f64 {
let width = window_width as f64;
let center = window_center as f64;
debug_assert!(width >= 1.);
let min = center - (width - 1.) / 2.;
let max = center - 0.5 + (width - 1.) / 2.;
if value <= min {
0.
} else if value > max {
y_max
} else {
((value - (center - 0.5)) / (width - 1.) + 0.5) * y_max
}
}
fn window_level_linear_exact(value: f64, window_width: f64, window_center: f64, y_max: f64) -> f64 {
let width = window_width as f64;
let center = window_center as f64;
debug_assert!(width >= 0.);
let min = center - width / 2.;
let max = center + width / 2.;
if value <= min {
0.
} else if value > max {
y_max
} else {
((value - center) / width + 0.5) * y_max
}
}
fn window_level_sigmoid(value: f64, window_width: f64, window_center: f64, y_max: f64) -> f64 {
let width = window_width as f64;
let center = window_center as f64;
assert!(width >= 1.);
y_max / (1. + f64::exp(-4. * (value - center) / width))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn modality_lut_baseline() {
let rescale = Rescale::new(1., -1024.);
assert_eq!(rescale.apply(0.), -1024.);
assert_eq!(rescale.apply(1.), -1023.);
assert_eq!(rescale.apply(2.), -1022.);
assert_eq!(rescale.apply(1024.), 0.);
}
#[test]
fn window_level_linear_example() {
let window_level = WindowLevel {
width: 4096.,
center: 2048.,
};
let window_level_transform = WindowLevelTransform::linear(window_level);
let y_max = 255.;
assert_eq!(window_level_transform.apply(-2., y_max), 0.);
assert_eq!(window_level_transform.apply(-1., y_max), 0.);
assert_eq!(window_level_transform.apply(0., y_max), 0.);
assert_eq!(window_level_transform.apply(4095.5, y_max), y_max);
assert_eq!(window_level_transform.apply(4096., y_max), y_max);
assert_eq!(window_level_transform.apply(4097., y_max), y_max);
let x = 1024.;
let y = window_level_transform.apply(x, y_max);
let expected_y = ((x - 2047.5) / 4095. + 0.5) * 255.;
assert!((y - expected_y).abs() < 1e-3);
}
#[test]
fn window_level_linear_1() {
let window_level = WindowLevel {
width: 300.,
center: 50.,
};
let window_level_transform = WindowLevelTransform::linear(window_level);
let y_max = 255.;
let y = window_level_transform.apply(-120., y_max);
assert_eq!(y, 0.);
let y = window_level_transform.apply(-100.5, y_max);
assert_eq!(y, 0.);
let y = window_level_transform.apply(-100., y_max);
assert_eq!(y, 0.);
let y = window_level_transform.apply(201., y_max);
assert_eq!(y, 255.);
let y = window_level_transform.apply(200., y_max);
assert_eq!(y, 255.);
let y = window_level_transform.apply(50., y_max);
assert!(y > 127. && y < 129.);
}
}