1use crate::error::{AlgorithmError, Result};
32
33pub fn histogram_u8(data: &[u8], bins: usize) -> Result<Vec<u32>> {
44 if bins == 0 {
45 return Err(AlgorithmError::InvalidParameter {
46 parameter: "bins",
47 message: "Number of bins must be greater than zero".to_string(),
48 });
49 }
50
51 if data.is_empty() {
52 return Err(AlgorithmError::EmptyInput {
53 operation: "histogram_u8",
54 });
55 }
56
57 let mut histogram = vec![0u32; bins];
58
59 if bins == 256 {
61 const LANES: usize = 16;
62 let chunks = data.len() / LANES;
63
64 for i in 0..chunks {
66 let start = i * LANES;
67 let end = start + LANES;
68
69 for &value in &data[start..end] {
70 histogram[value as usize] += 1;
71 }
72 }
73
74 let remainder_start = chunks * LANES;
76 for &value in &data[remainder_start..] {
77 histogram[value as usize] += 1;
78 }
79 } else {
80 let scale = bins as f32 / 256.0;
82
83 for &value in data {
84 let bin = ((f32::from(value) * scale) as usize).min(bins - 1);
85 histogram[bin] += 1;
86 }
87 }
88
89 Ok(histogram)
90}
91
92pub fn histogram_u16(data: &[u16], bins: usize) -> Result<Vec<u32>> {
103 if bins == 0 {
104 return Err(AlgorithmError::InvalidParameter {
105 parameter: "bins",
106 message: "Number of bins must be greater than zero".to_string(),
107 });
108 }
109
110 if data.is_empty() {
111 return Err(AlgorithmError::EmptyInput {
112 operation: "histogram_u16",
113 });
114 }
115
116 let mut histogram = vec![0u32; bins];
117 let scale = bins as f32 / 65536.0;
118
119 const LANES: usize = 8;
120 let chunks = data.len() / LANES;
121
122 for i in 0..chunks {
124 let start = i * LANES;
125 let end = start + LANES;
126
127 for &value in &data[start..end] {
128 let bin = ((f32::from(value) * scale) as usize).min(bins - 1);
129 histogram[bin] += 1;
130 }
131 }
132
133 let remainder_start = chunks * LANES;
135 for &value in &data[remainder_start..] {
136 let bin = ((f32::from(value) * scale) as usize).min(bins - 1);
137 histogram[bin] += 1;
138 }
139
140 Ok(histogram)
141}
142
143pub fn histogram_f32(
156 data: &[f32],
157 bins: usize,
158 min_value: f32,
159 max_value: f32,
160) -> Result<Vec<u32>> {
161 if bins == 0 {
162 return Err(AlgorithmError::InvalidParameter {
163 parameter: "bins",
164 message: "Number of bins must be greater than zero".to_string(),
165 });
166 }
167
168 if data.is_empty() {
169 return Err(AlgorithmError::EmptyInput {
170 operation: "histogram_f32",
171 });
172 }
173
174 if min_value >= max_value {
175 return Err(AlgorithmError::InvalidParameter {
176 parameter: "range",
177 message: format!("Invalid range: min={min_value}, max={max_value}"),
178 });
179 }
180
181 let mut histogram = vec![0u32; bins];
182 let range = max_value - min_value;
183 let scale = (bins - 1) as f32 / range;
184
185 const LANES: usize = 8;
186 let chunks = data.len() / LANES;
187
188 for i in 0..chunks {
190 let start = i * LANES;
191 let end = start + LANES;
192
193 for &value in &data[start..end] {
194 if value >= min_value && value <= max_value {
195 let bin = ((value - min_value) * scale) as usize;
196 let bin = bin.min(bins - 1);
197 histogram[bin] += 1;
198 }
199 }
200 }
201
202 let remainder_start = chunks * LANES;
204 for &value in &data[remainder_start..] {
205 if value >= min_value && value <= max_value {
206 let bin = ((value - min_value) * scale) as usize;
207 let bin = bin.min(bins - 1);
208 histogram[bin] += 1;
209 }
210 }
211
212 Ok(histogram)
213}
214
215pub fn cumulative_histogram(histogram: &[u32]) -> Result<Vec<u32>> {
221 if histogram.is_empty() {
222 return Err(AlgorithmError::EmptyInput {
223 operation: "cumulative_histogram",
224 });
225 }
226
227 let mut cumulative = Vec::with_capacity(histogram.len());
228 let mut sum = 0u32;
229
230 for &count in histogram {
231 sum = sum.saturating_add(count);
232 cumulative.push(sum);
233 }
234
235 Ok(cumulative)
236}
237
238pub fn equalize_histogram(data: &[u8], output: &mut [u8]) -> Result<()> {
246 if data.len() != output.len() {
247 return Err(AlgorithmError::InvalidParameter {
248 parameter: "buffers",
249 message: format!(
250 "Buffer size mismatch: input={}, output={}",
251 data.len(),
252 output.len()
253 ),
254 });
255 }
256
257 if data.is_empty() {
258 return Err(AlgorithmError::EmptyInput {
259 operation: "equalize_histogram",
260 });
261 }
262
263 let histogram = histogram_u8(data, 256)?;
265
266 let cdf = cumulative_histogram(&histogram)?;
268
269 let cdf_min = cdf.iter().copied().find(|&x| x > 0).unwrap_or(0);
271 let total_pixels = data.len() as u32;
272
273 let mut lut = [0u8; 256];
275 for (i, &cdf_val) in cdf.iter().enumerate() {
276 if cdf_val > 0 {
277 let normalized = ((cdf_val - cdf_min) as f32 / (total_pixels - cdf_min) as f32) * 255.0;
278 lut[i] = normalized.round() as u8;
279 }
280 }
281
282 const LANES: usize = 16;
284 let chunks = data.len() / LANES;
285
286 for i in 0..chunks {
287 let start = i * LANES;
288 let end = start + LANES;
289
290 for j in start..end {
291 output[j] = lut[data[j] as usize];
292 }
293 }
294
295 let remainder_start = chunks * LANES;
297 for i in remainder_start..data.len() {
298 output[i] = lut[data[i] as usize];
299 }
300
301 Ok(())
302}
303
304pub fn histogram_quantile(histogram: &[u32], quantile: f32) -> Result<usize> {
315 if histogram.is_empty() {
316 return Err(AlgorithmError::EmptyInput {
317 operation: "histogram_quantile",
318 });
319 }
320
321 if !(0.0..=1.0).contains(&quantile) {
322 return Err(AlgorithmError::InvalidParameter {
323 parameter: "quantile",
324 message: format!("Quantile must be in [0, 1], got {quantile}"),
325 });
326 }
327
328 let total: u64 = histogram.iter().map(|&x| u64::from(x)).sum();
329 if total == 0 {
330 return Err(AlgorithmError::InsufficientData {
331 operation: "histogram_quantile",
332 message: "Histogram is empty (all bins are zero)".to_string(),
333 });
334 }
335
336 let target = (total as f32 * quantile) as u64;
337 let mut cumulative = 0u64;
338
339 for (i, &count) in histogram.iter().enumerate() {
340 cumulative += u64::from(count);
341 if cumulative >= target {
342 return Ok(i);
343 }
344 }
345
346 Ok(histogram.len() - 1)
347}
348
349pub fn histogram_quantiles(histogram: &[u32], quantiles: &[f32]) -> Result<Vec<usize>> {
355 if histogram.is_empty() {
356 return Err(AlgorithmError::EmptyInput {
357 operation: "histogram_quantiles",
358 });
359 }
360
361 for &q in quantiles {
362 if !(0.0..=1.0).contains(&q) {
363 return Err(AlgorithmError::InvalidParameter {
364 parameter: "quantile",
365 message: format!("All quantiles must be in [0, 1], got {q}"),
366 });
367 }
368 }
369
370 let total: u64 = histogram.iter().map(|&x| u64::from(x)).sum();
371 if total == 0 {
372 return Err(AlgorithmError::InsufficientData {
373 operation: "histogram_quantiles",
374 message: "Histogram is empty (all bins are zero)".to_string(),
375 });
376 }
377
378 let mut sorted_quantiles: Vec<(usize, f32)> = quantiles.iter().copied().enumerate().collect();
380 sorted_quantiles.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
381
382 let mut results = vec![0usize; quantiles.len()];
383 let mut cumulative = 0u64;
384 let mut q_idx = 0;
385
386 for (bin, &count) in histogram.iter().enumerate() {
387 cumulative += u64::from(count);
388
389 while q_idx < sorted_quantiles.len() {
390 let (orig_idx, q) = sorted_quantiles[q_idx];
391 let target = (total as f32 * q) as u64;
392
393 if cumulative >= target {
394 results[orig_idx] = bin;
395 q_idx += 1;
396 } else {
397 break;
398 }
399 }
400
401 if q_idx >= sorted_quantiles.len() {
402 break;
403 }
404 }
405
406 Ok(results)
407}
408
409#[derive(Debug, Clone)]
411pub struct HistogramStats {
412 pub count: u64,
414 pub mean: f64,
416 pub std_dev: f64,
418 pub min_bin: usize,
420 pub max_bin: usize,
422 pub median_bin: usize,
424}
425
426pub fn histogram_statistics(histogram: &[u32]) -> Result<HistogramStats> {
432 if histogram.is_empty() {
433 return Err(AlgorithmError::EmptyInput {
434 operation: "histogram_statistics",
435 });
436 }
437
438 let total: u64 = histogram.iter().map(|&x| u64::from(x)).sum();
439 if total == 0 {
440 return Err(AlgorithmError::InsufficientData {
441 operation: "histogram_statistics",
442 message: "Histogram is empty (all bins are zero)".to_string(),
443 });
444 }
445
446 let min_bin = histogram.iter().position(|&x| x > 0).unwrap_or(0);
448 let max_bin = histogram
449 .iter()
450 .rposition(|&x| x > 0)
451 .unwrap_or(histogram.len() - 1);
452
453 let mut sum = 0.0;
455 for (bin, &count) in histogram.iter().enumerate() {
456 sum += bin as f64 * f64::from(count);
457 }
458 let mean = sum / total as f64;
459
460 let mut variance_sum = 0.0;
462 for (bin, &count) in histogram.iter().enumerate() {
463 let diff = bin as f64 - mean;
464 variance_sum += diff * diff * f64::from(count);
465 }
466 let std_dev = (variance_sum / total as f64).sqrt();
467
468 let median_bin = histogram_quantile(histogram, 0.5)?;
470
471 Ok(HistogramStats {
472 count: total,
473 mean,
474 std_dev,
475 min_bin,
476 max_bin,
477 median_bin,
478 })
479}
480
481pub fn clahe(
496 data: &[u8],
497 output: &mut [u8],
498 width: usize,
499 height: usize,
500 tile_size: usize,
501 _clip_limit: f32,
502) -> Result<()> {
503 if data.len() != width * height || output.len() != width * height {
504 return Err(AlgorithmError::InvalidParameter {
505 parameter: "buffers",
506 message: format!(
507 "Buffer size mismatch: input={}, output={}, expected={}",
508 data.len(),
509 output.len(),
510 width * height
511 ),
512 });
513 }
514
515 if tile_size == 0 || tile_size > width.min(height) {
516 return Err(AlgorithmError::InvalidParameter {
517 parameter: "tile_size",
518 message: format!("Invalid tile size: {tile_size}"),
519 });
520 }
521
522 equalize_histogram(data, output)?;
525
526 Ok(())
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 #[test]
534 fn test_histogram_u8_uniform() {
535 let data = vec![128u8; 1000];
536 let hist = histogram_u8(&data, 256)
537 .expect("Histogram computation should succeed for uniform data");
538
539 assert_eq!(hist[128], 1000);
540 assert_eq!(hist.iter().sum::<u32>(), 1000);
541 }
542
543 #[test]
544 fn test_histogram_u8_full_range() {
545 let data: Vec<u8> = (0..=255).collect();
546 let hist =
547 histogram_u8(&data, 256).expect("Histogram computation should succeed for full range");
548
549 for count in &hist {
550 assert_eq!(*count, 1);
551 }
552 }
553
554 #[test]
555 fn test_cumulative_histogram() {
556 let histogram = vec![10, 20, 30, 40];
557 let cumulative = cumulative_histogram(&histogram)
558 .expect("Cumulative histogram computation should succeed");
559
560 assert_eq!(cumulative, vec![10, 30, 60, 100]);
561 }
562
563 #[test]
564 fn test_histogram_quantile_median() {
565 let histogram = vec![0, 0, 50, 0, 50, 0, 0];
566 let median =
567 histogram_quantile(&histogram, 0.5).expect("Median computation should succeed");
568
569 assert!(median == 2 || median == 4);
570 }
571
572 #[test]
573 fn test_histogram_quantiles() {
574 let histogram = vec![10, 20, 30, 40];
575 let quantiles = vec![0.0, 0.25, 0.5, 0.75, 1.0];
576 let results = histogram_quantiles(&histogram, &quantiles)
577 .expect("Multiple quantiles computation should succeed");
578
579 assert_eq!(results.len(), 5);
580 assert_eq!(results[0], 0); assert_eq!(results[4], 3); }
583
584 #[test]
585 fn test_histogram_statistics() {
586 let histogram = vec![10, 20, 30, 20, 10];
587 let stats = histogram_statistics(&histogram)
588 .expect("Histogram statistics computation should succeed");
589
590 assert_eq!(stats.count, 90);
591 assert_eq!(stats.min_bin, 0);
592 assert_eq!(stats.max_bin, 4);
593 assert_eq!(stats.median_bin, 2);
594 }
595
596 #[test]
597 fn test_equalize_histogram() {
598 let data = vec![0u8, 0, 255, 255];
599 let mut output = vec![0u8; 4];
600
601 equalize_histogram(&data, &mut output).expect("Histogram equalization should succeed");
602
603 assert!(output[0] < output[2]);
605 }
606
607 #[test]
608 fn test_empty_histogram() {
609 let data: Vec<u8> = vec![];
610 let result = histogram_u8(&data, 256);
611
612 assert!(result.is_err());
613 }
614
615 #[test]
616 fn test_invalid_quantile() {
617 let histogram = vec![10, 20, 30];
618 let result = histogram_quantile(&histogram, 1.5);
619
620 assert!(result.is_err());
621 }
622}