#[cfg(test)]
mod tests {
use super::*;
use crate::colormap::ColorMaps;
use crate::helpers::density_options_from_fcs;
use crate::options::{AxisOptions, BasePlotOptions, DensityPlotOptions, PlotOptions};
use crate::histogram_data::HistogramData;
use crate::options::HistogramPlotOptions;
use crate::plots::density::DensityPlot;
use crate::plots::histogram::HistogramPlot;
use crate::render::RenderConfig;
use flow_fcs::{Fcs, Parameter, TransformType};
use polars::prelude::*;
use std::fs::File;
use std::io::Write;
use std::sync::Arc;
fn create_test_fcs() -> anyhow::Result<Fcs> {
let temp_path = std::env::temp_dir().join("test_fcs_plots.tmp");
{
let mut f = File::create(&temp_path)?;
f.write_all(b"test")?;
}
let mut columns = Vec::new();
columns.push(Column::new(
"FSC-A".into(),
(0..100).map(|i| i as f32 * 100.0).collect::<Vec<f32>>(),
));
columns.push(Column::new(
"SSC-A".into(),
(0..100).map(|i| i as f32 * 50.0).collect::<Vec<f32>>(),
));
columns.push(Column::new(
"FL1-A".into(),
(0..100).map(|i| i as f32 * 10.0).collect::<Vec<f32>>(),
));
columns.push(Column::new(
"Time".into(),
(0..100).map(|i| i as f32).collect::<Vec<f32>>(),
));
let df = DataFrame::new(columns).expect("Failed to create test DataFrame");
use flow_fcs::parameter::ParameterMap;
use flow_fcs::parameter::ParameterProcessing;
let mut params = ParameterMap::default();
params.insert(
"FSC-A".into(),
Parameter::new(&1, "FSC-A", "FSC-A", &TransformType::Linear),
);
params.insert(
"SSC-A".into(),
Parameter::new(&2, "SSC-A", "SSC-A", &TransformType::Linear),
);
params.insert(
"FL1-A".into(),
Parameter::new(
&3,
"FL1-A",
"FL1-A",
&TransformType::Arcsinh { cofactor: 200.0 },
),
);
params.insert(
"Time".into(),
Parameter::new(&4, "Time", "Time", &TransformType::Linear),
);
use flow_fcs::keyword::Keyword;
use flow_fcs::metadata::Metadata;
let mut metadata = Metadata::new();
metadata.keywords.insert(
"$FIL".to_string(),
Keyword::String("test_file.fcs".to_string()),
);
Ok(Fcs {
header: flow_fcs::Header::new(),
metadata,
parameters: params,
data_frame: Arc::new(df),
file_access: flow_fcs::file::AccessWrapper::new(temp_path.to_str().unwrap_or(""))?,
})
}
#[test]
fn test_base_plot_options_default() {
let options = BasePlotOptions::default();
assert_eq!(options.width, 400);
assert_eq!(options.height, 400);
assert_eq!(options.margin, 10);
assert_eq!(options.x_label_area_size, 50);
assert_eq!(options.y_label_area_size, 50);
assert_eq!(options.title, "Density Plot");
}
#[test]
fn test_base_plot_options_builder() {
let options = BasePlotOptions::new()
.width(800)
.height(600)
.margin(20)
.x_label_area_size(60)
.y_label_area_size(70)
.title("Custom Plot".to_string())
.build()
.unwrap();
assert_eq!(options.width, 800);
assert_eq!(options.height, 600);
assert_eq!(options.margin, 20);
assert_eq!(options.x_label_area_size, 60);
assert_eq!(options.y_label_area_size, 70);
assert_eq!(options.title, "Custom Plot");
}
#[test]
fn test_base_plot_options_builder_partial() {
let options = BasePlotOptions::new()
.width(1000)
.height(750)
.build()
.unwrap();
assert_eq!(options.width, 1000);
assert_eq!(options.height, 750);
assert_eq!(options.margin, 10);
assert_eq!(options.title, "Density Plot");
}
#[test]
fn test_axis_options_default() {
let options = AxisOptions::default();
assert_eq!(*options.range.start(), 0.0);
assert_eq!(*options.range.end(), 200_000.0);
assert!(matches!(options.transform, TransformType::Arcsinh { .. }));
assert_eq!(options.label, None);
}
#[test]
fn test_axis_options_builder() {
let options = AxisOptions::new()
.range(0.0..=1000.0)
.transform(TransformType::Linear)
.label("X-Axis".to_string())
.build()
.unwrap();
assert_eq!(*options.range.start(), 0.0);
assert_eq!(*options.range.end(), 1000.0);
assert_eq!(options.transform, TransformType::Linear);
assert_eq!(options.label, Some("X-Axis".to_string()));
}
#[test]
fn test_axis_options_arcsinh_transform() {
let options = AxisOptions::new()
.range(0.0..=5000.0)
.transform(TransformType::Arcsinh { cofactor: 150.0 })
.build()
.unwrap();
assert_eq!(
options.transform,
TransformType::Arcsinh { cofactor: 150.0 }
);
}
#[test]
fn test_density_plot_options_default() {
let options = DensityPlotOptions::default();
assert_eq!(options.base.width, 400);
assert_eq!(options.base.height, 400);
assert_eq!(*options.x_axis.range.start(), 0.0);
assert_eq!(*options.x_axis.range.end(), 200_000.0);
assert!(matches!(options.colormap, ColorMaps::Viridis(_)));
}
#[test]
fn test_density_plot_options_builder() {
let base = BasePlotOptions::new()
.width(800)
.height(600)
.title("Test Plot".to_string())
.build()
.unwrap();
let x_axis = AxisOptions::new()
.range(0.0..=10_000.0)
.transform(TransformType::Linear)
.label("FSC-A".to_string())
.build()
.unwrap();
let y_axis = AxisOptions::new()
.range(0.0..=5_000.0)
.transform(TransformType::Arcsinh { cofactor: 150.0 })
.label("SSC-A".to_string())
.build()
.unwrap();
let options = DensityPlotOptions::new()
.base(base)
.x_axis(x_axis)
.y_axis(y_axis)
.build()
.unwrap();
assert_eq!(options.base.width, 800);
assert_eq!(options.base.height, 600);
assert_eq!(options.base.title, "Test Plot");
assert_eq!(*options.x_axis.range.start(), 0.0);
assert_eq!(*options.x_axis.range.end(), 10_000.0);
assert_eq!(*options.y_axis.range.start(), 0.0);
assert_eq!(*options.y_axis.range.end(), 5_000.0);
assert_eq!(options.x_axis.label, Some("FSC-A".to_string()));
assert_eq!(options.y_axis.label, Some("SSC-A".to_string()));
}
#[test]
fn test_density_plot_options_plot_options_trait() {
let options = DensityPlotOptions::default();
let base = options.base();
assert_eq!(base.width, 400);
assert_eq!(base.height, 400);
}
#[test]
fn test_density_options_from_fcs_fsc_ssc() {
let fcs = create_test_fcs().unwrap();
let x_param = fcs.find_parameter("FSC-A").unwrap();
let y_param = fcs.find_parameter("SSC-A").unwrap();
let builder = density_options_from_fcs(&fcs, x_param, y_param).unwrap();
let options = builder.build().unwrap();
assert_eq!(*options.x_axis.range.start(), 0.0);
assert_eq!(*options.x_axis.range.end(), 200_000.0);
assert_eq!(*options.y_axis.range.start(), 0.0);
assert_eq!(*options.y_axis.range.end(), 200_000.0);
assert_eq!(options.x_axis.transform, TransformType::Linear);
assert_eq!(options.y_axis.transform, TransformType::Linear);
assert_eq!(options.base.title, "test_file.fcs");
}
#[test]
fn test_density_options_from_fcs_fluorescence() {
let fcs = create_test_fcs().unwrap();
let x_param = fcs.find_parameter("FL1-A").unwrap();
let y_param = fcs.find_parameter("FL1-A").unwrap();
let builder = density_options_from_fcs(&fcs, x_param, y_param).unwrap();
let options = builder.build().unwrap();
assert!(*options.x_axis.range.start() >= 0.0);
assert!(*options.x_axis.range.end() <= 1000.0);
assert!(matches!(
options.x_axis.transform,
TransformType::Arcsinh { .. }
));
}
#[test]
fn test_density_options_from_fcs_time() {
let fcs = create_test_fcs().unwrap();
let x_param = fcs.find_parameter("Time").unwrap();
let y_param = fcs.find_parameter("Time").unwrap();
let builder = density_options_from_fcs(&fcs, x_param, y_param).unwrap();
let options = builder.build().unwrap();
assert!(*options.x_axis.range.start() == 0.0);
assert!(*options.x_axis.range.end() >= 99.0);
}
#[test]
fn test_density_options_from_fcs_customization() {
let fcs = create_test_fcs().unwrap();
let x_param = fcs.find_parameter("FSC-A").unwrap();
let y_param = fcs.find_parameter("SSC-A").unwrap();
let builder = density_options_from_fcs(&fcs, x_param, y_param).unwrap();
let options = builder.width(1200).height(900).build().unwrap();
assert_eq!(options.base.width, 1200);
assert_eq!(options.base.height, 900);
}
#[test]
fn test_get_percentile_bounds() {
let values = vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];
let range = crate::get_percentile_bounds(&values, 0.1, 0.9);
assert!(*range.start() <= 20.0);
assert!(*range.end() >= 90.0);
}
#[test]
fn test_get_percentile_bounds_single_value() {
let values = vec![42.0];
let range = crate::get_percentile_bounds(&values, 0.01, 0.99);
assert!(*range.start() <= 42.0);
assert!(*range.end() >= 42.0);
}
#[test]
fn test_get_percentile_bounds_empty() {
let values = vec![];
let range = crate::get_percentile_bounds(&values, 0.01, 0.99);
}
#[test]
fn test_create_axis_specs_linear() {
let range_x = 0.0..=1000.0;
let range_y = 0.0..=500.0;
let x_transform = TransformType::Linear;
let y_transform = TransformType::Linear;
let (x_spec, y_spec) =
crate::create_axis_specs(&range_x, &range_y, &x_transform, &y_transform).unwrap();
assert!(x_spec.start <= 0.0);
assert!(x_spec.end >= 1000.0);
assert!(y_spec.start <= 0.0);
assert!(y_spec.end >= 500.0);
}
#[test]
fn test_create_axis_specs_arcsinh() {
let range_x = 0.0..=1000.0;
let range_y = 0.0..=500.0;
let x_transform = TransformType::Arcsinh { cofactor: 200.0 };
let y_transform = TransformType::Arcsinh { cofactor: 150.0 };
let (x_spec, y_spec) =
crate::create_axis_specs(&range_x, &range_y, &x_transform, &y_transform).unwrap();
assert_eq!(x_spec.start, 0.0);
assert_eq!(x_spec.end, 1000.0);
assert_eq!(y_spec.start, 0.0);
assert_eq!(y_spec.end, 500.0);
}
#[test]
fn test_density_plot_render_empty_data() {
let plot = DensityPlot::new();
let options = DensityPlotOptions::default();
let data: Vec<(f32, f32)> = vec![];
let mut render_config = RenderConfig::default();
let result = plot.render(data.into(), &options, &mut render_config);
assert!(result.is_ok());
}
#[test]
fn test_density_plot_render_small_dataset() {
let plot = DensityPlot::new();
let options = DensityPlotOptions::new()
.width(100)
.height(100)
.build()
.unwrap();
let data: Vec<(f32, f32)> = vec![(100.0, 200.0), (150.0, 250.0), (200.0, 300.0)];
let mut render_config = RenderConfig::default();
let result = plot.render(data.into(), &options, &mut render_config);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
assert_eq!(bytes[0], 0xFF);
assert_eq!(bytes[1], 0xD8);
}
#[test]
fn test_histogram_plot_render_raw_values() {
let plot = HistogramPlot::new();
let options = HistogramPlotOptions::new()
.base(BasePlotOptions::new().width(200).height(200).build().unwrap())
.x_axis(
AxisOptions::new()
.range(0.0..=100.0)
.build()
.unwrap(),
)
.histogram_filled(true)
.num_bins(20usize)
.build()
.unwrap();
let data = HistogramData::from_values(vec![
10.0, 15.0, 20.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0, 50.0,
]);
let mut render_config = RenderConfig::default();
let result = plot.render(data, &options, &mut render_config);
assert!(result.is_ok());
let bytes = result.unwrap();
assert!(!bytes.is_empty());
assert_eq!(bytes[0], 0xFF);
assert_eq!(bytes[1], 0xD8);
}
#[test]
fn test_histogram_plot_render_unfilled() {
let plot = HistogramPlot::new();
let options = HistogramPlotOptions::new()
.base(BasePlotOptions::new().width(200).height(200).build().unwrap())
.x_axis(
AxisOptions::new()
.range(0.0..=50.0)
.build()
.unwrap(),
)
.histogram_filled(false)
.num_bins(10usize)
.build()
.unwrap();
let data = HistogramData::from_values(vec![5.0, 10.0, 10.0, 15.0, 20.0]);
let mut render_config = RenderConfig::default();
let result = plot.render(data, &options, &mut render_config);
assert!(result.is_ok());
}
#[test]
fn test_histogram_plot_render_pre_binned() {
let plot = HistogramPlot::new();
let options = HistogramPlotOptions::new()
.base(BasePlotOptions::new().width(200).height(200).build().unwrap())
.build()
.unwrap();
let data = HistogramData::pre_binned(
vec![0.0, 1.0, 2.0, 3.0, 4.0],
vec![5.0, 10.0, 7.0, 3.0],
)
.unwrap();
let mut render_config = RenderConfig::default();
let result = plot.render(data, &options, &mut render_config);
assert!(result.is_ok());
}
#[test]
fn test_histogram_plot_render_overlaid() {
let plot = HistogramPlot::new();
let options = HistogramPlotOptions::new()
.base(BasePlotOptions::new().width(200).height(200).build().unwrap())
.x_axis(
AxisOptions::new()
.range(0.0..=100.0)
.build()
.unwrap(),
)
.histogram_filled(true)
.scale_to_peak(true)
.baseline_separation(0.2)
.num_bins(15usize)
.build()
.unwrap();
let data = HistogramData::overlaid(vec![
(vec![10.0, 20.0, 20.0, 30.0], 0),
(vec![25.0, 35.0, 35.0, 45.0], 1),
]);
let mut render_config = RenderConfig::default();
let result = plot.render(data, &options, &mut render_config);
assert!(result.is_ok());
}
#[test]
fn test_histogram_plot_render_empty() {
let plot = HistogramPlot::new();
let options = HistogramPlotOptions::new()
.base(BasePlotOptions::new().width(100).height(100).build().unwrap())
.build()
.unwrap();
let data = HistogramData::from_values(vec![]);
let mut render_config = RenderConfig::default();
let result = plot.render(data, &options, &mut render_config);
assert!(result.is_ok());
}
#[test]
fn test_density_plot_render_with_progress_callback() {
let plot = DensityPlot::new();
let options = DensityPlotOptions::new()
.width(200)
.height(200)
.build()
.unwrap();
let data: Vec<(f32, f32)> = (0..1000)
.map(|i| (i as f32 * 10.0, i as f32 * 5.0))
.collect();
let mut progress_calls = 0;
let mut render_config = RenderConfig {
progress: Some(Box::new(move |_info| {
progress_calls += 1;
Ok(())
})),
..Default::default()
};
let result = plot.render(data.into(), &options, &mut render_config);
assert!(result.is_ok());
assert!(progress_calls > 0);
}
#[test]
fn test_transform_application_in_helper() {
let fcs = create_test_fcs().unwrap();
let x_param = fcs.find_parameter("FL1-A").unwrap();
assert!(matches!(x_param.transform, TransformType::Arcsinh { .. }));
let raw_data = fcs.get_parameter_events_slice("FL1-A").unwrap();
assert!(!raw_data.is_empty());
let transformed: Vec<f32> = raw_data
.iter()
.map(|&v| x_param.transform.transform(&v))
.collect();
if raw_data[0] != 0.0 {
assert_ne!(transformed[0], raw_data[0]);
}
}
#[test]
fn test_builder_with_invalid_range() {
let options = AxisOptions::new()
.range(100.0..=50.0) .build();
assert!(options.is_ok());
}
#[test]
fn test_density_plot_options_builder_chaining() {
let options = DensityPlotOptions::new()
.width(800)
.height(600)
.build()
.unwrap();
assert_eq!(options.base.width, 800);
assert_eq!(options.base.height, 600);
}
#[test]
fn test_render_config_default() {
let config = RenderConfig::default();
assert!(config.progress.is_none());
}
#[test]
fn test_density_plot_new() {
let plot = DensityPlot::new();
assert!(std::mem::size_of_val(&plot) == 0); }
#[test]
fn test_calculate_density_per_pixel_batch_empty() {
use crate::density_calc::calculate_density_per_pixel_batch;
use crate::ScatterPlotData;
let requests: Vec<(ScatterPlotData, DensityPlotOptions)> = vec![];
let results = calculate_density_per_pixel_batch(&requests);
assert_eq!(results.len(), 0);
}
#[test]
fn test_calculate_density_per_pixel_batch_single_plot() {
use crate::density_calc::{calculate_density_per_pixel, calculate_density_per_pixel_batch};
use crate::ScatterPlotData;
let options = DensityPlotOptions::new()
.width(100)
.height(100)
.build()
.unwrap();
let data = vec![(100.0, 200.0), (150.0, 250.0), (200.0, 300.0)];
let batch_results = calculate_density_per_pixel_batch(&[(data.clone().into(), options.clone())]);
assert_eq!(batch_results.len(), 1);
let direct_result = calculate_density_per_pixel(&data, 100, 100, &options);
assert_eq!(batch_results[0].len(), direct_result.len());
}
#[test]
fn test_calculate_density_per_pixel_batch_multiple_plots() {
use crate::density_calc::calculate_density_per_pixel_batch;
use crate::ScatterPlotData;
let options1 = DensityPlotOptions::new()
.width(100)
.height(100)
.build()
.unwrap();
let options2 = DensityPlotOptions::new()
.width(200)
.height(200)
.build()
.unwrap();
let data1 = vec![(100.0, 200.0), (150.0, 250.0)];
let data2 = vec![(50.0, 100.0), (75.0, 125.0), (100.0, 150.0)];
let requests = vec![
(data1.into(), options1),
(data2.into(), options2),
];
let results = calculate_density_per_pixel_batch(&requests);
assert_eq!(results.len(), 2);
assert!(!results[0].is_empty());
assert!(!results[1].is_empty());
}
#[test]
fn test_calculate_density_per_pixel_batch_different_sizes() {
use crate::density_calc::calculate_density_per_pixel_batch;
use crate::ScatterPlotData;
let requests: Vec<(ScatterPlotData, DensityPlotOptions)> = vec![
(
vec![(100.0, 200.0)].into(),
DensityPlotOptions::new().width(800).height(600).build().unwrap(),
),
(
vec![(50.0, 100.0)].into(),
DensityPlotOptions::new().width(1024).height(768).build().unwrap(),
),
(
vec![(200.0, 300.0)].into(),
DensityPlotOptions::new().width(640).height(480).build().unwrap(),
),
];
let results = calculate_density_per_pixel_batch(&requests);
assert_eq!(results.len(), 3);
for result in &results {
assert!(!result.is_empty());
}
}
#[test]
fn test_render_batch_empty() {
use crate::ScatterPlotData;
let plot = DensityPlot::new();
let requests: Vec<(ScatterPlotData, DensityPlotOptions)> = vec![];
let mut render_config = RenderConfig::default();
let results = plot.render_batch(&requests, &mut render_config).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_render_batch_single_plot() {
use crate::ScatterPlotData;
let plot = DensityPlot::new();
let options = DensityPlotOptions::new()
.width(100)
.height(100)
.build()
.unwrap();
let data = vec![(100.0, 200.0), (150.0, 250.0)];
let requests = vec![(data.into(), options)];
let mut render_config = RenderConfig::default();
let results = plot.render_batch(&requests, &mut render_config).unwrap();
assert_eq!(results.len(), 1);
assert!(!results[0].is_empty());
assert_eq!(results[0][0], 0xFF);
assert_eq!(results[0][1], 0xD8);
}
#[test]
fn test_render_batch_multiple_plots() {
use crate::ScatterPlotData;
let plot = DensityPlot::new();
let options1 = DensityPlotOptions::new()
.width(100)
.height(100)
.build()
.unwrap();
let options2 = DensityPlotOptions::new()
.width(200)
.height(200)
.build()
.unwrap();
let data1 = vec![(100.0, 200.0), (150.0, 250.0)];
let data2 = vec![(50.0, 100.0), (75.0, 125.0), (100.0, 150.0)];
let requests = vec![
(data1.into(), options1),
(data2.into(), options2),
];
let mut render_config = RenderConfig::default();
let results = plot.render_batch(&requests, &mut render_config).unwrap();
assert_eq!(results.len(), 2);
for result in &results {
assert!(!result.is_empty());
assert_eq!(result[0], 0xFF);
assert_eq!(result[1], 0xD8);
}
}
}