Skip to content

Conversation

@alejoe91
Copy link
Member

@alejoe91 alejoe91 commented Dec 16, 2025

Useful function to instantiate a SortingAnalyzer from an NWB file as good as we can :)

TODO

  • Add tests

@alejoe91 alejoe91 added the extractors Related to extractors module label Dec 16, 2025
@alejoe91 alejoe91 requested a review from h-mayorquin December 16, 2025 13:50
@chrishalcrow
Copy link
Member

My experience of trying to load recordings is that the channel locations are often not saved with the nwb recording, but that they are saved somewhere else in the nwb file. @bendichter mentioned in a meeting that they'd thought about this problem, and maybe had a solution?

@alejoe91
Copy link
Member Author

This will require some key metadata (e.g., an electrodes table and rel_x/rel_y available). In case some key stuff is missing, it will throw an error!

1 similar comment
@alejoe91
Copy link
Member Author

This will require some key metadata (e.g., an electrodes table and rel_x/rel_y available). In case some key stuff is missing, it will throw an error!

return outputs


def load_analyzer_from_nwb(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't like the name read_nwb_as_analyzer() ? to match the kilosort one.

templates_ext = ComputeTemplates(sorting_analyzer=analyzer)
templates_avg_data = np.array([t for t in units["waveform_mean"].values]).astype("float")
total_ms = templates_avg_data.shape[1] / analyzer.sampling_frequency * 1000
template_params = get_default_analyzer_extension_params("templates")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a strange guess.
Do we except nwd to have the same template params as spikeinterface actual version ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a proper way to do it ?
I think I would go directly to the 1/3 2/2 + warnings meachanism.

Comment on lines +1991 to +1992
tm = pd.DataFrame(index=sorting.unit_ids)
qm = pd.DataFrame(index=sorting.unit_ids)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we set the correct dtype from the new extension system ?

Copy link
Collaborator

@h-mayorquin h-mayorquin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first read

template_metric_columns = ComputeTemplateMetrics.get_metric_columns()
quality_metric_columns = ComputeQualityMetrics.get_metric_columns()

tm = pd.DataFrame(index=sorting.unit_ids)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is tm?

return analyzer


def create_dummy_probegroup_from_locations(locations, shape="circle", shape_params={"radius": 1}):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should make this private as we might want to change this.

return probegroup


def make_df(group):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should make this private as we might want to change this. Plus, this is a super generic name that we don't want to contaminate any namespace with.

num_channels=len(channel_ids),
num_samples=num_samples,
is_filtered=True,
dtype="float32",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need the dtype and why is it fixed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should make this optional at the Analyzer level (same for is_filtered)

Comment on lines +1838 to +1851
t_start_tmp = 0 if t_start is None else t_start

sorting_tmp = NwbSortingExtractor(
file_path=file_path,
electrical_series_path=electrical_series_path,
unit_table_path=unit_table_path,
stream_mode=stream_mode,
stream_cache_path=stream_cache_path,
cache=cache,
storage_options=storage_options,
use_pynwb=use_pynwb,
t_start=t_start_tmp,
sampling_frequency=sampling_frequency,
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use session_start_time instead.

Comment on lines +1894 to +1909
if electrodes_indices is not None:
# here we assume all groups are the same for each unit, so we just check one.
if "group_name" in electrodes_table.columns:
group_names = np.array([electrodes_table.iloc[int(ei[0])]["group_name"] for ei in electrodes_indices])
if len(np.unique(group_names)) > 0:
if group_name is None:
raise Exception(
f"More than one group, use group_name option to select units. Available groups: {np.unique(group_names)}"
)
else:
unit_mask = group_names == group_name
if verbose:
print(f"Selecting {sum(unit_mask)} / {len(units)} units from {group_name}")
sorting = sorting.select_units(unit_ids=sorting.unit_ids[unit_mask])
units = units.loc[units.index[unit_mask]]
electrodes_indices = units["electrodes"]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could use the same trick as the "aggregation_key" when instantiating a sorting analyzer from grouped recordings/sortings

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

Labels

extractors Related to extractors module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants