Skip to content

Commit 0be22b3

Browse files
authored
Merge pull request #140 from rohangirishrao/save-script-params
Fix for ` misc.save_script_parameters` (Issue #136) After discussing today we decided to: * merge this PR into `devel` - it's about fixing the broken function, which is done βœ… * move πŸ”œ the suggested changes to a new issue (or multiple ones) and then decide there πŸ”€ about the milestone question
2 parents 376ceb7 + a4cb525 commit 0be22b3

8 files changed

Lines changed: 205 additions & 6 deletions

File tree

β€Ž.vscode/settings.jsonβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"bdvp",
1616
"bigstitcher",
1717
"biop",
18+
"caplog",
1819
"clij",
1920
"Dscijava",
2021
"flatfield",

β€ŽTESTING.mdβ€Ž

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Testing πŸ§ͺ🧫 in Fiji / ImageJ2
22

3+
## Using 🎭 Poetry, pytest πŸπŸ”¬ and Python 3 for plain Python code
4+
5+
The easiest way to run [`pytest`][pytest] (using Python 3) is when you're
6+
already having a working [poetry] setup. In that case tests can simply be run by
7+
using the `run-poetry.sh` wrapper script, for example:
8+
9+
```bash
10+
scripts/run-poetry.sh run pytest tests/test_misc.py
11+
```
12+
313
## Using pytest πŸπŸ”¬ and Python 3 for plain Python code
414

515
Those parts of the package that do not interact / depend on ImageJ objects can
@@ -22,7 +32,7 @@ test -d "venv" || python3 -m venv venv
2232
source venv/bin/activate
2333

2434
# install dependencies / requirements:
25-
MOCKS_REL="0.2.0"
35+
MOCKS_REL="0.14.0"
2636
URL_PFX="https://github.com/imcf/imcf-fiji-mocks/releases/download/v$MOCKS_REL"
2737
pip install --upgrade \
2838
$URL_PFX/imcf_fiji_mocks-${MOCKS_REL}-py2.py3-none-any.whl \

β€Žpoetry.lockβ€Ž

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

β€Žpyproject.tomlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ version = "0.0.0"
2121

2222
[tool.poetry.dependencies]
2323
# IMPORTANT: see the "poetry.lock.md" file when changing dependencies!!!
24-
imcf-fiji-mocks = ">=0.14.0"
24+
imcf-fiji-mocks = ">=0.15.0.a0"
2525
python = ">=2.7"
2626
python-micrometa = "^15.2.3"
2727
sjlogging = ">=0.5.2"

β€Žscripts/run-poetry.shβ€Ž

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,19 @@ if [ -z "$RUN_ON_UNCLEAN" ]; then
2121
fi
2222
fi
2323

24+
TOML_STATUS=$(git status --porcelain pyproject.toml)
25+
if [ -n "$TOML_STATUS" ]; then
26+
echo "==== ERROR: stopping to preserve changes in 'pyproject.toml'! ===="
27+
echo
28+
git status pyproject.toml --porcelain
29+
echo
30+
echo "--------"
31+
echo "Refusing to continue as 'pyproject.toml' would be re-set at the end"
32+
echo "of this script. Please stash your changes and re-run the script!"
33+
echo
34+
exit 2
35+
fi
36+
2437
### clean up old poetry artifacts:
2538
rm -rf dist/
2639

β€Žsrc/imcflibs/imagej/misc.pyβ€Ž

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,3 +766,110 @@ def bytes_to_human_readable(size):
766766
# If the value is larger than the largest unit, fall back to TB with
767767
# the current value (already divided accordingly).
768768
return "%3.1f %s" % (size, "TB")
769+
770+
771+
def _is_password_style(item): # pragma: no cover (jython)
772+
"""Check if a script-parameter item is declared with `style="password"`.
773+
774+
Parameters
775+
----------
776+
item : org.scijava.module.ModuleItem
777+
The module item to check, obtained e.g. by calling `inputs()` on an
778+
instance of `org.scijava.script.ScriptInfo`.
779+
780+
Returns
781+
-------
782+
bool
783+
"""
784+
return WidgetStyle.isStyle(item, TextWidget.PASSWORD_STYLE)
785+
786+
787+
def save_script_parameters(
788+
script_globals, destination, save_file_name="script_parameters.txt"
789+
):
790+
"""Save all Fiji script parameters to a text file.
791+
792+
Record all input parameters defined in the Fiji script header (e.g.
793+
`#@ String`) to a text file such that they can be stored e.g. next to the
794+
input data and the analysis results in order to document how a specific
795+
processing run was executed.
796+
797+
The following parameters are excluded:
798+
799+
- Parameters explicitly declared with `style="password"`.
800+
- Runtime keys (case insensitive):
801+
- `USERNAME`
802+
- `SJLOG` (SciJava LogService)
803+
- `COMMAND` (SciJava CommandService)
804+
- `RM` (RoiManager)
805+
806+
Parameters
807+
----------
808+
script_globals : dict
809+
The globals dictionary from the running Fiji instance. Must be passed
810+
explicitly as `globals()` by the calling code.
811+
destination : str
812+
Directory where the script parameters file will be saved.
813+
save_file_name : str, optional
814+
Name of the script parameters file, by default "script_parameters.txt".
815+
816+
Examples
817+
--------
818+
In a Fiji script, you can call this function as follows to save the parameters:
819+
820+
>>> save_script_parameters(script_globals=globals(), destination="/data")
821+
Saved script parameters to: /data/script_parameters.txt
822+
"""
823+
try:
824+
module = script_globals.get("org.scijava.script.ScriptModule")
825+
# Access script metadata and inputs
826+
script_info = module.getInfo()
827+
inputs = module.getInputs()
828+
except:
829+
timed_log("ScriptModule inspection failed - skipping saving of parameters.")
830+
return
831+
832+
# NOTE: the two parameters are intentionally kept separate for (1) consistency
833+
# reasons with other scripts and (b) as this allows for easier modification of just
834+
# the output file e.g. in subsequent runs.
835+
destination = str(destination)
836+
out_path = os.path.join(destination, save_file_name)
837+
838+
# Keys to skip explicitly
839+
skip_keys = ["USERNAME", "SJLOG", "COMMAND", "RM"]
840+
841+
saved = skipped = passwords = 0
842+
with open(out_path, "w") as f:
843+
for item in script_info.inputs():
844+
key = item.getName()
845+
846+
# Skip if any keys are in the skip list
847+
if any(skip in key.upper() for skip in skip_keys):
848+
log.info("Skipping parameter from skip-list: %s", key)
849+
skipped += 1
850+
continue
851+
852+
# Skip if parameter is declared with password style
853+
if _is_password_style(item):
854+
log.info("Skipping password-style parameter: %s", key)
855+
passwords += 1
856+
continue
857+
858+
# TODO: discuss if this approach is fine within Fiji/Jython
859+
try:
860+
val = inputs.get(key)
861+
if val is None: # required for testing in CPython
862+
raise KeyError("failure looking up value for '%s'" % key)
863+
f.write("%s: %s\n" % (key, str(val)))
864+
saved += 1
865+
except:
866+
log.warning("Unable to fetch value for parameter: %s", key)
867+
pass
868+
869+
log.info(
870+
"Saved %i parameters (skipped %i password-style and %i others).",
871+
saved,
872+
passwords,
873+
skipped,
874+
)
875+
timed_log("Saved %i script parameters to: %s" % (saved, out_path))
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#@ String(label="Username") USERNAME
2+
#@ String(label="Password", style="password") PASSWORD
3+
#@ File(label="Path for results", style="directory") outputPath
4+
#@ Integer threshold
5+
#@ Boolean(label="Yes/No?") choice
6+
#@ RoiManager rm
7+
#@ CommandService command
8+
#@ LogService sjlog
9+
10+
import os
11+
12+
import imcflibs.log
13+
from imcflibs.imagej import misc
14+
15+
imcflibs.log.enable_console_logging()
16+
log = imcflibs.log.LOG
17+
18+
log.warning("Starting...")
19+
misc.save_script_parameters(script_globals=globals(), destination=outputPath)
20+
log.warning("Saved parameters to: %s\script_parameters.txt", outputPath)

β€Žtests/test_misc.pyβ€Ž

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,54 @@
11
"""Tests for `imcflibs.imagej.misc` utility functions."""
22

3+
import logging
4+
5+
from org.scijava.script import ScriptInfo, ScriptModule
6+
7+
import imcflibs.imagej.misc
8+
39
from imcflibs.imagej.misc import bytes_to_human_readable
10+
from imcflibs.imagej.misc import save_script_parameters
11+
12+
13+
PASSWORD_ITEMS = ["OMERO_PASSWD"]
14+
15+
16+
def test_save_script_parameters_fail(caplog):
17+
"""Tests save_script_parameters with an invalid script_globals object."""
18+
caplog.clear()
19+
20+
save_script_parameters(script_globals=None, destination="")
21+
assert "ScriptModule inspection failed" in caplog.messages[0]
22+
23+
24+
def test_save_script_parameters(tmp_path, monkeypatch, caplog):
25+
"""Tests save_script_parameters."""
26+
caplog.set_level(logging.DEBUG)
27+
caplog.clear()
28+
29+
base = tmp_path / "saved_parameters"
30+
base.mkdir()
31+
32+
def _is_password_style(item):
33+
return item.getName() in PASSWORD_ITEMS
34+
35+
monkeypatch.setattr(imcflibs.imagej.misc, "_is_password_style", _is_password_style)
36+
37+
script_module = ScriptModule(
38+
input_names=["AAA", "BBB", "OMERO_PASSWD", "SJLOG", "NOT_THERE"],
39+
inputs={"AAA": "aaa", "BBB": "bbb", "OMERO_PASSWD": "ultra-secret"},
40+
)
41+
script_globals = {"org.scijava.script.ScriptModule": script_module}
42+
save_script_parameters(script_globals, destination=base)
43+
assert "Skipping parameter from skip-list" in caplog.text
44+
assert "Skipping password-style parameter" in caplog.text
45+
assert "Unable to fetch value for parameter: NOT_THERE" in caplog.text
46+
assert "Saved 2 parameters (skipped 1 password-style and 1 others)." in caplog.text
47+
assert "Saved 2 script parameters to" in caplog.text
48+
49+
with open(str(base) + "/script_parameters.txt", "r") as f:
50+
contents = f.read()
51+
assert contents == "AAA: aaa\nBBB: bbb\n"
452

553

654
def test_bytes_to_human_readable_simple():

0 commit comments

Comments
Β (0)