Skip to content

Review fixes for PR #65: map_channel_to_electrode, LFP boundaries, STTC, ImpedanceMeasurements#67

Closed
MilagrosMarin wants to merge 4 commits into
mainfrom
review/pr-65-review
Closed

Review fixes for PR #65: map_channel_to_electrode, LFP boundaries, STTC, ImpedanceMeasurements#67
MilagrosMarin wants to merge 4 commits into
mainfrom
review/pr-65-review

Conversation

@MilagrosMarin

Copy link
Copy Markdown

Overview

This PR proposes fixes to be incorporated into PR #65 (Changes to Ephys Schema by @judewerth) before it merges to main. The diff shows only the review corrections on top of Jude's changes.


Fixes Applied

A — Lazy imports for neo, quantities, elephant

import neo, import quantities as pq, and from elephant.spike_train_correlation import spike_time_tiling_coefficient moved from module level to lazy imports inside STTC.make().

Why: These are optional heavy dependencies used only by STTC.make(). Importing at module level causes ephys_no_curation to fail entirely on any environment without these packages (HPC nodes, minimal containers, dev machines not running spike sorting) — breaking LFP, frame analysis, and everything else that depends on this module. Follows the same lazy-import pattern applied to spikeinterface in the workflow repo.


B — map_channel_to_electrode: restricted fetch + conflict detection + edge case guards

# Before (unrestricted — fetches ALL probe configs):
electrode_mapping, channel_mapping = probe.ElectrodeConfig.Electrode.fetch("electrode", "channel_idx")

# After:
electrode_mapping, channel_mapping = (
    probe.ElectrodeConfig.Electrode & {"probe_type": probe_type}
).fetch("electrode", "channel_idx")

if len(electrode_mapping) == 0:
    raise ValueError(f"No electrode configuration found for probe_type='{probe_type}'...")

pairs = np.unique(np.column_stack([electrode_mapping, channel_mapping]), axis=0)
if len(pairs) != len(np.unique(pairs[:, 0])):
    raise ValueError("Conflicting channel-electrode mappings found ...")
electrode_mapping, channel_mapping = pairs[:, 0], pairs[:, 1]

Also replaced np.empty(num_electrodes, dtype=int) with np.full(num_electrodes, -1, dtype=int).

Why:

  • The original fetch was unrestricted — pulled mappings from all probe configs regardless of probe_type. Silently wrong the moment a second probe type is added.
  • probe_type is already in ElectrodeConfig.Electrode's PK (via -> ProbeType.Electrode), so no join through ElectrodeConfig is needed.
  • np.unique deduplication handles multiple configs for the same probe type that share the same mapping. Conflicting mappings raise a ValueError explicitly.
  • Empty fetch guard: if no ElectrodeConfig exists yet, the fetch returns empty arrays and np.column_stack would crash with a cryptic message. Added explicit check first.
  • np.emptynp.full(..., -1): np.empty produces uninitialized memory for indices not covered by the config. Sentinel value -1 makes uncovered positions detectable rather than silently returning garbage.

Non-blocking suggestion for @judewerth: The return variable electrode_ids and the docstring Returns: electrodes (array-like)... are misleading when electrode_to_channel=True — in that direction the function returns channel indices, not electrode indices. Worth updating in a follow-up.


C — LFP boundary trimming: elif bug for single-file sessions

# Before (elif skips end trim when session is in a single file):
if file_relpath == file_paths[0]:
    lfps = lfps[:, start_idx:]
elif file_relpath == file_paths[-1]:   # never runs when first == last
    lfps = lfps[:, :end_idx]

# After (two independent if blocks, single slice):
start_idx = 0
end_idx = lfps.shape[1]
is_first_file = file_relpath == file_paths[0]
is_last_file = file_relpath == file_paths[-1]
if is_first_file or is_last_file:
    file_start = datetime.strptime(...)  # parsed once
    if is_first_file:
        start_idx = int((key['start_time'] - file_start).total_seconds() * fs)
    if is_last_file:
        end_idx = int((key['end_time'] - file_start).total_seconds() * fs)
lfps = lfps[:, start_idx:end_idx]

Why: When a session lives entirely within one .rhd file, file_paths[0] == file_paths[-1]. The elif means only the start boundary is trimmed — sessions in a single file silently include LFP data beyond end_time.


D — # REMOVE LATER comment in STTC.make()

Replaced with: # clip spike times to session duration to guard against edge-case spikes beyond end_time. The line itself is necessary — without it, neo.SpikeTrain raises when spike times exceed t_stop.


E — ImpedanceMeasurements.make(): existence check before fetch

Moved if not (EphysSessionProbe & key) before fetch("port_id"). Previously a missing EphysSessionProbe would cause port_id.pop() to raise KeyError instead of the intended ValueError.


Test plan

  • Verify from element_array_ephys.ephys_no_curation import map_channel_to_electrode, get_probe_type succeeds without neo/elephant installed
  • Verify map_channel_to_electrode returns correct electrode indices for a known session
  • Verify LFP extraction for a session within a single .rhd file produces the correct duration
  • Verify STTC.make() runs end-to-end for a spike-sorted session

🤖 Generated with Claude Code

judewerth and others added 4 commits March 2, 2026 11:55
1) map channel to electrode function
2) Fix bug in trace extraction (currently doesn't account for file boundaries
3) Add impedance measurement tables
4) Add STTC tables
…ies, STTC, ImpedanceMeasurements)

- Move neo/quantities/elephant imports to lazy imports inside STTC.make()
  to avoid breaking the module in environments without these packages
- Fix map_channel_to_electrode: restrict ElectrodeConfig.Electrode fetch
  by probe_type (was unrestricted), add conflict detection across multiple
  configs, and deduplicate consistent mappings safely
- Fix LFP boundary trimming: replace elif with two independent if blocks
  so single-file sessions correctly trim both start and end boundaries
- Replace # REMOVE LATER comment in STTC.make() with proper explanation
- Fix ImpedanceMeasurements.make(): move EphysSessionProbe existence check
  before the fetch to prevent KeyError on empty result

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…_to_electrode

- Add explicit check when no ElectrodeConfig rows exist for the probe_type
  to prevent np.column_stack from crashing on empty arrays
- Replace np.empty with np.full(..., -1) so unset lookup positions return
  a detectable sentinel value (-1) rather than uninitialized memory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@MilagrosMarin

Copy link
Copy Markdown
Author

Recreating with correct base targeting judewerth:main.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants