Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 32 additions & 13 deletions src/imcflibs/imagej/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,16 +784,17 @@ 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.
`#@ String`) to a text file such that they can be stored e.g. next to the
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
``<script_name>_run-<timestamp>_params.txt``.

The following parameters are excluded:

- Parameters explicitly declared with `style="password"`.
Expand All @@ -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 ``<script_name>_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)

Expand All @@ -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
Expand Down
44 changes: 39 additions & 5 deletions tests/test_misc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests for `imcflibs.imagej.misc` utility functions."""

import logging
import os

from org.scijava.script import ScriptInfo, ScriptModule

Expand All @@ -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]


Expand All @@ -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-<timestamp>_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"
Expand Down
Loading