Skip to content

Commit c2aecb6

Browse files
olichek1o0
andauthored
One3 (#926)
* set ONE on the v3 branch * auto -> remote mode; fix unit tests and imports * fix the version reading from log bug * Fix brainbox.io tests - flake * set ONE on the latest v3.0.0 branch * bump ONE requirement after release --------- Co-authored-by: Miles Wells <k1o0@5tk.co>
1 parent 27c5a36 commit c2aecb6

File tree

12 files changed

+96
-41
lines changed

12 files changed

+96
-41
lines changed

brainbox/io/one.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,8 @@ def download_spike_sorting_object(self, obj, spike_sorter=None, dataset_types=No
918918
:param missing: 'raise' (default) or 'ignore'
919919
:return:
920920
"""
921-
spike_sorter = (spike_sorter or self.spike_sorter) or 'iblsorter'
921+
if spike_sorter is None:
922+
spike_sorter = self.spike_sorter if self.spike_sorter is not None else 'iblsorter'
922923
if len(self.collections) == 0:
923924
return {}, {}, {}
924925
self.collection = self._get_spike_sorting_collection(spike_sorter=spike_sorter)

brainbox/tests/test_behavior.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pathlib import Path
22
import unittest
33
from unittest import mock
4+
from functools import partial
45
import numpy as np
56
import pickle
67
import copy
@@ -250,7 +251,10 @@ def test_query_criterion(self):
250251
'ready4ephysrig': ['2019-04-10', 'abf5109c-d780-44c8-9561-83e857c7bc01'],
251252
'ready4recording': ['2019-04-11', '7dc3c44b-225f-4083-be3d-07b8562885f4']
252253
}
253-
with mock.patch.object(one.alyx, 'rest', return_value={'json': {'trained_criteria': status_map}}):
254+
255+
# Mock output of subjects read endpoint only
256+
side_effect = partial(self._rest_mock, one.alyx.rest, {'json': {'trained_criteria': status_map}})
257+
with mock.patch.object(one.alyx, 'rest', side_effect=side_effect):
254258
eid, n_sessions, n_days = train.query_criterion(subject, 'in_training', one=one)
255259
self.assertEqual('01390fcc-4f86-4707-8a3b-4d9309feb0a1', eid)
256260
self.assertEqual(1, n_sessions)
@@ -267,3 +271,24 @@ def test_query_criterion(self):
267271
self.assertIsNone(n_sessions)
268272
self.assertIsNone(n_days)
269273
self.assertRaises(ValueError, train.query_criterion, subject, 'foobar', one=one)
274+
275+
def _rest_mock(self, alyx_rest, return_value, *args, **kwargs):
276+
"""Mock return value of AlyxClient.rest function depending on input.
277+
278+
If using the subjects endpoint, return `return_value`. Otherwise, calls the original method.
279+
280+
Parameters
281+
----------
282+
alyx_rest : function
283+
one.webclient.AlyxClient.rest method.
284+
return_value : any
285+
The mock data to return.
286+
287+
Returns
288+
-------
289+
dict, list
290+
Either `return_value` or the original method output.
291+
"""
292+
if args[0] == 'subjects':
293+
return return_value
294+
return alyx_rest(*args, **kwargs)

ibllib/io/video.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,26 @@ def get_video_meta(video_path, one=None):
151151

152152

153153
def url_from_eid(eid, label=None, one=None):
154-
"""Return the video URL(s) for a given eid
155-
156-
:param eid: The session id
157-
:param label: The video label (e.g. 'body') or a tuple thereof
158-
:param one: An instance of ONE
159-
:return: The URL string if the label is a string, otherwise a dict of urls with labels as keys
154+
"""Return the video URL(s) for a given eid.
155+
156+
Parameters
157+
----------
158+
eid : UUID, str
159+
The session ID.
160+
label : str, tuple of str
161+
The video label (e.g. 'body') or a tuple thereof.
162+
one : one.api.One
163+
An instance of ONE.
164+
165+
Returns
166+
-------
167+
str, dict of str
168+
The URL string if the label is a string, otherwise a dict of urls with labels as keys.
169+
170+
Raises
171+
------
172+
ValueError
173+
Video label is unreckognized. See `VIDEO_LABELS` for valid labels.
160174
"""
161175
valid_labels = VIDEO_LABELS
162176
if not (label is None or np.isin(label, valid_labels).all()):

ibllib/pipes/ephys_tasks.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -702,11 +702,16 @@ def _fetch_iblsorter_run_version(log_file):
702702
'\x1b[0m15:39:37.919 [I] ibl:90 Starting Pykilosort version ibl_1.3.0^[[0m\n'
703703
"""
704704
with open(log_file) as fid:
705-
line = fid.readline()
706-
version = re.search('version (.*), output', line)
707-
version = version or re.search('version (.*)', line) # old versions have output, new have a version line
708-
version = re.sub(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])', '', version.group(1))
709-
return version
705+
for m in range(50):
706+
line = fid.readline()
707+
print(line.strip())
708+
version = re.search('version (.*)', line)
709+
if not line or version:
710+
break
711+
if version is not None:
712+
version = re.sub(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])', '', version.group(1))
713+
version = version.replace(',', ' ').split(' ')[0] # breaks the string after the first space
714+
return version
710715

711716
def _run_iblsort(self, ap_file):
712717
"""

ibllib/pipes/mesoscope_tasks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1135,7 +1135,7 @@ def register_fov(self, meta: dict, suffix: str = None) -> (list, list):
11351135
assert set(fov.keys()) >= {'MLAPDV', 'nXnYnZ', 'roiUUID'}
11361136
# Field of view
11371137
alyx_FOV = {
1138-
'session': self.session_path.as_posix() if dry else self.path2eid(),
1138+
'session': self.session_path.as_posix() if dry else str(self.path2eid()),
11391139
'imaging_type': 'mesoscope', 'name': f'FOV_{i:02}',
11401140
'stack': stack_ids.get(fov['roiUUID'])
11411141
}

ibllib/pipes/tasks.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
from one.api import ONE
9292
from one import webclient
9393
import one.alf.io as alfio
94+
from one.alf.path import ALFPath
9495

9596
_logger = logging.getLogger(__name__)
9697
TASK_STATUS_SET = {'Waiting', 'Held', 'Started', 'Errored', 'Empty', 'Complete', 'Incomplete', 'Abandoned'}
@@ -493,10 +494,13 @@ def assert_expected_inputs(self, raise_error=True, raise_ambiguous=False):
493494
# Some sessions may contain revisions and without ONE it's difficult to determine which
494495
# are the default datasets. Likewise SDSC may contain multiple datasets with different
495496
# UUIDs in the name after patching data.
496-
variant_datasets = alfio.find_variants(files, extra=False)
497+
valid_alf_files = filter(ALFPath.is_valid_alf, files)
498+
variant_datasets = alfio.find_variants(valid_alf_files, extra=False)
499+
if len(variant_datasets) < len(files):
500+
_logger.warning('Some files are not ALF datasets and will not be checked for ambiguity')
497501
if any(map(len, variant_datasets.values())):
498502
# Keep those with variants and make paths relative to session for logging purposes
499-
to_frag = lambda x: x.relative_to(self.session_path).as_posix() # noqa
503+
to_frag = lambda x: x.relative_to_session().as_posix() # noqa
500504
ambiguous = {
501505
to_frag(k): [to_frag(x) for x in v]
502506
for k, v in variant_datasets.items() if any(v)}

ibllib/qc/task_qc_viewer/task_qc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ def show_session_task_qc(qc_or_session=None, bpod_only=False, local=False, one=N
244244
task_qc = qc_or_session
245245
qc = QcFrame(task_qc)
246246
else: # assumed to be eid or session path
247-
one = one or ONE(mode='local' if local else 'auto')
247+
one = one or ONE(mode='local' if local else 'remote')
248248
if not is_session_path(Path(qc_or_session)):
249249
eid = one.to_eid(qc_or_session)
250250
session_path = one.eid2path(eid)

ibllib/tests/qc/test_base_qc.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@ class TestQC(unittest.TestCase):
1616
"""Test base QC class."""
1717

1818
eid = None
19-
"""str: An experiment UUID to use for updating QC fields."""
19+
"""UUID: An experiment UUID to use for updating QC fields."""
2020

2121
@classmethod
2222
def setUpClass(cls):
23-
_, eid = register_new_session(one, subject='ZM_1150')
24-
cls.eid = str(eid)
23+
_, cls.eid = register_new_session(one, subject='ZM_1150')
2524

2625
def setUp(self) -> None:
2726
ses = one.alyx.rest('sessions', 'partial_update', id=self.eid, data={'qc': 'NOT_SET'})
@@ -67,7 +66,7 @@ def test_update(self) -> None:
6766
current = self.qc.update(outcome)
6867
self.assertIs(spec.QC.PASS, current, 'Failed to update QC field')
6968
# Check that extended QC field was updated
70-
extended = one.alyx.get('/sessions/' + self.eid, clobber=True)['extended_qc']
69+
extended = one.alyx.get('/sessions/' + str(self.eid), clobber=True)['extended_qc']
7170
updated = 'experimenter' in extended and extended['experimenter'] == outcome
7271
self.assertTrue(updated, 'failed to update extended_qc field')
7372
# Check that outcome property is set
@@ -78,7 +77,7 @@ def test_update(self) -> None:
7877
namespace = 'task'
7978
current = self.qc.update(outcome, namespace=namespace)
8079
self.assertIs(spec.QC.FAIL, current, 'Failed to update QC field')
81-
extended = one.alyx.get('/sessions/' + self.eid, clobber=True)['extended_qc']
80+
extended = one.alyx.get('/sessions/' + str(self.eid), clobber=True)['extended_qc']
8281
updated = namespace in extended and extended[namespace] == outcome.upper()
8382
self.assertTrue(updated, 'failed to update extended_qc field')
8483

@@ -87,7 +86,7 @@ def test_update(self) -> None:
8786
namespace = 'task'
8887
current = self.qc.update(outcome)
8988
self.assertNotEqual(spec.QC.PASS, current, 'QC field updated with less severe outcome')
90-
extended = one.alyx.get('/sessions/' + self.eid, clobber=True)['extended_qc']
89+
extended = one.alyx.get('/sessions/' + str(self.eid), clobber=True)['extended_qc']
9190
updated = namespace in extended and extended[namespace] != outcome
9291
self.assertTrue(updated, 'failed to update extended_qc field')
9392

@@ -96,7 +95,7 @@ def test_update(self) -> None:
9695
namespace = 'task'
9796
current = self.qc.update(outcome, override=True, namespace=namespace)
9897
self.assertEqual(spec.QC.NOT_SET, current, 'QC field updated with less severe outcome')
99-
extended = one.alyx.get('/sessions/' + self.eid, clobber=True)['extended_qc']
98+
extended = one.alyx.get('/sessions/' + str(self.eid), clobber=True)['extended_qc']
10099
updated = namespace in extended and extended[namespace] == outcome
101100
self.assertTrue(updated, 'failed to update extended_qc field')
102101

ibllib/tests/test_io.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,9 @@ def test_delete_empty_folders(self):
370370
class TestVideo(unittest.TestCase):
371371
@classmethod
372372
def setUpClass(cls) -> None:
373-
cls.one = ONE(**TEST_DB)
373+
cls.one = ONE(**TEST_DB, mode='local')
374+
if cls.one._cache.sessions.empty:
375+
cls.one.load_cache()
374376
if 'public' in cls.one.alyx._par.HTTP_DATA_SERVER:
375377
cls.one.alyx._par = cls.one.alyx._par.set(
376378
'HTTP_DATA_SERVER', cls.one.alyx._par.HTTP_DATA_SERVER.rsplit('/', 1)[0])
@@ -398,7 +400,7 @@ def test_label_from_path(self):
398400
self.assertIsNone(label)
399401

400402
def test_url_from_eid(self):
401-
assert self.one.mode != 'remote'
403+
self.one.mode = 'local'
402404
actual = video.url_from_eid(self.eid, 'left', self.one)
403405
self.assertEqual(self.url, actual)
404406
actual = video.url_from_eid(self.eid, one=self.one)
@@ -410,10 +412,12 @@ def test_url_from_eid(self):
410412

411413
# Test remote mode
412414
old_mode = self.one.mode
413-
self.one.mode = 'remote'
414-
actual = video.url_from_eid(self.eid, label='left', one=self.one)
415-
self.assertEqual(self.url, actual)
416-
self.one.mode = old_mode
415+
try:
416+
self.one.mode = 'remote'
417+
actual = video.url_from_eid(self.eid, label='left', one=self.one)
418+
self.assertEqual(self.url, actual)
419+
finally:
420+
self.one.mode = old_mode
417421

418422
# Test arg checks
419423
with self.assertRaises(ValueError):

ibllib/tests/test_mesoscope.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pathlib import Path
99
import subprocess
1010
from copy import deepcopy
11+
import uuid
1112

1213
from one.api import ONE
1314
import numpy as np
@@ -286,7 +287,7 @@ def test_update_surgery_json(self):
286287
task.update_surgery_json(meta, normal_vector)
287288
finally:
288289
# ONE function is cached so we must reset the mode for other tests
289-
one.mode = 'auto'
290+
one.mode = 'remote'
290291

291292

292293
class TestRegisterFOV(unittest.TestCase):
@@ -311,17 +312,19 @@ def test_register_fov(self):
311312
'bottomLeft': [2317.3, -2181.4, -466.3], 'bottomRight': [2862.7, -2206.9, -679.4],
312313
'center': [2596.1, -1900.5, -588.6]}
313314
meta = {'FOV': [{'MLAPDV': mlapdv, 'nXnYnZ': [512, 512, 1], 'roiUUID': 0}]}
314-
with unittest.mock.patch.object(task.one.alyx, 'rest') as mock_rest:
315+
eid = uuid.uuid4()
316+
with unittest.mock.patch.object(task.one.alyx, 'rest') as mock_rest, \
317+
unittest.mock.patch.object(task.one, 'path2eid', return_value=eid):
315318
task.register_fov(meta, 'estimate')
316319
calls = mock_rest.call_args_list
317-
self.assertEqual(3, len(calls))
320+
self.assertEqual(2, len(calls))
318321

319-
args, kwargs = calls[1]
322+
args, kwargs = calls[0]
320323
self.assertEqual(('fields-of-view', 'create'), args)
321-
expected = {'data': {'session': None, 'imaging_type': 'mesoscope', 'name': 'FOV_00', 'stack': None}}
324+
expected = {'data': {'session': str(eid), 'imaging_type': 'mesoscope', 'name': 'FOV_00', 'stack': None}}
322325
self.assertEqual(expected, kwargs)
323326

324-
args, kwargs = calls[2]
327+
args, kwargs = calls[1]
325328
self.assertEqual(('fov-location', 'create'), args)
326329
expected = ['field_of_view', 'default_provenance', 'coordinate_system', 'n_xyz', 'provenance', 'x', 'y', 'z',
327330
'brain_region']
@@ -350,7 +353,7 @@ def tearDown(self) -> None:
350353
The ONE function is cached and therefore the One object persists beyond this test.
351354
Here we return the mode back to the default after testing behaviour in offline mode.
352355
"""
353-
self.one.mode = 'auto'
356+
self.one.mode = 'remote'
354357

355358

356359
class TestImagingMeta(unittest.TestCase):

ibllib/tests/test_oneibl.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from one.api import ONE
1818
from one.webclient import AlyxClient
1919
import one.alf.exceptions as alferr
20-
from one.util import QC_TYPE
20+
from one.alf.cache import QC_TYPE
2121
import iblutil.io.params as iopar
2222

2323
from ibllib.oneibl import patcher, registration, data_handlers as handlers
@@ -612,7 +612,7 @@ def test_server_upload_data(self, register_dataset_mock):
612612

613613
def test_getData(self):
614614
"""Test for DataHandler.getData method."""
615-
one = ONE(**TEST_DB, mode='auto')
615+
one = ONE(**TEST_DB, mode='remote')
616616
session_path = Path('KS005/2019-04-01/001')
617617
task = ChoiceWorldTrialsBpod(session_path, one=one, collection='raw_behavior_data')
618618
task.get_signatures()
@@ -698,7 +698,7 @@ class TestSDSCDataHandler(unittest.TestCase):
698698
def setUp(self):
699699
tmp = tempfile.TemporaryDirectory()
700700
self.addCleanup(tmp.cleanup)
701-
self.one = ONE(**TEST_DB, mode='auto')
701+
self.one = ONE(**TEST_DB, mode='remote')
702702
self.patch_path = Path(tmp.name, 'patch')
703703
self.root_path = Path(tmp.name, 'root')
704704
self.root_path.mkdir(), self.patch_path.mkdir()

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ ibl-neuropixel>=1.6.2
2727
iblutil>=1.13.0
2828
iblqt>=0.4.2
2929
mtscomp>=1.0.1
30-
ONE-api>=2.11
30+
ONE-api==3.0b3
3131
phylib>=2.6.0
3232
psychofit
3333
slidingRP>=1.1.1 # steinmetz lab refractory period metrics

0 commit comments

Comments
 (0)