Skip to main content

smlm_hawk_core/
lib.rs

1pub extern crate num_traits;
2extern crate rayon;
3
4#[cfg(feature = "json")]
5extern crate serde;
6#[cfg(feature = "json")]
7extern crate serde_json;
8#[cfg(feature = "json")]
9#[macro_use]
10extern crate serde_derive;
11
12pub mod algorithm;
13pub mod concurrency;
14pub mod indexers;
15pub mod config;
16#[cfg(feature = "json")]
17pub mod io;
18pub mod output_stream;
19pub mod pstreams;
20pub mod stream;
21pub mod striding;
22#[cfg(test)]
23mod test_utils;
24pub mod updaters;
25pub mod ui;
26pub mod utils;
27mod validation;
28
29pub use crate::config::{Config, AlgorithmConfig, NegativeHandling, OutputStyle};
30pub use crate::indexers::{Indexer, IndexFactory, SequentialIndexer, InterleavedIndexer, SeparateIndexer};
31pub use crate::updaters::{SeparateUpdater, AbsUpdater, Updater};
32
33use crate::concurrency::{CancellationToken};
34use crate::validation::{ValidationError};
35use crate::ui::{ProgressUpdater, ThreadSafePU, ModProgressUpdater};
36
37use rayon::prelude::*;
38
39use num_traits::identities::{Zero};
40use num_traits::sign::{Signed};
41//use std::io::{Write};
42use std::ops::{Sub, AddAssign};
43use std::sync::{Mutex, PoisonError};
44
45
46#[derive(Debug, PartialEq)]
47pub enum Error
48{
49	UnknownMemoryOrder,
50	Cancellation,
51	PoisonedMutex(String)
52}
53
54impl Error
55{
56	pub fn unknown_memory_order() -> Self
57	{
58		Self::UnknownMemoryOrder
59	}
60
61	pub fn code(&self) -> i32
62	{
63		match self
64		{
65			Self::UnknownMemoryOrder => 1,
66			Self::Cancellation => 2,
67			Self::PoisonedMutex(_) => 3
68		}
69	}
70
71	pub fn to_string(&self) -> String
72	{
73		match self
74		{
75			Self::UnknownMemoryOrder => String::from("Unknown Memory Order"),
76			Self::Cancellation => String::from("Cancelled"),
77			Self::PoisonedMutex(e) => format!("Poisoned Mutex {}", e.to_string())
78		}
79	}
80}
81
82impl<T> From<PoisonError<T>> for Error
83{
84	fn from(error: PoisonError<T>) -> Self
85	{
86		Self::PoisonedMutex(error.to_string())
87	}
88}
89
90impl TryFrom<i32> for Error
91{
92    type Error = ();
93    fn try_from(error_code: i32) -> Result<Self, Self::Error>
94    {
95        match error_code
96        {
97            1 => Ok(Self::UnknownMemoryOrder),
98            2 => Ok(Self::Cancellation),
99            3 => Ok(Self::PoisonedMutex(String::new())),
100            _ => Err(())
101        }
102    }
103}
104
105pub const fn get_version() -> &'static str
106{
107	env!("CARGO_PKG_VERSION")
108}
109
110#[cfg(feature = "json")]
111pub fn get_metadata(config: &Config) -> String
112{
113	format!("hawk_core:\nVersion: {}\nconfig: {}", get_version(), config.to_io())
114}
115
116pub fn validate(n_data: u64, config: &Config) -> Result<(), ValidationError>
117{
118	validation::validate(n_data, config)
119}
120
121fn hawk_iterator_inner<'a, T, I: Iterator<Item=&'a [T]>, U: Updater, X: Indexer>(input_image: I, config: &AlgorithmConfig, output_image: &mut Vec<Vec<T>>, output_data: &mut Vec<T>, indexer: X) -> ()
122where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + 'a,
123      <T as Sub>::Output : AddAssign
124{
125	for pixel_timeseries in input_image
126	{
127		for x in output_data.iter_mut()
128		{
129			*x = T::zero();
130		}
131		//algorithm::hawk_timeseries(pixel_timeseries, config, &mut output_data);		
132		algorithm::custom_transform_hawk_timeseries::<T, U, &X>(pixel_timeseries, config, output_data, &indexer);
133		output_image.push(output_data.clone());
134	}
135}
136
137fn hawk_iterator_with<'a, T, I: Iterator<Item=&'a [T]>, U: Updater, X: Indexer + IndexFactory>(mut input_image: I, config: &AlgorithmConfig) -> Vec<Vec<T>>
138where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + 'a,
139      <T as Sub>::Output : AddAssign
140{
141	let mut output_image = Vec::new();
142	let output_size : u64;
143	let mut output_data : Vec<T>;
144	let indexer : X;
145
146	match input_image.next()
147	{
148		None => return output_image,
149		Some(pixel_timeseries) =>
150		{
151			output_size = utils::output_size(pixel_timeseries.len() as u64, config);
152			indexer = X::create(pixel_timeseries.len(), config);
153			output_data = vec![T::zero(); output_size as usize];
154			//algorithm::hawk_timeseries(pixel_timeseries, config, &mut output_data);
155			algorithm::custom_transform_hawk_timeseries::<T, U, &X>(pixel_timeseries, config, &mut output_data, &indexer);
156			output_image.push(output_data.clone());
157		}
158	}
159
160	hawk_iterator_inner::<T, I, U, X>(input_image, config, &mut output_image, &mut output_data, indexer);
161	output_image
162}
163
164fn hawk_iterator_with_indexer<'a, T, I: Iterator<Item=&'a [T]>, X: Indexer + IndexFactory>(input_image: I, config: &AlgorithmConfig) -> Vec<Vec<T>>
165where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + 'a,
166      <T as Sub>::Output : AddAssign
167{
168	match config.negative_handling()
169	{
170		NegativeHandling::Absolute => hawk_iterator_with::<T, I, AbsUpdater, X>(input_image, config),
171		NegativeHandling::Separate => hawk_iterator_with::<T, I, SeparateUpdater, SeparateIndexer<X>>(input_image, config)
172	}
173}
174
175pub fn hawk_iterator<'a, T, I: Iterator<Item=&'a [T]>>(input_image: I, config: &AlgorithmConfig) -> Vec<Vec<T>>
176where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + 'a,
177      <T as Sub>::Output : AddAssign
178{
179	match config.output_style()
180	{
181		OutputStyle::Sequential => hawk_iterator_with_indexer::<T, I, SequentialIndexer>(input_image, config),
182		OutputStyle::Interleaved => hawk_iterator_with_indexer::<T, I, InterleavedIndexer>(input_image, config)
183	}
184}
185
186
187fn contiguous_shortcut<T, U: Updater, I: Indexer + IndexFactory + Send + Sync, PU: ProgressUpdater + Send + Sync>(image_array: &[T], output_array: &mut [T], config: &AlgorithmConfig, cancellation_token: CancellationToken, n_pixels: usize, n_timepoints: usize, pu: PU) -> Result<(), Error>
188where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + Send + Sync + std::fmt::Display + std::fmt::Debug,
189{
190    let indexer = I::create(n_timepoints, config);
191    let output_size = utils::output_size(n_timepoints as u64, config) as usize;
192    // println!("output_size: {output_size}");
193
194    let output_image_mutex = Mutex::new(output_array);
195    let pu_mutex = Mutex::new(pu);
196    let _  = (0..n_pixels).into_par_iter().try_for_each(| pixel |
197    {
198    	if cancellation_token.is_cancelled()
199    	{
200    		match pu_mutex.lock()
201    		{
202    			Ok(mut pu) => pu.finish(),
203    			Err(_) => {}
204    		}
205    		return Err(Error::Cancellation)
206    	}
207        let start = pixel * n_timepoints;
208        let end = start + n_timepoints;
209        let mut output_data = vec![T::zero(); output_size];
210        // let stdout = std::io::stdout();
211    	
212    	// {
213    	// 	let mut handle = stdout.lock();
214
215    	// 	let _ = handle.write_all(format!("{pixel} = {start} -> {end}\n").as_bytes());
216        	algorithm::custom_transform_hawk_timeseries::<T, U, &I>(&image_array[start..end], config, &mut output_data, &indexer);
217        // 	let _ = handle.write_all(format!("{pixel} = {start} -> {end} = OK\n").as_bytes());
218    	// }
219        match output_image_mutex.lock()
220        {
221            Ok(mut image) => 
222            {
223                let output_start = pixel * output_size;
224                let output_end = output_start + output_size;
225                // println!("Outputting {pixel} = {output_start} -> {output_end}");
226                image[output_start..output_end].clone_from_slice(&output_data);
227            }
228            Err(e) =>
229            {
230                return Err(Error::PoisonedMutex(e.to_string()));
231            }
232        }
233
234        match pu_mutex.lock()
235        {
236            Ok(mut pu) => 
237            {
238                pu.update_progress(pixel as u64, n_pixels as u64);
239                Ok(())
240            },
241            Err(e) => 
242            {
243                Err(Error::from(e))
244            }
245        }
246    });
247    pu_mutex.into_inner().map(|mut pu| pu.finish()).map_err(Error::from)
248}
249
250
251fn hawk_contiguous_with<T, I: Indexer + IndexFactory + Send + Sync, PU: ProgressUpdater + Send + Sync>(image_array: &[T], output_array: &mut [T], config: &AlgorithmConfig, cancellation_token: CancellationToken, n_pixels: usize, n_timepoints: usize, pu: PU) -> Result<(), Error>
252where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + Send + Sync + std::fmt::Display + std::fmt::Debug,
253{
254    match config.negative_handling()
255    {
256        NegativeHandling::Absolute => contiguous_shortcut::<T, AbsUpdater, I, PU>(image_array, output_array, config, cancellation_token, n_pixels, n_timepoints, pu),
257        NegativeHandling::Separate => contiguous_shortcut::<T, SeparateUpdater, SeparateIndexer<I>, PU>(image_array, output_array, config, cancellation_token, n_pixels, n_timepoints, pu)
258    }
259}
260
261pub fn hawk_contiguous<T, PU: ProgressUpdater + Send + Sync>(image_array: &[T], output_array: &mut [T], config: &AlgorithmConfig, cancellation_token: CancellationToken, n_pixels: usize, n_timepoints: usize, pu: PU) -> Result<(), Error>
262where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + Send + Sync + std::fmt::Display + std::fmt::Debug,
263{
264    match config.output_style()
265    {
266        OutputStyle::Sequential => hawk_contiguous_with::<T, SequentialIndexer, PU>(image_array, output_array, config, cancellation_token, n_pixels, n_timepoints, pu),
267        OutputStyle::Interleaved => hawk_contiguous_with::<T, InterleavedIndexer, PU>(image_array, output_array, config, cancellation_token, n_pixels, n_timepoints, pu)
268    }
269}
270
271fn perform_with<T, U: Updater, X: Indexer + IndexFactory + Sync, PU: ProgressUpdater + Send + Sync>(input_image: &Vec<Vec<T>>, config: &AlgorithmConfig, pu: PU) -> Result<Vec<Vec<T>>, String>
272where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + Send + Sync,
273      <T as Sub>::Output : AddAssign
274{
275	let output_size = utils::output_size(input_image[0].len() as u64, config);
276	let indexer = X::create(input_image[0].len(), config);
277	let output_image = vec![Vec::new(); input_image.len()];
278	let output_image_mutex = Mutex::new(output_image);
279	let mut ts_pu = ThreadSafePU::new(ModProgressUpdater::new(pu, 100));
280
281	let n_pixels = input_image.len();
282	let _ = input_image.par_iter().enumerate().try_for_each(|(pixel, t_series)| 
283	{
284		//ts_ui.thread_safe_show_message(&format!("Operating on: {}\n", pixel));
285		let _ = ts_pu.thread_safe_update_progress(pixel as u64, n_pixels as u64).map_err(|e| e.to_string())?; // upcasting so fine
286		let mut output_data = vec![T::zero(); output_size as usize];
287		//algorithm::hawk_timeseries(t_series, config, &mut output_data);
288		algorithm::custom_transform_hawk_timeseries::<T, U, &X>(t_series, config, &mut output_data, &indexer);
289		match output_image_mutex.lock()
290		{
291			Ok(mut image) => 
292			{
293				image[pixel] = output_data;
294				Ok(())
295			}
296			Err(e) =>
297			{
298				Err(e.to_string())
299			}
300		}
301	})?;
302	ts_pu.finish();
303	match output_image_mutex.into_inner()
304	{
305		Ok(image) => Ok(image),
306		Err(poisoned) => Ok(poisoned.into_inner())
307	}
308}
309
310fn perform_with_indexer<T, I: Indexer + IndexFactory + Sync, PU: ProgressUpdater + Send + Sync>(input_image: &Vec<Vec<T>>, config: &AlgorithmConfig, pu: PU) -> Result<Vec<Vec<T>>, String>
311where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + Send + Sync,
312      <T as Sub>::Output : AddAssign
313{
314	match config.negative_handling()
315	{
316		NegativeHandling::Absolute => perform_with::<T, AbsUpdater, I, PU>(input_image, config, pu),
317		NegativeHandling::Separate => perform_with::<T, SeparateUpdater, SeparateIndexer<I>, PU>(input_image, config, pu)
318	}
319}
320
321pub fn hawk_image_vectors_parallel<T, PU: ProgressUpdater + Send + Sync>(input_image: &Vec<Vec<T>>, config: &AlgorithmConfig, pu: PU) -> Result<Vec<Vec<T>>, String>
322where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug + Send + Sync,
323      <T as Sub>::Output : AddAssign
324{
325	match config.output_style()
326	{
327		OutputStyle::Sequential => perform_with_indexer::<T, SequentialIndexer, PU>(input_image, config, pu),
328		OutputStyle::Interleaved => perform_with_indexer::<T, InterleavedIndexer, PU>(input_image, config, pu)
329	}
330}
331
332pub fn hawk_timeseries<T>(input: &[T], output: &mut [T], config: &AlgorithmConfig) -> ()
333where T: Sub<Output=T> + Zero + AddAssign + Signed + Copy + PartialOrd + std::fmt::Display + std::fmt::Debug,
334{
335	// pretty sure we can replace this with:
336	// hawk_contiguous(input, output, config, 1, input.len())
337	let images = hawk_iterator(std::iter::once(input), config);
338	if images.is_empty()
339	{
340		return
341	}
342	output.clone_from_slice(&images[0])
343}
344
345
346#[cfg(test)]
347mod tests 
348{
349	use super::*;
350
351	#[test]
352	fn hawk_timeseries_test() 
353	{
354		let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
355        	let mut output_data = [0; 19];
356        	hawk_timeseries(&input, &mut output_data, &AlgorithmConfig::default());
357        	let expected = [1, 1, 1, 1, 1, 1, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 16, 16, 16];
358        	assert_eq!(output_data, expected);
359	}
360
361	#[cfg(feature = "json")]
362	#[test]
363	fn metadata_test()
364	{
365		let config = Config::default();
366		let expected = r#"hawk_core:
367Version: 0.1.0
368config: {"Version1":{"threading":"Parallel","memory":"Contiguous","run_style":"InMemory","algorithm":{"n_levels":3,"output_style":"Sequential","negative_handling":"Absolute"},"validation":null}}"#;
369		assert_eq!(get_metadata(&config).as_str(), expected)
370	}
371}