use astrors_fork::fits;
use astrors_fork::io;
use astrors_fork::io::hdulist::*;
use astrors_fork::io::header::*;
use ndarray::Array2;
use polars::{lazy::prelude::*, prelude::*};
use rayon::prelude::*;
use std::fs;
use std::ops::Mul;
pub enum ExperimentType {
Xrr,
Xrs,
Other,
}
impl ExperimentType {
pub fn from_str(exp_type: &str) -> Result<Self, &str> {
match exp_type.to_lowercase().as_str() {
"xrr" => Ok(ExperimentType::Xrr),
"xrs" => Ok(ExperimentType::Xrs),
"other" => Ok(ExperimentType::Other),
_ => Err("Invalid experiment type"),
}
}
pub fn get_keys(&self) -> Vec<HeaderValue> {
match self {
ExperimentType::Xrr => vec![
HeaderValue::SampleTheta,
HeaderValue::CCDTheta,
HeaderValue::BeamlineEnergy,
HeaderValue::BeamCurrent,
HeaderValue::EPUPolarization,
HeaderValue::HorizontalExitSlitSize,
HeaderValue::HigherOrderSuppressor,
HeaderValue::Exposure,
],
ExperimentType::Xrs => vec![HeaderValue::BeamlineEnergy],
ExperimentType::Other => vec![],
}
}
}
pub enum HeaderValue {
SampleTheta,
CCDTheta,
BeamlineEnergy,
EPUPolarization,
BeamCurrent,
HorizontalExitSlitSize,
HigherOrderSuppressor,
Exposure,
}
impl HeaderValue {
pub fn unit(&self) -> &str {
match self {
HeaderValue::SampleTheta => "[deg]",
HeaderValue::CCDTheta => "[deg]",
HeaderValue::BeamlineEnergy => "[eV]",
HeaderValue::BeamCurrent => "[mA]",
HeaderValue::EPUPolarization => "[deg]",
HeaderValue::HorizontalExitSlitSize => "[um]",
HeaderValue::HigherOrderSuppressor => "[mm]",
HeaderValue::Exposure => "[s]",
}
}
pub fn hdu(&self) -> &str {
match self {
HeaderValue::SampleTheta => "Sample Theta",
HeaderValue::CCDTheta => "CCD Theta",
HeaderValue::BeamlineEnergy => "Beamline Energy",
HeaderValue::BeamCurrent => "Beam Current",
HeaderValue::EPUPolarization => "EPU Polarization",
HeaderValue::HorizontalExitSlitSize => "Horizontal Exit Slit Size",
HeaderValue::HigherOrderSuppressor => "Higher Order Suppressor",
HeaderValue::Exposure => "EXPOSURE",
}
}
pub fn name(&self) -> &str {
match self {
HeaderValue::SampleTheta => "Sample Theta [deg]",
HeaderValue::CCDTheta => "CCD Theta [deg]",
HeaderValue::BeamlineEnergy => "Beamline Energy [eV]",
HeaderValue::BeamCurrent => "Beam Current [mA]",
HeaderValue::EPUPolarization => "EPU Polarization [deg]",
HeaderValue::HorizontalExitSlitSize => "Horizontal Exit Slit Size [um]",
HeaderValue::HigherOrderSuppressor => "Higher Order Suppressor [mm]",
HeaderValue::Exposure => "EXPOSURE [s]",
}
}
pub fn round(&self, value: f64) -> f64 {
match self {
HeaderValue::Exposure => (value * 1000.0).round() / 1000.0,
HeaderValue::HigherOrderSuppressor => (value * 100.0).round() / 100.0,
HeaderValue::HorizontalExitSlitSize => (value * 10.0).round() / 10.0,
_ => value,
}
}
}
pub struct FitsLoader {
pub path: String,
pub hdul: HDUList,
}
impl FitsLoader {
pub fn new(path: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let hdul = fits::fromfile(path)?;
Ok(FitsLoader {
path: path.to_string(),
hdul,
})
}
pub fn get_card(&self, hdu: usize, card_name: &str) -> Result<card::Card, ()> {
match &self.hdul.hdus[hdu] {
io::hdulist::HDU::Primary(hdu) => Ok(hdu.header.get_card(card_name).cloned().unwrap()),
io::hdulist::HDU::Image(hdu) => Ok(hdu.header.get_card(card_name).cloned().unwrap()),
_ => Err(()),
}
}
pub fn get_scan_num(&self) -> i32 {
self.path
.rsplit('/')
.next()
.and_then(|filename| filename.split('-').last())
.and_then(|scan_id| scan_id.split('.').next())
.and_then(|scan_id| scan_id.trim_start_matches('0').parse::<i32>().ok())
.unwrap_or(0)
}
pub fn get_all_cards(&self) -> Vec<card::Card> {
match &self.hdul.hdus[0] {
io::hdulist::HDU::Primary(hdu) => {
hdu.header.iter().cloned().collect::<Vec<card::Card>>()
}
_ => vec![],
}
}
fn get_data(
&self,
data: &io::hdus::image::ImageData,
) -> Result<(Vec<i64>, Vec<u32>), Box<dyn std::error::Error + Send + Sync>> {
let bzero = self.get_card(2, "BZERO").unwrap().value.as_int().unwrap();
println!("{:?}", bzero);
match data {
io::hdus::image::ImageData::I16(image) => {
let flat_data: Vec<i64> =
image.iter().map(|&x| i64::from(x as i64 + bzero)).collect();
let shape = image.dim();
Ok((flat_data, vec![shape[0] as u32, shape[1] as u32]))
}
_ => Err("Unsupported image data type".into()),
}
}
pub fn get_image(
&self,
) -> Result<(Vec<i64>, Vec<u32>), Box<dyn std::error::Error + Send + Sync>> {
match &self.hdul.hdus[2] {
io::hdulist::HDU::Image(i_hdu) => self.get_data(&i_hdu.data),
_ => Err("Image HDU not found".into()),
}
}
pub fn to_polars(
&self,
keys: &Vec<HeaderValue>,
) -> Result<DataFrame, Box<dyn std::error::Error + Send + Sync>> {
let mut s_vec = if keys.is_empty() {
self.get_all_cards()
.iter()
.map(|card| {
let name = card.keyword.as_str();
let value = card.value.as_float().unwrap_or(0.0);
Series::new(name.into(), vec![value])
})
.collect::<Vec<_>>()
} else {
keys.iter()
.filter_map(|key| {
let val = self.get_card(0, key.hdu()).unwrap();
Some(Series::new(
key.name().into(),
vec![key.round(val.value.as_float().unwrap_or(0.0))],
))
})
.collect::<Vec<_>>()
};
let (image, size) = match self.get_image() {
Ok(data) => data,
Err(e) => return Err(e),
};
s_vec.push(Series::new("Scan ID".into(), vec![self.get_scan_num()]));
s_vec.push(vec_i64("Raw", image));
s_vec.push(vec_u32("Raw Shape", size));
DataFrame::new(s_vec).map_err(From::from)
}
}
fn vec_i64(name: &str, img: Vec<i64>) -> Series {
let new_series = [img.iter().collect::<Series>()];
Series::new(name.into(), new_series)
}
fn vec_u32(name: &str, img: Vec<u32>) -> Series {
let new_series = [img.iter().collect::<Series>()];
Series::new(name.into(), new_series)
}
pub struct ExperimentLoader {
pub dir: String,
pub ccd_files: Vec<FitsLoader>,
pub experiment_type: ExperimentType,
}
impl ExperimentLoader {
pub fn new(
dir: &str,
experiment_type: ExperimentType,
) -> Result<Self, Box<dyn std::error::Error>> {
let ccd_files: Vec<_> = fs::read_dir(dir)?
.filter_map(Result::ok)
.filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("fits"))
.collect();
let ccd_files = ccd_files
.par_iter() .map(|entry| FitsLoader::new(entry.path().to_str().unwrap()))
.collect::<Result<Vec<_>, Box<dyn std::error::Error + Send + Sync>>>();
let ccd_files = match ccd_files {
Ok(ccd_files) => ccd_files,
Err(e) => return Err(e),
};
Ok(ExperimentLoader {
dir: dir.to_string(),
ccd_files,
experiment_type,
})
}
pub fn to_polars(&self) -> Result<DataFrame, Box<dyn std::error::Error>> {
let keys = self.experiment_type.get_keys();
let dfs = self
.ccd_files
.par_iter()
.map(|ccd| ccd.to_polars(&keys))
.collect::<Result<Vec<_>, _>>();
let mut dfs = match dfs {
Ok(dfs) => dfs,
Err(e) => return Err(e),
};
let mut df = dfs.pop().ok_or("No data found")?;
for mut d in dfs {
df.vstack_mut(&mut d)?;
}
Ok(post_process(df))
}
}
pub fn post_process(df: DataFrame) -> DataFrame {
let h = physical_constants::PLANCK_CONSTANT_IN_EV_PER_HZ;
let c = physical_constants::SPEED_OF_LIGHT_IN_VACUUM * 1e10;
println!("{:?}", h * c);
let lz = df
.clone()
.lazy()
.sort(["Scan ID"], Default::default())
.with_column(
col("Beamline Energy [eV]")
.pow(-1)
.mul(lit(h * c))
.alias("Lambda [Å]"),
);
let angle_offset = lz
.clone()
.filter(col("Sample Theta [deg]").eq(0.0))
.last()
.select(&[col("CCD Theta [deg]"), col("Sample Theta [deg]")])
.with_column(
as_struct(vec![col("Sample Theta [deg]"), col("CCD Theta [deg]")])
.map(
move |s| {
let struc = s.struct_()?;
let th_series = struc.field_by_name("Sample Theta [deg]")?;
let theta = th_series.f64()?;
let ccd_th_series = struc.field_by_name("CCD Theta [deg]")?;
let ccd_theta = ccd_th_series.f64()?;
let out: Float64Chunked = theta
.into_iter()
.zip(ccd_theta.iter())
.map(|(theta, ccd_theta)| match (theta, ccd_theta) {
(Some(theta), Some(ccd_theta)) => {
Some(theta_offset(theta, ccd_theta))
}
_ => Some(0.0),
})
.collect();
Ok(Some(out.into_series()))
},
GetOutput::from_type(DataType::Float64),
)
.alias("Theta Offset [deg]"),
)
.select(&[col("Theta Offset [deg]")])
.collect()
.unwrap()
.get(0)
.unwrap()[0]
.try_extract::<f64>()
.unwrap();
let lz = lz
.with_column(lit(angle_offset).alias("Theta Offset [deg]"))
.with_column(
as_struct(vec![col("Sample Theta [deg]"), col("Lambda [Å]")])
.map(
move |s| {
let struc = s.struct_()?;
let th_series = struc.field_by_name("Sample Theta [deg]")?;
let theta = th_series.f64()?;
let lam_series = struc.field_by_name("Lambda [Å]")?;
let lam = lam_series.f64()?;
let out: Float64Chunked = theta
.into_iter()
.zip(lam.iter())
.map(|(theta, lam)| match (theta, lam) {
(Some(theta), Some(lam)) => Some(q(lam, theta, angle_offset)),
_ => None,
})
.collect();
Ok(Some(out.into_series()))
},
GetOutput::from_type(DataType::Float64),
)
.alias("Q [Å⁻¹]"),
);
lz.collect().unwrap()
}
pub fn get_image(image_data: &[u16], shape: (usize, usize)) -> Result<Array2<u16>, PolarsError> {
let image_array = Array2::from_shape_vec(shape, image_data.to_vec())
.map_err(|_| PolarsError::ComputeError("Invalid image data".into()))?;
Ok(image_array)
}
pub fn read_fits(file_path: &str) -> Result<DataFrame, Box<dyn std::error::Error>> {
let loader = match FitsLoader::new(file_path) {
Ok(loader) => loader,
Err(e) => return Err(e),
};
let df = match loader.to_polars(&vec![]) {
Ok(df) => df,
Err(e) => return Err(e),
};
Ok(df)
}
pub fn read_experiment(dir: &str, exp_type: &str) -> Result<DataFrame, Box<dyn std::error::Error>> {
let exp = ExperimentType::from_str(exp_type)?;
let df = ExperimentLoader::new(dir, exp)?.to_polars()?;
Ok(df)
}
pub fn simple_update(df: &mut DataFrame, dir: &str) -> Result<(), Box<dyn std::error::Error>> {
let ccd_files: Vec<_> = fs::read_dir(dir)?
.filter_map(Result::ok)
.filter(|entry| entry.path().extension().and_then(|ext| ext.to_str()) == Some("fits"))
.collect();
let not_loaded = ccd_files.len() as isize - df.height() as isize;
if not_loaded == 0 {
return Ok(());
} else if not_loaded < 0 {
return Err("Files out of sync with loaded data, Restart".into());
}
let ccd_files = ccd_files[..not_loaded as usize]
.par_iter() .map(|entry| FitsLoader::new(entry.path().to_str().unwrap()))
.collect::<Result<Vec<_>, Box<dyn std::error::Error + Send + Sync>>>();
let ccd_files = match ccd_files {
Ok(ccd_files) => ccd_files,
Err(e) => return Err(e),
};
let mut new_df = ExperimentLoader {
dir: dir.to_string(),
ccd_files,
experiment_type: ExperimentType::Xrr,
}
.to_polars()?;
df.vstack_mut(&mut new_df)?;
Ok(())
}
pub fn theta_offset(theta: f64, ccd_theta: f64) -> f64 {
let ccd_theta = ccd_theta / 2.0;
ccd_theta - theta
}
pub fn q(lam: f64, theta: f64, angle_offset: f64) -> f64 {
let theta = theta - angle_offset;
match 4.0 * std::f64::consts::PI * theta.to_radians().sin() / lam {
q if q < 0.0 => 0.0,
q => q,
}
}
pub fn load() {
let test_path = "/home/hduva/projects/pyref-ccd/test/";
let data = read_experiment(test_path, "xrr").unwrap();
println!("{:?}", data);
}