Skip to content

Commit

Permalink
Add fonctions to get the sum of a multi-order map in a given S-MOC
Browse files Browse the repository at this point in the history
  • Loading branch information
fxpineau committed May 7, 2024
1 parent 52c52cf commit e668d8f
Show file tree
Hide file tree
Showing 12 changed files with 521 additions and 103 deletions.
10 changes: 10 additions & 0 deletions src/deser/fits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use crate::elemset::{
range::MocRanges,
};
use crate::idx::Idx;
use crate::moc::range::op::convert::convert_to_u64;
use crate::moc::{
cell::CellMOC,
range::{RangeMOC, RangeMocIter},
Expand Down Expand Up @@ -103,6 +104,15 @@ impl<T: Idx, Q: MocQty<T>, R: BufRead> MocType<T, Q, R> {
MocType::Cells(cells) => cells.into_cell_moc_iter().ranges().into_range_moc(),
}
}
/// WARNING: you have to be sure that 'P' is the same qty as 'Q'!!
pub fn collect_to_u64<P: MocQty<u64>>(self) -> RangeMOC<u64, P> {
match self {
MocType::Ranges(ranges) => convert_to_u64::<T, Q, _, P>(ranges).into_range_moc(),
MocType::Cells(cells) => {
convert_to_u64::<T, Q, _, P>(cells.into_cell_moc_iter().ranges()).into_range_moc()
}
}
}
}

#[derive(Debug)]
Expand Down
287 changes: 202 additions & 85 deletions src/deser/fits/multiordermap.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
use std::f64::consts::PI;
use std::io::{BufRead, BufReader, Read, Seek};
use std::{
f64::consts::PI,
io::{BufRead, BufReader, Read, Seek},
};

use byteorder::{BigEndian, ReadBytesExt};

