use bitflags::bitflags;
use ndarray::{Array2, ArrayView2, ArrayViewMut2, Zip};
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MaskClasses: u8 {
const CLOUD = 0b0000_0001;
const CLOUD_SHADOW = 0b0000_0010;
const WATER = 0b0000_0100;
const SNOW = 0b0000_1000;
const ALL = Self::CLOUD.bits() | Self::CLOUD_SHADOW.bits()
| Self::WATER.bits() | Self::SNOW.bits();
}
}
impl Default for MaskClasses {
fn default() -> Self {
Self::ALL
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QaType {
S2SCL,
LandsatPixelQA,
}
pub fn decode_qa_mask(
qa: ArrayView2<i16>,
qa_type: QaType,
classes: MaskClasses,
) -> Array2<bool> {
match qa_type {
QaType::S2SCL => qa.mapv(|v| s2_scl_is_masked(v, classes)),
QaType::LandsatPixelQA => qa.mapv(|v| landsat_qa_is_masked(v, classes)),
}
}
pub fn apply_mask<T: Copy>(result: ArrayViewMut2<T>, mask: ArrayView2<bool>, na: T) {
Zip::from(result)
.and(mask)
.for_each(|r, m| {
if *m {
*r = na;
}
});
}
fn s2_scl_is_masked(value: i16, classes: MaskClasses) -> bool {
match value {
0 | 1 | 254 | 255 => true,
3 => classes.contains(MaskClasses::CLOUD_SHADOW),
4 | 5 => false,
6 => classes.contains(MaskClasses::WATER),
7 => false,
8 => false,
9 | 10 | 11 => classes.contains(MaskClasses::CLOUD),
12 => classes.contains(MaskClasses::SNOW),
_ => false,
}
}
fn landsat_qa_is_masked(value: i16, classes: MaskClasses) -> bool {
if value < 0 {
return true; }
let cloud_shadow = (value >> 2) & 0x3;
let water = (value >> 4) & 0x3;
let cloud = (value >> 6) & 0x3;
let cloud_conf = (value >> 8) & 0x3;
let snow = (value >> 10) & 0x3;
let is_cloud = cloud >= 2 || cloud_conf >= 2;
let is_shadow = cloud_shadow >= 2;
let is_water = water >= 2;
let is_snow = snow >= 2;
(classes.contains(MaskClasses::CLOUD) && is_cloud)
|| (classes.contains(MaskClasses::CLOUD_SHADOW) && is_shadow)
|| (classes.contains(MaskClasses::WATER) && is_water)
|| (classes.contains(MaskClasses::SNOW) && is_snow)
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::Array2;
#[test]
fn s2_scl_clear_vegetation_not_masked() {
let qa = Array2::from_shape_vec((2, 2), vec![4, 5, 4, 5]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::ALL);
assert!(mask.iter().all(|m| !m));
}
#[test]
fn s2_scl_cloud_masked() {
let qa = Array2::from_shape_vec((1, 3), vec![9, 10, 11]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::ALL);
assert!(mask.iter().all(|m| *m));
}
#[test]
fn s2_scl_shadow_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![3]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::ALL);
assert!(mask.iter().all(|m| *m));
}
#[test]
fn s2_scl_water_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![6]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::ALL);
assert!(mask.iter().all(|m| *m));
}
#[test]
fn s2_scl_snow_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![12]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::ALL);
assert!(mask.iter().all(|m| *m));
}
#[test]
fn s2_scl_selective_cloud_only() {
let qa = Array2::from_shape_vec((1, 4), vec![3, 6, 10, 12]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::CLOUD);
assert!(!mask[[0, 0]]); assert!(!mask[[0, 1]]); assert!(mask[[0, 2]]); assert!(!mask[[0, 3]]); }
#[test]
fn s2_scl_nodata_always_masked() {
let qa = Array2::from_shape_vec((1, 4), vec![0, 1, 254, 255]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::S2SCL, MaskClasses::empty());
assert!(mask.iter().all(|m| *m));
}
#[test]
fn landsat_qa_clear_not_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![8192]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::ALL);
assert!(!mask[[0, 0]]);
}
#[test]
fn landsat_qa_cloud_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![128]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::ALL);
assert!(mask[[0, 0]]);
}
#[test]
fn landsat_qa_shadow_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![8]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::ALL);
assert!(mask[[0, 0]]);
}
#[test]
fn landsat_qa_water_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![32]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::ALL);
assert!(mask[[0, 0]]);
}
#[test]
fn landsat_qa_snow_masked() {
let qa = Array2::from_shape_vec((1, 1), vec![2048]).unwrap();
let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::ALL);
assert!(mask[[0, 0]]);
}
#[test]
fn landsat_qa_selective_water_only() {
let qa = Array2::from_shape_vec((1, 3), vec![8, 32, 128]).unwrap(); let mask = decode_qa_mask(qa.view(), QaType::LandsatPixelQA, MaskClasses::WATER);
assert!(!mask[[0, 0]]); assert!(mask[[0, 1]]); assert!(!mask[[0, 2]]); }
#[test]
fn apply_mask_sets_na_values() {
let mut data = Array2::from_shape_vec((2, 2), vec![100, 200, 300, 400]).unwrap();
let mask = Array2::from_shape_vec((2, 2), vec![true, false, false, true]).unwrap();
apply_mask(data.view_mut(), mask.view(), -9999i16);
assert_eq!(data[[0, 0]], -9999);
assert_eq!(data[[0, 1]], 200);
assert_eq!(data[[1, 0]], 300);
assert_eq!(data[[1, 1]], -9999);
}
}