Skip to content

Commit e336e97

Browse files
Copilotjlelia
andcommitted
Fix synergy test for refactored output format and add missing docstrings
Co-authored-by: jlelia <[email protected]>
1 parent 23fd5b3 commit e336e97

File tree

3 files changed

+99
-20
lines changed

3 files changed

+99
-20
lines changed

src/cellpyability/toolbox.py

Lines changed: 83 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,18 @@
1515
from scipy.optimize import curve_fit
1616
import shutil
1717

18-
# Creates logging protocol for CellPyAbility
19-
# Include 'logger = cellpyability_logger()' at start of script
2018
def cellpyability_logger():
19+
"""
20+
Creates and configures the CellPyAbility logger.
21+
22+
Logs all messages (DEBUG and above) to cellpyability.log in current working directory.
23+
Logs INFO and above to console output.
24+
25+
Returns:
26+
--------
27+
logger : logging.Logger
28+
Configured logger instance
29+
"""
2130
logger = logging.getLogger("CellPyAbility")
2231
logger.setLevel(logging.DEBUG) # print all messages to log
2332

@@ -92,17 +101,45 @@ def get_output_base_dir(output_dir=None):
92101
logger.info(f'Output directory {output_base} established ...')
93102
return output_base
94103

95-
# The next two functions will be used in get_cellprofiler_path()
96104
def save_txt(config_file, path):
105+
"""
106+
Save a path string to a text file.
107+
108+
Parameters:
109+
-----------
110+
config_file : str or Path
111+
Path to the configuration file to write
112+
path : str or Path
113+
Path to save in the configuration file
114+
"""
97115
with open(config_file, 'w') as file:
98116
file.write(str(path))
99117
logger.info(f'Path saved successfully in {config_file} as: {path}')
100118

101119
def prompt_path():
120+
"""
121+
Prompt user to enter the CellProfiler executable path.
122+
123+
Returns:
124+
--------
125+
str
126+
User-provided path with quotes and whitespace stripped
127+
"""
102128
return input("Enter the path to the CellProfiler program: ").strip().strip('"').strip("'")
103129

104-
# Include 'cp_path = get_cellprofiler_path()' at start of script
105130
def get_cellprofiler_path():
131+
"""
132+
Get the path to the CellProfiler executable.
133+
134+
Checks for saved path in cellprofiler_path.txt, then checks default installation
135+
locations on Windows and macOS. If not found, prompts user for the path.
136+
Saves the path for future use.
137+
138+
Returns:
139+
--------
140+
str or Path
141+
Path to the CellProfiler executable
142+
"""
106143
# Store CellProfiler path in current working directory (PyPI-compatible)
107144
config_file = Path.cwd() / "cellprofiler_path.txt"
108145

@@ -300,9 +337,51 @@ def rename_counts(cp_csv, counts_csv):
300337

301338
# Define models at module level so they are accessible
302339
def fivePL(x, A, B, C, D, G):
340+
"""
341+
Five-parameter logistic (5PL) dose-response model.
342+
343+
Parameters:
344+
-----------
345+
x : array-like
346+
Dose/concentration values
347+
A : float
348+
Minimum asymptote (response at infinite dose)
349+
B : float
350+
Hill slope
351+
C : float
352+
Inflection point (IC50/EC50)
353+
D : float
354+
Maximum asymptote (response at zero dose)
355+
G : float
356+
Asymmetry factor
357+
358+
Returns:
359+
--------
360+
array-like
361+
Predicted response values
362+
"""
303363
return ((A - D) / (1.0 + (x / C) ** B) ** G) + D
304364

305365
def hill(x, Emax, EC50, HillSlope):
366+
"""
367+
Hill equation for dose-response curves.
368+
369+
Parameters:
370+
-----------
371+
x : array-like
372+
Dose/concentration values
373+
Emax : float
374+
Maximum effect
375+
EC50 : float
376+
Half-maximal effective concentration
377+
HillSlope : float
378+
Hill coefficient (slope factor)
379+
380+
Returns:
381+
--------
382+
array-like
383+
Predicted response values
384+
"""
306385
return Emax * (x**HillSlope) / (EC50**HillSlope + x**HillSlope)
307386

