diff --git a/src/imcflibs/imagej/misc.py b/src/imcflibs/imagej/misc.py index 045301d..36e1a0a 100644 --- a/src/imcflibs/imagej/misc.py +++ b/src/imcflibs/imagej/misc.py @@ -784,9 +784,7 @@ def _is_password_style(item): # pragma: no cover (jython) return WidgetStyle.isStyle(item, TextWidget.PASSWORD_STYLE) -def save_script_parameters( - script_globals, destination, save_file_name="script_parameters.txt" -): +def save_script_parameters(script_globals, destination="", overwrite=False): """Save all Fiji script parameters to a text file. Record all input parameters defined in the Fiji script header (e.g. @@ -794,6 +792,9 @@ def save_script_parameters( input data and the analysis results in order to document how a specific processing run was executed. + The file name is generated automatically following the pattern + ``_run-_params.txt``. + The following parameters are excluded: - Parameters explicitly declared with `style="password"`. @@ -808,30 +809,49 @@ def save_script_parameters( script_globals : dict The globals dictionary from the running Fiji instance. Must be passed explicitly as `globals()` by the calling code. - destination : str - Directory where the script parameters file will be saved. - save_file_name : str, optional - Name of the script parameters file, by default "script_parameters.txt". + destination : str, optional + Directory where the script parameters file will be saved. If empty + (the default), parameter saving is skipped. + overwrite : bool, optional + If True, use a fixed filename ``_params.txt`` that gets + overwritten on each run. If False (the default), a timestamp is + included in the filename to preserve parameters from each run. Examples -------- In a Fiji script, you can call this function as follows to save the parameters: >>> save_script_parameters(script_globals=globals(), destination="/data") - Saved script parameters to: /data/script_parameters.txt + Saved 2 script parameters to: /data/example1_run-2026-04-10--10-41_params.txt """ + if destination == "": + timed_log("No destination provided for saving script parameters - skipping.") + return + try: module = script_globals.get("org.scijava.script.ScriptModule") - # Access script metadata and inputs script_info = module.getInfo() inputs = module.getInputs() except: timed_log("ScriptModule inspection failed - skipping saving of parameters.") return - # NOTE: the two parameters are intentionally kept separate for (1) consistency - # reasons with other scripts and (b) as this allows for easier modification of just - # the output file e.g. in subsequent runs. + script_location = script_info.getLocation() + if script_location: + # getLocation() returns "file:/path/to/script.py" + # strip the "file:" prefix to get the actual path + if script_location.startswith("file:"): + script_location = script_location[5:] + script_name = os.path.splitext(os.path.basename(script_location))[0] + else: + script_name = "script" + + if overwrite: + save_file_name = "%s_params.txt" % script_name + else: + timestamp = time.strftime("%Y-%m-%d--%H-%M") + save_file_name = "%s_run-%s_params.txt" % (script_name, timestamp) + destination = str(destination) out_path = os.path.join(destination, save_file_name) @@ -855,7 +875,6 @@ def save_script_parameters( passwords += 1 continue - # TODO: discuss if this approach is fine within Fiji/Jython try: val = inputs.get(key) if val is None: # required for testing in CPython diff --git a/tests/test_misc.py b/tests/test_misc.py index 9f137cb..81266bf 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1,6 +1,7 @@ """Tests for `imcflibs.imagej.misc` utility functions.""" import logging +import os from org.scijava.script import ScriptInfo, ScriptModule @@ -9,15 +10,22 @@ from imcflibs.imagej.misc import bytes_to_human_readable from imcflibs.imagej.misc import save_script_parameters - PASSWORD_ITEMS = ["OMERO_PASSWD"] -def test_save_script_parameters_fail(caplog): +def test_save_script_parameters_no_destination(caplog): + """Tests save_script_parameters skips saving without a destination.""" + caplog.clear() + + save_script_parameters(script_globals=None) + assert "No destination provided" in caplog.messages[0] + + +def test_save_script_parameters_fail(tmp_path, caplog): """Tests save_script_parameters with an invalid script_globals object.""" caplog.clear() - save_script_parameters(script_globals=None, destination="") + save_script_parameters(script_globals=None, destination=tmp_path) assert "ScriptModule inspection failed" in caplog.messages[0] @@ -38,19 +46,45 @@ def _is_password_style(item): input_names=["AAA", "BBB", "OMERO_PASSWD", "SJLOG", "NOT_THERE"], inputs={"AAA": "aaa", "BBB": "bbb", "OMERO_PASSWD": "ultra-secret"}, ) + script_module.getInfo().location = "file:/some/path/example1.py" script_globals = {"org.scijava.script.ScriptModule": script_module} - save_script_parameters(script_globals, destination=base) + save_script_parameters(script_globals, destination=base, overwrite=True) assert "Skipping parameter from skip-list" in caplog.text assert "Skipping password-style parameter" in caplog.text assert "Unable to fetch value for parameter: NOT_THERE" in caplog.text assert "Saved 2 parameters (skipped 1 password-style and 1 others)." in caplog.text assert "Saved 2 script parameters to" in caplog.text - with open(str(base) + "/script_parameters.txt", "r") as f: + with open(str(base) + "/example1_params.txt", "r") as f: contents = f.read() assert contents == "AAA: aaa\nBBB: bbb\n" +def test_save_script_parameters_timestamped(tmp_path, monkeypatch): + """Tests save_script_parameters default naming without a script location.""" + + def _is_password_style(item): + return False + + monkeypatch.setattr(imcflibs.imagej.misc, "_is_password_style", _is_password_style) + + script_module = ScriptModule(input_names=["AAA"], inputs={"AAA": "aaa"}) + script_globals = {"org.scijava.script.ScriptModule": script_module} + save_script_parameters(script_globals, destination=tmp_path) + + # the folder should now contain exactly one file + files = os.listdir(str(tmp_path)) + assert len(files) == 1 + + # its name is "script_run-_params.txt" + file_name = files[0] + assert file_name.startswith("script_run-") + assert file_name.endswith("_params.txt") + + with open(os.path.join(str(tmp_path), file_name), "r") as f: + assert f.read() == "AAA: aaa\n" + + def test_bytes_to_human_readable_simple(): """Ensure common sizes are formatted into human-readable strings.""" assert bytes_to_human_readable(500) == "500.0 bytes"