From d4951e744582e735cd878444bcb927d43de43b97 Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Mon, 31 Jul 2023 11:25:53 +0200 Subject: [PATCH 1/5] (WIP) extend probe constructor --- src/probeinterface/io.py | 38 +++++++++++++++---------- src/probeinterface/probe.py | 57 ++++++++++++++++++++++++++++++++----- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 78f283b..6069d4e 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -942,10 +942,11 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe imro_table_header = tuple(map(int, imro_table_header_str[1:].split(","))) if len(imro_table_header) == 3: # In older versions of neuropixel arrays (phase 3A), imro tables were structured differently. - probe_serial_number, probe_option, num_contact = imro_table_header + serial_number, probe_option, num_contact = imro_table_header imDatPrb_type = "Phase3a" elif len(imro_table_header) == 2: imDatPrb_type, num_contact = imro_table_header + serial_number = None else: raise ValueError(f"read_imro error, the header has a strange length: {imro_table_header}") @@ -953,6 +954,8 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe imDatPrb_type = probe_number_to_probe_type[imDatPrb_pn] probe_description = npx_probe[imDatPrb_type] + probe_name = probe_description["probe_name"] + fields = probe_description["fields_in_imro_table"] contact_info = {k: [] for k in fields} for field_values_str in imro_table_values_list: # Imro table values look like '(value, value, value, ... ' @@ -986,7 +989,7 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe contact_ids = [f"e{elec_id}" for elec_id in elec_ids] # construct Probe object - probe = Probe(ndim=2, si_units="um") + probe = Probe(ndim=2, si_units="um", model_name=probe_name, manufacturer="IMEC", serial_number=serial_number) probe.set_contacts( positions=positions, shapes="square", @@ -1009,10 +1012,7 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe probe.set_planar_contour(contour) # this is scalar annotations - probe_name = probe_description["probe_name"] probe.annotate( - name=probe_name, - manufacturer="IMEC", probe_type=imDatPrb_type, ) @@ -1295,7 +1295,8 @@ def read_openephys( slot = np_probe.attrib["slot"] port = np_probe.attrib["port"] dock = np_probe.attrib["dock"] - np_serial_number = np_probe.attrib["probe_serial_number"] + part_number = np_probe.attrib["part_number"] + serial_number = np_probe.attrib["probe_serial_number"] # read channels channels = np_probe.find("CHANNELS") channel_names = np.array(list(channels.attrib.keys())) @@ -1368,14 +1369,15 @@ def read_openephys( contact_ids.append(f"e{contact_id}") np_probe_dict = { - "channel_names": channel_names, + "model_name": pname, "shank_ids": shank_ids, "contact_ids": contact_ids, "positions": positions, "slot": slot, "port": port, "dock": dock, - "serial_number": np_serial_number, + "serial_number": serial_number, + "part_number": part_number, "ptype": ptype, } # Sequentially assign probe names @@ -1468,7 +1470,7 @@ def read_openephys( np_probe = np_probes[probe_idx] positions = np_probe_info["positions"] shank_ids = np_probe_info["shank_ids"] - pname = np_probe.attrib["probe_name"] + pname = np_probes_info["name"] ptype = np_probe_info["ptype"] if ptype in npx_probe: @@ -1491,7 +1493,14 @@ def read_openephys( if contact_ids is not None: contact_ids = np.array(contact_ids)[chans_saved] - probe = Probe(ndim=2, si_units="um") + probe = Probe( + ndim=2, + si_units="um", + name=np_probe_info["name"], + serial_number=np_probe_info["serial_number"], + manufacturer="IMEC", + model_name=np_probe_info["model_name"], + ) probe.set_contacts( positions=positions, shapes="square", @@ -1499,11 +1508,10 @@ def read_openephys( shape_params={"width": contact_width}, ) probe.annotate( - name=pname, - manufacturer="IMEC", - probe_name=pname, - probe_part_number=np_probe.attrib["probe_part_number"], - probe_serial_number=np_probe.attrib["probe_serial_number"], + part_number=np_probe.attrib["part_number"], + slot=np_probe.attrib["slot"], + dock=np_probe.attrib["dock"], + port=np_probe.attrib["port"], ) if contact_ids is not None: diff --git a/src/probeinterface/probe.py b/src/probeinterface/probe.py index ba2548c..a5160f8 100644 --- a/src/probeinterface/probe.py +++ b/src/probeinterface/probe.py @@ -15,7 +15,7 @@ class Probe: """ - def __init__(self, ndim=2, si_units="um"): + def __init__(self, ndim=2, si_units="um", name=None, serial_number=None, model_name=None, manufacturer=None): """ Some attributes are protected and have to be set with setters: * set_contacts(...) @@ -27,7 +27,18 @@ def __init__(self, ndim=2, si_units="um"): Handles 2D or 3D probe si_units: str 'um', 'mm', 'm' + name: str + The name of the probe + serial_number: str + The serial number of the probe + model_name: str + The model of the probe + manufacturer: str + The manufacturer of the probe + Returns + ------- + Probe: instance of Probe """ assert ndim in (2, 3) @@ -58,7 +69,13 @@ def __init__(self, ndim=2, si_units="um"): # annotation: a dict that contains all meta information about # the probe (name, manufacturor, date of production, ...) - self.annotations = dict(name="") + self.annotations = dict() + self.annotate( + name=name if name is not None else "", + serial_number=serial_number if serial_number is not None else "", + model=model_name if model_name is not None else "", + manufacturer=manufacturer if manufacturer is not None else "", + ) # same idea but handle in vector way for contacts self.contact_annotations = dict() @@ -90,17 +107,43 @@ def contact_ids(self): def shank_ids(self): return self._shank_ids + @property + def name(self): + return self.annotations.get("name", "") + + @property + def serial_number(self): + return self.annotations.get("serial_number", "") + + @property + def model_name(self): + return self.annotations.get("model_name", "") + + @property + def manufacturer(self): + return self.annotations.get("manufacturer", "") + def get_title(self): if self.contact_positions is None: txt = "Undefined probe" else: n = self.get_contact_count() - name = self.annotations.get("name", "") - manufacturer = self.annotations.get("manufacturer", "") - if len(name) > 0 or len(manufacturer): - txt = f"{manufacturer} - {name} - {n}ch" + name = self.name + serial_number = self.serial_number + model_name = self.model_name + manufacturer = self.manufacturer + txt = "" + if len(name) > 0: + txt += f"{name}" else: - txt = f"Probe - {n}ch" + txt += f"Probe" + if len(manufacturer) > 0: + txt += f" - {manufacturer}" + if len(model_name) > 0: + txt += f" - {model_name}" + if len(serial_number) > 0: + txt += f" - {serial_number}" + txt += f" - {n}ch" if self.shank_ids is not None: num_shank = self.get_shank_count() txt += f" - {num_shank}shanks" From 886e7b13dcd295d218f694d378bc1dc5581e67ad Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Mon, 31 Jul 2023 14:54:00 +0200 Subject: [PATCH 2/5] Update io and tests --- src/probeinterface/io.py | 48 ++++++++++++++++++++++----------- src/probeinterface/library.py | 8 +++++- src/probeinterface/probe.py | 6 +++-- tests/test_io/test_openephys.py | 23 ++++++++-------- tests/test_io/test_spikeglx.py | 38 ++++++++++++++------------ 5 files changed, 76 insertions(+), 47 deletions(-) diff --git a/src/probeinterface/io.py b/src/probeinterface/io.py index 6069d4e..6775066 100644 --- a/src/probeinterface/io.py +++ b/src/probeinterface/io.py @@ -520,7 +520,7 @@ def read_maxwell(file: Union[str, Path], well_name: str = "well000", rec_name: s prb["channel_groups"][1]["geometry"] = geometry prb["channel_groups"][1]["channels"] = channels - probe = Probe(ndim=2, si_units="um") + probe = Probe(ndim=2, si_units="um", manufacturer="Maxwell Biosystems") chans = np.array(prb["channel_groups"][1]["channels"], dtype="int64") positions = np.array([prb["channel_groups"][1]["geometry"][c] for c in chans], dtype="float64") @@ -567,7 +567,7 @@ def read_3brain(file: Union[str, Path], mea_pitch: float = 42, electrode_width: cols = channels["Col"] - 1 positions = np.vstack((rows, cols)).T * mea_pitch - probe = Probe(ndim=2, si_units="um") + probe = Probe(ndim=2, si_units="um", manufacturer="3Brain") probe.set_contacts(positions=positions, shapes="square", shape_params={"width": electrode_width}) probe.annotate_contacts(row=rows) probe.annotate_contacts(col=cols) @@ -600,7 +600,7 @@ def write_prb(file, probegroup, total_nb_channels=None, radius=None, group_mode= assert group_mode in ("by_probe", "by_shank") if len(probegroup.probes) == 0: - raise ValueError("Bad boy") + raise ValueError("The probe group must have at least one probe") for probe in probegroup.probes: if probe.device_channel_indices is None: @@ -942,11 +942,10 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe imro_table_header = tuple(map(int, imro_table_header_str[1:].split(","))) if len(imro_table_header) == 3: # In older versions of neuropixel arrays (phase 3A), imro tables were structured differently. - serial_number, probe_option, num_contact = imro_table_header + probe_serial_number, probe_option, num_contact = imro_table_header imDatPrb_type = "Phase3a" elif len(imro_table_header) == 2: imDatPrb_type, num_contact = imro_table_header - serial_number = None else: raise ValueError(f"read_imro error, the header has a strange length: {imro_table_header}") @@ -989,7 +988,7 @@ def _read_imro_string(imro_str: str, imDatPrb_pn: Optional[str] = None) -> Probe contact_ids = [f"e{elec_id}" for elec_id in elec_ids] # construct Probe object - probe = Probe(ndim=2, si_units="um", model_name=probe_name, manufacturer="IMEC", serial_number=serial_number) + probe = Probe(ndim=2, si_units="um", model_name=probe_name, manufacturer="IMEC") probe.set_contacts( positions=positions, shapes="square", @@ -1102,10 +1101,27 @@ def read_spikeglx(file: Union[str, Path]) -> Probe: assert "imroTbl" in meta, "Could not find imroTbl field in meta file!" imro_table = meta["imroTbl"] + + # read serial number + imDatPrb_serial_number = meta.get("imDatPrb_sn", None) + if imDatPrb_serial_number is None: # this is for Phase3A + imDatPrb_serial_number = meta.get("imProbeSN", None) + + # read other metadata imDatPrb_pn = meta.get("imDatPrb_pn", None) + imDatPrb_port = meta.get("imDatPrb_port", None) + imDatPrb_slot = meta.get("imDatPrb_slot", None) + imDatPrb_part_number = meta.get("imDatPrb_pn", None) probe = _read_imro_string(imro_str=imro_table, imDatPrb_pn=imDatPrb_pn) + # add serial number and other annotations + probe.annotate(serial_number=imDatPrb_serial_number) + probe.annotate(part_number=imDatPrb_part_number) + probe.annotate(port=imDatPrb_port) + probe.annotate(slot=imDatPrb_slot) + probe.annotate(serial_number=imDatPrb_serial_number) + # sometimes we need to slice the probe when not all channels are saved saved_chans = get_saved_channel_indices_from_spikeglx_meta(meta_file) # remove the SYS chans @@ -1295,8 +1311,8 @@ def read_openephys( slot = np_probe.attrib["slot"] port = np_probe.attrib["port"] dock = np_probe.attrib["dock"] - part_number = np_probe.attrib["part_number"] - serial_number = np_probe.attrib["probe_serial_number"] + probe_part_number = np_probe.attrib["probe_part_number"] + probe_serial_number = np_probe.attrib["probe_serial_number"] # read channels channels = np_probe.find("CHANNELS") channel_names = np.array(list(channels.attrib.keys())) @@ -1376,8 +1392,8 @@ def read_openephys( "slot": slot, "port": port, "dock": dock, - "serial_number": serial_number, - "part_number": part_number, + "serial_number": probe_serial_number, + "part_number": probe_part_number, "ptype": ptype, } # Sequentially assign probe names @@ -1470,7 +1486,7 @@ def read_openephys( np_probe = np_probes[probe_idx] positions = np_probe_info["positions"] shank_ids = np_probe_info["shank_ids"] - pname = np_probes_info["name"] + pname = np_probe_info["name"] ptype = np_probe_info["ptype"] if ptype in npx_probe: @@ -1508,10 +1524,10 @@ def read_openephys( shape_params={"width": contact_width}, ) probe.annotate( - part_number=np_probe.attrib["part_number"], - slot=np_probe.attrib["slot"], - dock=np_probe.attrib["dock"], - port=np_probe.attrib["port"], + part_number=np_probe_info["part_number"], + slot=np_probe_info["slot"], + dock=np_probe_info["dock"], + port=np_probe_info["port"], ) if contact_ids is not None: @@ -1639,7 +1655,7 @@ def read_mearec(file: Union[str, Path]) -> Probe: description = electrodes_info["description"][()] mearec_description = description.decode("utf-8") if isinstance(description, bytes) else description - probe = Probe(ndim=2, si_units="um") + probe = Probe(ndim=2, si_units="um", model_name=mearec_name) plane = "yz" # default if "plane" in electrodes_info_keys: diff --git a/src/probeinterface/library.py b/src/probeinterface/library.py index e5686d6..6a0cb0c 100644 --- a/src/probeinterface/library.py +++ b/src/probeinterface/library.py @@ -71,7 +71,7 @@ def get_from_cache(manufacturer, probe_name): return probe -def get_probe(manufacturer, probe_name): +def get_probe(manufacturer, probe_name, name=None): """ Get probe from ProbeInterface library @@ -81,6 +81,8 @@ def get_probe(manufacturer, probe_name): The probe manufacturer (e.g. 'cambridgeneurotech') probe_name : str The probe name + name : str or None + Optional name for the probe Returns ---------- @@ -93,5 +95,9 @@ def get_probe(manufacturer, probe_name): if probe is None: download_probeinterface_file(manufacturer, probe_name) probe = get_from_cache(manufacturer, probe_name) + if probe.annotations["manufacturer"] == "": + probe.annotations["manufacturer"] = manufacturer + if name is not None: + probe.annotations["name"] = name return probe diff --git a/src/probeinterface/probe.py b/src/probeinterface/probe.py index a5160f8..1905a28 100644 --- a/src/probeinterface/probe.py +++ b/src/probeinterface/probe.py @@ -73,7 +73,7 @@ def __init__(self, ndim=2, si_units="um", name=None, serial_number=None, model_n self.annotate( name=name if name is not None else "", serial_number=serial_number if serial_number is not None else "", - model=model_name if model_name is not None else "", + model_name=model_name if model_name is not None else "", manufacturer=manufacturer if manufacturer is not None else "", ) # same idea but handle in vector way for contacts @@ -947,7 +947,9 @@ def to_image(self, values, pixel_size=0.5, num_pixel=None, method="linear", xlim except ImportError: raise ImportError("to_image() requires the scipy package") assert self.ndim == 2 - assert values.shape == (self.get_contact_count(),), "Bad boy: values must have size equal contact count" + assert values.shape == ( + self.get_contact_count(), + ), "Shape mismatch: values must have the same size as contact count" if xlims is None: x0 = np.min(self.contact_positions[:, 0]) diff --git a/tests/test_io/test_openephys.py b/tests/test_io/test_openephys.py index c6b086d..0ef312e 100644 --- a/tests/test_io/test_openephys.py +++ b/tests/test_io/test_openephys.py @@ -13,7 +13,7 @@ def test_NP2(): # NP2 probe = read_openephys(data_path / "OE_Neuropix-PXI" / "settings.xml") assert probe.get_shank_count() == 1 - assert "2.0 - Single Shank" in probe.annotations["name"] + assert "2.0 - Single Shank" in probe.model_name def test_NP1_subset(): @@ -23,7 +23,7 @@ def test_NP1_subset(): ) assert probe_ap.get_shank_count() == 1 - assert "1.0" in probe_ap.annotations["name"] + assert "1.0" in probe_ap.model_name assert len(probe_ap.contact_positions) == 200 probe_lf = read_openephys( @@ -31,7 +31,7 @@ def test_NP1_subset(): ) assert probe_lf.get_shank_count() == 1 - assert "1.0" in probe_lf.annotations["name"] + assert "1.0" in probe_lf.model_name assert len(probe_lf.contact_positions) == 200 # Not specifying the stream_name should raise an Exception, because both the ProbeA-AP and @@ -47,7 +47,7 @@ def test_multiple_probes(): ) assert probeA.get_shank_count() == 1 - assert "1.0" in probeA.annotations["name"] + assert "1.0" in probeA.model_name probeB = read_openephys( data_path / "OE_Neuropix-PXI-multi-probe" / "settings.xml", @@ -69,10 +69,10 @@ def test_multiple_probes(): assert probeD.get_shank_count() == 1 - assert probeA.annotations["probe_serial_number"] == "17131307831" - assert probeB.annotations["probe_serial_number"] == "20403311724" - assert probeC.annotations["probe_serial_number"] == "20403311714" - assert probeD.annotations["probe_serial_number"] == "21144108671" + assert probeA.serial_number == "17131307831" + assert probeB.serial_number == "20403311724" + assert probeC.serial_number == "20403311714" + assert probeD.serial_number == "21144108671" probeA2 = read_openephys( data_path / "OE_Neuropix-PXI-multi-probe" / "settings_2.xml", @@ -89,7 +89,7 @@ def test_multiple_probes(): ) assert probeB2.get_shank_count() == 1 - assert "2.0 - Multishank" in probeB2.annotations["name"] + assert "2.0 - Multishank" in probeB2.model_name ypos = probeB2.contact_positions[:, 1] assert np.min(ypos) >= 0 @@ -103,10 +103,11 @@ def test_older_than_06_format(): ) assert probe.get_shank_count() == 4 - assert "2.0 - Multishank" in probe.annotations["name"] + assert "2.0 - Multishank" in probe.model_name ypos = probe.contact_positions[:, 1] assert np.min(ypos) >= 0 if __name__ == "__main__": - test_NP1_subset() + test_multiple_probes() + test_older_than_06_format() diff --git a/tests/test_io/test_spikeglx.py b/tests/test_io/test_spikeglx.py index 07c6a59..3e2e1ad 100644 --- a/tests/test_io/test_spikeglx.py +++ b/tests/test_io/test_spikeglx.py @@ -34,12 +34,12 @@ def test_get_saved_channel_indices_from_spikeglx_meta(): def test_NP1(): probe = read_spikeglx(data_path / "Noise_g0_t0.imec0.ap.meta") - assert "1.0" in probe.annotations["name"] + assert "1.0" in probe.model_name def test_NP2_1_shanks(): probe = read_spikeglx(data_path / "p2_g0_t0.imec0.ap.meta") - assert "2.0" in probe.annotations["name"] + assert "2.0" in probe.model_name assert probe.get_shank_count() == 1 @@ -47,8 +47,8 @@ def test_NP_phase3A(): # Data provided by rtraghavan probe = read_spikeglx(data_path / "phase3a.imec.ap.meta") - assert probe.annotations["name"] == "Phase3a" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Phase3a" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == "Phase3a" assert probe.ndim == 2 @@ -65,8 +65,8 @@ def test_NP_phase3A(): def test_NP2_4_shanks(): probe = read_spikeglx(data_path / "NP2_4_shanks.imec0.ap.meta") - assert probe.annotations["name"] == "Neuropixels 2.0 - Four Shank" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Neuropixels 2.0 - Four Shank" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == 24 assert probe.ndim == 2 @@ -89,8 +89,8 @@ def test_NP2_4_shanks_with_different_electrodes_saved(): # Data provided by Jennifer Colonell probe = read_spikeglx(data_path / "NP2_4_shanks_save_different_electrodes.imec0.ap.meta") - assert probe.annotations["name"] == "Neuropixels 2.0 - Four Shank" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Neuropixels 2.0 - Four Shank" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == 24 assert probe.ndim == 2 @@ -113,7 +113,7 @@ def test_NP2_4_shanks_with_different_electrodes_saved(): def test_NP1_large_depth_span(): # Data provided by Tom Bugnon NP1 with large Depth span probe = read_spikeglx(data_path / "allan-longcol_g0_t0.imec0.ap.meta") - assert "1.0" in probe.annotations["name"] + assert "1.0" in probe.model_name assert probe.get_shank_count() == 1 ypos = probe.contact_positions[:, 1] assert (np.max(ypos) - np.min(ypos)) > 7600 @@ -123,7 +123,7 @@ def test_NP1_other_example(): # Data provided by Tom Bugnon NP1 probe = read_spikeglx(data_path / "doppio-checkerboard_t0.imec0.ap.meta") print(probe) - assert "1.0" in probe.annotations["name"] + assert "1.0" in probe.model_name assert probe.get_shank_count() == 1 ypos = probe.contact_positions[:, 1] assert (np.max(ypos) - np.min(ypos)) > 7600 @@ -140,8 +140,8 @@ def test_NPH_long_staggered(): # Data provided by Nate Dolensek probe = read_spikeglx(data_path / "non_human_primate_long_staggered.imec0.ap.meta") - assert probe.annotations["name"] == "Neuropixels 1.0-NHP - long SOI90 staggered" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Neuropixels 1.0-NHP - long SOI90 staggered" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == 1030 assert probe.ndim == 2 @@ -195,8 +195,8 @@ def test_NPH_short_linear_probe_type_0(): # Data provided by Jonathan A Michaels probe = read_spikeglx(data_path / "non_human_primate_short_linear_probe_type_0.meta") - assert probe.annotations["name"] == "Neuropixels 1.0-NHP - short" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Neuropixels 1.0-NHP - short" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == 1015 assert probe.ndim == 2 @@ -246,8 +246,8 @@ def test_ultra_probe(): # Data provided by Alessio probe = read_spikeglx(data_path / "npUltra.meta") - assert probe.annotations["name"] == "Ultra probe" - assert probe.annotations["manufacturer"] == "IMEC" + assert probe.model_name == "Ultra probe" + assert probe.manufacturer == "IMEC" assert probe.annotations["probe_type"] == 1100 # Test contact geometry @@ -271,4 +271,8 @@ def test_ultra_probe(): def test_CatGT_NP1(): probe = read_spikeglx(data_path / "catgt.meta") - assert "1.0" in probe.annotations["name"] + assert "1.0" in probe.model_name + + +if __name__ == "__main__": + test_NP1() From f56a9d770fd73223023d2a6a5a450713c63e8f87 Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Tue, 29 Aug 2023 16:33:29 +0200 Subject: [PATCH 3/5] Make constructor annotations optional --- src/probeinterface/probe.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/probeinterface/probe.py b/src/probeinterface/probe.py index 1905a28..2b68901 100644 --- a/src/probeinterface/probe.py +++ b/src/probeinterface/probe.py @@ -70,12 +70,15 @@ def __init__(self, ndim=2, si_units="um", name=None, serial_number=None, model_n # annotation: a dict that contains all meta information about # the probe (name, manufacturor, date of production, ...) self.annotations = dict() - self.annotate( - name=name if name is not None else "", - serial_number=serial_number if serial_number is not None else "", - model_name=model_name if model_name is not None else "", - manufacturer=manufacturer if manufacturer is not None else "", - ) + if name is not None: + self.annotate(name=name) + if serial_number is not None: + self.annotate(serial_number=serial_number) + if model_name is not None: + self.annotate(model_name=model_name) + if manufacturer is not None: + self.annotate(manufacturer=manufacturer) + # same idea but handle in vector way for contacts self.contact_annotations = dict() From a61710e08ba7b2d231e242e76152357e610dcbce Mon Sep 17 00:00:00 2001 From: Alessio Buccino Date: Tue, 12 Sep 2023 15:38:45 +0200 Subject: [PATCH 4/5] Use setters for key annotations --- src/probeinterface/probe.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/src/probeinterface/probe.py b/src/probeinterface/probe.py index 2b68901..8cdcceb 100644 --- a/src/probeinterface/probe.py +++ b/src/probeinterface/probe.py @@ -70,14 +70,12 @@ def __init__(self, ndim=2, si_units="um", name=None, serial_number=None, model_n # annotation: a dict that contains all meta information about # the probe (name, manufacturor, date of production, ...) self.annotations = dict() - if name is not None: - self.annotate(name=name) - if serial_number is not None: - self.annotate(serial_number=serial_number) - if model_name is not None: - self.annotate(model_name=model_name) - if manufacturer is not None: - self.annotate(manufacturer=manufacturer) + + # set key properties + self.name = name + self.serial_number = serial_number + self.model_name = model_name + self.manufacturer = manufacturer # same idea but handle in vector way for contacts self.contact_annotations = dict() @@ -114,18 +112,38 @@ def shank_ids(self): def name(self): return self.annotations.get("name", "") + @name.setter + def name(self, value): + if value is not None: + self.annotate(name=value) + @property def serial_number(self): return self.annotations.get("serial_number", "") + @serial_number.setter + def serial_number(self, value): + if value is not None: + self.annotate(serial_number=value) + @property def model_name(self): return self.annotations.get("model_name", "") + @model_name.setter + def model_name(self, value): + if value is not None: + self.annotate(model_name=value) + @property def manufacturer(self): return self.annotations.get("manufacturer", "") + @manufacturer.setter + def manufacturer(self, value): + if value is not None: + self.annotate(manufacturer=value) + def get_title(self): if self.contact_positions is None: txt = "Undefined probe" From cc9eee15c3d9f25c2f8565f78976dd4c42976913 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Sep 2023 13:38:56 +0000 Subject: [PATCH 5/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/probeinterface/probe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/probeinterface/probe.py b/src/probeinterface/probe.py index 8cdcceb..705d7f5 100644 --- a/src/probeinterface/probe.py +++ b/src/probeinterface/probe.py @@ -72,7 +72,7 @@ def __init__(self, ndim=2, si_units="um", name=None, serial_number=None, model_n self.annotations = dict() # set key properties - self.name = name + self.name = name self.serial_number = serial_number self.model_name = model_name self.manufacturer = manufacturer