diff --git a/.gitignore b/.gitignore index 4db5390..34f55be 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ MouseCells/ .ipynb_checkpoints/ coverage.xml MouseCells_sAHP/ +MouseCells_APThreshold/ tests/exp_data/X/ \ No newline at end of file diff --git a/bluepyefe/ecode/DeHyperPol.py b/bluepyefe/ecode/DeHyperPol.py index 8cd542b..75280e9 100644 --- a/bluepyefe/ecode/DeHyperPol.py +++ b/bluepyefe/ecode/DeHyperPol.py @@ -137,9 +137,9 @@ def generate(self): toff = int(self.toff / self.dt) time = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(time.shape, self.hypamp) - current[ton:tmid] += self.amp - current[tmid:toff] += self.amp2 + current = numpy.full(time.shape, numpy.float64(self.hypamp)) + current[ton:tmid] += numpy.float64(self.amp) + current[tmid:toff] += numpy.float64(self.amp2) return time, current diff --git a/bluepyefe/ecode/HyperDePol.py b/bluepyefe/ecode/HyperDePol.py index 911ad9a..18bfb3e 100644 --- a/bluepyefe/ecode/HyperDePol.py +++ b/bluepyefe/ecode/HyperDePol.py @@ -140,9 +140,9 @@ def generate(self): toff = int(self.toff / self.dt) time = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(time.shape, self.hypamp) - current[ton:tmid] += self.amp - current[tmid:toff] += self.amp2 + current = numpy.full(time.shape, numpy.float64(self.hypamp)) + current[ton:tmid] += numpy.float64(self.amp) + current[tmid:toff] += numpy.float64(self.amp2) return time, current diff --git a/bluepyefe/ecode/SpikeRec.py b/bluepyefe/ecode/SpikeRec.py index 6d1dd9d..9ff7f4b 100644 --- a/bluepyefe/ecode/SpikeRec.py +++ b/bluepyefe/ecode/SpikeRec.py @@ -185,16 +185,16 @@ def generate(self): """Generate the step current array from the parameters of the ecode""" t = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(t.shape, self.hypamp) + current = numpy.full(t.shape, numpy.float64(self.hypamp)) spike_start = int(self.tspike[0] / self.dt) spike_end = int((self.tspike[0] + self.spike_duration) / self.dt) - current[spike_start:spike_end] += self.amp + current[spike_start:spike_end] += numpy.float64(self.amp) for i in range(1, len(self.tspike)): spike_start = int(spike_end + (self.delta / self.dt)) spike_end = spike_start + int(self.spike_duration / self.dt) - current[spike_start:spike_end] += self.amp + current[spike_start:spike_end] += numpy.float64(self.amp) return t, current diff --git a/bluepyefe/ecode/negCheops.py b/bluepyefe/ecode/negCheops.py index 9bef57a..52aa54e 100644 --- a/bluepyefe/ecode/negCheops.py +++ b/bluepyefe/ecode/negCheops.py @@ -141,7 +141,7 @@ def generate(self): toff = int(self.toff / self.dt) time = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(time.shape, self.hypamp) + current = numpy.full(time.shape, numpy.float64(self.hypamp)) # First peak mid = int(0.5 * (ton + t1)) diff --git a/bluepyefe/ecode/posCheops.py b/bluepyefe/ecode/posCheops.py index ccc1761..fa3f90d 100644 --- a/bluepyefe/ecode/posCheops.py +++ b/bluepyefe/ecode/posCheops.py @@ -140,7 +140,7 @@ def generate(self): toff = int(self.toff / self.dt) time = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(time.shape, self.hypamp) + current = numpy.full(time.shape, numpy.float64(self.hypamp)) # First peak mid = int(0.5 * (self.ton + self.t1) / self.dt) diff --git a/bluepyefe/ecode/ramp.py b/bluepyefe/ecode/ramp.py index d98a7aa..8295b87 100644 --- a/bluepyefe/ecode/ramp.py +++ b/bluepyefe/ecode/ramp.py @@ -128,7 +128,7 @@ def generate(self): toff_idx = int(self.toff / self.dt) t = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(t.shape, self.hypamp) + current = numpy.full(t.shape, numpy.float64(self.hypamp)) current[ton_idx:toff_idx] += numpy.linspace( 0.0, self.amp, toff_idx - ton_idx ) diff --git a/bluepyefe/ecode/sAHP.py b/bluepyefe/ecode/sAHP.py index 9b6304b..1095687 100644 --- a/bluepyefe/ecode/sAHP.py +++ b/bluepyefe/ecode/sAHP.py @@ -239,10 +239,10 @@ def generate(self): toff = int(self.toff / self.dt) time = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(time.shape, self.hypamp) - current[ton:tmid] += self.amp - current[tmid2:toff] += self.amp - current[tmid:tmid2] += self.amp2 + current = numpy.full(time.shape, numpy.float64(self.hypamp)) + current[ton:tmid] += numpy.float64(self.amp) + current[tmid2:toff] += numpy.float64(self.amp) + current[tmid:tmid2] += numpy.float64(self.amp2) return time, current diff --git a/bluepyefe/ecode/step.py b/bluepyefe/ecode/step.py index 440c8a0..ca0f21f 100644 --- a/bluepyefe/ecode/step.py +++ b/bluepyefe/ecode/step.py @@ -176,7 +176,7 @@ def generate(self): toff_idx = int(self.toff / self.dt) t = numpy.arange(0.0, self.tend, self.dt) - current = numpy.full(t.shape, self.hypamp) - current[ton_idx:toff_idx] += self.amp + current = numpy.full(t.shape, numpy.float64(self.hypamp)) + current[ton_idx:toff_idx] += numpy.float64(self.amp) return t, current diff --git a/bluepyefe/protocol.py b/bluepyefe/protocol.py index d1b2176..394e893 100644 --- a/bluepyefe/protocol.py +++ b/bluepyefe/protocol.py @@ -198,6 +198,9 @@ def reduce_ecode(self, ecode, operator): else: mean_param = operator([numpy.nan if c[key] is None else c[key] for c in params]) + if numpy.isnan(mean_param): + mean_param = None + setattr(ecode, key, mean_param) return ecode diff --git a/tests/ecode/test_apthresh.py b/tests/ecode/test_apthresh.py new file mode 100644 index 0000000..6730c9a --- /dev/null +++ b/tests/ecode/test_apthresh.py @@ -0,0 +1,142 @@ +"""bluepyefe.ecode.APThreshold tests""" + +import unittest +import pytest +import glob +import json + +import bluepyefe.extract +import bluepyefe.tools +from tests.utils import download_apthresh_datafiles + + +def get_apthresh_config(absolute_amplitude=False): + download_apthresh_datafiles() + + interesting_efeatures = { + "Spikecount": {}, + "mean_frequency": {}, + "ISI_CV": {}, + "AP1_amp": {}, + "AP_width": {}, + } + + files_metadata1 = [] + for file in glob.glob("./tests/exp_data/X/X_APThreshold_ch0_*.ibw"): + files_metadata1.append( + { + "i_file": file, + "v_file": file.replace("ch0", "ch1"), + "i_unit": "A", + "v_unit": "V", + "t_unit": "ms", + "dt": 0.25, + "ljp": 14, + "ton": 10, # in ms + "tmid": 260, # in ms + "tmid2": 360, # in ms + "toff": 1360, # in ms + } + ) + files_metadata2 = [] + for file in glob.glob("./tests/exp_data/X/X_IDthresh_ch0_*.ibw"): + files_metadata2.append( + { + "i_file": file, + "v_file": file.replace("ch0", "ch1"), + "i_unit": "A", + "v_unit": "V", + "t_unit": "ms", + "dt": 0.25, + "ljp": 14, + } + ) + + files_metadata = { + "MouseNeuron1": {"APThreshold": files_metadata1, "IDthresh": files_metadata2}, + } + + if absolute_amplitude: + targets = { + "APThreshold": { + "amplitudes": [0.0, 0.225, 0.5, 0.69, 0.41, 0.595], + "tolerances": [0.01], + "efeatures": interesting_efeatures, + "location": "soma", + } + } + + else: + targets = { + "APThreshold": { + "amplitudes": [150], + "tolerances": [10.0], + "efeatures": interesting_efeatures, + "location": "soma", + } + } + + return files_metadata, bluepyefe.extract.convert_legacy_targets(targets) + +class APThreshTest(unittest.TestCase): + def test_extract_apthresh(self): + for absolute_amplitude in [True, False]: + with self.subTest(absolute_amplitude=absolute_amplitude): + self.run_test_with_absolute_amplitude(absolute_amplitude) + + def run_test_with_absolute_amplitude(self, absolute_amplitude): + files_metadata, targets = get_apthresh_config(absolute_amplitude) + + cells = bluepyefe.extract.read_recordings(files_metadata=files_metadata) + + cells = bluepyefe.extract.extract_efeatures_at_targets( + cells=cells, targets=targets + ) + + bluepyefe.extract.compute_rheobase(cells, protocols_rheobase=["IDthresh"]) + + self.assertEqual(len(cells), 1) + self.assertEqual(len(cells[0].recordings), 21) + self.assertLess(abs(cells[0].rheobase - 0.1103), 0.01) + + # amplitude test for one recording + # sort the recordings because they can be in any order, + # and we want to select the same one each time we test + apthresh_recs = [rec for rec in cells[0].recordings if rec.protocol_name == "APThreshold"] + rec1 = sorted(apthresh_recs, key=lambda x: x.amp)[1] + self.assertLess(abs(rec1.amp - 0.1740), 0.01) + self.assertLess(abs(rec1.amp_rel - 157.7), 0.1) + + + protocols = bluepyefe.extract.group_efeatures( + cells, + targets, + use_global_rheobase=True, + protocol_mode="mean", + absolute_amplitude=absolute_amplitude + ) + + _ = bluepyefe.extract.create_feature_protocol_files( + cells=cells, protocols=protocols, output_directory="MouseCells_APThreshold" + ) + + for protocol in protocols: + if protocol.name == "APThreshold" and protocol.amplitude == 150: + for target in protocol.feature_targets: + if target.efel_feature_name == "Spikecount": + self.assertEqual(target.mean, 14) + break + + bluepyefe.extract.plot_all_recordings_efeatures( + cells, protocols, output_dir="MouseCells_APThreshold/" + ) + + with open("MouseCells_APThreshold/features.json") as fp: + features = json.load(fp) + with open("MouseCells_APThreshold/protocols.json") as fp: + protocols = json.load(fp) + + self.assertEqual(len(features), len(protocols)) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/ecode/test_sahp.py b/tests/ecode/test_sahp.py new file mode 100644 index 0000000..71aceeb --- /dev/null +++ b/tests/ecode/test_sahp.py @@ -0,0 +1,144 @@ +"""bluepyefe.ecode.sAHP tests""" + +import unittest +import glob +import json + +import bluepyefe.extract +import bluepyefe.tools +from tests.utils import download_sahp_datafiles + + +def get_sahp_config(absolute_amplitude=False): + download_sahp_datafiles() + + interesting_efeatures = { + "Spikecount": {}, + "mean_frequency": {}, + "ISI_CV": {}, + "AP1_amp": {}, + "AP_width": {}, + } + + files_metadata1 = [] + for file in glob.glob("./tests/exp_data/X/X_sAHP_ch0_*.ibw"): + files_metadata1.append( + { + "i_file": file, + "v_file": file.replace("ch0", "ch1"), + "i_unit": "A", + "v_unit": "V", + "t_unit": "ms", + "dt": 0.25, + "ljp": 14, + "ton": 10, # in ms + "tmid": 260, # in ms + "tmid2": 360, # in ms + "toff": 1360, # in ms + } + ) + files_metadata2 = [] + for file in glob.glob("./tests/exp_data/X/X_IDthresh_ch0_*.ibw"): + files_metadata2.append( + { + "i_file": file, + "v_file": file.replace("ch0", "ch1"), + "i_unit": "A", + "v_unit": "V", + "t_unit": "ms", + "dt": 0.25, + "ljp": 14, + } + ) + + files_metadata = { + "MouseNeuron1": {"sAHP": files_metadata1, "IDthresh": files_metadata2}, + } + + if absolute_amplitude: + targets = { + "sAHP": { + "amplitudes": [0.315, 0.225, 0.5, 0.69, 0.41, 0.595], + "tolerances": [0.1], + "efeatures": interesting_efeatures, + "location": "soma", + } + } + + else: + targets = { + "sAHP": { + "amplitudes": [285, 200, 450, 625, 370, 540], + "tolerances": [10.0], + "efeatures": interesting_efeatures, + "location": "soma", + } + } + + return files_metadata, bluepyefe.extract.convert_legacy_targets(targets) + + +class SAHPTest(unittest.TestCase): + def test_extract_sahp(self): + for absolute_amplitude in [True, False]: + with self.subTest(absolute_amplitude=absolute_amplitude): + self.run_test_with_absolute_amplitude(absolute_amplitude) + + def run_test_with_absolute_amplitude(self, absolute_amplitude): + files_metadata, targets = get_sahp_config(absolute_amplitude) + + cells = bluepyefe.extract.read_recordings(files_metadata=files_metadata) + + cells = bluepyefe.extract.extract_efeatures_at_targets( + cells=cells, targets=targets + ) + + bluepyefe.extract.compute_rheobase(cells, protocols_rheobase=["IDthresh"]) + + self.assertEqual(len(cells), 1) + self.assertEqual(len(cells[0].recordings), 24) + self.assertLess(abs(cells[0].rheobase - 0.1103), 0.01) + + # amplitude test for one recording + # sort the recordings because they can be in any order, + # and we want to select the same one each time we test + sahp_recs = [rec for rec in cells[0].recordings if rec.protocol_name == "sAHP"] + rec1 = sorted(sahp_recs, key=lambda x: x.amp2)[1] + self.assertLess(abs(rec1.amp - 0.0953), 0.01) + self.assertLess(abs(rec1.amp2 - 0.3153), 0.01) + self.assertLess(abs(rec1.amp_rel - 86.4), 0.1) + self.assertLess(abs(rec1.amp2_rel - 285.8), 0.1) + + + protocols = bluepyefe.extract.group_efeatures( + cells, + targets, + use_global_rheobase=True, + protocol_mode="mean", + absolute_amplitude=absolute_amplitude + ) + + _ = bluepyefe.extract.create_feature_protocol_files( + cells=cells, protocols=protocols, output_directory="MouseCells_sAHP" + ) + + for protocol in protocols: + if protocol.name == "sAHP" and protocol.amplitude == 625: + for target in protocol.feature_targets: + if target.efel_feature_name == "Spikecount": + self.assertEqual(target.mean, 6) + break + + bluepyefe.extract.plot_all_recordings_efeatures( + cells, protocols, output_dir="MouseCells_sAHP/" + ) + + with open("MouseCells_sAHP/features.json") as fp: + features = json.load(fp) + with open("MouseCells_sAHP/protocols.json") as fp: + protocols = json.load(fp) + + self.assertEqual(len(features), len(protocols)) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/test_extractor.py b/tests/test_extractor.py index a4d2574..b0e8655 100644 --- a/tests/test_extractor.py +++ b/tests/test_extractor.py @@ -75,74 +75,6 @@ def get_config(absolute_amplitude=False): return files_metadata, bluepyefe.extract.convert_legacy_targets(targets) -def get_sahp_config(absolute_amplitude=False): - download_sahp_datafiles() - - interesting_efeatures = { - "Spikecount": {}, - "mean_frequency": {}, - "ISI_CV": {}, - "AP1_amp": {}, - "AP_width": {}, - } - - files_metadata1 = [] - for file in glob.glob("./tests/exp_data/X/X_sAHP_ch0_*.ibw"): - files_metadata1.append( - { - "i_file": file, - "v_file": file.replace("ch0", "ch1"), - "i_unit": "A", - "v_unit": "V", - "t_unit": "s", - "dt": 0.00025, - "ljp": 14, - "ton": 10, - "tmid": 260, - "tmid2": 360, - "toff": 1360, - } - ) - files_metadata2 = [] - for file in glob.glob("./tests/exp_data/X/X_IDthresh_ch0_*.ibw"): - files_metadata2.append( - { - "i_file": file, - "v_file": file.replace("ch0", "ch1"), - "i_unit": "A", - "v_unit": "V", - "t_unit": "s", - "dt": 0.00025, - "ljp": 14, - } - ) - - files_metadata = { - "MouseNeuron1": {"sAHP": files_metadata1, "IDthresh": files_metadata2}, - } - - if absolute_amplitude: - targets = { - "sAHP": { - "amplitudes": [0.315, 0.225, 0.5, 0.69, 0.41, 0.595], - "tolerances": [0.01], - "efeatures": interesting_efeatures, - "location": "soma", - } - } - - else: - targets = { - "sAHP": { - "amplitudes": [285, 200, 450, 625, 370, 540], - "tolerances": [10.0], - "efeatures": interesting_efeatures, - "location": "soma", - } - } - - return files_metadata, bluepyefe.extract.convert_legacy_targets(targets) - class ExtractorTest(unittest.TestCase): def test_extract(self): @@ -289,61 +221,7 @@ def test_extract_absolute(self): self.assertEqual(len(features), len(protocols)) - def test_extract_sahp(self): - - files_metadata, targets = get_sahp_config() - - cells = bluepyefe.extract.read_recordings(files_metadata=files_metadata) - - cells = bluepyefe.extract.extract_efeatures_at_targets( - cells=cells, targets=targets - ) - - bluepyefe.extract.compute_rheobase(cells, protocols_rheobase=["IDthresh"]) - - self.assertEqual(len(cells), 1) - self.assertEqual(len(cells[0].recordings), 24) - self.assertLess(abs(cells[0].rheobase - 0.1103), 0.01) - # amplitude test for one recording - # sort the recordings because they can be in any order, - # and we want to select the same one each time we test - sahp_recs = [rec for rec in cells[0].recordings if rec.protocol_name == "sAHP"] - rec1 = sorted(sahp_recs, key=lambda x: x.amp2)[1] - self.assertLess(abs(rec1.amp - 0.0953), 0.01) - self.assertLess(abs(rec1.amp2 - 0.3153), 0.01) - self.assertLess(abs(rec1.amp_rel - 86.4), 0.1) - self.assertLess(abs(rec1.amp2_rel - 285.8), 0.1) - - - protocols = bluepyefe.extract.group_efeatures( - cells, - targets, - use_global_rheobase=True, - protocol_mode="mean" - ) - - _ = bluepyefe.extract.create_feature_protocol_files( - cells=cells, protocols=protocols, output_directory="MouseCells_sAHP" - ) - - for protocol in protocols: - if protocol.name == "sAHP" and protocol.amplitude == 625: - for target in protocol.feature_targets: - if target.efel_feature_name == "Spikecount": - self.assertEqual(target.mean, 6) - break - - bluepyefe.extract.plot_all_recordings_efeatures( - cells, protocols, output_dir="MouseCells_sAHP/" - ) - - with open("MouseCells_sAHP/features.json") as fp: - features = json.load(fp) - with open("MouseCells_sAHP/protocols.json") as fp: - protocols = json.load(fp) - - self.assertEqual(len(features), len(protocols)) if __name__ == "__main__": diff --git a/tests/utils.py b/tests/utils.py index feecb0d..d057ff4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -4,9 +4,18 @@ import shutil from pathlib import Path +def download_datafiles(pathname, channels, numbers, output_dir, gb_url): + paths = [f"{pathname}_{ch}_{n}.ibw" for ch in channels for n in numbers] + + Path(output_dir).mkdir(exist_ok=True, parents=True) + for path in paths: + output_path = f"{output_dir}{path}" + if not Path(output_path).is_file(): + with urllib.request.urlopen(f"{gb_url}{path}") as response, open(output_path, "wb") as out_file: + shutil.copyfileobj(response, out_file) def download_sahp_datafiles(): - """Download data files for sAHP and IDthresh traces""" + """Download data files for sAHP and IDthresh traces.""" output_dir = "./tests/exp_data/X/" gb_url = "https://raw.githubusercontent.com/BlueBrain/SSCxEModelExamples/main/feature_extraction/input-traces/C060109A1-SR-C1/" sahp_pathname = "X_sAHP" @@ -16,13 +25,19 @@ def download_sahp_datafiles(): idthresh_ch = ["ch0", "ch1"] idthresh_numbers = list(range(349, 358)) + list(range(362, 371)) - sahp_paths = [f"{sahp_pathname}_{ch}_{n}.ibw" for ch in sahp_ch for n in sahp_numbers] - idthresh_paths = [f"{idthresh_pathname}_{ch}_{n}.ibw" for ch in idthresh_ch for n in idthresh_numbers] - pathnames = sahp_paths + idthresh_paths + download_datafiles(sahp_pathname, sahp_ch, sahp_numbers, output_dir, gb_url) + download_datafiles(idthresh_pathname, idthresh_ch, idthresh_numbers, output_dir, gb_url) - Path(output_dir).mkdir(exist_ok=True, parents=True) - for pathname in pathnames: - output_path = f"{output_dir}{pathname}" - if not Path(output_path).is_file(): - with urllib.request.urlopen(f"{gb_url}{pathname}") as response, open(output_path, "wb") as out_file: - shutil.copyfileobj(response, out_file) \ No newline at end of file +def download_apthresh_datafiles(): + """Download data files for APThreshold and IDthresh traces.""" + output_dir = "./tests/exp_data/X/" + gb_url = "https://raw.githubusercontent.com/BlueBrain/SSCxEModelExamples/main/feature_extraction/input-traces/C060109A1-SR-C1/" + apthresh_pathname = "X_APThreshold" + apthresh_ch = ["ch0", "ch1"] + apthresh_numbers = list(range(254, 257)) + idthresh_pathname = "X_IDthresh" + idthresh_ch = ["ch0", "ch1"] + idthresh_numbers = list(range(349, 358)) + list(range(362, 371)) + + download_datafiles(apthresh_pathname, apthresh_ch, apthresh_numbers, output_dir, gb_url) + download_datafiles(idthresh_pathname, idthresh_ch, idthresh_numbers, output_dir, gb_url)