308387
def fit_response_curve(x, y, name):
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
Row Drug Concentration,0.0,6.103515625e-09,2.44140625e-08,9.765625e-08,3.90625e-07,1.5625e-06,6.25e-06,2.5e-05,0.0001,0.0004
1+
Drug Y (M),0.0,6.103515625e-09,2.441406249999996e-08,9.765624999999987e-08,3.906249999999996e-07,1.5624999999999988e-06,6.249999999999997e-06,2.4999999999999994e-05,0.0001,0.0004
22
0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0
33
3.90625e-07,0.0,0.030946751592267896,0.012015661544450884,0.2531808625591223,0.6142383771150097,0.9677389960256152,0.9316918731297796,0.8047634312298755,0.5467837824072979,0.028962647109758904
4-
1.5625e-06,0.0,0.07953190897452611,0.08496439416815316,0.20301738215191545,0.8519715881066756,1.0107631828643693,0.9533215365698291,0.8280294113993946,0.5639091273981162,0.029023472726199484
5-
6.25e-06,0.0,0.05351524691666354,0.03513250669772261,0.2735871309968979,0.8264150570068889,0.9841027476438996,0.9358210303233951,0.8131855924198923,0.5586656313813382,0.045500815203827163
6-
2.5e-05,0.0,0.05319167649583978,0.08349744897983535,0.2062700613718521,0.729943966890402,0.9789236654841811,0.9260712538469191,0.835268209513788,0.61121571152568,0.09249274025676549
4+
1.562500000000002e-06,0.0,0.07953190897452611,0.08496439416815316,0.20301738215191545,0.8519715881066756,1.0107631828643693,0.9533215365698291,0.8280294113993946,0.5639091273981162,0.029023472726199484
5+
6.249999999999997e-06,0.0,0.05351524691666354,0.03513250669772261,0.2735871309968979,0.8264150570068889,0.9841027476438996,0.9358210303233951,0.8131855924198923,0.5586656313813382,0.045500815203827163
6+
2.4999999999999994e-05,0.0,0.05319167649583978,0.08349744897983535,0.2062700613718521,0.729943966890402,0.9789236654841811,0.9260712538469191,0.835268209513788,0.61121571152568,0.09249274025676549
77
0.0001,0.0,0.09873056490781451,0.14723825085831954,0.3103372264416291,0.8537961590820602,1.0162890964616222,0.9656480763683999,0.8882421695100602,0.6392428401127285,0.09648776720545657

tests/test_module_outputs.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_synergy_module():
138138

139139
test_data_dir = Path(__file__).parent / "data"
140140
counts_file = test_data_dir / "test_synergy_counts.csv"
141-
expected_stats = test_data_dir / "test_synergy_stats.csv"
141+
expected_bliss = test_data_dir / "test_synergy_BlissMatrix.csv"
142142

143143
# Run Synergy analysis (output goes to ./cellpyability_output/synergy_output/)
144144
print(f"Running Synergy analysis with counts file: {counts_file}")
@@ -156,40 +156,40 @@ def test_synergy_module():
156156
)
157157

158158
# Check output file (in current working directory)
159-
output_stats = Path.cwd() / "cellpyability_output/synergy_output/test_synergy_stats.csv"
159+
output_bliss = Path.cwd() / "cellpyability_output/synergy_output/test_synergy_BlissMatrix.csv"
160160

161161
try:
162-
if not output_stats.exists():
163-
print(f"[FAIL] FAILED: Output file not created: {output_stats}")
162+
if not output_bliss.exists():
163+
print(f"[FAIL] FAILED: Output file not created: {output_bliss}")
164164
return False
165165

166-
print(f"Output file created: {output_stats}")
166+
print(f"Output file created: {output_bliss}")
167167

168168
# Compare files
169-
match, message = compare_csv_files(output_stats, expected_stats, tolerance=1e-10)
169+
match, message = compare_csv_files(output_bliss, expected_bliss, tolerance=1e-10)
170170

171171
if match:
172-
print(f"[PASS] PASSED: Synergy Stats output matches expected file")
172+
print(f"[PASS] PASSED: Synergy BlissMatrix output matches expected file")
173173
print(f" {message}")
174174
result = True
175175
else:
176-
print(f"[FAIL] FAILED: Synergy Stats output does not match")
176+
print(f"[FAIL] FAILED: Synergy BlissMatrix output does not match")
177177
print(f" {message}")
178178

179179
# Show first few rows for debugging
180180
print("\n First rows of output:")
181-
df_out = pd.read_csv(output_stats)
181+
df_out = pd.read_csv(output_bliss)
182182
print(df_out.head())
183183
print("\n First rows of expected:")
184-
df_exp = pd.read_csv(expected_stats)
184+
df_exp = pd.read_csv(expected_bliss)
185185
print(df_exp.head())
186186

187187
result = False
188188
finally:
189189
# Clean up output files - use ignore_errors for Windows compatibility
190-
if output_stats.exists():
190+
if output_bliss.exists():
191191
try:
192-
shutil.rmtree(output_stats.parent, ignore_errors=True)
192+
shutil.rmtree(output_bliss.parent, ignore_errors=True)
193193
except Exception as e:
194194
print(f"Warning: Could not clean up output directory: {e}")
195195

0 commit comments

Comments
 (0)