Skip to content

Commit 3ef4b0b

Browse files
authored
Merge pull request #92 from CardiacModelling/js_new_directory_builder
Improve script and test output. Build using pyproject.tml with setuptools_scm
2 parents c397bff + ab5112c commit 3ef4b0b

9 files changed

Lines changed: 268 additions & 133 deletions

pcpostprocess/directory_builder.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import datetime
2+
import os
3+
import sys
4+
5+
from ._version import __commit_id__, __version__
6+
7+
8+
def get_git_revision_hash():
9+
"""
10+
Get the hash for the git commit currently being used.
11+
12+
@return The most recent commit hash or a suitable message
13+
14+
"""
15+
16+
return __commit_id__
17+
18+
19+
def get_build_type():
20+
if "dev" in __version__:
21+
return "Develop"
22+
else:
23+
return "Release"
24+
25+
26+
def setup_output_directory(dirname: str = None, subdir_name: str = None):
27+
"""
28+
Create an output directory if one doesn't already exist. Place an info
29+
file in this directory which lists the date/time created, the version of
30+
pcpostprocess, the command-line arguments provided, and the most recent git
31+
commit. The two parameters allow for a user specified top-level directory and
32+
a script-defined name for a subdirectory.
33+
34+
@param Optional directory name
35+
@param Optional subdirectory name
36+
37+
@return The path to the created file directory (String)
38+
"""
39+
40+
if dirname is None:
41+
if subdir_name:
42+
dirname = os.path.join("output", f"{subdir_name}")
43+
else:
44+
dirname = os.path.join("output", "output")
45+
46+
if subdir_name is not None:
47+
dirname = os.path.join(dirname, subdir_name)
48+
if not os.path.exists(dirname):
49+
os.makedirs(dirname)
50+
51+
with open(os.path.join(dirname, "pcpostprocess_info.txt"), "w") as description_fout:
52+
git_hash = get_git_revision_hash()
53+
datetimestr = str(datetime.datetime.now())
54+
description_fout.write("pcpostprocess output "
55+
"https://github.com/CardiacModelling/pcpostprocess\n")
56+
description_fout.write(f"Date: {datetimestr}\n")
57+
description_fout.write(f"Version: {__version__}\n")
58+
description_fout.write(f"Build type: {get_build_type()}\n")
59+
description_fout.write(f"Commit: {git_hash}\n")
60+
command = " ".join(sys.argv)
61+
description_fout.write(f"Command: {command}\n")
62+
return dirname
63+

pcpostprocess/scripts/run_herg_qc.py

Lines changed: 66 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import argparse
2-
import datetime
32
import importlib.util
43
import json
54
import logging
@@ -20,6 +19,7 @@
2019
from syncropatch_export.voltage_protocols import VoltageProtocol
2120

2221
from pcpostprocess.detect_ramp_bounds import detect_ramp_bounds
22+
from pcpostprocess.directory_builder import setup_output_directory
2323
from pcpostprocess.hergQC import hERGQC
2424
from pcpostprocess.infer_reversal import infer_reversal_potential
2525
from pcpostprocess.leak_correct import fit_linear_leak, get_leak_corrected
@@ -45,7 +45,7 @@ def main():
4545
parser = argparse.ArgumentParser()
4646
parser.add_argument('data_directory')
4747
parser.add_argument('-c', '--no_cpus', default=1, type=int)
48-
parser.add_argument('--output_dir')
48+
parser.add_argument('--output_dir', default="output")
4949
parser.add_argument('-w', '--wells', nargs='+')
5050
parser.add_argument('--protocols', nargs='+')
5151
parser.add_argument('--reversal_spread_threshold', type=float, default=10)
@@ -62,19 +62,7 @@ def main():
6262

6363
logging.basicConfig(level=args.log_level)
6464

65-
if args.output_dir is None:
66-
args.output_dir = os.path.join('output', 'hergqc')
67-
68-
if not os.path.exists(args.output_dir):
69-
os.makedirs(args.output_dir)
70-
71-
with open(os.path.join(args.output_dir, 'info.txt'), 'w') as description_fout:
72-
git_hash = get_git_revision_hash()
73-
datetimestr = str(datetime.datetime.now())
74-
description_fout.write(f"Date: {datetimestr}\n")
75-
description_fout.write(f"Commit {git_hash}\n")
76-
command = " ".join(sys.argv)
77-
description_fout.write(f"Command: {command}\n")
65+
output_dir = setup_output_directory(args.output_dir, "run_herg_qc")
7866

