sicd_rs/
lib.rs

1//! Sensor Independent Complex Data support
2//!
3//! The primary interface for general sicd reading is `read_sicd`.
4//!
5//! It is a future goal to have functions for each version, but for now a single
6//! function call and `match` statement are used.
7use memmap2::Mmap;
8use ndarray::{azip, par_azip, Array2, ArrayView2};
9use quick_xml::DeError;
10use std::fs::File;
11use std::path::Path;
12use std::slice::from_raw_parts;
13use std::str::{from_utf8, FromStr, Utf8Error};
14use zerocopy::{BE, F32};
15
16use num_complex::{Complex, Complex32};
17use quick_xml::de::from_str;
18use serde::Deserialize;
19use thiserror::Error;
20
21use nitf_rs::{Nitf, NitfError};
22
23pub mod dep;
24pub mod v1_3_0;
25
26/// Construct a [Sicd] object from a file `path`.
27///
28/// This is specifically for cases where the version of the Sicd is not known
29/// and makes use of several `enums` to parse the data.
30///
31/// # Example
32/// ```no_run
33/// use std::path::Path;
34/// use sicd_rs::SicdVersion;
35///
36/// let sicd_path = Path::new("../example.nitf");
37/// let sicd = sicd_rs::read_sicd(sicd_path).unwrap();
38/// // Then use convenience methods provided by SicdMeta enum, or match off of version
39/// let meta = sicd.meta.get_v1_meta();
40///
41/// ```
42///
43pub fn read_sicd(path: &Path) -> Result<Sicd, SicdError> {
44    let file = File::open(path)?;
45    Sicd::from_file(file)
46}
47
48#[derive(Error, Debug)]
49pub enum SicdError {
50    /// "unknown sicd version {}"
51    #[error("unknown sicd version {0}")]
52    VersionError(String),
53    /// "metadata for version {} is not implemented"
54    #[error("metadata for version {0} is not implemented")]
55    Unimpl(String),
56    #[error("file does not appear to be a SICD")]
57    NotASicd,
58    // Wrappers for built in errors
59    #[error(transparent)]
60    IOError(#[from] std::io::Error),
61    #[error(transparent)]
62    NitfError(#[from] NitfError),
63    #[error(transparent)]
64    UTF8(#[from] Utf8Error),
65    #[error(transparent)]
66    DESER(#[from] DeError),
67}
68
69/// SICD file structure
70///
71// TODO: Implement printing (Debug, Display?)
72pub struct Sicd<'a> {
73    /// Nitf file object and associated metadata
74    pub nitf: Nitf,
75    /// Parsed SICD xml metadata
76    pub meta: SicdMeta,
77    /// SICD Version
78    pub version: SicdVersion,
79    /// Image data from Nitf Image segements
80    pub image_data: Vec<ImageData<'a>>,
81}
82
83/// Image data structure. Currently only implements Complex<F32<BE>> (e.g. big-endian complex float) data type
84#[derive(Debug)]
85pub struct ImageData<'a> {
86    /// Raw byte array
87    pub array: ArrayView2<'a, Complex<F32<BE>>>,
88    /// Need to hold onto this to access data
89    _mmap: Mmap,
90}
91
92impl<'a> ImageData<'a> {
93    fn initialize(mmap: Mmap, n_rows: usize, n_cols: usize) -> Self {
94        let byte_slice_len = mmap.len();
95        let new_size = byte_slice_len / std::mem::size_of::<Complex<F32<BE>>>();
96        let f32_ptr = mmap.as_ptr() as *const Complex<F32<BE>>;
97        let float_slice = unsafe { from_raw_parts(f32_ptr, new_size) };
98        let array = ArrayView2::from_shape((n_rows, n_cols), float_slice).unwrap();
99        Self { array, _mmap: mmap }
100    }
101}
102pub trait ToNative {
103    /// Performs allocation of a native-aligned Complex32 array
104    fn to_native(&self) -> Array2<Complex32>;
105    /// Performs allocation of a native-aligned Complex32 array using `rayon`
106    fn par_to_native(&self) -> Array2<Complex32>;
107}
108
109impl<'a> ToNative for ArrayView2<'a, Complex<F32<BE>>> {
110    fn to_native(&self) -> Array2<Complex32> {
111        let mut out = Array2::from_elem((self.nrows(), self.ncols()), Complex32::default());
112        azip!((be in self, ne in &mut out) {
113           ne.re = be.re.get();
114           ne.im = be.im.get();
115        });
116        out
117    }
118
119    fn par_to_native(&self) -> Array2<Complex32> {
120        let mut out = Array2::from_elem((self.nrows(), self.ncols()), Complex32::default());
121        par_azip!((be in self, ne in &mut out) {
122           ne.re = be.re.get();
123           ne.im = be.im.get();
124        });
125        out
126    }
127}
128
129#[derive(Debug, Deserialize, PartialEq)]
130pub enum SicdVersion {
131    V0_3_1,
132    V0_4_0,
133    V0_4_1,
134    V0_5_0,
135    V1_0_0,
136    V1_0_1,
137    V1_1_0,
138    V1_2_0,
139    V1_2_1,
140    V1_3_0,
141}
142
143impl FromStr for SicdVersion {
144    type Err = SicdError;
145    fn from_str(s: &str) -> Result<Self, Self::Err> {
146        match s.split("urn:SICD:").collect::<String>().as_str() {
147            "0.3.1" => Ok(SicdVersion::V0_3_1),
148            "0.4.0" => Ok(SicdVersion::V0_4_0),
149            "0.4.1" => Ok(SicdVersion::V0_4_1),
150            "0.5.0" => Ok(SicdVersion::V0_5_0),
151            "1.0.0" => Ok(SicdVersion::V1_0_0),
152            "1.0.1" => Ok(SicdVersion::V1_0_1),
153            "1.1.0" => Ok(SicdVersion::V1_1_0),
154            "1.2.0" => Ok(SicdVersion::V1_2_0),
155            "1.2.1" => Ok(SicdVersion::V1_2_1),
156            "1.3.0" => Ok(SicdVersion::V1_3_0),
157            _ => Err(SicdError::VersionError(s.to_string())),
158        }
159    }
160}
161
162#[derive(Debug)]
163pub enum SicdMeta {
164    V0_3_1, // Not implemented
165    V0_4_0(dep::v0_4_0::SicdMeta),
166    V0_4_1, // Not implemented
167    V0_5_0(dep::v0_5_0::SicdMeta),
168    V1(v1_3_0::SicdMeta),
169}
170
171impl SicdMeta {
172    pub fn get_v0_3_1_meta(self) -> SicdError {
173        SicdError::Unimpl("0.3.1".to_string())
174    }
175    pub fn get_v0_4_0_meta(self) -> Option<dep::v0_4_0::SicdMeta> {
176        match self {
177            Self::V0_4_0(meta) => Some(meta),
178            _ => None,
179        }
180    }
181    pub fn get_v0_4_1_meta(self) -> SicdError {
182        SicdError::Unimpl("0.4.1".to_string())
183    }
184    pub fn get_v0_5_0_meta(self) -> Option<dep::v0_5_0::SicdMeta> {
185        match self {
186            Self::V0_5_0(meta) => Some(meta),
187            _ => None,
188        }
189    }
190    pub fn get_v1_meta(self) -> Option<v1_3_0::SicdMeta> {
191        match self {
192            Self::V1(meta) => Some(meta),
193            _ => None,
194        }
195    }
196}
197impl<'a> Sicd<'a> {
198    pub fn from_file(mut file: File) -> Result<Self, SicdError> {
199        let nitf = Nitf::from_reader(&mut file)?;
200        if nitf.nitf_header.numdes.val == 0 {
201            return Err(SicdError::NotASicd)
202        }
203        let dex_data = nitf.data_extension_segments[0].get_data_map(&mut file)?;
204        let sicd_str = from_utf8(&dex_data[..])?;
205        let (version, meta) = parse_sicd(sicd_str)?;
206
207        let image_data: Vec<_> = nitf
208            .image_segments
209            .iter()
210            .map(|seg| {
211                ImageData::initialize(
212                    seg.get_data_map(&mut file).unwrap(),
213                    seg.header.nrows.val as usize,
214                    seg.header.ncols.val as usize,
215                )
216            })
217            .collect();
218
219        Ok(Self {
220            nitf,
221            meta,
222            version,
223            image_data,
224        })
225    }
226}
227
228#[derive(Debug, Deserialize, PartialEq, Clone)]
229struct VersionGetter {
230    #[serde(rename = "@xmlns")]
231    pub version: String,
232}
233
234fn parse_sicd(sicd_str: &str) -> Result<(SicdVersion, SicdMeta), SicdError> {
235    // This feels bad
236    let tmp: VersionGetter = from_str(sicd_str)?;
237    let sicd_version = SicdVersion::from_str(&tmp.version)?;
238    use SicdError::Unimpl;
239    match sicd_version {
240        SicdVersion::V0_3_1 => Err(Unimpl("V0_3_1".to_string())),
241        SicdVersion::V0_4_0 => Ok((SicdVersion::V0_4_0, SicdMeta::V0_4_0(from_str(sicd_str)?))),
242        SicdVersion::V0_4_1 => Err(Unimpl("V0_4_1".to_string())),
243        SicdVersion::V0_5_0 => Ok((SicdVersion::V0_5_0, SicdMeta::V0_5_0(from_str(sicd_str)?))),
244        // Don't need to worry about anything else, all versions past 1.0 are backwards compatible
245        other => Ok((other, SicdMeta::V1(from_str(sicd_str)?))),
246    }
247}