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