7967
spec = importlib.util.spec_from_file_location(
8068
'export_config',
@@ -107,7 +95,7 @@ def main():
10795
sys.modules['export_config'] = export_config
10896
spec.loader.exec_module(export_config)
10997

110-
export_config.savedir = args.output_dir
98+
export_config.savedir = output_dir
11199

112100
args.saveID = export_config.saveID
113101
args.savedir = export_config.savedir
@@ -181,7 +169,9 @@ def main():
181169
**pool_kws) as pool:
182170

183171
pool_argument_list = zip(readnames, savenames, times_list,
184-
[args for i in readnames])
172+
[args for i in readnames],
173+
[output_dir for i in readnames])
174+
185175
well_selections, qc_dfs = \
186176
list(zip(*pool.starmap(run_qc_for_protocol, pool_argument_list)))
187177

@@ -193,17 +183,17 @@ def main():
193183
times = sorted(res_dict[protocol])
194184
if len(times) == 4:
195185
qc3_bookend_dict = qc3_bookend(protocol, savename,
196-
times, args)
186+
times, args, wells, output_dir)
197187
else:
198188
qc3_bookend_dict = {well: True for well in qc_df.well.unique()}
199189

200190
qc_df['qc3.bookend'] = [qc3_bookend_dict[well] for well in qc_df.well]
201191

202-
savedir = args.output_dir
192+
savedir = output_dir
203193
saveID = export_config.saveID
204194

205-
if not os.path.exists(os.path.join(args.output_dir, savedir)):
206-
os.makedirs(os.path.join(args.output_dir, savedir))
195+
if not os.path.exists(os.path.join(output_dir, savedir)):
196+
os.makedirs(os.path.join(output_dir, savedir))
207197

208198
#  qc_df will be updated and saved again, but it's useful to save them here for debugging
209199
# Write qc_df to file
@@ -282,7 +272,8 @@ def main():
282272

283273
args_list = list(zip(readnames, savenames, times_list, [wells_to_export] *
284274
len(savenames),
285-
[args for i in readnames]))
275+
[args for i in readnames],
276+
[output_dir for i in readnames]))
286277

287278
with multiprocessing.Pool(min(args.no_cpus, no_protocols),
288279
**pool_kws) as pool:
@@ -344,7 +335,7 @@ def main():
344335

345336
chrono_dict = {times[0]: prot for prot, times in zip(savenames, times_list)}
346337

347-
with open(os.path.join(args.output_dir, 'chrono.txt'), 'w') as fout:
338+
with open(os.path.join(output_dir, 'chrono.txt'), 'w') as fout:
348339
for key in sorted(chrono_dict):
349340
val = chrono_dict[key]
350341
#  Output order of protocols
@@ -382,8 +373,8 @@ def main():
382373

383374
qc_styled_df = create_qc_table(qc_df)
384375
logging.info(qc_styled_df)
385-
qc_styled_df.to_excel(os.path.join(args.output_dir, 'qc_table.xlsx'))
386-
qc_styled_df.to_latex(os.path.join(args.output_dir, 'qc_table.tex'))
376+
qc_styled_df.to_excel(os.path.join(output_dir, 'qc_table.xlsx'))
377+
qc_styled_df.to_latex(os.path.join(output_dir, 'qc_table.tex'))
387378

388379
# Save in csv format
389380
qc_df.to_csv(os.path.join(savedir, 'QC-%s.csv' % saveID))
@@ -395,11 +386,11 @@ def main():
395386
#  Load only QC vals. TODO use a new variabile name to avoid confusion
396387
qc_vals_df = extract_df[['well', 'sweep', 'protocol', 'Rseal', 'Cm', 'Rseries']].copy()
397388
qc_vals_df['drug'] = 'before'
398-
qc_vals_df.to_csv(os.path.join(args.output_dir, 'qc_vals_df.csv'))
389+
qc_vals_df.to_csv(os.path.join(output_dir, 'qc_vals_df.csv'))
399390

