Skip to content

Commit 95a0553

Browse files
authored
Merge pull request #402 from int-brain-lab/globus_download_localserver
Globus download localserver
2 parents 4f14070 + e3c272a commit 95a0553

File tree

12 files changed

+1105
-204
lines changed

12 files changed

+1105
-204
lines changed

brainbox/io/one.py

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,24 @@ def _channels_traj2bunch(xyz_chans, brain_atlas):
9595
return channels
9696

9797

98+
def _channels_bunch2alf(channels):
99+
channels_ = {
100+
'mlapdv': np.c_[channels['x'], channels['y'], channels['z']] * 1e6,
101+
'brainLocationIds_ccf_2017': channels['atlas_id'],
102+
'localCoordinates': np.c_[channels['lateral_um'], channels['axial_um']]}
103+
return channels_
104+
105+
98106
def _channels_alf2bunch(channels, brain_regions=None):
99107
# reformat the dictionary according to the standard that comes out of Alyx
100108
channels_ = {
101109
'x': channels['mlapdv'][:, 0].astype(np.float64) / 1e6,
102110
'y': channels['mlapdv'][:, 1].astype(np.float64) / 1e6,
103111
'z': channels['mlapdv'][:, 2].astype(np.float64) / 1e6,
104112
'acronym': None,
105-
'atlas_id': channels['brainLocationIds_ccf_2017']
113+
'atlas_id': channels['brainLocationIds_ccf_2017'],
114+
'axial_um': channels['localCoordinates'][:, 1],
115+
'lateral_um': channels['localCoordinates'][:, 0],
106116
}
107117
if brain_regions:
108118
channels_['acronym'] = brain_regions.get(channels_['atlas_id'])['acronym']
@@ -207,24 +217,33 @@ def _load_channels_locations_from_disk(eid, collection=None, one=None, revision=
207217
channels_aligned = one.load_object(eid, 'channels', collection=ac_collection)
208218
channels[probe] = channel_locations_interpolation(channels_aligned, channels[probe])
209219
# only have to reformat channels if we were able to load coordinates from disk
210-
channels[probe] = _channels_alf2bunch(channels[probe], brain_regions=brain_regions)
220+
channels[probe] = _channels_alf2bunch(channels[probe], brain_regions=brain_regions)
211221
return channels
212222

213223

214-
def channel_locations_interpolation(channels_aligned, channels):
224+
def channel_locations_interpolation(channels_aligned, channels, brain_regions=None):
215225
"""
216226
oftentimes the channel map for different spike sorters may be different so interpolate the alignment onto
217227
if there is no spike sorting in the base folder, the alignment doesn't have the localCoordinates field
218228
so we reconstruct from the Neuropixel map. This only happens for early pykilosort sorts
219229
:param channels_aligned: Bunch or dictionary of aligned channels containing at least keys
220-
'mlapdv' and 'brainLocationIds_ccf_2017' - those are the guide for the interpolation
230+
'localCoordinates', 'mlapdv' and 'brainLocationIds_ccf_2017'
231+
OR
232+
'x', 'y', 'z', 'acronym', 'axial_um'
233+
those are the guide for the interpolation
221234
:param channels: Bunch or dictionary of aligned channels containing at least keys 'localCoordinates'
222-
:return: Bunch or dictionary of channels with extra keys 'mlapdv' and 'brainLocationIds_ccf_2017'
235+
:param brain_regions: None (default) or ibllib.atlas.BrainRegions object
236+
if None will return a dict with keys 'localCoordinates', 'mlapdv', 'brainLocationIds_ccf_2017
237+
if a brain region object is provided, outputts a dict with keys
238+
'x', 'y', 'z', 'acronym', 'atlas_id', 'axial_um', 'lateral_um'
239+
:return: Bunch or dictionary of channels with brain coordinates keys
223240
"""
224241
nch = channels['localCoordinates'].shape[0]
242+
if set(['x', 'y', 'z']).issubset(set(channels_aligned.keys())):
243+
channels_aligned = _channels_bunch2alf(channels_aligned)
225244
if 'localCoordinates' in channels_aligned.keys():
226245
aligned_depths = channels_aligned['localCoordinates'][:, 1]
227-
else:
246+
else: # this is a edge case for a few spike sorting sessions
228247
assert channels_aligned['mlapdv'].shape[0] == 384
229248
NEUROPIXEL_VERSION = 1
230249
from ibllib.ephys.neuropixel import trace_header
@@ -238,7 +257,10 @@ def channel_locations_interpolation(channels_aligned, channels):
238257
# the brain locations have to be interpolated by nearest neighbour
239258
fcn_interp = interp1d(depth_aligned, channels_aligned['brainLocationIds_ccf_2017'][ind_aligned], kind='nearest')
240259
channels['brainLocationIds_ccf_2017'] = fcn_interp(depths)[iinv].astype(np.int32)
241-
return channels
260+
if brain_regions is not None:
261+
return _channels_alf2bunch(channels, brain_regions=brain_regions)
262+
else:
263+
return channels
242264

243265

244266
def _load_channel_locations_traj(eid, probe=None, one=None, revision=None, aligned=False,
@@ -531,7 +553,7 @@ def merge_clusters_channels(dic_clus, channels, keys_to_add_extra=None):
531553
dic_clus : dict of one.alf.io.AlfBunch
532554
1 bunch per probe, containing cluster information
533555
channels : dict of one.alf.io.AlfBunch
534-
1 bunch per probe, containing channels bunch with keys ('acronym', 'atlas_id')
556+
1 bunch per probe, containing channels bunch with keys ('acronym', 'atlas_id', 'x', 'y', z', 'localCoordinates')
535557
keys_to_add_extra : list of str
536558
Any extra keys to load into channels bunches
537559
@@ -541,7 +563,7 @@ def merge_clusters_channels(dic_clus, channels, keys_to_add_extra=None):
541563
clusters (1 bunch per probe) with new keys values.
542564
"""
543565
probe_labels = list(channels.keys()) # Convert dict_keys into list
544-
keys_to_add_default = ['acronym', 'atlas_id', 'x', 'y', 'z']
566+
keys_to_add_default = ['acronym', 'atlas_id', 'x', 'y', 'z', 'axial_um', 'lateral_um']
545567

546568
if keys_to_add_extra is None:
547569
keys_to_add = keys_to_add_default
@@ -550,10 +572,9 @@ def merge_clusters_channels(dic_clus, channels, keys_to_add_extra=None):
550572
keys_to_add = list(set(keys_to_add_extra + keys_to_add_default))
551573

552574
for label in probe_labels:
553-
try:
554-
clu_ch = dic_clus[label]['channels']
555-
556-
for key in keys_to_add:
575+
clu_ch = dic_clus[label]['channels']
576+
for key in keys_to_add:
577+
try:
557578
assert key in channels[label].keys() # Check key is in channels
558579
ch_key = channels[label][key]
559580
nch_key = len(ch_key) if ch_key is not None else 0
@@ -564,11 +585,9 @@ def merge_clusters_channels(dic_clus, channels, keys_to_add_extra=None):
564585
f'Probe {label}: merging channels and clusters for key "{key}" has {nch_key} on channels'
565586
f' but expected {max(clu_ch)}. Data in new cluster key "{key}" is returned empty.')
566587
dic_clus[label][key] = []
567-
except AssertionError:
568-
_logger.warning(
569-
f'Either clusters or channels does not have key {label}, could not'
570-
f' merge')
571-
continue
588+
except AssertionError:
589+
_logger.warning(f'Either clusters or channels does not have key {key}, could not merge')
590+
continue
572591

573592
return dic_clus
574593

brainbox/task/passive.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ def get_stim_aligned_activity(stim_events, spike_times, spike_depths, z_score_fl
200200
stim_activity = {}
201201
for stim_type, stim_times in stim_events.items():
202202

203+
# Get rid of any nan values
204+
stim_times = stim_times[~np.isnan(stim_times)]
203205
stim_intervals = np.c_[stim_times - pre_stim, stim_times + post_stim]
204206
base_intervals = np.c_[stim_times - base_stim, stim_times - pre_stim]
205207
out_intervals = stim_intervals[:, 1] > times[-1]

ibllib/ephys/np2_converter.py

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import numpy as np
55
from pathlib import Path
66
import copy
7+
import shutil
78
import logging
89
_logger = logging.getLogger('ibllib')
910

@@ -14,17 +15,19 @@ class NP2Converter:
1415
individual shanks
1516
"""
1617

17-
def __init__(self, ap_file, post_check=True, delete_original=False):
18+
def __init__(self, ap_file, post_check=True, delete_original=False, compress=True):
1819
"""
1920
:param ap_file: ap.bin spikeglx file to process
2021
:param post_check: whether to apply post-check integrity test to ensure split content is
2122
identical to original content (only applicable to NP2.4)
22-
:param delete_original: whether to delete the original ap file after data has been
23+
:param delete_original: whether to delete the original ap file after data has been split
24+
:param compress: whether to apply mtscomp to extracted .bin files
2325
split into shanks (only applicable to NP2.4)
2426
"""
2527
self.ap_file = Path(ap_file)
2628
self.sr = spikeglx.Reader(ap_file)
2729
self.post_check = post_check
30+
self.compress = compress
2831
self.delete_original = delete_original
2932
self.np_version = spikeglx._get_neuropixel_version_from_meta(self.sr.meta)
3033
self.check_metadata()
@@ -142,8 +145,10 @@ def _process_NP24(self, overwrite=False):
142145

143146
if self.post_check:
144147
self.check_NP24()
148+
if self.compress:
149+
self.compress_NP24(overwrite=overwrite)
145150
if self.delete_original:
146-
self.delete()
151+
self.delete_NP24()
147152

148153
return 1
149154

@@ -174,11 +179,15 @@ def _prepare_files_NP24(self, overwrite=False):
174179
probe_path = self.ap_file.parent.parent.joinpath(label + chr(97 + int(sh)) + self.extra)
175180

176181
if not probe_path.exists() or overwrite:
182+
if self.sr.is_mtscomp:
183+
ap_file_bin = self.ap_file.with_suffix('.bin').name
184+
else:
185+
ap_file_bin = self.ap_file.name
177186
probe_path.mkdir(parents=True, exist_ok=True)
178-
_shank_info['ap_file'] = probe_path.joinpath(self.ap_file.name)
187+
_shank_info['ap_file'] = probe_path.joinpath(ap_file_bin)
179188
_shank_info['ap_open_file'] = open(_shank_info['ap_file'], 'wb')
180189
_shank_info['lf_file'] = probe_path.joinpath(
181-
self.ap_file.name.replace('ap', 'lf'))
190+
ap_file_bin.replace('ap', 'lf'))
182191
_shank_info['lf_open_file'] = open(_shank_info['lf_file'], 'wb')
183192
else:
184193
self.already_exists = True
@@ -210,6 +219,7 @@ def _process_NP21(self, overwrite=False):
210219
wg = WindowGenerator(self.nsamples, self.samples_window, self.samples_overlap)
211220

212221
for first, last in wg.firstlast:
222+
213223
chunk_lf = self.extract_lfp(self.sr[first:last, :self.napch].T)
214224
chunk_lf_sync = self.extract_lfp_sync(self.sr[first:last, self.idxsyncch:].T)
215225

@@ -224,6 +234,9 @@ def _process_NP21(self, overwrite=False):
224234

225235
self._writemetadata_lf()
226236

237+
if self.compress:
238+
self.compress_NP21(overwrite=overwrite)
239+
227240
return 1
228241

229242
def _prepare_files_NP21(self, overwrite=False):
@@ -242,8 +255,9 @@ def _prepare_files_NP21(self, overwrite=False):
242255
shank_info = {}
243256
self.already_exists = False
244257

245-
lf_file = self.ap_file.parent.joinpath(self.ap_file.name.replace('ap', 'lf'))
246-
if not lf_file.exists() or overwrite:
258+
lf_file = self.ap_file.parent.joinpath(self.ap_file.name.replace('ap', 'lf')).with_suffix('.bin')
259+
lf_cbin_file = lf_file.with_suffix('.cbin')
260+
if not (lf_file.exists() or lf_cbin_file.exists()) or overwrite:
247261
for sh in n_shanks:
248262
_shank_info = {}
249263
# channels for individual shank + sync channel
@@ -293,15 +307,66 @@ def check_NP24(self):
293307

294308
self.check_completed = True
295309

310+
def compress_NP24(self, overwrite=False, **kwargs):
311+
"""
312+
Compress spikeglx files
313+
:return:
314+
"""
315+
for sh in self.shank_info.keys():
316+
bin_file = self.shank_info[sh]['ap_file']
317+
if overwrite:
318+
cbin_file = bin_file.with_suffix('.cbin')
319+
cbin_file.unlink()
320+
321+
sr_ap = spikeglx.Reader(bin_file)
322+
cbin_file = sr_ap.compress_file(**kwargs)
323+
sr_ap.close()
324+
bin_file.unlink()
325+
self.shank_info[sh]['ap_file'] = cbin_file
326+
327+
bin_file = self.shank_info[sh]['lf_file']
328+
if overwrite:
329+
cbin_file = bin_file.with_suffix('.cbin')
330+
cbin_file.unlink()
331+
sr_lf = spikeglx.Reader(bin_file)
332+
cbin_file = sr_lf.compress_file(**kwargs)
333+
sr_lf.close()
334+
bin_file.unlink()
335+
self.shank_info[sh]['lf_file'] = cbin_file
336+
337+
def compress_NP21(self, overwrite=False):
338+
"""
339+
Compress spikeglx files
340+
:return:
341+
"""
342+
for sh in self.shank_info.keys():
343+
if not self.sr.is_mtscomp:
344+
cbin_file = self.sr.compress_file()
345+
self.sr.close()
346+
self.ap_file.unlink()
347+
self.ap_file = cbin_file
348+
self.sr = spikeglx.Reader(self.ap_file)
349+
350+
bin_file = self.shank_info[sh]['lf_file']
351+
if overwrite:
352+
cbin_file = bin_file.with_suffix('.cbin')
353+
cbin_file.unlink()
354+
sr_lf = spikeglx.Reader(bin_file)
355+
cbin_file = sr_lf.compress_file()
356+
sr_lf.close()
357+
bin_file.unlink()
358+
self.shank_info[sh]['lf_file'] = cbin_file
359+
296360
def delete_NP24(self):
297361
"""
298362
Delete the original ap file that doesn't has all shanks in one file
299363
300364
:return:
301365
"""
302366
if self.check_completed and self.delete_original:
303-
# TODO need to delete the original wahhhhh
304-
pass
367+
_logger.info(f'Removing original files in folder {self.ap_file.parent}')
368+
self.sr.close()
369+
shutil.rmtree(self.ap_file.parent)
305370

306371
def _split2shanks(self, chunk, etype='ap'):
307372
"""

ibllib/io/extractors/signatures.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,13 @@
163163
# DATA REQUIRED FOR TASKS
164164
EPHYSTRIALS = [('_iblrig_taskData.raw.*', 'raw_behavior_data', True),
165165
('_iblrig_taskSettings.raw.*', 'raw_behavior_data', True),
166-
('_spikeglx_sync.npy', 'raw_ephys_data/**', True),
167-
('_spikeglx_sync.polarities.npy', 'raw_ephys_data/**', True),
168-
('_spikeglx_sync.times.npy', 'raw_ephys_data/**', True),
166+
('_spikeglx_sync.npy', 'raw_ephys_data*', True),
167+
('_spikeglx_sync.polarities.npy', 'raw_ephys_data*', True),
168+
('_spikeglx_sync.times.npy', 'raw_ephys_data*', True),
169169
('_iblrig_encoderEvents.raw', 'raw_behavior_data', True),
170170
('_iblrig_encoderPositions.raw', 'raw_behavior_data', True),
171-
('*wiring.json', 'raw_ephys_data/**', False),
172-
('*.meta', 'raw_ephys_data/**', True)]
171+
('*wiring.json', 'raw_ephys_data*', False),
172+
('*.meta', 'raw_ephys_data*', True)]
173173

174174
EPHYSPASSIVE = [('_iblrig_taskSettings.raw*', 'raw_behavior_data', True),
175175
('_spikeglx_sync.channels.*', 'raw_ephys_data*', True),

ibllib/io/spikeglx.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,18 @@ def get_neuropixel_version_from_files(ephys_files):
589589
return '3A'
590590

591591

592+
def get_probes_from_folder(session_path):
593+
# should glob the ephys files and get out the labels
594+
# This assumes the meta files exist on the server (this is the case for now but should it be?)
595+
ephys_files = glob_ephys_files(session_path, ext='meta')
596+
probes = []
597+
for files in ephys_files:
598+
if files['label']:
599+
probes.append(files['label'])
600+
601+
return probes
602+
603+
592604
def glob_ephys_files(session_path, suffix='.meta', ext='bin', recursive=True, bin_exists=True):
593605
"""
594606
From an arbitrary folder (usually session folder) gets the ap and lf files and labels

0 commit comments

Comments
 (0)