ad_plugins_rs/
file_nexus.rs1use std::path::{Path, PathBuf};
16
17use ad_core_rs::error::{ADError, ADResult};
18use ad_core_rs::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
19use ad_core_rs::ndarray_pool::NDArrayPool;
20use ad_core_rs::plugin::file_base::{NDFileMode, NDFileWriter};
21use ad_core_rs::plugin::file_controller::FilePluginController;
22use ad_core_rs::plugin::runtime::{
23 NDPluginProcess, ParamChangeResult, PluginParamSnapshot, ProcessResult,
24};
25
26use rust_hdf5::H5File;
27
28pub struct NexusWriter {
30 current_path: Option<PathBuf>,
31 file: Option<H5File>,
32 frame_count: usize,
33}
34
35impl NexusWriter {
36 pub fn new() -> Self {
37 Self {
38 current_path: None,
39 file: None,
40 frame_count: 0,
41 }
42 }
43
44 pub fn frame_count(&self) -> usize {
45 self.frame_count
46 }
47}
48
49impl Default for NexusWriter {
50 fn default() -> Self {
51 Self::new()
52 }
53}
54
55impl NDFileWriter for NexusWriter {
56 fn open_file(&mut self, path: &Path, _mode: NDFileMode, _array: &NDArray) -> ADResult<()> {
57 self.current_path = Some(path.to_path_buf());
58 self.frame_count = 0;
59
60 let h5file = H5File::create(path)
61 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus create error: {}", e)))?;
62
63 let entry = h5file
65 .create_group("entry")
66 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus group error: {}", e)))?;
67 let instrument = entry
68 .create_group("instrument")
69 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus group error: {}", e)))?;
70 let _detector = instrument
71 .create_group("detector")
72 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus group error: {}", e)))?;
73 let _data_group = entry
74 .create_group("data")
75 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus group error: {}", e)))?;
76
77 self.file = Some(h5file);
78 Ok(())
79 }
80
81 fn write_file(&mut self, array: &NDArray) -> ADResult<()> {
82 let h5file = self
83 .file
84 .as_ref()
85 .ok_or_else(|| ADError::UnsupportedConversion("no NeXus file open".into()))?;
86
87 let shape = array.dims.iter().rev().map(|d| d.size).collect::<Vec<_>>();
88
89 let dataset_name = if self.frame_count == 0 {
92 "data".to_string()
93 } else {
94 format!("data_{}", self.frame_count)
95 };
96
97 let detector_group = h5file
99 .root_group()
100 .group("entry")
101 .map_err(|e| ADError::UnsupportedConversion(e.to_string()))?
102 .group("instrument")
103 .map_err(|e| ADError::UnsupportedConversion(e.to_string()))?
104 .group("detector")
105 .map_err(|e| ADError::UnsupportedConversion(e.to_string()))?;
106
107 macro_rules! write_typed {
108 ($t:ty, $v:expr) => {{
109 let ds = detector_group
110 .new_dataset::<$t>()
111 .shape(&shape[..])
112 .create(&dataset_name)
113 .map_err(|e| {
114 ADError::UnsupportedConversion(format!("NeXus dataset error: {}", e))
115 })?;
116 ds.write_raw($v).map_err(|e| {
117 ADError::UnsupportedConversion(format!("NeXus write error: {}", e))
118 })?;
119 for attr in array.attributes.iter() {
121 let val_str = attr.value.as_string();
122 let _ = ds
123 .new_attr::<rust_hdf5::types::VarLenUnicode>()
124 .shape(())
125 .create(attr.name.as_str())
126 .and_then(|a| {
127 let s: rust_hdf5::types::VarLenUnicode =
128 val_str.parse().unwrap_or_default();
129 a.write_scalar(&s)
130 });
131 }
132 }};
133 }
134
135 match &array.data {
136 NDDataBuffer::U8(v) => write_typed!(u8, v),
137 NDDataBuffer::U16(v) => write_typed!(u16, v),
138 NDDataBuffer::I16(v) => write_typed!(i16, v),
139 NDDataBuffer::I32(v) => write_typed!(i32, v),
140 NDDataBuffer::U32(v) => write_typed!(u32, v),
141 NDDataBuffer::F32(v) => write_typed!(f32, v),
142 NDDataBuffer::F64(v) => write_typed!(f64, v),
143 _ => {
144 let raw = array.data.as_u8_slice();
145 let ds = detector_group
146 .new_dataset::<u8>()
147 .shape([raw.len()])
148 .create(&dataset_name)
149 .map_err(|e| {
150 ADError::UnsupportedConversion(format!("NeXus dataset error: {}", e))
151 })?;
152 ds.write_raw(raw).map_err(|e| {
153 ADError::UnsupportedConversion(format!("NeXus write error: {}", e))
154 })?;
155 }
156 }
157
158 self.frame_count += 1;
159 Ok(())
160 }
161
162 fn read_file(&mut self) -> ADResult<NDArray> {
163 let path = self
164 .current_path
165 .as_ref()
166 .ok_or_else(|| ADError::UnsupportedConversion("no file open".into()))?;
167
168 let h5file = H5File::open(path)
169 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus open error: {}", e)))?;
170
171 let ds = h5file
173 .dataset("entry/instrument/detector/data")
174 .map_err(|e| ADError::UnsupportedConversion(format!("NeXus dataset error: {}", e)))?;
175
176 let shape = ds.shape();
177 let dims: Vec<NDDimension> = shape.iter().rev().map(|&s| NDDimension::new(s)).collect();
178
179 if let Ok(data) = ds.read_raw::<u8>() {
180 let mut arr = NDArray::new(dims, NDDataType::UInt8);
181 arr.data = NDDataBuffer::U8(data);
182 return Ok(arr);
183 }
184 if let Ok(data) = ds.read_raw::<u16>() {
185 let mut arr = NDArray::new(dims, NDDataType::UInt16);
186 arr.data = NDDataBuffer::U16(data);
187 return Ok(arr);
188 }
189 if let Ok(data) = ds.read_raw::<f64>() {
190 let mut arr = NDArray::new(dims, NDDataType::Float64);
191 arr.data = NDDataBuffer::F64(data);
192 return Ok(arr);
193 }
194
195 Err(ADError::UnsupportedConversion(
196 "unsupported data type in NeXus file".into(),
197 ))
198 }
199
200 fn close_file(&mut self) -> ADResult<()> {
201 self.file = None;
202 self.current_path = None;
203 Ok(())
204 }
205
206 fn supports_multiple_arrays(&self) -> bool {
207 true
208 }
209}
210
211pub struct NexusFileProcessor {
216 ctrl: FilePluginController<NexusWriter>,
217}
218
219impl NexusFileProcessor {
220 pub fn new() -> Self {
221 Self {
222 ctrl: FilePluginController::new(NexusWriter::new()),
223 }
224 }
225}
226
227impl Default for NexusFileProcessor {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233impl NDPluginProcess for NexusFileProcessor {
234 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
235 self.ctrl.process_array(array)
236 }
237
238 fn plugin_type(&self) -> &str {
239 "NDFileNexus"
240 }
241
242 fn register_params(
243 &mut self,
244 base: &mut asyn_rs::port::PortDriverBase,
245 ) -> asyn_rs::error::AsynResult<()> {
246 self.ctrl.register_params(base)?;
247 use asyn_rs::param::ParamType;
248 base.create_param("NEXUS_TEMPLATE_PATH", ParamType::Octet)?;
249 base.create_param("NEXUS_TEMPLATE_FILE", ParamType::Octet)?;
250 base.create_param("NEXUS_TEMPLATE_VALID", ParamType::Int32)?;
251 Ok(())
252 }
253
254 fn on_param_change(
255 &mut self,
256 reason: usize,
257 params: &PluginParamSnapshot,
258 ) -> ParamChangeResult {
259 self.ctrl.on_param_change(reason, params)
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 fn temp_path(prefix: &str) -> PathBuf {
268 use std::sync::atomic::{AtomicU32, Ordering};
269 static COUNTER: AtomicU32 = AtomicU32::new(0);
270 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
271 std::env::temp_dir().join(format!("adcore_test_{}_{}.nxs", prefix, n))
272 }
273
274 #[test]
275 fn test_nexus_write_read() {
276 let path = temp_path("nexus_basic");
277 let mut writer = NexusWriter::new();
278
279 let mut arr = NDArray::new(
280 vec![NDDimension::new(4), NDDimension::new(4)],
281 NDDataType::UInt8,
282 );
283 if let NDDataBuffer::U8(ref mut v) = arr.data {
284 for i in 0..16 {
285 v[i] = i as u8;
286 }
287 }
288
289 writer.open_file(&path, NDFileMode::Single, &arr).unwrap();
290 writer.write_file(&arr).unwrap();
291 writer.close_file().unwrap();
292
293 let h5file = H5File::open(&path).unwrap();
295 let ds = h5file.dataset("entry/instrument/detector/data").unwrap();
296 let data: Vec<u8> = ds.read_raw().unwrap();
297 assert_eq!(data.len(), 16);
298 assert_eq!(data[0], 0);
299 assert_eq!(data[15], 15);
300
301 std::fs::remove_file(&path).ok();
302 }
303
304 #[test]
305 fn test_nexus_multiple_frames() {
306 let path = temp_path("nexus_multi");
307 let mut writer = NexusWriter::new();
308
309 let arr = NDArray::new(
310 vec![NDDimension::new(4), NDDimension::new(4)],
311 NDDataType::UInt8,
312 );
313
314 writer.open_file(&path, NDFileMode::Stream, &arr).unwrap();
315 writer.write_file(&arr).unwrap();
316 writer.write_file(&arr).unwrap();
317 writer.close_file().unwrap();
318
319 assert_eq!(writer.frame_count(), 2);
320
321 let data = std::fs::read(&path).unwrap();
322 assert_eq!(&data[0..8], b"\x89HDF\r\n\x1a\n");
323
324 std::fs::remove_file(&path).ok();
325 }
326}