400-
extract_df.to_csv(os.path.join(args.output_dir, 'subtraction_qc.csv'))
391+
extract_df.to_csv(os.path.join(output_dir, 'subtraction_qc.csv'))
401392

402-
with open(os.path.join(args.output_dir, 'passed_wells.txt'), 'w') as fout:
393+
with open(os.path.join(output_dir, 'passed_wells.txt'), 'w') as fout:
403394
for well, passed in passed_qc_dict.items():
404395
if passed:
405396
fout.write(well)
@@ -474,9 +465,9 @@ def agg_func(x):
474465
return ret_df
475466

476467

477-
def extract_protocol(readname, savename, time_strs, selected_wells, args):
468+
def extract_protocol(readname, savename, time_strs, selected_wells, args, savedir):
478469
logging.info(f"extracting {savename}")
479-
savedir = args.output_dir
470+
480471
saveID = args.saveID
481472

482473
traces_dir = os.path.join(savedir, 'traces')
@@ -607,8 +598,8 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
607598
before_leak_currents = []
608599
after_leak_currents = []
609600

610-
out_dir = os.path.join(savedir,
611-
f"{saveID}-{savename}-leak_fit-before")
601+
out_path = os.path.join(savedir, "leak_correction",
602+
f"{saveID}-{savename}-leak_fit-before")
612603

613604
for sweep in range(before_current.shape[0]):
614605
row_dict = {
@@ -630,14 +621,14 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
630621
before_params, before_leak = fit_linear_leak(before_current[sweep, :],
631622
voltages, times,
632623
*ramp_bounds,
633-
output_dir=out_dir,
624+
output_dir=out_path,
634625
save_fname=f"{well}_sweep{sweep}.png"
635626
)
636627

637628
before_leak_currents.append(before_leak)
638629

639-
out_dir = os.path.join(savedir,
640-
f"{saveID}-{savename}-leak_fit-after")
630+
out_path = os.path.join(savedir,
631+
f"{saveID}-{savename}-leak_fit-after")
641632
# Convert linear regression parameters into conductance and reversal
642633
row_dict['gleak_before'] = before_params[1]
643634
row_dict['E_leak_before'] = -before_params[0] / before_params[1]
@@ -646,7 +637,7 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
646637
voltages, times,
647638
*ramp_bounds,
648639
save_fname=f"{well}_sweep{sweep}.png",
649-
output_dir=out_dir)
640+
output_dir=out_path)
650641

651642
after_leak_currents.append(after_leak)
652643

@@ -687,7 +678,8 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
687678
row_dict['QC.Erev'] = E_rev < -50 and E_rev > -120
688679

689680
# Check QC6 for each protocol (not just the staircase)
690-
plot_dir = os.path.join(savedir, 'debug')
681+
plot_dir = os.path.join(savedir, "QC", "debug",
682+
f"debug_{well}_{savename}")
691683

692684
if not os.path.exists(plot_dir):
693685
os.makedirs(plot_dir)
@@ -743,11 +735,11 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
743735
res = \
744736
get_time_constant_of_first_decay(subtracted_trace, times, desc,
745737
args=args,
746-
output_path=os.path.join(args.output_dir,
747-
'debug',
748-
'-120mV time constant',
738+
output_path=os.path.join(savedir,
739+
"debug",
740+
"-120mV time constant",
749741
f"{savename}-{well}-sweep"
750-
"{sweep}-time-constant-fit.pdf"))
742+
f"{sweep}-time-constant-fit.pdf"))
751743

752744
row_dict['-120mV decay time constant 1'] = res[0][0]
753745
row_dict['-120mV decay time constant 2'] = res[0][1]
@@ -810,7 +802,7 @@ def extract_protocol(readname, savename, time_strs, selected_wells, args):
810802
return extract_df
811803

812804

813-
def run_qc_for_protocol(readname, savename, time_strs, args):
805+
def run_qc_for_protocol(readname, savename, time_strs, args, output_dir):
814806
df_rows = []
815807

