M16: ImageJ feature parity — 8 major feature additions (245 tests)

1. Spatial Calibration: SpatialCalibration struct, Feret's diameter,
   shape descriptors (circularity, aspect ratio, roundness, solidity)
2. Enhanced Particle Analysis: ParticleFilter with area/circularity
   filters, exclude_on_edges, Feret/solidity in results
3. Stack Tools: make_substack, concatenate, insert/delete/reverse/sort
4. Morphology: top_hat, bottom_hat, morph_gradient, morph_laplacian,
   ultimate_eroded_points, gray-scale dilate/erode
5. ROI Composites: union, intersection, difference, RoiStyle,
   multi-measure across stack
6. Plot/Graph (new ri-plot crate): XYPlot, scatter, z/t profiles,
   CurveFit (linear, polynomial, exponential, gaussian, power)
7. Bio-Formats: DICOM reader (tag parser + pixel extraction),
   NIfTI-1 reader (.nii + .nii.gz), integrated into load_image()
8. Type Conversion: u8/u16/f32 conversions, rgb_to_gray, gray_to_rgb,
   apply_lut_permanent
This commit is contained in:
osobh
2026-03-14 12:39:08 -07:00
parent b3520373fc
commit 3c84a68e13
18 changed files with 3107 additions and 16 deletions
+12
View File
@@ -1,3 +1,6 @@
pub mod dicom;
pub mod nifti;
use std::path::Path;
use image::{ColorType, GenericImageView};
@@ -137,6 +140,15 @@ fn to_u8(buf: &TypedBuffer, idx: usize) -> u8 {
/// Preserves channel structure: grayscale → 1ch, RGB → 3ch, RGBA → 4ch.
/// Supports PNG, JPEG, TIFF, BMP, GIF via the `image` crate.
pub fn load_image(path: &Path) -> Result<Hyperstack, RiError> {
// Check for DICOM/NIfTI extensions first
let path_lower = path.to_string_lossy().to_lowercase();
if path_lower.ends_with(".dcm") || path_lower.ends_with(".dicom") {
return dicom::load_dicom(path);
}
if path_lower.ends_with(".nii") || path_lower.ends_with(".nii.gz") {
return nifti::load_nifti(path);
}
let img = image::open(path).map_err(|e| RiError::ImageDecode(e.to_string()))?;
let file_size_bytes = std::fs::metadata(path).map(|m| m.len()).ok();
let (w, h) = img.dimensions();