Skip to content

Commit 4f031a6

Browse files
hdf5 and tdms preview shows time stamp channels
1 parent 960fe17 commit 4f031a6

3 files changed

Lines changed: 130 additions & 4 deletions

File tree

rust/crates/sift_cli/src/cmd/import/hdf5/detect_hdf5_schema.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,39 @@ pub fn detect_config(
130130
Ok((data, _)) if data.is_empty() => Err(no_match_error(
131131
&datasets, schema, time_index, time_field, time_name,
132132
)),
133-
Ok(other) => Ok(other),
133+
Ok((data, mut channel_configs)) => {
134+
let time_rows = build_time_channel_rows(&data);
135+
channel_configs.splice(0..0, time_rows);
136+
Ok((data, channel_configs))
137+
}
134138
Err(e) => Err(e),
135139
}
136140
}
137141

142+
pub(super) fn build_time_channel_rows(data: &[Hdf5DataConfig]) -> Vec<ChannelConfig> {
143+
let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
144+
let mut rows: Vec<ChannelConfig> = Vec::new();
145+
for cfg in data {
146+
let dataset_name = group_path_to_channel_name(&cfg.time_dataset);
147+
let name = match &cfg.time_field {
148+
Some(field) if !field.is_empty() => format!("{dataset_name}.{field}"),
149+
_ if cfg.time_dataset == cfg.value_dataset => {
150+
format!("{dataset_name}.{}", cfg.time_index)
151+
}
152+
_ => dataset_name,
153+
};
154+
if seen.insert(name.clone()) {
155+
rows.push(ChannelConfig {
156+
name,
157+
description: "[time]".into(),
158+
data_type: ChannelDataType::Int64 as i32,
159+
..Default::default()
160+
});
161+
}
162+
}
163+
rows
164+
}
165+
138166
fn no_match_error(
139167
datasets: &[Dataset],
140168
selected: Hdf5Schema,

rust/crates/sift_cli/src/cmd/import/hdf5/tests.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use crate::cli::hdf5::Hdf5Schema;
99
use crate::cli::time::TimeFormat;
1010
use crate::cli::{CommonImportArgs, ImportHdf5Args};
1111
use crate::cmd::import::hdf5::detect_hdf5_schema::{
12-
basename, enum_types_for, hdf5_to_sift_data_type, is_time_dataset_name, parent_path,
12+
basename, build_time_channel_rows, enum_types_for, hdf5_to_sift_data_type,
13+
is_time_dataset_name, parent_path,
1314
};
1415
use crate::cmd::import::hdf5::import::build_hdf5_config;
1516
use crate::cmd::import::utils::group_path_to_channel_name;
@@ -373,3 +374,73 @@ fn group_path_to_channel_name_no_leading_slash() {
373374
"group1.current"
374375
);
375376
}
377+
378+
fn make_data_config(
379+
time_dataset: &str,
380+
value_dataset: &str,
381+
time_index: u64,
382+
time_field: Option<&str>,
383+
) -> sift_rs::data_imports::v2::Hdf5DataConfig {
384+
sift_rs::data_imports::v2::Hdf5DataConfig {
385+
time_dataset: time_dataset.into(),
386+
time_index,
387+
value_dataset: value_dataset.into(),
388+
value_index: 0,
389+
channel_config: None,
390+
time_field: time_field.map(Into::into),
391+
value_field: None,
392+
}
393+
}
394+
395+
#[test]
396+
fn build_time_channel_rows_one_d_dedups_shared_time_dataset() {
397+
let configs = vec![
398+
make_data_config("/group_a/time", "/group_a/voltage", 0, None),
399+
make_data_config("/group_a/time", "/group_a/current", 0, None),
400+
make_data_config("/group_b/timestamp", "/group_b/value", 0, None),
401+
];
402+
let rows = build_time_channel_rows(&configs);
403+
assert_eq!(rows.len(), 2);
404+
assert_eq!(rows[0].name, "group_a.time");
405+
assert_eq!(rows[0].description, "[time]");
406+
assert_eq!(rows[0].data_type, ChannelDataType::Int64 as i32);
407+
assert_eq!(rows[1].name, "group_b.timestamp");
408+
assert_eq!(rows[1].description, "[time]");
409+
}
410+
411+
#[test]
412+
fn build_time_channel_rows_two_d_includes_time_index() {
413+
let configs = vec![
414+
make_data_config("/sensor_0", "/sensor_0", 0, None),
415+
make_data_config("/sensor_1", "/sensor_1", 0, None),
416+
];
417+
let rows = build_time_channel_rows(&configs);
418+
assert_eq!(rows.len(), 2);
419+
assert_eq!(rows[0].name, "sensor_0.0");
420+
assert_eq!(rows[1].name, "sensor_1.0");
421+
}
422+
423+
#[test]
424+
fn build_time_channel_rows_compound_renders_dataset_dot_field() {
425+
let configs = vec![
426+
make_data_config("/measurements/run1", "/measurements/run1", 0, Some("ts")),
427+
make_data_config("/measurements/run1", "/measurements/run1", 0, Some("ts")),
428+
make_data_config(
429+
"/measurements/run2",
430+
"/measurements/run2",
431+
0,
432+
Some("timestamp"),
433+
),
434+
];
435+
let rows = build_time_channel_rows(&configs);
436+
assert_eq!(rows.len(), 2);
437+
assert_eq!(rows[0].name, "measurements.run1.ts");
438+
assert_eq!(rows[0].description, "[time]");
439+
assert_eq!(rows[1].name, "measurements.run2.timestamp");
440+
}
441+
442+
#[test]
443+
fn build_time_channel_rows_empty_when_no_data_configs() {
444+
let rows = build_time_channel_rows(&[]);
445+
assert!(rows.is_empty());
446+
}

