diff --git a/src/schema/meta/associations.yaml b/src/schema/meta/associations.yaml index 7945c79eb8..7e32b01add 100644 --- a/src/schema/meta/associations.yaml +++ b/src/schema/meta/associations.yaml @@ -94,7 +94,7 @@ coordsystem: electrodes: selectors: - - intersects([suffix], ['eeg', 'emg', 'ieeg', 'meg', 'ecephys', 'icephys']) + - intersects([suffix], ['eeg', 'emg', 'ieeg', 'meg', 'ecephys', 'icephys', 'channels']) - extension != '.json' target: suffix: electrodes @@ -105,11 +105,12 @@ electrodes: probes: selectors: - - intersects([suffix], ['ecephys', 'icephys']) + - intersects([suffix], ['ecephys', 'icephys', 'electrodes']) - extension != '.json' target: suffix: probes extension: .tsv + inherit: true physio: selectors: diff --git a/src/schema/meta/context.yaml b/src/schema/meta/context.yaml index 7b630d61f5..7af71201c0 100644 --- a/src/schema/meta/context.yaml +++ b/src/schema/meta/context.yaml @@ -271,6 +271,25 @@ properties: path: description: 'Path to associated electrodes.tsv file' type: string + name: + description: 'Contents of the name column' + type: array + items: + type: string + probes: + description: 'Probes file' + type: object + required: [path] + additionalProperties: false + properties: + path: + description: 'Path to associated probes.tsv file' + type: string + probe_name: + description: 'Contents of the probe_name column' + type: array + items: + type: string coordsystem: description: 'Coordinate system file (first found)' type: object diff --git a/src/schema/rules/checks/microephys.yaml b/src/schema/rules/checks/microephys.yaml index 25d43cd7f3..42f0c5ff86 100644 --- a/src/schema/rules/checks/microephys.yaml +++ b/src/schema/rules/checks/microephys.yaml @@ -16,3 +16,47 @@ MicroephysRequiredCoordsystemWithSpace: - '"space" in entities' checks: - associations.coordsystem != null + +# Cross-file consistency checks for the channels -> electrodes -> probes hierarchy. +# Direction: child -> parent only (references must resolve). +# We do not check parent -> child (completeness) because not all electrodes +# need channels (broken contacts, reference-only) and not all probes need +# electrodes (subset recordings, run-specific channel selections). + +MicroephysElectrodeProbeReference: + issue: + code: MICROEPHYS_ELECTRODE_PROBE_MISMATCH + message: | + All probe_name values in electrodes.tsv (except "n/a") must match + a probe_name entry in the corresponding probes.tsv file. + level: warning + selectors: + - intersects([datatype], ["ecephys", "icephys"]) + - suffix == "electrodes" + - extension == ".tsv" + - associations.probes.probe_name != null + - columns.probe_name != null + checks: + - | + length(intersects(unique(columns.probe_name), associations.probes.probe_name)) + + count(unique(columns.probe_name), "n/a") + >= length(unique(columns.probe_name)) + +MicroephysChannelElectrodeReference: + issue: + code: MICROEPHYS_CHANNEL_ELECTRODE_MISMATCH + message: | + All electrode_name values in channels.tsv (except "n/a") must match + a name entry in the corresponding electrodes.tsv file. + level: warning + selectors: + - intersects([datatype], ["ecephys", "icephys"]) + - suffix == "channels" + - extension == ".tsv" + - associations.electrodes.name != null + - columns.electrode_name != null + checks: + - | + length(intersects(unique(columns.electrode_name), associations.electrodes.name)) + + count(unique(columns.electrode_name), "n/a") + >= length(unique(columns.electrode_name))