From 4677f01b21f28f8a6b11abdcaea5e5b20b3e5f8e Mon Sep 17 00:00:00 2001 From: olivier Date: Thu, 25 Jan 2024 13:18:32 +0000 Subject: [PATCH] viewspikes: a Gui to display electrophysiology features in IBL sessions --- atlasview/atlasview.py | 2 +- setup.py | 1 + viewspikes/README.md | 9 +- viewspikes/data.py | 110 ----------------- viewspikes/example_view_ephys_session.py | 15 +++ viewspikes/examples_local.py | 111 ----------------- viewspikes/examples_reproducible.py | 60 --------- viewspikes/examples_stream.py | 13 -- viewspikes/gui.py | 149 +++++------------------ viewspikes/main.py | 93 -------------- viewspikes/{view2p.ui => raster.ui} | 62 +++++----- 11 files changed, 87 insertions(+), 538 deletions(-) delete mode 100644 viewspikes/data.py create mode 100644 viewspikes/example_view_ephys_session.py delete mode 100644 viewspikes/examples_local.py delete mode 100644 viewspikes/examples_reproducible.py delete mode 100644 viewspikes/examples_stream.py delete mode 100644 viewspikes/main.py rename viewspikes/{view2p.ui => raster.ui} (51%) diff --git a/atlasview/atlasview.py b/atlasview/atlasview.py index 07fee99..79d402f 100644 --- a/atlasview/atlasview.py +++ b/atlasview/atlasview.py @@ -261,7 +261,7 @@ class ControllerTopView(PgImageController): """ TopView ControllerTopView """ - def __init__(self, qmain: TopView, res: int = 25, volume='image'): + def __init__(self, qmain: TopView, res: int = 25, volume='image', **kwargs): super(ControllerTopView, self).__init__(qmain) self.volume = volume self.atlas = AllenAtlas(res) diff --git a/setup.py b/setup.py index fcc00a5..1eca54a 100644 --- a/setup.py +++ b/setup.py @@ -12,6 +12,7 @@ entry_points={ 'console_scripts': [ 'atlas=atlasview.atlasview:main', + 'align=atlaselectrophysiology.ephys_atlas_gui:main', ] }, ) diff --git a/viewspikes/README.md b/viewspikes/README.md index a7154fc..ad27b2a 100644 --- a/viewspikes/README.md +++ b/viewspikes/README.md @@ -12,6 +12,13 @@ Pre-requisites: ``` conda activate iblenv -pip install easyqc +pip install viewephys pip install -U pyqtgraph ``` + + +## Roadmap +- multi-probe displays for sessions with several probes +- make sure NP2.0 4 shanks is supported +- display LFP +- speed up the raster plots in pyqtgraph diff --git a/viewspikes/data.py b/viewspikes/data.py deleted file mode 100644 index c0510af..0000000 --- a/viewspikes/data.py +++ /dev/null @@ -1,110 +0,0 @@ -from pathlib import Path -import shutil - -from brainbox.io import spikeglx -from one.webclient import dataset_record_to_url - -import numpy as np -import scipy.signal - -from one import alf - - -CHUNK_DURATION_SECS = 1 -OUTPUT_TO_TEST = True - - -def get_ks2_batch(ks2memmap, ibatch): - BATCH_SIZE = 65600 - NTR = 384 - offset = BATCH_SIZE * NTR * ibatch - from_to = np.array([0, BATCH_SIZE * NTR]) - slic = slice(from_to[0] + offset, from_to[1] + offset) - - ks2 = np.reshape(ks2memmap[slice(from_to[0] + offset, from_to[1] + offset)], (NTR, BATCH_SIZE)) - return ks2 - - -# ks2 proc -def get_ks2(raw, dsets, one): - kwm = next(dset for dset in dsets if dset['dataset_type'] == 'kilosort.whitening_matrix') - kwm = np.load(one._download_dataset(kwm)) - channels = [dset for dset in dsets if dset['dataset_type'].startswith('channels')] - malf_path = next(iter(one._download_datasets(channels))).parent - channels = alf.io.load_object(malf_path, 'channels') - _car = raw[channels['rawInd'], :] - np.mean(raw[channels.rawInd, :], axis=0) - sos = scipy.signal.butter(3, 300 / 30000 / 2, btype='highpass', output='sos') - ks2 = np.zeros_like(raw) - ks2[channels['rawInd'], :] = scipy.signal.sosfiltfilt(sos, _car) - std_carbutt = np.std(ks2) - ks2[channels['rawInd'], :] = np.matmul(kwm, ks2[channels['rawInd'], :]) - ks2 = ks2 * std_carbutt / np.std(ks2) - return ks2 - - -def get_spikes(dsets, one): - dtypes_spikes = ['spikes.clusters', 'spikes.amps', 'spikes.times', 'clusters.channels', - 'spikes.samples', 'spikes.depths'] - dsets_spikes = [dset for dset in dsets if dset['dataset_type'] in dtypes_spikes] - malf_path = next(iter(one._download_datasets(dsets_spikes))).parent - channels = alf.io.load_object(malf_path, 'channels') - clusters = alf.io.load_object(malf_path, 'clusters') - spikes = alf.io.load_object(malf_path, 'spikes') - return spikes, clusters, channels - - -def stream(pid, t, one=None, cache=True, dsets=None, typ='ap', tlen=1): - """ - NB: returned Reader object must be closed after use - :param pid: Probe UUID - :param t: - :param one: An instance of ONE - :param cache: - :param dsets: - :param typ: 'ap' or 'lf' - :param tlen: no. of seconds to stream - :return: sr, dsets, t0 - """ - - assert one - assert typ in ['lf', 'ap'] - t0 = np.floor(t / CHUNK_DURATION_SECS) * CHUNK_DURATION_SECS - if cache: - samples_folder = Path(one.alyx._par.CACHE_DIR).joinpath('cache', typ) - sample_file_name = Path(f"{pid}_{str(int(t0)).zfill(5)}.meta") - if dsets is None: - dsets = one.alyx.rest('datasets', 'list', probe_insertion=pid) - if cache and samples_folder.joinpath(sample_file_name).exists(): - print(f'loading {sample_file_name} from cache') - sr = spikeglx.Reader(samples_folder.joinpath(sample_file_name).with_suffix('.bin'), - open=True) - return sr, dsets, t0 - - dset_ch = next(dset for dset in dsets if dset['dataset_type'] == "ephysData.raw.ch" and - f'.{typ}.' in dset['name']) - dset_meta = next(dset for dset in dsets if dset['dataset_type'] == "ephysData.raw.meta" and - f'.{typ}.' in dset['name']) - dset_cbin = next(dset for dset in dsets if dset['dataset_type'] == f"ephysData.raw.{typ}" and - f'.{typ}.' in dset['name']) - - file_ch, file_meta = one._download_datasets([dset_ch, dset_meta]) - - first_chunk = int(t0 / CHUNK_DURATION_SECS) - last_chunk = int((t0 + tlen) / CHUNK_DURATION_SECS) - 1 - - sr = spikeglx.download_raw_partial( - one=one, - url_cbin=dataset_record_to_url(dset_cbin)[0], - url_ch=file_ch, - first_chunk=first_chunk, - last_chunk=last_chunk) - - if cache: - samples_folder.mkdir(exist_ok=True, parents=True) - out_meta = samples_folder.joinpath(sample_file_name) - shutil.copy(sr.file_meta_data, out_meta) - with open(out_meta.with_suffix('.bin'), 'wb') as fp: - sr.open() - sr._raw[:].tofile(fp) - - return sr, dsets, t0 diff --git a/viewspikes/example_view_ephys_session.py b/viewspikes/example_view_ephys_session.py new file mode 100644 index 0000000..5e1a78b --- /dev/null +++ b/viewspikes/example_view_ephys_session.py @@ -0,0 +1,15 @@ +from pathlib import Path +from one.api import ONE +from brainbox.io.one import EphysSessionLoader, SpikeSortingLoader +from iblapps.viewspikes.gui import view_raster + +PATH_CACHE = Path("/datadisk/Data/NAWG/01_lick_artefacts/openalyx") + +one = ONE(base_url="https://openalyx.internationalbrainlab.org", cache_dir=PATH_CACHE) + +pid = '5135e93f-2f1f-4301-9532-b5ad62548c49' +eid, pname = one.pid2eid(pid) + + +self = view_raster(pid=pid, one=one, stream=False) + diff --git a/viewspikes/examples_local.py b/viewspikes/examples_local.py deleted file mode 100644 index bd2ab40..0000000 --- a/viewspikes/examples_local.py +++ /dev/null @@ -1,111 +0,0 @@ -from pathlib import Path -import numpy as np -import scipy -import matplotlib.pyplot as plt -from easyqc.gui import viewseis - -import one.alf.io as alfio -from one.api import ONE -import spikeglx -import neuropixel -from neurodsp import voltage -from brainbox.plot import driftmap - -from needles2 import run_needles2 -from viewspikes.data import stream, get_ks2, get_spikes -from viewspikes.plots import plot_insertion, show_psd, overlay_spikes - -RAW_PATH = Path("/datadisk/Data/spike_sorting/benchmark/raw") -SORT_PATH = Path("/datadisk/team_drives/WG-Neural-Analysis/Spike-Sorting-Analysis/benchmarks") -SORTERS = ['ks2','ks3', 'pyks2.5'] - -"8413c5c6-b42b-4ec6-b751-881a54413628", -"8ca1a850-26ef-42be-8b28-c2e2d12f06d6", -"ce24bbe9-ae70-4659-9e9c-564d1a865de8", -"ce397420-3cd2-4a55-8fd1-5e28321981f4", - -# Example 1 -pid, t0 = ("ce24bbe9-ae70-4659-9e9c-564d1a865de8", 810) -bin_file = next(RAW_PATH.joinpath(pid).rglob("*.ap.bin")) -sr = spikeglx.Reader(bin_file) -sel = slice(int(t0 * sr.fs), int((t0 + 4) * sr.fs)) -raw = sr[sel, :-1].T - - -# Example 2: Plot Insertion for a given PID -av = run_needles2.view(lazy=True) -av.add_insertion_by_id(pid) - -# Example 3: Show the PSD -fig, ax = plt.subplots() -fig.set_size_inches(14, 7) -show_psd(raw, sr.fs, ax=ax) - -# Example 4: Display the raw / pre-proc -h = neuropixel.trace_header() -sos = scipy.signal.butter(3, 300 / sr.fs / 2, btype='highpass', output='sos') -butt = scipy.signal.sosfiltfilt(sos, raw) -fk_kwargs ={'dx': 1, 'vbounds': [0, 1e6], 'ntr_pad': 160, 'ntr_tap': 0, 'lagc': .01, 'btype': 'lowpass'} -destripe = voltage.destripe(raw, fs=sr.fs, fk_kwargs=fk_kwargs, tr_sel=np.arange(raw.shape[0])) -eqc_butt = viewseis(butt.T, si=1 / sr.fs, h=h, t0=t0, title='butt', taxis=0) -eqc_dest = viewseis(destripe.T, si=1 / sr.fs, h=h, t0=t0, title='destr', taxis=0) -eqc_dest_ = viewseis(destripe.T, si=1 / sr.fs, h=h, t0=t0, title='destr_', taxis=0) -# Example 5: overlay the spikes on the existing easyqc instances -from ibllib.plots import color_cycle -ss = {} -symbols = 'x+o' -eqcsort = {} -for i, sorter in enumerate(SORTERS): - alf_path = SORT_PATH.joinpath(sorter, pid,'alf') - ss[sorter] = {} - for k in ['spikes', 'clusters', 'channels']: - ss[sorter][k] = alfio.load_object(alf_path, k) - col = (np.array(color_cycle(i)) * 255).astype(np.uint8) - eqcsort[sorter] = viewseis(destripe.T, si=1 / sr.fs, h=h, t0=t0, title=sorter, taxis=0) - _, _, _ = overlay_spikes( - eqcsort[sorter], ss[sorter]['spikes'], ss[sorter]['clusters'], ss[sorter]['channels'], - label=sorter, symbol=symbols[i]) - _, _, _ = overlay_spikes( - eqc_dest_, ss[sorter]['spikes'], ss[sorter]['clusters'], ss[sorter]['channels'], - rgb=tuple(col), label=sorter, symbol=symbols[i]) - # overlay_spikes(eqc_dest, ss[sorter]['spikes'], ss[sorter]['clusters'], ss[sorter]['channels']) - # sc.setPen(pg.mkPen((0, 255, 0, 155), width=1)) - -## -from ibllib.dsp.fourier import fshift -from ibllib.dsp.voltage import destripe -eqc_butt = viewseis(butt.T, si=1 / sr.fs, h=h, t0=t0, title='butt', taxis=0) -bshift = fshift(butt, h['sample_shift'], axis=1) -eqc_buts = viewseis(bshift.T, si=1 / sr.fs, h=h, t0=t0, title='shift', taxis=0) - - - -## -from one.api import ONE -pid = "8413c5c6-b42b-4ec6-b751-881a54413628" -one = ONE() - -dtypes = ['spikes.amps', 'spikes.clusters', 'spikes.times', - 'clusters.channels', - 'clusters.mlapdv'] - -from iblatlas import atlas -from ibllib.pipes import histology -from ibllib.ephys import neuropixel - -import numpy as np -neuropixel.TIP_SIZE_UM -neuropixel.SITES_COORDINATES - -len(one.alyx.rest('datasets', 'list', insertion=pid)) - -# if we don't have the data on the flatiron -pi = one.alyx.rest('insertions', 'read', id=pid) -traj = one.alyx.rest('trajectories', 'list', probe_insertion=pid)[-1] -ins = atlas.Insertion.from_dict(traj) -xyz_channels = histology.interpolate_along_track( - ins.xyz, (neuropixel.SITES_COORDINATES[:, 1] + neuropixel.TIP_SIZE_UM) / 1e6) - - - - diff --git a/viewspikes/examples_reproducible.py b/viewspikes/examples_reproducible.py deleted file mode 100644 index e1e8f6a..0000000 --- a/viewspikes/examples_reproducible.py +++ /dev/null @@ -1,60 +0,0 @@ -from one.api import ONE, alfio -from atlaselectrophysiology.alignment_with_easyqc import viewer - -one = ONE() - - -pids = ['ce397420-3cd2-4a55-8fd1-5e28321981f4', - 'e31b4e39-e350-47a9-aca4-72496d99ff2a', - 'f8d0ecdc-b7bd-44cc-b887-3d544e24e561', - '6fc4d73c-2071-43ec-a756-c6c6d8322c8b', - 'c17772a9-21b5-49df-ab31-3017addea12e', - '0851db85-2889-4070-ac18-a40e8ebd96ba', - 'eeb27b45-5b85-4e5c-b6ff-f639ca5687de', - '69f42a9c-095d-4a25-bca8-61a9869871d3', - 'f03b61b4-6b13-479d-940f-d1608eb275cc', - 'f2ee886d-5b9c-4d06-a9be-ee7ae8381114', - 'f26a6ab1-7e37-4f8d-bb50-295c056e1062', - 'c4f6665f-8be5-476b-a6e8-d81eeae9279d', - '9117969a-3f0d-478b-ad75-98263e3bfacf', - 'febb430e-2d50-4f83-87a0-b5ffbb9a4943', - '8413c5c6-b42b-4ec6-b751-881a54413628', - '8b7c808f-763b-44c8-b273-63c6afbc6aae', - 'f936a701-5f8a-4aa1-b7a9-9f8b5b69bc7c', - '63517fd4-ece1-49eb-9259-371dc30b1dd6', - '8d59da25-3a9c-44be-8b1a-e27cdd39ca34', - '19baa84c-22a5-4589-9cbd-c23f111c054c', - '143dd7cf-6a47-47a1-906d-927ad7fe9117', - '84bb830f-b9ff-4e6b-9296-f458fb41d160', - 'b749446c-18e3-4987-820a-50649ab0f826', - '36362f75-96d8-4ed4-a728-5e72284d0995', - '9657af01-50bd-4120-8303-416ad9e24a51', - 'dab512bd-a02d-4c1f-8dbc-9155a163efc0'] - - - -INDEX = 2 -pid = pids[INDEX] -av = viewer(pid, one=one) -## -eid, pname = one.pid2eid(pid) -session_path = one.eid2path(eid) -probe_path = session_path.joinpath('alf', pname) - -from pathlib import Path - -local_path = Path("/datadisk/Data/spike_sorting/mic").joinpath(pid, pname) -spikes = alfio.load_object(probe_path, 'spikes') -spikes_ = alfio.load_object(local_path, 'spikes') - - -# from ibllib.plots import vertical_lines -# vertical_lines(rewards, ymin=0, ymax=3800, ax=ax) -from brainbox.plot import driftmap -import matplotlib.pyplot as plt -fig, ax = plt.subplots(nrows=2, ncols=1, sharex=True, sharey=True) -driftmap(spikes['times'], spikes['depths'], t_bin=0.1, d_bin=5, ax=ax[0]) -driftmap(spikes_['times'], spikes_['depths'], t_bin=0.1, d_bin=5, ax=ax[1]) - - - diff --git a/viewspikes/examples_stream.py b/viewspikes/examples_stream.py deleted file mode 100644 index 9ecc804..0000000 --- a/viewspikes/examples_stream.py +++ /dev/null @@ -1,13 +0,0 @@ -from one.api import ONE -from atlaselectrophysiology.alignment_with_easyqc import viewer - -one = ONE() - - -"da8dfec1-d265-44e8-84ce-6ae9c109b8bd", # SWC_043_2020-09-21_probe00 ok -"b749446c-18e3-4987-820a-50649ab0f826", # KS023_2019-12-10_probe01 ok -"f86e9571-63ff-4116-9c40-aa44d57d2da9", # CSHL049_2020-01-08_probe00 a bit stripy but fine -"675952a4-e8b3-4e82-a179-cc970d5a8b01", # CSH_ZAD_029_2020-09-19_probe01 a bit stripy as well - -pid = "af2a0072-e17e-4368-b80b-1359bf6d4647" -av = viewer(pid, one=one) diff --git a/viewspikes/gui.py b/viewspikes/gui.py index c84b766..32313bf 100644 --- a/viewspikes/gui.py +++ b/viewspikes/gui.py @@ -2,23 +2,19 @@ import numpy as np import scipy.signal - from PyQt5 import QtWidgets, QtCore, QtGui, uic import pyqtgraph as pg -from brainbox.processing import bincount2D -from easyqc.gui import viewseis -import one.alf.io as alfio -from one.alf.files import get_session_path -from iblutil.numerical import ismember -import spikeglx -from neurodsp import voltage +from iblutil.numerical import bincount2D +from viewephys.gui import viewephys +import neurodsp +from brainbox.io.one import EphysSessionLoader, SpikeSortingLoader +from iblatlas.atlas import BrainRegions -import qt -from brainbox.io.one import SpikeSortingLoader +regions = BrainRegions() T_BIN = .007 # time bin size in secs -D_BIN = 10 # depth bin size in um +D_BIN = 20 # depth bin size in um SNS_PALETTE = [(0.12156862745098039, 0.4666666666666667, 0.7058823529411765), (1.0, 0.4980392156862745, 0.054901960784313725), @@ -34,23 +30,24 @@ YMAX = 4000 -def view_raster(bin_file): - bin_file = Path(bin_file) - pname = bin_file.parent.name - session_path = get_session_path(bin_file) - ssl = SpikeSortingLoader(session_path=session_path, pname=pname) +def view_raster(pid, one, stream=True): + ssl = SpikeSortingLoader(one=one, pid=pid) + sl = EphysSessionLoader(one=one, eid=ssl.eid) + sl.load_trials() spikes, clusters, channels = ssl.load_spike_sorting(dataset_types=['spikes.samples']) - trials = alfio.load_object(ssl.session_path.joinpath('alf'), 'trials') - return RasterView(bin_file, spikes, clusters, trials=trials) + + return RasterView(ssl, spikes, clusters, channels, trials=sl.trials, stream=stream) class RasterView(QtWidgets.QMainWindow): - def __init__(self, bin_file, spikes, clusters, channels=None, trials=None, *args, **kwargs): - self.sr = spikeglx.Reader(bin_file) + def __init__(self, ssl, spikes, clusters, channels=None, trials=None, stream=True, *args, **kwargs): + self.ssl = ssl self.spikes = spikes self.clusters = clusters self.channels = channels self.trials = trials + # self.sr_lf = ssl.raw_electrophysiology(band='lf', stream=False) that would be cool to also have the LFP + self.sr = ssl.raw_electrophysiology(band='ap', stream=stream) self.eqcs = [] super(RasterView, self).__init__(*args, **kwargs) # wave by Diana Militano from the Noun Projectp @@ -68,7 +65,7 @@ def __init__(self, bin_file, spikes, clusters, channels=None, trials=None, *args iok = ~np.isnan(spikes.depths) self.raster, self.rtimes, self.depths = bincount2D( spikes.times[iok], spikes.depths[iok], T_BIN, D_BIN) - self.imageItem_raster.setImage(np.flip(self.raster.T)) + self.imageItem_raster.setImage(self.raster.T) transform = [T_BIN, 0., 0., 0., D_BIN, 0., - .5, - .5, 1.] self.transform = np.array(transform).reshape((3, 3)).T self.imageItem_raster.setTransform(QtGui.QTransform(*transform)) @@ -82,13 +79,12 @@ def __init__(self, bin_file, spikes, clusters, channels=None, trials=None, *args # self.view.layers[label] = {'layer': new_scatter, 'type': 'scatter'} self.line_eqc = pg.PlotCurveItem() self.plotItem_raster.addItem(self.line_eqc) - # self.plotItem_raster.removeItem(new_curve) ################################################## plot trials if self.trials is not None: trial_times = dict( - goCue_times=trials['goCue_times'], - error_times=trials['feedback_times'][trials['feedbackType'] == -1], - reward_times=trials['feedback_times'][trials['feedbackType'] == 1]) + goCue_times=trials['goCue_times'].values, + error_times=trials['feedback_times'][trials['feedbackType'] == -1].values, + reward_times=trials['feedback_times'][trials['feedbackType'] == 1].values) self.trial_lines = {} for i, k in enumerate(trial_times): self.trial_lines[k] = pg.PlotCurveItem() @@ -97,7 +93,6 @@ def __init__(self, bin_file, spikes, clusters, channels=None, trials=None, *args y = np.tile(np.array([0, 1, 1, 0]), int(trial_times[k].shape[0] / 2 + 1))[ :trial_times[k].shape[0] * 2] * YMAX self.trial_lines[k].setData(x=x.flatten(), y=y.flatten(), pen=pg.mkPen(np.array(SNS_PALETTE[i]) * 256)) - self.show() def mouseClick(self, event): @@ -131,108 +126,26 @@ def keyPressEvent(self, e): def show_ephys(self, t0, tlen=1): - first = int(t0 * self.sr.fs) - last = first + int(self.sr.fs * tlen) - - raw = self.sr[first:last, : - self.sr.nsync].T + s0 = int(self.ssl.samples2times(t0, direction='reverse')) + s1 = s0 + int(self.sr.fs * tlen) + raw = self.sr[s0:s1, : - self.sr.nsync].T butter_kwargs = {'N': 3, 'Wn': 300 / self.sr.fs * 2, 'btype': 'highpass'} + sos = scipy.signal.butter(**butter_kwargs, output='sos') butt = scipy.signal.sosfiltfilt(sos, raw) - destripe = voltage.destripe(raw, fs=self.sr.fs) + destripe = neurodsp.voltage.destripe(raw, fs=self.sr.fs) - self.eqc_raw = viewephys(butt, self.sr.fs, channels=None, br=None, title='butt', t0=t0, t_scalar=1) - self.eqc_des = viewephys(destripe, self.sr.fs, channels=None, br=None, title='destripe', t0=t0, t_scalar=1) + self.eqc_raw = viewephys(butt, self.sr.fs, channels=self.channels, br=regions, title='butt', t0=t0, t_scalar=1) + self.eqc_des = viewephys(destripe, self.sr.fs, channels=self.channels, br=regions, title='destripe', t0=t0, t_scalar=1) eqc_xrange = [t0 + tlen / 2 - 0.01, t0 + tlen / 2 + 0.01] self.eqc_des.viewBox_seismic.setXRange(*eqc_xrange) self.eqc_raw.viewBox_seismic.setXRange(*eqc_xrange) - # eqc2 = viewephys(butt - destripe, self.sr.fs, channels=None, br=None, title='diff') - # overlay spikes - tprobe = self.spikes.samples / self.sr.fs - slice_spikes = slice(np.searchsorted(tprobe, t0), np.searchsorted(tprobe, t0 + tlen)) - t = tprobe[slice_spikes] + # we slice the spikes using the samples according to ephys time, but display in session times + slice_spikes = slice(np.searchsorted(self.spikes['samples'], s0), np.searchsorted(self.spikes['samples'], s1)) + t = self.spikes['times'][slice_spikes] c = self.clusters.channels[self.spikes.clusters[slice_spikes]] self.eqc_raw.ctrl.add_scatter(t, c) self.eqc_des.ctrl.add_scatter(t, c) - - - - -def viewephys(data, fs, channels=None, br=None, title='ephys', t0=0, t_scalar=1e3): - """ - :param data: [nc, ns] - :param fs: - :param channels: - :param br: - :param title: - :return: - """ - width = 40 - height = 800 - nc, ns = data.shape - # ih = np.linspace(0, nc - 1, height).astype(np.int32) - # image = br.rgb[channels['ibr'][ih]].astype(np.uint8) - # image = np.tile(image[:, np.newaxis, :], (1, width, 1)) - # image = np.tile(image[np.newaxis, :, :], (width, 1, 1)) - from ibllib.ephys.neuropixel import trace_header - if channels is None or br is None: - channels = trace_header(version = 1) - eqc = viewseis(data.T, si=1 / fs * t_scalar, h=channels, title=title, taxis=0, t0=t0) - return eqc - else: - _, ir = ismember(channels['atlas_id'], br.id) - image = br.rgb[ir].astype(np.uint8) - image = image[np.newaxis, :, :] - - - eqc = viewseis(data.T, si=1 / fs * t_scalar, h=channels, title=title, taxis=0) - imitem = pg.ImageItem(image) - eqc.plotItem_header_v.addItem(imitem) - transform = [1, 0, 0, 0, 1, 0, -0.5, 0, 1.] - imitem.setTransform(QtGui.QTransform(*transform)) - eqc.plotItem_header_v.setLimits(xMin=-.5, xMax=.5) - # eqc.comboBox_header.setVisible(False) - - return eqc - - -COLOR_PLOTS = (pg.mkColor((31, 119, 180)),) - - -def view2p(tiff_file, title=None): - qt.create_app() - v2p = View2p._get_or_create(title=title) - v2p.update_tiff(tiff_file) - v2p.show() - return v2p - - -class View2p(QtWidgets.QMainWindow): - """ - This is the view in the MVC approach - """ - layers = None # used for additional scatter layers - - @staticmethod - def _instances(): - app = QtWidgets.QApplication.instance() - return [w for w in app.topLevelWidgets() if isinstance(w, View2p)] - - @staticmethod - def _get_or_create(title=None): - v2p = next(filter(lambda e: e.isVisible() and e.windowTitle() == title, - View2p._instances()), None) - if v2p is None: - v2p = View2p() - v2p.setWindowTitle(title) - return v2p - - def __init__(self, *args, **kwargs): - super(View2p, self).__init__(*args, **kwargs) - # wave by Diana Militano from the Noun Projectp - uic.loadUi(Path(__file__).parent.joinpath('view2p.ui'), self) - - def update_tiff(self, tiff_file): - pass \ No newline at end of file diff --git a/viewspikes/main.py b/viewspikes/main.py deleted file mode 100644 index 6c986e2..0000000 --- a/viewspikes/main.py +++ /dev/null @@ -1,93 +0,0 @@ -from pathlib import Path - -import scipy.signal -import numpy as np - -import spikeglx -from neurodsp import voltage -import neuropixel -from one.api import ONE -from easyqc.gui import viewseis - -from viewspikes.plots import plot_insertion, show_psd, overlay_spikes -from viewspikes.data import stream, get_spikes, get_ks2 - -folder_samples = Path('/datadisk/Data/spike_sorting/short_samples') -files_samples = list(folder_samples.rglob('*.bin')) - -one = ONE() -SIDE_BY_SIDE = False -# -# pins = one.alyx.rest('insertions', 'list', django=('json__extended_qc__alignment_count__gt,0')) -# pid, t0 = ('3e7618b8-34ca-4e48-ba3a-0e0f88a43131', 1002) # SWC_054_2020-10-10_probe01__ - sync w/ spikes !!! -# pid, t0 = ('04c9890f-2276-4c20-854f-305ff5c9b6cf', 1002.) # SWC_054_2020-10-10_probe00__04c9890f-2276-4c20-854f-305ff5c9b6cf - sync w/ spikes !!! -# pid, t0 = ('0925fb1b-cf83-4f55-bfb7-aa52f993a404', 500.) # DY_013_2020-03-06_probe00__0925fb1b-cf83-4f55-bfb7-aa52f993a404 -# pid, t0 = ('0ece5c6a-7d1e-4365-893d-ac1cc04f1d7b', 750.) # CSHL045_2020-02-27_probe01__0ece5c6a-7d1e-4365-893d-ac1cc04f1d7b -# pid, t0 = ('0ece5c6a-7d1e-4365-893d-ac1cc04f1d7b', 3000.) # CSHL045_2020-02-27_probe01__0ece5c6a-7d1e-4365-893d-ac1cc04f1d7b -pid, t0 = ('10ef1dcd-093c-4839-8f38-90a25edefb49', 2400.) -# pid, t0 = ('1a6a17cc-ba8c-4d79-bf20-cc897c9500dc', 5000) -# pid, t0 = ('2dd99c91-292f-44e3-bbf2-8cfa56015106', 2500) # NYU-23_2020-10-14_probe01__2dd99c91-292f-44e3-bbf2-8cfa56015106 -# pid, t0 = ('2dd99c91-292f-44e3-bbf2-8cfa56015106', 6000) # NYU-23_2020-10-14_probe01__2dd99c91-292f-44e3-bbf2-8cfa56015106 -# pid, t0 = ('30dfb8c6-9202-43fd-a92d-19fe68602b6f', 2400.) # ibl_witten_27_2021-01-16_probe00__30dfb8c6-9202-43fd-a92d-19fe68602b6f -# pid, t0 = ('31dd223c-0c7c-48b5-a513-41feb4000133', 3000.) # really good one : striping on not all channels -# pid, t0 = ('39b433d0-ec60-460f-8002-a393d81620a4', 2700.) # ZFM-01577_2020-10-27_probe01 needs FDNAT -# pid, t0 = ('47da98a8-f282-4830-92c2-af0e1d4f00e2', 2700.) - -# 67 frequency spike -# 458 /datadisk/Data/spike_sorting/short_samples/b45c8f3f-6361-41df-9bc1-9df98b3d30e6_01210.bin ERROR dans le chargement de la whitening matrix -# 433 /datadisk/Data/spike_sorting/short_samples/8d59da25-3a9c-44be-8b1a-e27cdd39ca34_04210.bin Cortex complètement silencieux. -# 531 /datadisk/Data/spike_sorting/short_samples/47be9ae4-290f-46ab-b047-952bc3a1a509_00010.bin Sympa pour le spike sorting, un bon example de trace pourrie à enlever avec FDNAT / Cadzow. Il y a du striping à la fin mais pas de souci pour KS2 ou pour le FK. -# 618 5b9ce60c-dcc9-4789-b2ff-29d873829fa5_03610.bin: gros cabossage plat laissé par le FK !! Tester un filtre K tout bête # spikes tous petits en comparaison. Le spike sorting a l'air décalé -# 681 /datadisk/Data/spike_sorting/short_samples/eab93ab0-26e3-4bd9-9c53-9f81c35172f4_02410.bin !! Spikes décalés. Superbe example de layering dans le cerveau avec 3 niveaux très clairement définis -# 739 /datadisk/Data/spike_sorting/short_samples/f03b61b4-6b13-479d-940f-d1608eb275cc_04210.bin: Autre example de layering ou les charactéristiques spectrales / spatiales sont très différentes. Spikes alignés -# 830 /datadisk/Data/spike_sorting/short_samples/b02c0ce6-2436-4fc0-9ea0-e7083a387d7e_03010.bin, très mauvaise qualité - spikes sont décalés ?!? - - - -file_ind = np.random.randint(len(files_samples)) -file_ind = 739 # very good quality spike sorting -print(file_ind, files_samples[file_ind]) - -pid, t0 = ('47da98a8-f282-4830-92c2-af0e1d4f00e2', 1425.) - -pid = files_samples[file_ind] -# pid, t0 = ("01c6065e-eb3c-49ba-9c25-c1f17b18d529", 500) -if isinstance(pid, Path): - file_sample = pid - pid, t0 = file_sample.stem.split('_') - t0 = float(t0) - sr = spikeglx.Reader(file_sample, open=True) - dsets = one.alyx.rest('datasets', 'list', probe_insertion=pid) -else: - sr, dsets, t0 = stream(pid, t0, one=one, samples_folder=folder_samples) - -# -plot_insertion(pid, one) - - -h = neuropixel.trace_header() -raw = sr[:, :-1].T - -sos = scipy.signal.butter(3, 300 / sr.fs / 2, btype='highpass', output='sos') -butt = scipy.signal.sosfiltfilt(sos, raw) -# show_psd(butt, sr.fs) - -fk_kwargs ={'dx': 1, 'vbounds': [0, 1e6], 'ntr_pad': 160, 'ntr_tap': 0, 'lagc': .01, 'btype': 'lowpass'} -destripe = voltage.destripe(raw, fs=sr.fs, fk_kwargs=fk_kwargs, tr_sel=np.arange(raw.shape[0])) -ks2 = get_ks2(raw, dsets, one) - -# get the spikes corresponding to current chunk, here needs to go through samples for sync reasons -spikes, clusters, channels = get_spikes(dsets, one) - -if SIDE_BY_SIDE: - hhh = {k: np.tile(h[k], 3) for k in h} - eqc_concat = viewseis(np.r_[butt, destripe, ks2], si=1 / sr.fs, h=hhh, t0=t0, title='concat') - overlay_spikes(eqc_concat, spikes, clusters, channels) -else: - eqc_butt = viewseis(butt.T, si=1 / sr.fs, h=h, t0=t0, title='butt', taxis=0) - eqc_dest = viewseis(destripe.T, si=1 / sr.fs, h=h, t0=t0, title='destr', taxis=0) - eqc_ks2 = viewseis(ks2.T, si=1 / sr.fs, h=h, t0=t0, title='ks2', taxis=0) - overlay_spikes(eqc_butt, spikes, clusters, channels) - overlay_spikes(eqc_dest, spikes, clusters, channels) - overlay_spikes(eqc_ks2, spikes, clusters, channels) -sr.close() diff --git a/viewspikes/view2p.ui b/viewspikes/raster.ui similarity index 51% rename from viewspikes/view2p.ui rename to viewspikes/raster.ui index f5b6819..a6a6bc7 100644 --- a/viewspikes/view2p.ui +++ b/viewspikes/raster.ui @@ -6,54 +6,54 @@ 0 0 - 810 - 606 + 999 + 656 MainWindow - - - - -1 - -1 - 801 - 551 - - - - - - - - - - - - -10 - 550 - 799 - 15 - - - - Qt::Horizontal - - + + + + + + + + + + + + Qt::Horizontal + + + + 0 0 - 810 + 999 22 + + + File + + + + + + + open... + +