816808
assert len(time_strs) == 2
@@ -836,7 +828,9 @@ def run_qc_for_protocol(readname, savename, time_strs, args):
836828
# Convert to s
837829
sampling_rate = before_trace.sampling_rate
838830

839-
savedir = args.output_dir
831+
savedir = os.path.join(output_dir, "QC")
832+
leak_correction_dir = os.path.join(savedir, "leak_correction")
833+
840834
if not os.path.exists(savedir):
841835
os.makedirs(savedir)
842836

@@ -910,14 +904,14 @@ def run_qc_for_protocol(readname, savename, time_strs, args):
910904
times,
911905
*ramp_bounds,
912906
save_fname=f"{well}-sweep{sweep}-before.png",
913-
output_dir=savedir)
907+
output_dir=leak_correction_dir)
914908

915909
after_params1, after_leak = fit_linear_leak(after_raw,
916910
voltage,
917911
times,
918912
*ramp_bounds,
919913
save_fname=f"{well}-sweep{sweep}-after.png",
920-
output_dir=savedir)
914+
output_dir=leak_correction_dir)
921915

922916
before_currents_corrected[sweep, :] = before_raw - before_leak
923917
after_currents_corrected[sweep, :] = after_raw - after_leak
@@ -988,10 +982,13 @@ def run_qc_for_protocol(readname, savename, time_strs, args):
988982
return selected_wells, df
989983

990984

991-
def qc3_bookend(readname, savename, time_strs, args):
992-
plot_dir = os.path.join(args.output_dir, args.savedir,
985+
def qc3_bookend(readname, savename, time_strs, args, wells, output_dir):
986+
plot_dir = os.path.join(output_dir, args.savedir,
993987
f"{args.saveID}-{savename}-qc3-bookend")
994988

989+
if not os.path.exists(plot_dir):
990+
os.makedirs(plot_dir)
991+
995992
filepath_first_before = os.path.join(args.data_directory,
996993
f"{readname}_{time_strs[0]}")
997994
filepath_last_before = os.path.join(args.data_directory,
@@ -1047,21 +1044,34 @@ def qc3_bookend(readname, savename, time_strs, args):
10471044
last_processed = {}
10481045

10491046
#  Iterate over all wells
1050-
for well in np.array(all_wells).flatten():
1047+
for well in np.array(wells).flatten():
10511048
first_before_current = first_before_current_dict[well][0, :]
10521049
first_after_current = first_after_current_dict[well][0, :]
10531050
last_before_current = last_before_current_dict[well][-1, :]
10541051
last_after_current = last_after_current_dict[well][-1, :]
10551052

1056-
output_directory = os.path.join(args.output_dir, "leak_correction")
10571053
save_fname = f"{well}_{savename}_before0.pdf"
10581054

1059-
#  Plot subtraction
1060-
get_leak_corrected(first_before_current,
1061-
voltage, times,
1062-
*ramp_bounds,
1063-
save_fname=save_fname,
1064-
output_dir=output_directory)
1055+
if args.debug:
1056+
qc3_output_dir = os.path.join(output_dir,
1057+
"QC",
1058+
"debug",
1059+
f"debug_{well}_{savename}",
1060+
"qc3_bookend")
1061+
1062+
if not os.path.exists(qc3_output_dir):
1063+
os.makedirs(qc3_output_dir)
1064+
1065+
leak_correct_dir = os.path.join(qc3_output_dir,
1066+
"leak_correction")
1067+
1068+
#  Plot subtraction
1069+
if args.debug:
1070+
get_leak_corrected(first_before_current,
1071+
voltage, times,
1072+
*ramp_bounds,
1073+
save_fname=save_fname,
1074+
output_dir=leak_correct_dir)
10651075

10661076
before_traces_first[well] = get_leak_corrected(first_before_current,
10671077
voltage, times,
@@ -1110,9 +1120,7 @@ def qc3_bookend(readname, savename, time_strs, args):
11101120

11111121
res_dict[well] = passed
11121122

1113-
save_fname = os.path.join(args.output_dir,
1114-
'debug',
1115-
f"debug_{well}_{savename}",
1123+
save_fname = os.path.join(output_dir,
11161124
'qc3_bookend')
11171125

11181126
ax.plot(times, trace1)

0 commit comments

Comments
 (0)