Skip to content

Commit 120b187

Browse files
committed
test: improve checker
1 parent 5f2c89d commit 120b187

8 files changed

Lines changed: 185 additions & 147 deletions

.github/workflows/params_test_default.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@
301301
}
302302
},
303303
"nwb": {
304-
"backend": "zarr",
304+
"backend": "hdf5",
305305
"ecephys": {
306306
"stub": false,
307307
"stub_seconds": 10,

.github/workflows/test_pipeline_custom.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ jobs:
5454
5555
- name: Check pipeline results
5656
run: |
57-
bash "$(pwd)/tests/check_pipeline_results.sh" --results-path "$(pwd)/sample_dataset/nwb/results" --num-streams 3 --num-success 2 --num-nwb 1
57+
python "$(pwd)/tests/check_pipeline_results.py" --results-path "$(pwd)/sample_dataset/nwb/results" --num-streams 3 --num-success 2 --num-nwb 1

.github/workflows/test_pipeline_default.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,4 @@ jobs:
5454
5555
- name: Check pipeline results
5656
run: |
57-
bash "$(pwd)/tests/check_pipeline_results.sh" --results-path "$(pwd)/sample_dataset/nwb_results" --num-streams 3 --num-success 2 --num-nwb 1
57+
python "$(pwd)/tests/check_pipeline_results.py" --results-path "$(pwd)/sample_dataset/nwb_results" --num-streams 3 --num-success 2 --num-nwb 1

.github/workflows/test_pipeline_spikeinterface.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,4 @@ jobs:
5555
5656
- name: Check pipeline results
5757
run: |
58-
bash "$(pwd)/tests/check_pipeline_results.sh" --results-path "$(pwd)/sample_dataset/spikeinterface_results" --num-streams 3 --num-success 2 --num-nwb 3
58+
python "$(pwd)/tests/check_pipeline_results.py" --results-path "$(pwd)/sample_dataset/spikeinterface_results" --num-streams 3 --num-success 2 --num-nwb 3

tests/check_pipeline_results.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python3
2+
"""Verify that a pipeline run produced the expected outputs.
3+
4+
The sample dataset contains 3 recordings (main, short, unsigned):
5+
- all 3 are preprocessed
6+
- all 3 have quality control run on them
7+
- the "short" (10s) recording is too short to be spike sorted, so only
8+
2 recordings produce a successful spike sorting output (and therefore
9+
only 2 postprocessed / curated outputs)
10+
11+
Failed/skipped recordings do NOT appear in the collected results (the result
12+
collector does not propagate ``error.txt`` markers), so each output folder is
13+
checked by counting the entries it contains:
14+
15+
- preprocessed/ : --num-streams ``*_recording.json`` files
16+
- spikesorted/ : --num-success stream folders
17+
- postprocessed/ : --num-success stream folders (zarr)
18+
- curated/ : --num-success stream folders
19+
- quality_control/ : --num-streams stream folders
20+
- nwb/ : --num-nwb ``*.nwb`` files/folders
21+
22+
Usage:
23+
check_pipeline_results.py --results-path PATH \
24+
[--num-streams N] [--num-success N] [--num-nwb N]
25+
"""
26+
27+
import argparse
28+
import sys
29+
from pathlib import Path
30+
31+
import spikeinterface as si
32+
import pynwb
33+
34+
35+
class Checker:
36+
def __init__(self):
37+
self.errors = []
38+
39+
def error(self, msg):
40+
self.errors.append(msg)
41+
print(f" ERROR: {msg}")
42+
43+
def check_count(self, label, entries, expected):
44+
print(f"Found {len(entries)} {label} entries (expected {expected}):")
45+
for e in sorted(entries):
46+
print(f" - {e.name}")
47+
if len(entries) != expected:
48+
self.error(f"expected {expected} {label} entries, found {len(entries)}")
49+
50+
51+
def subdirs(path: Path):
52+
return [p for p in path.iterdir() if p.is_dir()] if path.is_dir() else []
53+
54+
55+
def main():
56+
parser = argparse.ArgumentParser(
57+
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
58+
)
59+
parser.add_argument("--results-path", required=True, type=Path)
60+
parser.add_argument("--num-streams", type=int, default=3)
61+
parser.add_argument("--num-success", type=int, default=2)
62+
parser.add_argument("--num-nwb", type=int, default=1)
63+
args = parser.parse_args()
64+
65+
results_path = args.results_path
66+
print(f"Checking pipeline results in: {results_path}")
67+
print(f"Expecting: {args.num_streams} preprocessed, "
68+
f"{args.num_success} spike sorted / postprocessed / curated, "
69+
f"{args.num_streams} quality control, {args.num_nwb} NWB file(s)")
70+
71+
checker = Checker()
72+
73+
if not results_path.is_dir():
74+
print(f"ERROR: results path not found: {results_path}")
75+
sys.exit(1)
76+
77+
# --- preprocessed -----------------------------------------------------
78+
print("\n[preprocessed]")
79+
preprocessed_dir = results_path / "preprocessed"
80+
if not preprocessed_dir.is_dir():
81+
checker.error(f"preprocessed directory not found: {preprocessed_dir}")
82+
else:
83+
jsons = sorted(preprocessed_dir.glob("*_recording.json"))
84+
checker.check_count("preprocessed", jsons, args.num_streams)
85+
for json_file in jsons:
86+
print(f"\t- {json_file.name}")
87+
try:
88+
recording = si.load(json_file)
89+
print(f"\t - loaded recording: {recording}")
90+
except Exception as e:
91+
checker.error(f"failed to load preprocessed recording: {json_file} ({e})")
92+
93+
# --- spikesorted ------------------------------------------------------
94+
print("\n[spikesorted]")
95+
spikesorted_dir = results_path / "spikesorted"
96+
if not spikesorted_dir.is_dir():
97+
checker.error(f"spikesorted directory not found: {spikesorted_dir}")
98+
else:
99+
dirs = subdirs(spikesorted_dir)
100+
checker.check_count("spikesorted", dirs, args.num_success)
101+
for dir in dirs:
102+
print(f" - {dir.name}")
103+
try:
104+
sorting = si.load(dir)
105+
print(f"\t - loaded sorting: {sorting}")
106+
except Exception as e:
107+
checker.error(f"failed to load spikesorted recording: {dir} ({e})")
108+
109+
110+
111+
# --- postprocessed ----------------------------------------------------
112+
print("\n[postprocessed]")
113+
postprocessed_dir = results_path / "postprocessed"
114+
if not postprocessed_dir.is_dir():
115+
checker.error(f"postprocessed directory not found: {postprocessed_dir}")
116+
else:
117+
dirs = subdirs(postprocessed_dir)
118+
checker.check_count("postprocessed", dirs, args.num_success)
119+
for dir in dirs:
120+
print(f" - {dir.name}")
121+
try:
122+
analyzer = si.load(dir)
123+
print(f"\t - loaded postprocessed analyzer: {analyzer}")
124+
except Exception as e:
125+
checker.error(f"failed to load postprocessed analyzer: {dir} ({e})")
126+
127+
# --- curated ----------------------------------------------------------
128+
print("\n[curated]")
129+
curated_dir = results_path / "curated"
130+
if not curated_dir.is_dir():
131+
checker.error(f"curated directory not found: {curated_dir}")
132+
else:
133+
dirs = subdirs(curated_dir)
134+
checker.check_count("curated", dirs, args.num_success)
135+
for dir in dirs:
136+
print(f" - {dir.name}")
137+
try:
138+
curated_sorting = si.load(dir)
139+
print(f"\t - loaded curated sorting: {curated_sorting}")
140+
except Exception as e:
141+
checker.error(f"failed to load curated sorting: {dir} ({e})")
142+
143+
# --- quality_control --------------------------------------------------
144+
print("\n[quality_control]")
145+
qc_dir = results_path / "quality_control"
146+
if not qc_dir.is_dir():
147+
checker.error(f"quality_control directory not found: {qc_dir}")
148+
else:
149+
dirs = subdirs(qc_dir)
150+
checker.check_count("quality_control", dirs, args.num_streams)
151+
152+
# --- nwb --------------------------------------------------------------
153+
print("\n[nwb]")
154+
nwb_dir = results_path / "nwb"
155+
if not nwb_dir.is_dir():
156+
checker.error(f"nwb directory not found: {nwb_dir}")
157+
else:
158+
nwb_files = sorted(nwb_dir.glob("*.nwb"))
159+
checker.check_count("nwb", nwb_files, args.num_nwb)
160+
for nwb_file in nwb_files:
161+
print(f" - {nwb_file.name}")
162+
try:
163+
nwbfile = pynwb.read_nwb(nwb_file)
164+
print(f"\t - loaded NWB file: {nwbfile}")
165+
except Exception as e:
166+
checker.error(f"failed to load NWB file: {nwb_file} ({e})")
167+
168+
# --- summary ----------------------------------------------------------
169+
print()
170+
if checker.errors:
171+
print(f"Pipeline result check FAILED with {len(checker.errors)} error(s):")
172+
for e in checker.errors:
173+
print(f" - {e}")
174+
sys.exit(1)
175+
print("Pipeline result check PASSED")
176+
177+
178+
if __name__ == "__main__":
179+
main()

tests/check_pipeline_results.sh

Lines changed: 0 additions & 141 deletions
This file was deleted.

tests/test_pipeline_local_nwb.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ NXF_VER=$NXF_VERSION DATA_PATH=$DATA_PATH RESULTS_PATH=$RESULTS_PATH nextflow \
6060
--params_file $PARAMS_FILE $ARGS
6161

6262
# check results: 3 preprocessed entries and 2 successful spike sorting outputs
63-
bash "$(dirname "$SCRIPT_PATH")/check_pipeline_results.sh" --results-path "$RESULTS_PATH" --num-streams 3 --num-success 2 --num-nwb 1
63+
python "$(dirname "$SCRIPT_PATH")/check_pipeline_results.py" --results-path "$RESULTS_PATH" --num-streams 3 --num-success 2 --num-nwb 1

tests/test_pipeline_local_si.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ NXF_VER=$NXF_VERSION DATA_PATH=$DATA_PATH RESULTS_PATH=$RESULTS_PATH nextflow \
4343
--params_file $PARAMS_FILE $ARGS
4444

4545
# check results: 3 preprocessed entries and 2 successful spike sorting outputs
46-
bash "$(dirname "$SCRIPT_PATH")/check_pipeline_results.sh" --results-path "$RESULTS_PATH" --num-streams 3 --num-success 2 --num-nwb 1
46+
python "$(dirname "$SCRIPT_PATH")/check_pipeline_results.py" --results-path "$RESULTS_PATH" --num-streams 3 --num-success 2 --num-nwb 3

0 commit comments

Comments
 (0)