import csv import os from java.lang import ProcessBuilder, String from java.io import File, BufferedReader, InputStreamReader from java.util import ArrayList from ij import IJ def build_args(inputDir, outputDir, envPython, modelChoice, customModel, variant, attenCap, useGpu, segChannel, quantChannel, poolingMode, referenceReplicate, sidecarScript, batchSize=8): """Build the argument list for the sidecar CLI; returns a Java ArrayList of strings. envPython is the full path to the Python interpreter in the cellpose4_p63 env. sidecarScript is the resolved absolute path to sidecar/run_p63.py (computed in p63_Cellpose_Batch.py where __file__ resolution is already correct). Paths are converted to absolute strings so spaces are handled correctly. """ sidecar_script = str(sidecarScript) # model arg: named pretrained model or path to a custom model file if str(modelChoice) == "custom": model_arg = str(customModel) else: model_arg = str(modelChoice) # "cpsam" or "cyto3" cmd = ArrayList() for token in [ str(envPython), "-u", # force line-buffered stdout so IJ.log sees lines as they print sidecar_script, "--input-dir", str(inputDir), "--output-dir", str(outputDir), "--model", model_arg, "--seg-channel", str(int(segChannel)), "--quant-channel", str(int(quantChannel)), "--variant", str(variant), "--atten-cap", "{:.2f}".format(float(attenCap)), "--pooling-mode", str(poolingMode), ]: cmd.add(String(token)) cmd.add(String("--batch-size")) cmd.add(String(str(int(batchSize)))) if useGpu: cmd.add(String("--use-gpu")) if str(poolingMode) == "reference_replicate": cmd.add(String("--reference-replicate")) cmd.add(String(str(referenceReplicate))) return cmd def run_sidecar(args): """Launch the sidecar via ProcessBuilder, stream combined stdout+stderr to IJ.log; return the exit code. args should be the ArrayList returned by build_args. Raises RuntimeError on non-zero exit so the caller can surface a clear message. """ pb = ProcessBuilder(args) pb.redirectErrorStream(True) # merge stderr into stdout so we see tracebacks IJ.log("--- p63 sidecar starting ---") IJ.log("Command: " + " ".join(list(args))) process = pb.start() # Stream output line-by-line to IJ Log so errors are visible immediately reader = BufferedReader(InputStreamReader(process.getInputStream())) log_lines = [] line = reader.readLine() while line is not None: IJ.log(line) log_lines.append(line) line = reader.readLine() exit_code = process.waitFor() IJ.log("--- p63 sidecar finished (exit={}) ---".format(exit_code)) if exit_code != 0: tail = "\n".join(log_lines[-20:]) # last 20 lines capture the traceback raise RuntimeError( "Sidecar exited with code {}.\nLast output:\n{}".format(exit_code, tail) ) return exit_code def _parse_csv(path): """Read a CSV file into a list of dicts (one dict per row).""" rows = [] with open(path, "r") as fh: reader = csv.DictReader(fh) for row in reader: rows.append(dict(row)) return rows def read_summary_csv(outputDir): """Parse summary.csv from outputDir; return a list of dicts, one per image.""" return _parse_csv(os.path.join(str(outputDir), "summary.csv")) def read_cells_csv(outputDir): """Parse cells.csv from outputDir; return a list of dicts, one per cell.""" return _parse_csv(os.path.join(str(outputDir), "cells.csv"))