Add the ability to save a file that can be read by Mantid - #108
AnthonyLim23 wants to merge 37 commits into
Conversation
alexhroom
left a comment
There was a problem hiding this comment.
very cursory initial look - i'll test properly once you've added system tests.
as a general note, the docstring style is inconsistent: in some places it has
Parameters
----------
and in some places it has
## Parameters
both of which are not consistent to the docstring style used in the rest of the code (numpy style)
There was a problem hiding this comment.
can this file start with a docstring/comment explaining what it does and what it's for?
|
|
||
| def test_Mantid_install(): | ||
| """ | ||
| Lets first test that Mantid works |
There was a problem hiding this comment.
| Lets first test that Mantid works | |
| Test that Mantid has correctly installed. |
Function docstrings should be imperatives.
eeb5d75 to
c1923ac
Compare
|
👋 Hi, @AnthonyLim23, Conflicts have been detected against the base branch. Please rebase your branch against the base branch. This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there. |
|
I still need to update the docs and mantid test |
1d93326 to
e4e455c
Compare
| @@ -0,0 +1,3 @@ | |||
| //! Code for saving outputs to file. | |||
There was a problem hiding this comment.
could this be more specific?
| continue; | ||
| } else if let Ok(_dataset) = sample.dataset(&name) { | ||
| if name == "thickness" { | ||
| let default = Array1::from_shape_vec(1, vec![0.0f32]).unwrap(); |
There was a problem hiding this comment.
| let default = Array1::from_shape_vec(1, vec![0.0f32]).unwrap(); | |
| let default = Array1::from_shape_vec(1, vec![0.0f32])?; |
replace unwraps with ? in functions that return Result
| create_default_dataset(&source_parent.group(name).unwrap(), dest, name, shapes)?; | ||
| Ok(()) | ||
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_ok() { | ||
| // if dataset exists in both files | ||
| println!("{} dataset already exists", name); | ||
| let src_ds = source_parent.dataset(name)?; | ||
| let dst_ds = dest.dataset(name)?; | ||
| for att_name in src_ds.attr_names()? { | ||
| println!("copy attribute {}", att_name); | ||
| copy_attr(&src_ds, &dst_ds, &att_name)?; | ||
| } | ||
| Ok(()) | ||
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_err() { | ||
| // if dataset exists in source but not in destination | ||
| println!("copy dataset {}", name); | ||
| source_parent.dataset(name)?.copy_to(dest, name)?; | ||
| Ok(()) | ||
| } else if source_parent.group(name).is_ok() && dest.group(name).is_ok() { | ||
| // if group exists in both files | ||
| println!("group {} exists in both files, going deeper", name); | ||
| for member in source_parent.group(name).unwrap().member_names()? { | ||
| set_defaults( | ||
| &source_parent.group(name).unwrap(), | ||
| &dest.group(name).unwrap(), | ||
| member.as_str(), |
There was a problem hiding this comment.
| create_default_dataset(&source_parent.group(name).unwrap(), dest, name, shapes)?; | |
| Ok(()) | |
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_ok() { | |
| // if dataset exists in both files | |
| println!("{} dataset already exists", name); | |
| let src_ds = source_parent.dataset(name)?; | |
| let dst_ds = dest.dataset(name)?; | |
| for att_name in src_ds.attr_names()? { | |
| println!("copy attribute {}", att_name); | |
| copy_attr(&src_ds, &dst_ds, &att_name)?; | |
| } | |
| Ok(()) | |
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_err() { | |
| // if dataset exists in source but not in destination | |
| println!("copy dataset {}", name); | |
| source_parent.dataset(name)?.copy_to(dest, name)?; | |
| Ok(()) | |
| } else if source_parent.group(name).is_ok() && dest.group(name).is_ok() { | |
| // if group exists in both files | |
| println!("group {} exists in both files, going deeper", name); | |
| for member in source_parent.group(name).unwrap().member_names()? { | |
| set_defaults( | |
| &source_parent.group(name).unwrap(), | |
| &dest.group(name).unwrap(), | |
| member.as_str(), | |
| create_default_dataset(&source_parent.group(name)?, dest, name, shapes)?; | |
| Ok(()) | |
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_ok() { | |
| // if dataset exists in both files | |
| println!("{} dataset already exists", name); | |
| let src_ds = source_parent.dataset(name)?; | |
| let dst_ds = dest.dataset(name)?; | |
| for att_name in src_ds.attr_names()? { | |
| println!("copy attribute {}", att_name); | |
| copy_attr(&src_ds, &dst_ds, &att_name)?; | |
| } | |
| Ok(()) | |
| } else if source_parent.dataset(name).is_ok() && dest.dataset(name).is_err() { | |
| // if dataset exists in source but not in destination | |
| println!("copy dataset {}", name); | |
| source_parent.dataset(name)?.copy_to(dest, name)?; | |
| Ok(()) | |
| } else if source_parent.group(name).is_ok() && dest.group(name).is_ok() { | |
| // if group exists in both files | |
| println!("group {} exists in both files, going deeper", name); | |
| for member in source_parent.group(name)?.member_names()? { | |
| set_defaults( | |
| &source_parent.group(name)?, | |
| &dest.group(name)?, | |
| member.as_str(), |
replace unwraps with ? in functions that return Result
| let name = default.name().split("dataset_").last().unwrap().to_string(); | ||
| let key: String = default | ||
| .dataset("shape") | ||
| .unwrap() | ||
| .read_scalar::<hdf5::types::VarLenUnicode>() | ||
| .unwrap() | ||
| .as_str() | ||
| .to_string(); | ||
| let len: usize = *shapes.get(&key).unwrap(); | ||
| let dtype: &hdf5::types::VarLenUnicode = | ||
| &default.dataset("dtype").unwrap().read_scalar().unwrap(); | ||
| if dtype.as_str() == "int32" { | ||
| let default_value = default.dataset("default").unwrap().read_scalar::<i32>()?; | ||
| if let Err(e) = add_array(dest, &Array1::from_elem(len, default_value), &name) { | ||
| eprintln!("error: {e}"); | ||
| eprintln!("chain: {e:?}"); | ||
| }; | ||
| } else if dtype.as_str() == "float32" { | ||
| let default_value = default.dataset("default").unwrap().read_scalar::<f32>()?; | ||
| add_array(dest, &Array1::from_elem(len, default_value), &name)?; | ||
| } else if dtype.as_str() == "float64" { | ||
| let default_value = default.dataset("default").unwrap().read_scalar::<f64>()?; |
There was a problem hiding this comment.
| let name = default.name().split("dataset_").last().unwrap().to_string(); | |
| let key: String = default | |
| .dataset("shape") | |
| .unwrap() | |
| .read_scalar::<hdf5::types::VarLenUnicode>() | |
| .unwrap() | |
| .as_str() | |
| .to_string(); | |
| let len: usize = *shapes.get(&key).unwrap(); | |
| let dtype: &hdf5::types::VarLenUnicode = | |
| &default.dataset("dtype").unwrap().read_scalar().unwrap(); | |
| if dtype.as_str() == "int32" { | |
| let default_value = default.dataset("default").unwrap().read_scalar::<i32>()?; | |
| if let Err(e) = add_array(dest, &Array1::from_elem(len, default_value), &name) { | |
| eprintln!("error: {e}"); | |
| eprintln!("chain: {e:?}"); | |
| }; | |
| } else if dtype.as_str() == "float32" { | |
| let default_value = default.dataset("default").unwrap().read_scalar::<f32>()?; | |
| add_array(dest, &Array1::from_elem(len, default_value), &name)?; | |
| } else if dtype.as_str() == "float64" { | |
| let default_value = default.dataset("default").unwrap().read_scalar::<f64>()?; | |
| let name = default.name().split("dataset_").last()?.to_string(); | |
| let key: String = default | |
| .dataset("shape")? | |
| .read_scalar::<hdf5::types::VarLenUnicode>()? | |
| .as_str() | |
| .to_string(); | |
| let len: usize = *shapes.get(&key)?; | |
| let dtype: &hdf5::types::VarLenUnicode = | |
| &default.dataset("dtype")?.read_scalar()?; | |
| if dtype.as_str() == "int32" { | |
| let default_value = default.dataset("default")?.read_scalar::<i32>()?; | |
| if let Err(e) = add_array(dest, &Array1::from_elem(len, default_value), &name) { | |
| eprintln!("error: {e}"); | |
| eprintln!("chain: {e:?}"); | |
| }; | |
| } else if dtype.as_str() == "float32" { | |
| let default_value = default.dataset("default")?.read_scalar::<f32>()?; | |
| add_array(dest, &Array1::from_elem(len, default_value), &name)?; | |
| } else if dtype.as_str() == "float64" { | |
| let default_value = default.dataset("default")?.read_scalar::<f64>()?; |
replace unwraps with ? in functions that return Result
| println!("Attribute '{name}' already exists in destination dataset, skipping"); | ||
| return Ok(()); | ||
| } | ||
| let cname = CString::new(name).unwrap(); |
There was a problem hiding this comment.
| let cname = CString::new(name).unwrap(); | |
| let cname = CString::new(name)?; |
replace unwraps with ? in functions that return Result
| this includes the file path), then when saving | ||
| event data to histograms, the command would be | ||
|
|
||
| data.save(<output>, autofill=Ture, ref_file=<ref>) |
There was a problem hiding this comment.
| data.save(<output>, autofill=Ture, ref_file=<ref>) | |
| data.save(<output>, autofill=True, ref_file=<ref>) |
| /// The destination group | ||
| /// name: &str | ||
| /// The name of the attribute to copy | ||
| unsafe fn _copy_attr(src: &Location, dst: &Location, name: &str) -> Result<()> { |
There was a problem hiding this comment.
could you add some code comments explaining why using the low-level bindings is necessary in each case?
| new_ds.write(new_data)?; | ||
|
|
||
| for attr_name in &attr_names { | ||
| let _ = copy_attr(&old, &new_ds, attr_name); |
There was a problem hiding this comment.
| let _ = copy_attr(&old, &new_ds, attr_name); | |
| copy_attr(&old, &new_ds, attr_name)?; |
using let _ ignores the error if copy_attr returns an error
| /// ---------- | ||
| /// file_name: &str | ||
| /// The name of the file to get the period information from | ||
| pub fn get_p_info(file_name: &str) -> Result<(usize, usize)> { |
There was a problem hiding this comment.
change name to get_period_info
| /// Contains "correct" data that should be copied to the output file. | ||
| /// This is only need it the reference file needed is not the standard | ||
| /// muon nexus v2 file. The ref_file is generated from tools/make_default.py. | ||
| pub fn save_nexus(&self, filename: String, ref_file: String) -> Result<()> { |
There was a problem hiding this comment.
what is this function actually doing? it doesn't seem to actually save any of the data, just save the default values to an already-created data file. maybe it needs a better name and docstring?
also does it need to be in the python API? will a user ever use this directly or is it only used through save?
This PR adds code to clean up the current data (should not have null values in the event file) and add the missing meta-data so it can be loaded into Mantid.
The Python script I wrote for creating the reference file has been added under tools, for completeness.
The code that cleans and adds meta-data to the output is in Rust. At present it does not handle Dwell and this will be fixed as part of #77.
I will check the output against the schema and add the code needed to output a json of the missing/incorrect data as part of #109.
I have the following left:
To test:
Then check HIFI42.nxs loads in both Mantid's muon analysis GUI and Wimda.