From 63ebc3b7010a7cd55f0b96decb76b486af084900 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Tue, 21 Oct 2025 10:57:57 -0500
Subject: [PATCH 1/8] replacing json attribute with results attribute
---
plantcv/parallel/cli.py | 4 ++--
plantcv/parallel/jupyterconfig.py | 5 +----
plantcv/parallel/process_results.py | 2 +-
plantcv/parallel/run_parallel.py | 6 +++---
plantcv/parallel/workflowconfig.py | 5 ++---
5 files changed, 9 insertions(+), 13 deletions(-)
diff --git a/plantcv/parallel/cli.py b/plantcv/parallel/cli.py
index 424fe576e..6587f6cd9 100644
--- a/plantcv/parallel/cli.py
+++ b/plantcv/parallel/cli.py
@@ -39,8 +39,8 @@ def options():
# Import a configuration if provided
if args.config:
config.import_config(config_file=args.config)
- if args.config == config.json:
- raise ValueError("Configuration file would be overwritten by results, change the json field of config.")
+ if args.config == config.results:
+ raise ValueError("Configuration file would be overwritten by results, change the results field of config.")
if not config.validate_config():
raise ValueError("Invalid configuration file. Check errors above.")
diff --git a/plantcv/parallel/jupyterconfig.py b/plantcv/parallel/jupyterconfig.py
index 43b3278f9..2f1be2c3a 100644
--- a/plantcv/parallel/jupyterconfig.py
+++ b/plantcv/parallel/jupyterconfig.py
@@ -206,7 +206,6 @@ def save_config(self):
object.__setattr__(config, attr, getattr(self, attr))
# set a few manually due to property differences
config.workflow = self.workflow
- config.json = self.results
# save
config.save_config(config_file=self.config)
parallel_print("Saved " + self.config, verbose=self.verbose)
@@ -225,9 +224,7 @@ def import_config(self, config_file):
# Import the JSON configuration data
config = json.load(fp)
for key, value in config.items():
- if key == "json":
- object.__setattr__(self, "results", value)
- elif key != "_metadata_terms":
+ if key != "_metadata_terms":
object.__setattr__(self, key, value)
@staticmethod
diff --git a/plantcv/parallel/process_results.py b/plantcv/parallel/process_results.py
index e56eb291e..f698e4fc5 100644
--- a/plantcv/parallel/process_results.py
+++ b/plantcv/parallel/process_results.py
@@ -21,7 +21,7 @@ def process_results(config):
# process results from the checkpoint inside tmp_dir
job_dir = config.tmp_dir
# name outputs from config
- json_file = config.json
+ json_file = config.results
# Data dictionary
data = {"variables": {}, "entities": []}
if os.path.exists(json_file):
diff --git a/plantcv/parallel/run_parallel.py b/plantcv/parallel/run_parallel.py
index 11f65f3b8..6eb34d92e 100644
--- a/plantcv/parallel/run_parallel.py
+++ b/plantcv/parallel/run_parallel.py
@@ -37,8 +37,8 @@ def run_parallel(config):
os.makedirs(config.img_outdir, exist_ok=True)
# Remove JSON results file if append=False
- if not config.append and os.path.exists(config.json):
- os.remove(config.json)
+ if not config.append and os.path.exists(config.results):
+ os.remove(config.results)
# Read image metadata
###########################################
@@ -82,7 +82,7 @@ def run_parallel(config):
# Convert results start time
convert_results_start_time = time.time()
print("Converting json to csv... ", file=sys.stderr)
- plantcv.utils.json2csv(config.json, os.path.splitext(config.json)[0])
+ plantcv.utils.json2csv(config.results, os.path.splitext(config.results)[0])
convert_results_clock_time = time.time() - convert_results_start_time
parallel_print(f"Processing results took {convert_results_clock_time} seconds.", file=sys.stderr, verbose=verbose)
###########################################
diff --git a/plantcv/parallel/workflowconfig.py b/plantcv/parallel/workflowconfig.py
index c723718d5..a01f3c5b6 100644
--- a/plantcv/parallel/workflowconfig.py
+++ b/plantcv/parallel/workflowconfig.py
@@ -9,7 +9,7 @@ class WorkflowConfig:
def __init__(self):
object.__setattr__(self, "input_dir", "")
- object.__setattr__(self, "json", "")
+ object.__setattr__(self, "results", "")
object.__setattr__(self, "filename_metadata", [])
object.__setattr__(self, "workflow", "")
object.__setattr__(self, "img_outdir", "./output_images")
@@ -101,7 +101,7 @@ def validate_config(self):
checks.append(False)
# Validate JSON file
if self.json == "":
- print("Error: an output JSON file (json) is required but is currently undefined.", file=sys.stderr)
+ print("Error: an output JSON file (results) is required but is currently undefined.", file=sys.stderr)
checks.append(False)
# Validate workflow script
if not os.path.exists(self.workflow):
@@ -297,7 +297,6 @@ def _config_attr_lookup(config, attr, val):
# for all other attributes, get their data from list
config_control = {
"input_dir": ["Images will be read from {}", str],
- "json": ["output will be written to {}", str],
"filename_metadata": ["Filenames will be parsed into {}", list],
"workflow": ["Will run {} python script in each job", str],
"img_outdir": ["Output images will be written to {}", str],
From dfea726f76a20fc59d63185ca372c6246571cf03 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:07:50 -0500
Subject: [PATCH 2/8] changing json attribute name in docs
---
docs/parallel_config.md | 4 ++--
docs/pipeline_parallel.md | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/parallel_config.md b/docs/parallel_config.md
index b23b7f514..5e329a792 100644
--- a/docs/parallel_config.md
+++ b/docs/parallel_config.md
@@ -47,7 +47,7 @@ Validate parameters/structure of configuration data.
* **input_dir**: (str, required): path/name of input images directory (validates that it exists).
-* **json**: (str, required): path/name of output JSON data file (appends new data if it already exists).
+* **results**: (str, required): path/name of output JSON data file (appends new data if it already exists).
* **filename_metadata**: (list, required): list of metadata terms used to construct filenames. for example:
@@ -206,7 +206,7 @@ config.import_config(config_file="my_config.json")
# Change configuration values directly in Python as needed. At a minimum you must specify input_dir, json, filename_metadata, workflow.
config.input_dir = "./my_images"
-config.json = "output.json"
+config.results = "output.json"
config.filename_metadata = ["plantbarcode", "timestamp"]
config.workflow = "my_workflow.py"
diff --git a/docs/pipeline_parallel.md b/docs/pipeline_parallel.md
index ffb3a2ee6..b0d18f118 100644
--- a/docs/pipeline_parallel.md
+++ b/docs/pipeline_parallel.md
@@ -125,7 +125,7 @@ Sample image filename: `cam1_16-08-06-16:45_el1100s1_p19.jpg`
```
{
"input_dir": "/shares/mgehan_share/raw_data/raw_image/2016-08_pat-edger/data/split-round1/split-cam1",
- "json": "edger-round1-brassica.json",
+ "results": "edger-round1-brassica.json",
"filename_metadata": ["camera", "timestamp", "id", "other"],
"workflow": "/home/mgehan/pat-edger/round1-python-pipelines/2016-08_pat-edger_brassica-cam1-splitimg.py",
"img_outdir": "/shares/mgehan_share/raw_data/raw_image/2016-08_pat-edger/data/split-round1/split-cam1/output",
@@ -203,7 +203,7 @@ in a list to the `filename_metadata` parameter.
```bash
{
"input_dir": "input_directory",
- "json": "output.json",
+ "results": "output.json",
"filename_metadata": ["camera", "plantbarcode", "timestamp"],
"workflow": "user-workflow.py",
"img_outdir": "output_directory",
@@ -263,7 +263,7 @@ Finally, we filter the basename for top view rgb images with "TV_VIS.*".
```bash
{
"input_dir": "input_directory",
- "json": "output.json",
+ "results": "output.json",
"filename_metadata": [""],
"workflow": "user-workflow.py",
"img_outdir": "output_directory",
@@ -315,7 +315,7 @@ To identify each image within our workflow, we will name them based on the `imgt
```
{
"input_dir": "/shares/mgehan_share/raw_data/raw_image/2016-08_pat-edger/data/split-round1/split-cam1",
- "json": "edger-round1-brassica.json",
+ "results": "edger-round1-brassica.json",
"filename_metadata": ["imgtype", "timestamp", "id", "other"],
"workflow": "/home/mgehan/pat-edger/round1-python-pipelines/2016-08_pat-edger_brassica-cam1-splitimg.py",
"img_outdir": "/shares/mgehan_share/raw_data/raw_image/2016-08_pat-edger/data/split-round1/split-cam1/output",
From 027c3620ffc05c66e52292444fd3e6ab12dc7140 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:08:18 -0500
Subject: [PATCH 3/8] changing test config
---
tests/testdata/workflowconfig_template.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/testdata/workflowconfig_template.json b/tests/testdata/workflowconfig_template.json
index 75555258c..c2aa846a9 100644
--- a/tests/testdata/workflowconfig_template.json
+++ b/tests/testdata/workflowconfig_template.json
@@ -1,6 +1,6 @@
{
"input_dir": "",
- "json": "",
+ "results": "",
"filename_metadata": [],
"include_all_subdirs": true,
"workflow": "",
From 8a39fea7b375675558fb55d091024e727a74eb38 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:12:44 -0500
Subject: [PATCH 4/8] tests update
---
tests/parallel/test_cli.py | 6 +++---
tests/parallel/test_job_builder.py | 6 +++---
tests/parallel/test_parsers.py | 8 ++++----
tests/parallel/test_workflowconfig.py | 6 +++---
4 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/tests/parallel/test_cli.py b/tests/parallel/test_cli.py
index d6695a2ec..bb272c193 100644
--- a/tests/parallel/test_cli.py
+++ b/tests/parallel/test_cli.py
@@ -24,7 +24,7 @@ def test_parallel_cli_invalid_config(parallel_test_data, tmpdir):
conf_file = tmpdir.mkdir("cache").join("config.json")
config = WorkflowConfig()
# Set valid values in config
- config.json = "valid_config.json"
+ config.results = "valid_config.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.img_outdir = str(conf_file.dirpath())
@@ -42,7 +42,7 @@ def test_parallel_cli_overwriting_config(parallel_test_data, tmpdir):
conf_file = tmpdir.mkdir("cache").join("config.json")
config = WorkflowConfig()
# Set valid values in config
- config.json = conf_file.strpath
+ config.results = conf_file.strpath
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.img_outdir = str(conf_file.dirpath())
@@ -63,7 +63,7 @@ def test_parallel_cli_valid_config(parallel_test_data, tmpdir):
config = WorkflowConfig()
# Set valid values in config
config.input_dir = parallel_test_data.flat_imgdir
- config.json = conf_file.dirpath().join(os.path.basename(parallel_test_data.new_results_file)).strpath
+ config.results = conf_file.dirpath().join(os.path.basename(parallel_test_data.new_results_file)).strpath
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.metadata_regex = {"filepath":".*"}
config.workflow = parallel_test_data.workflow_script
diff --git a/tests/parallel/test_job_builder.py b/tests/parallel/test_job_builder.py
index 323133755..eca23490b 100644
--- a/tests/parallel/test_job_builder.py
+++ b/tests/parallel/test_job_builder.py
@@ -9,7 +9,7 @@ def test_job_builder_single_image(parallel_test_data, tmpdir):
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.snapshot_imgdir
- config.json = "output.json"
+ config.results = "output.json"
config.tmp_dir = str(tmp_dir)
config.filename_metadata = ["imgtype", "camera", "rotation", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
@@ -41,7 +41,7 @@ def test_job_builder_coprocess(parallel_test_data, tmpdir):
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.snapshot_imgdir
- config.json = "output.json"
+ config.results = "output.json"
config.tmp_dir = str(tmp_dir)
config.filename_metadata = ["imgtype", "camera", "rotation", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
@@ -74,7 +74,7 @@ def test_job_builder_auto_name(parallel_test_data, tmpdir):
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.snapshot_imgdir
- config.json = "output.json"
+ config.results = "output.json"
config.tmp_dir = str(tmp_dir)
config.filename_metadata = ["imgtype", "camera", "rotation", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
diff --git a/tests/parallel/test_parsers.py b/tests/parallel/test_parsers.py
index 9588b1ed2..c20c5fd78 100644
--- a/tests/parallel/test_parsers.py
+++ b/tests/parallel/test_parsers.py
@@ -12,7 +12,7 @@ def test_metadata_parser_snapshots(parallel_test_data, imgformat):
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.snapshot_imgdir
- config.json = "output.json"
+ config.results = "output.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.metadata_filters = {"imgtype": "VIS", "camera": "SV"}
@@ -37,7 +37,7 @@ def test_metadata_parser_images(parallel_test_data, subdirs, imgformat, outlengt
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.flat_imgdir
- config.json = "output.json"
+ config.results = "output.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.metadata_filters = {"imgtype": "VIS"}
@@ -57,7 +57,7 @@ def test_metadata_parser_phenodata(parallel_test_data):
# Create config instance
config = WorkflowConfig()
config.input_dir = parallel_test_data.phenodata_dir
- config.json = "output.json"
+ config.results = "output.json"
config.workflow = parallel_test_data.workflow_script
config.imgformat = "jpg"
@@ -85,7 +85,7 @@ def test_read_checkpoint_data(parallel_test_data):
os.chdir(parallel_test_data.datadir)
config = WorkflowConfig()
config.input_dir = parallel_test_data.phenodata_dir
- config.json = "output.json"
+ config.results = "output.json"
config.workflow = parallel_test_data.workflow_script
config.imgformat = "jpg"
config.checkpoint = True
diff --git a/tests/parallel/test_workflowconfig.py b/tests/parallel/test_workflowconfig.py
index a3de08275..3535dd27f 100644
--- a/tests/parallel/test_workflowconfig.py
+++ b/tests/parallel/test_workflowconfig.py
@@ -43,7 +43,7 @@ def test_validate_config(parallel_test_data, tmpdir):
config = WorkflowConfig()
# Set valid values in config
config.input_dir = parallel_test_data.flat_imgdir
- config.json = "valid_config.json"
+ config.results = "valid_config.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.img_outdir = str(img_outdir)
@@ -59,7 +59,7 @@ def test_invalid_startdate(parallel_test_data, tmpdir):
config = WorkflowConfig()
# Set valid values in config
config.input_dir = parallel_test_data.flat_imgdir
- config.json = "valid_config.json"
+ config.results = "valid_config.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = parallel_test_data.workflow_script
config.img_outdir = str(img_outdir)
@@ -76,7 +76,7 @@ def test_invalid_enddate(parallel_test_data, tmpdir):
config = WorkflowConfig()
# Set valid values in config
config.input_dir = config.input_dir = parallel_test_data.flat_imgdir
- config.json = "valid_config.json"
+ config.results = "valid_config.json"
config.filename_metadata = ["imgtype", "camera", "frame", "zoom", "lifter", "gain", "exposure", "id"]
config.workflow = config.workflow = parallel_test_data.workflow_script
config.img_outdir = str(img_outdir)
From 11485394b49611e2b534a138cc0329a8ffbb32d5 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Tue, 21 Oct 2025 11:29:26 -0500
Subject: [PATCH 5/8] updating tests
---
plantcv/parallel/jupyterconfig.py | 1 +
plantcv/parallel/workflowconfig.py | 4 ++--
tests/parallel/test_jupyterconfig.py | 2 +-
tests/parallel/test_process_results.py | 8 ++++----
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/plantcv/parallel/jupyterconfig.py b/plantcv/parallel/jupyterconfig.py
index 2f1be2c3a..728b9afa3 100644
--- a/plantcv/parallel/jupyterconfig.py
+++ b/plantcv/parallel/jupyterconfig.py
@@ -206,6 +206,7 @@ def save_config(self):
object.__setattr__(config, attr, getattr(self, attr))
# set a few manually due to property differences
config.workflow = self.workflow
+ config.results = self.results
# save
config.save_config(config_file=self.config)
parallel_print("Saved " + self.config, verbose=self.verbose)
diff --git a/plantcv/parallel/workflowconfig.py b/plantcv/parallel/workflowconfig.py
index a01f3c5b6..9266ea497 100644
--- a/plantcv/parallel/workflowconfig.py
+++ b/plantcv/parallel/workflowconfig.py
@@ -99,8 +99,8 @@ def validate_config(self):
print(f"Error: input directory (input_dir) is required and {self.input_dir} does not exist.",
file=sys.stderr)
checks.append(False)
- # Validate JSON file
- if self.json == "":
+ # Validate JSON results file
+ if self.results == "":
print("Error: an output JSON file (results) is required but is currently undefined.", file=sys.stderr)
checks.append(False)
# Validate workflow script
diff --git a/tests/parallel/test_jupyterconfig.py b/tests/parallel/test_jupyterconfig.py
index 862af3dea..41d0a33af 100644
--- a/tests/parallel/test_jupyterconfig.py
+++ b/tests/parallel/test_jupyterconfig.py
@@ -79,7 +79,7 @@ def test_jupcon_run(parallel_test_data, tmpdir):
jupcon.notebook = jupcon.find_notebook()
jupcon.input_dir = parallel_test_data.flat_imgdir
jupcon.workflow = "example.py"
- jupcon.results = "example"
+ jupcon.results = "example.json"
jupcon.run()
assert os.path.exists(jupcon.results)
diff --git a/tests/parallel/test_process_results.py b/tests/parallel/test_process_results.py
index 4e26532de..6e6ee988e 100644
--- a/tests/parallel/test_process_results.py
+++ b/tests/parallel/test_process_results.py
@@ -11,7 +11,7 @@ def test_process_results(parallel_test_data, tmpdir):
config = type("smallconfig", (),
{"tmp_dir": parallel_test_data.parallel_results_dir,
"checkpoint": False,
- "json": result_file})
+ "results": result_file})
# Run twice to create appended results
process_results(config)
process_results(config)
@@ -28,7 +28,7 @@ def test_process_results_new_output(parallel_test_data, tmpdir):
config = type("smallconfig", (),
{"tmp_dir": parallel_test_data.parallel_results_dir,
"checkpoint": False,
- "json": result_file})
+ "results": result_file})
process_results(config)
# Assert output matches expected values
@@ -42,7 +42,7 @@ def test_process_results_valid_json(parallel_test_data):
config = type("smallconfig", (),
{"tmp_dir": parallel_test_data.parallel_results_dir,
"checkpoint": "false",
- "json": parallel_test_data.valid_json_file})
+ "results": parallel_test_data.valid_json_file})
# Test when the file is a valid json file but doesn't contain expected keys
with pytest.raises(RuntimeError):
process_results(config)
@@ -56,6 +56,6 @@ def test_process_results_invalid_json(tmpdir):
config = type("smallconfig", (),
{"tmp_dir": os.path.split(str(result_file))[0],
"checkpoint": "false",
- "json": result_file})
+ "results": result_file})
with pytest.raises(RuntimeError):
process_results(config)
From 0e3cc02fbe48fd902df5b74dbd2149a15b97c0e6 Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Fri, 24 Oct 2025 10:13:05 -0500
Subject: [PATCH 6/8] mentioning breaking change
---
docs/updating.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/docs/updating.md b/docs/updating.md
index fc3769815..1dae94aab 100644
--- a/docs/updating.md
+++ b/docs/updating.md
@@ -59,6 +59,10 @@ automatically. Alternatively, you can run `pip install -e .` to reinstall the pa
### Breaking changes between v4 and v5
+#### plantcv.parallel.WorkflowConfig
+
+Renamed the "json" attribute to "results" for clarity about what it controls and for consistency with new [jupyterconfig](parallel_jupyterconfig.md)
+
#### plantcv.spectral_index.egi
Renamed the input parameter `rgb_img` to `img` to reflect the flexibility of using the [EGI index function](spectral_index.md)
From 2c6bc0dce5e8c93553702caa86fe8f1db4af89ea Mon Sep 17 00:00:00 2001
From: Josh Sumner <51797700+joshqsumner@users.noreply.github.com>
Date: Fri, 31 Oct 2025 13:27:01 -0500
Subject: [PATCH 7/8] updating key name
---
tests/parallel/test_workflowconfig.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/parallel/test_workflowconfig.py b/tests/parallel/test_workflowconfig.py
index d3382a970..e21363e70 100644
--- a/tests/parallel/test_workflowconfig.py
+++ b/tests/parallel/test_workflowconfig.py
@@ -112,7 +112,7 @@ def test_too_many_cluster_config_cores(parallel_test_data):
# Create config instance
config = WorkflowConfig()
config.input_dir = config.input_dir = parallel_test_data.flat_imgdir
- config.json = "valid_config.json"
+ config.results = "valid_config.json"
config.workflow = config.workflow = parallel_test_data.workflow_script
# Set invalid values in config
# input_dir and json are not defined by default, but are required
From bf9116fe41840e61cab48bb8945d6fbe3ccf2af2 Mon Sep 17 00:00:00 2001
From: Noah Fahlgren
Date: Fri, 30 Jan 2026 16:47:44 -0600
Subject: [PATCH 8/8] Fix merge conflict mistake
---
plantcv/parallel/cli.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plantcv/parallel/cli.py b/plantcv/parallel/cli.py
index 3808ad208..326f345f3 100644
--- a/plantcv/parallel/cli.py
+++ b/plantcv/parallel/cli.py
@@ -39,7 +39,7 @@ def options():
# Import a configuration if provided
if args.config:
config.import_config(config_file=args.config)
- if args.config == config.json:
+ if args.config == config.results:
print("Configuration file would be overwritten by results, change the results field of config.",
file=sys.stderr)
sys.exit(1)