rust/crates/sift_cli/src/cmd/import/tdms/detect_tdms_config.rs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,19 @@ fn detect_config(path: &Path, fallback_method: TdmsFallbackMethod) -> Result<Vec
212212
let channels: Vec<(String, &Channel)> = file.channels(&group).into_iter().collect();
213213
let time_channel_name = find_time_channel(&channels);
214214

215+
if let Some(time_name) = time_channel_name.as_ref() {
216+
if let Some((_, time_channel)) = channels.iter().find(|(n, _)| n == time_name) {
217+
let time_dt = tdms_to_sift_data_type(time_channel.data_type)
218+
.unwrap_or(ChannelDataType::Int64);
219+
channels_vec.push(ChannelConfig {
220+
name: format!("{group}.{time_name}"),
221+
description: "[time]".into(),
222+
data_type: time_dt as i32,
223+
..Default::default()
224+
});
225+
}
226+
}
227+
215228
for (channel_name, channel) in &channels {
216229
if Some(channel_name) == time_channel_name.as_ref() {
217230
continue;
@@ -221,7 +234,8 @@ fn detect_config(path: &Path, fallback_method: TdmsFallbackMethod) -> Result<Vec
221234
continue;
222235
};
223236

224-
let has_timing = is_waveform_channel(channel) || time_channel_name.is_some();
237+
let is_waveform = is_waveform_channel(channel);
238+
let has_timing = is_waveform || time_channel_name.is_some();
225239
if !has_timing {
226240
match fallback_method {
227241
TdmsFallbackMethod::IgnoreError => continue,
@@ -235,10 +249,23 @@ fn detect_config(path: &Path, fallback_method: TdmsFallbackMethod) -> Result<Vec
235249
}
236250
}
237251

252+
let description = {
253+
let raw = get_string_property(channel, "description");
254+
if is_waveform {
255+
if raw.is_empty() {
256+
"[waveform timing]".to_string()
257+
} else {
258+
format!("[waveform timing] {raw}")
259+
}
260+
} else {
261+
raw
262+
}
263+
};
264+
238265
channels_vec.push(ChannelConfig {
239266
name: format!("{}.{}", group, channel_name),
240267
units: get_string_property(channel, "unit_string"),
241-
description: get_string_property(channel, "description"),
268+
description,
242269
data_type: data_type as i32,
243270
..Default::default()
244271
});

0 commit comments

Comments
 (0)