use crate::deser::{
fits::{
common::{
check_keyword_and_parse_uint_val, check_keyword_and_val, consume_primary_hdu,
next_36_chunks_of_80_bytes,
use crate::{
deser::{
fits::{
common::{
check_keyword_and_parse_uint_val, check_keyword_and_val, consume_primary_hdu,
next_36_chunks_of_80_bytes,
},
error::FitsError,
keywords::{FitsCard, MocKeywords, MocKeywordsMap, MocOrder, Ordering},
},
error::FitsError,
keywords::{FitsCard, MocKeywords, MocKeywordsMap, MocOrder, Ordering},
gz::{is_gz, uncompress},
},
gz::{is_gz, uncompress},
elem::valuedcell::valued_cells_to_moc_with_opt,
idx::Idx,
moc::range::RangeMOC,
mom::{HpxMOMIterator, HpxMomIter},
qty::Hpx,
};
use crate::elem::valuedcell::valued_cells_to_moc_with_opt;
use crate::idx::Idx;
use crate::moc::range::RangeMOC;
use crate::qty::Hpx;

/// We expect the FITS file to be a BINTABLE containing a multi-order map.
/// To be fast (in execution and development), we start by a non-flexible approach in which we
Expand Down Expand Up @@ -92,80 +97,27 @@ pub fn from_fits_multiordermap<R: Read + Seek>(
}

fn from_fits_multiordermap_internal<R: BufRead>(
mut reader: R,
reader: R,
cumul_from: f64,
cumul_to: f64,
asc: bool,
strict: bool,
no_split: bool,
reverse_decent: bool,
) -> Result<RangeMOC<u64, Hpx<u64>>, FitsError> {
let mut header_block = [b' '; 2880];
consume_primary_hdu(&mut reader, &mut header_block)?;
// Read the extention HDU
let mut it80 = next_36_chunks_of_80_bytes(&mut reader, &mut header_block)?;
// See Table 10 and 17 in https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf
check_keyword_and_val(it80.next().unwrap(), b"XTENSION", b"'BINTABLE'")?;
check_keyword_and_val(it80.next().unwrap(), b"BITPIX ", b"8")?;
check_keyword_and_val(it80.next().unwrap(), b"NAXIS ", b"2")?;
let n_bytes_per_row = check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"NAXIS1 ")?;
let n_rows = check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"NAXIS2 ")?;
check_keyword_and_val(it80.next().unwrap(), b"PCOUNT ", b"0")?;
check_keyword_and_val(it80.next().unwrap(), b"GCOUNT ", b"1")?;
let _n_cols = check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"TFIELDS ")?;
check_keyword_and_val(it80.next().unwrap(), b"TTYPE1 ", b"'UNIQ '")?;
check_keyword_and_val(it80.next().unwrap(), b"TFORM1 ", b"'K '")?;
check_keyword_and_val(it80.next().unwrap(), b"TTYPE2 ", b"'PROBDENSITY'")?;
check_keyword_and_val(it80.next().unwrap(), b"TFORM2 ", b"'D '")?;
// nbits = |BITPIX|xGCOUNTx(PCOUNT+NAXIS1xNAXIS2x...xNAXISn)
// In our case (bitpix = 8, GCOUNT = 1, PCOUNT = 0) => nbytes = n_cells * size_of(T)
// let data_size n_bytes as usize * n_cells as usize; // N_BYTES ok since BITPIX = 8
// Read MOC keywords
let mut moc_kws = MocKeywordsMap::new();
'hr: loop {
for kw_record in &mut it80 {
// Parse only MOC related keywords and ignore others
if let Some(mkw) = MocKeywords::is_moc_kw(kw_record) {
if let Some(previous_mkw) = moc_kws.insert(mkw?) {
// A FITS keyword MUST BE uniq (I may be more relax here, taking the last one and not complaining)
// return Err(FitsError::MultipleKeyword(previous_mkw.keyword_str().to_string()))
eprintln!("WARNING: Keyword '{}' found more than once in a same HDU! We use the first occurrence.", previous_mkw.keyword_str());
moc_kws.insert(previous_mkw);
}
// else keyword added without error
} else if &kw_record[0..4] == b"END " {
break 'hr;
}
}
// Read next 2880 bytes
it80 = next_36_chunks_of_80_bytes(&mut reader, &mut header_block)?;
}
// Check header params
moc_kws.check_pixtype()?;
moc_kws.check_ordering(Ordering::Nuniq)?;
moc_kws.check_coordsys()?;
// - get MOC depth
let depth_max = match moc_kws.get::<MocOrder>() {
Some(MocKeywords::MOCOrder(MocOrder { depth })) => *depth,
_ => return Err(FitsError::MissingKeyword(MocOrder::keyword_string())),
};
// Read data
let n_byte_skip = (n_bytes_per_row - 16) as usize;
let mut sink = vec![0; n_byte_skip];
let area_per_cell = (PI / 3.0) / (1_u64 << (depth_max << 1) as u32) as f64; // = 4pi / (12*4^depth)
let mut uniq_val_dens: Vec<(u64, f64, f64)> = Vec::with_capacity(n_rows as usize);
for _ in 0..n_rows {
let uniq = u64::read::<_, BigEndian>(&mut reader)?;
let dens = reader.read_f64::<BigEndian>()?;
let (cdepth, _ipix) = Hpx::<u64>::from_uniq_hpx(uniq);
let n_sub_cells = (1_u64 << (((depth_max - cdepth) << 1) as u32)) as f64;
uniq_val_dens.push((uniq, dens * n_sub_cells * area_per_cell, dens));
/*{
// Discard remaining row bytes
io::copy(&mut reader.by_ref().take(n_byte_skip), &mut io::sink());
}*/
reader.read_exact(&mut sink)?;
}
let data_it = MultiOrderMapIterator::open(reader)?;
let depth_max = data_it.depth_max;
let area_per_cell = data_it.area_per_cell;
let uniq_val_dens = data_it
.map(|res_uniq_dens| {
res_uniq_dens.map(|(uniq, dens)| {
let (cdepth, _ipix) = Hpx::<u64>::from_uniq_hpx(uniq);
let n_sub_cells = (1_u64 << (((depth_max - cdepth) << 1) as u32)) as f64;
let value = dens * n_sub_cells * area_per_cell;
(uniq, value, dens)
})
})
.collect::<Result<Vec<(u64, f64, f64)>, FitsError>>()?;
// Build the MOC
let ranges = valued_cells_to_moc_with_opt(
depth_max,
Expand All @@ -180,13 +132,157 @@ fn from_fits_multiordermap_internal<R: BufRead>(
Ok(RangeMOC::new(depth_max, ranges))
}

/// Returns the sum of the multi-order map values associated with cells inside the given MOC.
/// If a cell is partially covered by the MOC, we apply on the value a factor equals to the ratio
/// of the cell area covered by the MOC over the total cell area.
pub fn sum_from_fits_multiordermap<R: Read + Seek>(
mut reader: BufReader<R>,
moc: &RangeMOC<u64, Hpx<u64>>,
) -> Result<f64, FitsError> {
if is_gz(&mut reader)? {
let reader = uncompress(reader);
sum_from_fits_multiordermap_internal(reader, moc)
} else {
sum_from_fits_multiordermap_internal(reader, moc)
}
}

fn sum_from_fits_multiordermap_internal<R: BufRead>(
reader: R,
moc: &RangeMOC<u64, Hpx<u64>>,
) -> Result<f64, FitsError> {
let data_it = MultiOrderMapIterator::open(reader)?;
let depth_max = data_it.depth_max;
let area_per_cell = data_it.area_per_cell;
let mom = data_it
.map(|res_uniq_dens| {
res_uniq_dens.map(|(uniq, dens)| {
let (cdepth, _ipix) = Hpx::<u64>::from_uniq_hpx(uniq);
let n_sub_cells = (1_u64 << (((depth_max - cdepth) << 1) as u32)) as f64;
let value = dens * n_sub_cells * area_per_cell;
(uniq, value)
})
})
.collect::<Result<Vec<(u64, f64)>, FitsError>>()?;
let mom_it = HpxMomIter::<u64, Hpx<u64>, f64, _>::new(mom.into_iter());
Ok(mom_it.sum_values_in_hpxmoc(moc))
}

struct MultiOrderMapIterator<R: BufRead> {
/// Reader
reader: R,
/// MOM depth
depth_max: u8,
/// Area of a cell at the max MOM depth
area_per_cell: f64,
/// Number of rows to be read
n_rows: u64,
/// Number of rows already returned
n_rows_consumed: u64,
/// Used to consume row bytes we are not interested in
sink: Vec<u8>,
}

impl<R: BufRead> MultiOrderMapIterator<R> {
fn open(mut reader: R) -> Result<Self, FitsError> {
let mut header_block = [b' '; 2880];
consume_primary_hdu(&mut reader, &mut header_block)?;
// Read the extention HDU
let mut it80 = next_36_chunks_of_80_bytes(&mut reader, &mut header_block)?;
// See Table 10 and 17 in https://fits.gsfc.nasa.gov/standard40/fits_standard40aa-le.pdf
check_keyword_and_val(it80.next().unwrap(), b"XTENSION", b"'BINTABLE'")?;
check_keyword_and_val(it80.next().unwrap(), b"BITPIX ", b"8")?;
check_keyword_and_val(it80.next().unwrap(), b"NAXIS ", b"2")?;
let n_bytes_per_row =
check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"NAXIS1 ")?;
let n_rows = check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"NAXIS2 ")?;
check_keyword_and_val(it80.next().unwrap(), b"PCOUNT ", b"0")?;
check_keyword_and_val(it80.next().unwrap(), b"GCOUNT ", b"1")?;
let _n_cols = check_keyword_and_parse_uint_val::<u64>(it80.next().unwrap(), b"TFIELDS ")?;
check_keyword_and_val(it80.next().unwrap(), b"TTYPE1 ", b"'UNIQ '")?;
check_keyword_and_val(it80.next().unwrap(), b"TFORM1 ", b"'K '")?;
check_keyword_and_val(it80.next().unwrap(), b"TTYPE2 ", b"'PROBDENSITY'")?;
check_keyword_and_val(it80.next().unwrap(), b"TFORM2 ", b"'D '")?;
// nbits = |BITPIX|xGCOUNTx(PCOUNT+NAXIS1xNAXIS2x...xNAXISn)
// In our case (bitpix = 8, GCOUNT = 1, PCOUNT = 0) => nbytes = n_cells * size_of(T)
// let data_size n_bytes as usize * n_cells as usize; // N_BYTES ok since BITPIX = 8
// Read MOC keywords
let mut moc_kws = MocKeywordsMap::new();
'hr: loop {
for kw_record in &mut it80 {
// Parse only MOC related keywords and ignore others
if let Some(mkw) = MocKeywords::is_moc_kw(kw_record) {
if let Some(previous_mkw) = moc_kws.insert(mkw?) {
// A FITS keyword MUST BE uniq (I may be more relax here, taking the last one and not complaining)
// return Err(FitsError::MultipleKeyword(previous_mkw.keyword_str().to_string()))
eprintln!("WARNING: Keyword '{}' found more than once in a same HDU! We use the first occurrence.", previous_mkw.keyword_str());
moc_kws.insert(previous_mkw);
}
// else keyword added without error
} else if &kw_record[0..4] == b"END " {
break 'hr;
}
}
// Read next 2880 bytes
it80 = next_36_chunks_of_80_bytes(&mut reader, &mut header_block)?;
}
// Check header params
moc_kws.check_pixtype()?;
moc_kws.check_ordering(Ordering::Nuniq)?;
moc_kws.check_coordsys()?;
// - get MOC depth
let depth_max = match moc_kws.get::<MocOrder>() {
Some(MocKeywords::MOCOrder(MocOrder { depth })) => Ok(*depth),
_ => Err(FitsError::MissingKeyword(MocOrder::keyword_string())),
}?;
let n_byte_skip = (n_bytes_per_row - 16) as usize;
let sink = vec![0; n_byte_skip];
let area_per_cell = (PI / 3.0) / (1_u64 << (depth_max << 1) as u32) as f64; // = 4pi / (12*4^depth)
Ok(Self {
reader,
depth_max,
area_per_cell,
n_rows,
n_rows_consumed: 0,
sink,
})
}
}
impl<R: BufRead> Iterator for MultiOrderMapIterator<R> {
type Item = Result<(u64, f64), FitsError>;

fn next(&mut self) -> Option<Self::Item> {
if self.n_rows_consumed < self.n_rows {
self.n_rows_consumed += 1;
Some(
u64::read::<_, BigEndian>(&mut self.reader)
.and_then(|uniq| {
self.reader.read_f64::<BigEndian>().and_then(|dens| {
self
.reader
.read_exact(&mut self.sink)
.map(|()| (uniq, dens))
})
})
.map_err(FitsError::Io),
)
} else {
None
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
let n_rows_remaining = (self.n_rows - self.n_rows_consumed) as usize;
(n_rows_remaining, Some(n_rows_remaining))
}
}

#[cfg(test)]
mod tests {

use super::from_fits_multiordermap;
use std::fs::File;
use std::io::BufReader;
use std::path::PathBuf;
use std::{fs::File, io::BufReader, path::PathBuf};

use super::{from_fits_multiordermap, sum_from_fits_multiordermap};

#[test]
fn test_mutliordermap() {
Expand All @@ -208,4 +304,25 @@ mod tests {
}
}
}

#[test]
fn test_mutliordermap_sum() {
let path_buf1 = PathBuf::from("resources/LALInference.multiorder.fits");
let path_buf2 = PathBuf::from("../resources/LALInference.multiorder.fits");
let file = File::open(&path_buf1)
.or_else(|_| File::open(&path_buf2))
.unwrap();
let reader = BufReader::new(file);
// First create MOC
let moc = from_fits_multiordermap(reader, 0.0, 0.9, false, true, true, false).unwrap();

// Then compute the sum inside the MOC (should be 90%, i.e, 0.9).
let file = File::open(&path_buf1)
.or_else(|_| File::open(&path_buf2))
.unwrap();
let reader = BufReader::new(file);
let sum = sum_from_fits_multiordermap(reader, &moc).unwrap();
println!("value: {}", sum);
assert!((0.8999..0.9001).contains(&sum));
}
}
2 changes: 1 addition & 1 deletion src/deser/fits/skymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ fn from_fits_skymap_internal<R: BufRead>(
// TODO: ALSO SUPPORT B or 1B and TTYPE = M (for MASK)!!
Err(FitsError::UnexpectedValue(
String::from("TFORM1"),
String::from("'D', '1D', 'E', '1E' or '1024E'"),
String::from("['D', '1D', 'E', '1E' or '1024E']"),
String::from(tform1),
))
}?;
Expand Down
2 changes: 1 addition & 1 deletion src/deser/stcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ impl SpaceVisitor for Stc2Moc {
self,
interval: &PositionInterval,
) -> Result<Self::Value, Self::Error> {
// We use compound visitor onyl to check interval parameters
// We use compound visitor only to check interval parameters
self.new_compound_visitor(&interval.pre, &interval.post)?;
let depth = self.depth;
let corners = interval
Expand Down
7 changes: 7 additions & 0 deletions src/elemset/range/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ impl<'a, T: Idx, Q: MocQty<T>> SNORanges<'a, T> for MocRanges<T, Q> {
self.0.contains_range(x)
}

fn range_fraction(&self, x: &Range<T>) -> f64 {
self.0.range_fraction(x)
}
fn intersects(&self, rhs: &Self) -> bool {
self.0.intersects(&rhs.0)
}
Expand Down Expand Up @@ -292,6 +295,10 @@ impl<'a, T: Idx, Q: MocQty<T>> SNORanges<'a, T> for BorrowedMocRanges<'a, T, Q>
self.0.contains_range(x)
}

fn range_fraction(&self, x: &Range<T>) -> f64 {
self.0.range_fraction(x)
}

fn intersects(&self, rhs: &Self) -> bool {
self.0.intersects(&rhs.0)
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub mod mocranges2d;

pub mod deser;

pub mod mom;

#[cfg(feature = "storage")]
pub mod storage;
pub mod utils;
Expand Down
Loading

0 comments on commit e668d8f

Please sign in to comment.