From 1e6ed7cac95aa714698bde2abee7a1745320f277 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 14:11:10 -0400 Subject: [PATCH 01/13] add new module for computing mud --- src/diffpy/labpdfproc/mud_calculator.py | 100 ++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 src/diffpy/labpdfproc/mud_calculator.py diff --git a/src/diffpy/labpdfproc/mud_calculator.py b/src/diffpy/labpdfproc/mud_calculator.py new file mode 100644 index 0000000..93a0ba1 --- /dev/null +++ b/src/diffpy/labpdfproc/mud_calculator.py @@ -0,0 +1,100 @@ +import numpy as np +from scipy.optimize import dual_annealing +from scipy.signal import convolve + +from diffpy.utils.parsers.loaddata import loadData + + +def _top_hat(x, slit_width): + """ + create a top-hat function, return 1.0 for values within the specified slit width and 0 otherwise + """ + return np.where((x >= -slit_width) & (x <= slit_width), 1.0, 0) + + +def _model_function(x, diameter, x0, I0, mud, slope): + """ + compute the model function with the following steps: + 1. Recenter x to h by subtracting x0 (so that the circle is centered at 0 and it is easier to compute length l) + 2. Compute length l that is the effective length for computing intensity I = I0 * e^{-mu * l}: + - For h within the diameter range, l is the chord length of the circle at position h + - For h outside this range, l = 0 + 3. Apply a linear adjustment to I0 by taking I0 as I0 - slope * x + """ + min_radius = -diameter / 2 + max_radius = diameter / 2 + h = x - x0 + length = np.piecewise( + h, + [h < min_radius, (min_radius <= h) & (h <= max_radius), h > max_radius], + [0, lambda h: 2 * np.sqrt((diameter / 2) ** 2 - h**2), 0], + ) + return (I0 - slope * x) * np.exp(-mud / diameter * length) + + +def _extend_x_and_convolve(x, diameter, slit_width, x0, I0, mud, slope): + """ + extend x values and I values for padding (so that we don't have tails in convolution), then perform convolution + (note that the convolved I values are the same as modeled I values if slit width is close to 0) + """ + n_points = len(x) + x_left_pad = np.linspace(x.min() - n_points * (x[1] - x[0]), x.min(), n_points) + x_right_pad = np.linspace(x.max(), x.max() + n_points * (x[1] - x[0]), n_points) + x_extended = np.concatenate([x_left_pad, x, x_right_pad]) + I_extended = _model_function(x_extended, diameter, x0, I0, mud, slope) + kernel = _top_hat(x_extended - x_extended.mean(), slit_width) + I_convolved = I_extended # this takes care of the case where slit width is close to 0 + if kernel.sum() != 0: + kernel /= kernel.sum() + I_convolved = convolve(I_extended, kernel, mode="same") + padding_length = len(x_left_pad) + return I_convolved[padding_length:-padding_length] + + +def _objective_function(params, x, observed_data): + """ + compute the objective function for fitting a model to the observed/experimental data + by minimizing the sum of squared residuals between the observed data and the convolved model data + """ + diameter, slit_width, x0, I0, mud, slope = params + convolved_model_data = _extend_x_and_convolve(x, diameter, slit_width, x0, I0, mud, slope) + residuals = observed_data - convolved_model_data + return np.sum(residuals**2) + + +def _compute_single_mud(x_data, I_data): + """ + perform dual annealing optimization and extract the parameters + """ + bounds = [ + (1e-5, x_data.max() - x_data.min()), # diameter: [small positive value, upper bound] + (0, (x_data.max() - x_data.min()) / 2), # slit width: [0, upper bound] + (x_data.min(), x_data.max()), # x0: [min x, max x] + (1e-5, I_data.max()), # I0: [small positive value, max observed intensity] + (1e-5, 20), # muD: [small positive value, upper bound] + (-10000, 10000), # slope: [lower bound, upper bound] + ] + result = dual_annealing(_objective_function, bounds, args=(x_data, I_data)) + diameter, slit_width, x0, I0, mud, slope = result.x + convolved_fitted_signal = _extend_x_and_convolve(x_data, diameter, slit_width, x0, I0, mud, slope) + residuals = I_data - convolved_fitted_signal + rmse = np.sqrt(np.mean(residuals**2)) + return mud, rmse + + +def compute_mud(filepath): + """ + compute the best-fit mu*D value from a z-scan file + + Parameters + ---------- + filepath str + the path to the z-scan file + + Returns + ------- + a float contains the best-fit mu*D value + """ + x_data, I_data = loadData(filepath, unpack=True) + best_mud, _ = min((_compute_single_mud(x_data, I_data) for _ in range(10)), key=lambda pair: pair[1]) + return best_mud From 17afd1336bcba9e3c1e908c2c30c9a8c4e2fd069 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 14:28:48 -0400 Subject: [PATCH 02/13] add test for computing mud --- tests/test_mud_calculator.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/test_mud_calculator.py diff --git a/tests/test_mud_calculator.py b/tests/test_mud_calculator.py new file mode 100644 index 0000000..6c7351f --- /dev/null +++ b/tests/test_mud_calculator.py @@ -0,0 +1,22 @@ +from pathlib import Path + +import numpy as np +import pytest + +from diffpy.labpdfproc.mud_calculator import _extend_x_and_convolve, compute_mud + + +def test_compute_mud(tmp_path): + diameter, slit_width, x0, I0, mud, slope = 1, 0.1, 0, 1e5, 3, 0 + x_data = np.linspace(-1, 1, 50) + convolved_I_data = _extend_x_and_convolve(x_data, diameter, slit_width, x0, I0, mud, slope) + + directory = Path(tmp_path) + file = directory / "testfile" + with open(file, "w") as f: + for x, I in zip(x_data, convolved_I_data): + f.write(f"{x}\t{I}\n") + + expected_mud = 3 + actual_mud = compute_mud(file) + assert actual_mud == pytest.approx(expected_mud, rel=0.1, abs=0.1) From 3b0846282be35c16e22e917ca4029d58ad504f15 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 14:56:06 -0400 Subject: [PATCH 03/13] add input argument for z-scan file and function for computing muD from that file --- src/diffpy/labpdfproc/labpdfprocapp.py | 6 ++++++ src/diffpy/labpdfproc/tools.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/diffpy/labpdfproc/labpdfprocapp.py b/src/diffpy/labpdfproc/labpdfprocapp.py index a9f5d58..f273a2a 100644 --- a/src/diffpy/labpdfproc/labpdfprocapp.py +++ b/src/diffpy/labpdfproc/labpdfprocapp.py @@ -124,6 +124,12 @@ def get_args(override_cli_inputs=None): ), default=None, ) + p.add_argument( + "-z", + "--z-scan-file", + help="Path to the z-scan file to be loaded to overload the mu*D value.", + default=None, + ) args = p.parse_args(override_cli_inputs) return args diff --git a/src/diffpy/labpdfproc/tools.py b/src/diffpy/labpdfproc/tools.py index 30520d6..90274b2 100644 --- a/src/diffpy/labpdfproc/tools.py +++ b/src/diffpy/labpdfproc/tools.py @@ -1,6 +1,7 @@ import copy from pathlib import Path +from diffpy.labpdfproc.mud_calculator import compute_mud from diffpy.utils.tools import get_package_info, get_user_info WAVELENGTHS = {"Mo": 0.71, "Ag": 0.59, "Cu": 1.54} @@ -134,6 +135,28 @@ def set_wavelength(args): return args +def set_mud(args): + """ + Set the mud based on the given input arguments + + Parameters + ---------- + args argparse.Namespace + the arguments from the parser + + Returns + ------- + args argparse.Namespace + """ + if args.z_scan_file: + filepath = Path(args.z_scan_file).resolve() + if not filepath.is_file(): + raise FileNotFoundError(f"Cannot find {args.z_scan_file}. Please specify a valid file path.") + args.z_scan_file = filepath + args.mud = compute_mud(filepath) + return args + + def _load_key_value_pair(s): items = s.split("=") key = items[0].strip() @@ -234,6 +257,7 @@ def preprocessing_args(args): args = set_input_lists(args) args.output_directory = set_output_directory(args) args = set_wavelength(args) + args = set_mud(args) args = load_user_metadata(args) return args From c7fb8ecd52269de25f8c4804aa6e11192df10a46 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 15:42:16 -0400 Subject: [PATCH 04/13] add test functions for overloading mud with simulated data added in conftest --- src/diffpy/labpdfproc/tests/conftest.py | 17 ++++++++++++++ src/diffpy/labpdfproc/tests/test_tools.py | 28 +++++++++++++++++++++++ src/diffpy/labpdfproc/tools.py | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/diffpy/labpdfproc/tests/conftest.py b/src/diffpy/labpdfproc/tests/conftest.py index 075e95f..603ae8a 100644 --- a/src/diffpy/labpdfproc/tests/conftest.py +++ b/src/diffpy/labpdfproc/tests/conftest.py @@ -11,6 +11,8 @@ def user_filesystem(tmp_path): input_dir.mkdir(parents=True, exist_ok=True) home_dir = base_dir / "home_dir" home_dir.mkdir(parents=True, exist_ok=True) + test_dir = base_dir / "test_dir" + test_dir.mkdir(parents=True, exist_ok=True) chi_data = "dataformat = twotheta\n mode = xray\n # chi_Q chi_I\n 1 2\n 3 4\n 5 6\n 7 8\n" xy_data = "1 2\n 3 4\n 5 6\n 7 8" @@ -51,4 +53,19 @@ def user_filesystem(tmp_path): with open(home_dir / "diffpyconfig.json", "w") as f: json.dump(home_config_data, f) + z_scan_data = """ + -1.00000000 100000.00000000 + -0.77777778 100000.00000000 + -0.55555556 100000.00000000 + -0.33333333 10687.79256604 + -0.11111111 5366.53289631 + 0.11111111 5366.53289631 + 0.33333333 10687.79256604 + 0.55555556 100000.00000000 + 0.77777778 100000.00000000 + 1.00000000 100000.00000000 + """ + with open(test_dir / "testfile.xy", "w") as f: + f.write(z_scan_data) + yield tmp_path diff --git a/src/diffpy/labpdfproc/tests/test_tools.py b/src/diffpy/labpdfproc/tests/test_tools.py index e17ed42..63fd952 100644 --- a/src/diffpy/labpdfproc/tests/test_tools.py +++ b/src/diffpy/labpdfproc/tests/test_tools.py @@ -13,6 +13,7 @@ load_user_metadata, preprocessing_args, set_input_lists, + set_mud, set_output_directory, set_wavelength, ) @@ -188,6 +189,32 @@ def test_set_wavelength_bad(inputs, msg): actual_args = set_wavelength(actual_args) +def test_set_mud(user_filesystem): + cli_inputs = ["2.5", "data.xy"] + actual_args = get_args(cli_inputs) + actual_args = set_mud(actual_args) + assert actual_args.mud == pytest.approx(2.5, rel=0.1, abs=0.1) + assert actual_args.z_scan_file is None + + cwd = Path(user_filesystem) + test_dir = cwd / "test_dir" + os.chdir(cwd) + inputs = ["--z-scan-file", "test_dir/testfile.xy"] + expected = [3, str(test_dir / "testfile.xy")] + cli_inputs = ["2.5", "data.xy"] + inputs + actual_args = get_args(cli_inputs) + actual_args = set_mud(actual_args) + assert actual_args.mud == pytest.approx(expected[0], rel=0.1, abs=0.1) + assert actual_args.z_scan_file == expected[1] + + +def test_set_mud_bad(): + cli_inputs = ["2.5", "data.xy", "--z-scan-file", "invalid file"] + actual_args = get_args(cli_inputs) + with pytest.raises(FileNotFoundError, match="Cannot find invalid file. Please specify a valid file path."): + actual_args = set_mud(actual_args) + + params5 = [ ([], []), ( @@ -317,5 +344,6 @@ def test_load_metadata(mocker, user_filesystem): "username": "cli_username", "email": "cli@email.com", "package_info": {"diffpy.labpdfproc": "1.2.3", "diffpy.utils": "3.3.0"}, + "z_scan_file": None, } assert actual_metadata == expected_metadata diff --git a/src/diffpy/labpdfproc/tools.py b/src/diffpy/labpdfproc/tools.py index 90274b2..a4301ed 100644 --- a/src/diffpy/labpdfproc/tools.py +++ b/src/diffpy/labpdfproc/tools.py @@ -152,7 +152,7 @@ def set_mud(args): filepath = Path(args.z_scan_file).resolve() if not filepath.is_file(): raise FileNotFoundError(f"Cannot find {args.z_scan_file}. Please specify a valid file path.") - args.z_scan_file = filepath + args.z_scan_file = str(filepath) args.mud = compute_mud(filepath) return args From 9510d6088f9e628afcd0f972b826715c1f099507 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Thu, 12 Sep 2024 21:37:14 -0400 Subject: [PATCH 05/13] small tweak to argparse help message for z-scan --- src/diffpy/labpdfproc/labpdfprocapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/labpdfproc/labpdfprocapp.py b/src/diffpy/labpdfproc/labpdfprocapp.py index f273a2a..1cbb78b 100644 --- a/src/diffpy/labpdfproc/labpdfprocapp.py +++ b/src/diffpy/labpdfproc/labpdfprocapp.py @@ -127,7 +127,7 @@ def get_args(override_cli_inputs=None): p.add_argument( "-z", "--z-scan-file", - help="Path to the z-scan file to be loaded to overload the mu*D value.", + help="Path to the z-scan file to be loaded to determine the mu*D value", default=None, ) args = p.parse_args(override_cli_inputs) From 2616b1953d9b71498ffb4e7c80676e93aac95dee Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Sep 2024 13:32:42 -0400 Subject: [PATCH 06/13] move tests folder at the root --- {src/diffpy/labpdfproc/tests => tests}/__init__.py | 0 {src/diffpy/labpdfproc/tests => tests}/conftest.py | 0 {src/diffpy/labpdfproc/tests => tests}/debug.py | 0 {src/diffpy/labpdfproc/tests => tests}/run.py | 0 {src/diffpy/labpdfproc/tests => tests}/test_example.py | 0 {src/diffpy/labpdfproc/tests => tests}/test_fixtures.py | 0 {src/diffpy/labpdfproc/tests => tests}/test_functions.py | 0 {src/diffpy/labpdfproc/tests => tests}/test_tools.py | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename {src/diffpy/labpdfproc/tests => tests}/__init__.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/conftest.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/debug.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/run.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/test_example.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/test_fixtures.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/test_functions.py (100%) rename {src/diffpy/labpdfproc/tests => tests}/test_tools.py (100%) diff --git a/src/diffpy/labpdfproc/tests/__init__.py b/tests/__init__.py similarity index 100% rename from src/diffpy/labpdfproc/tests/__init__.py rename to tests/__init__.py diff --git a/src/diffpy/labpdfproc/tests/conftest.py b/tests/conftest.py similarity index 100% rename from src/diffpy/labpdfproc/tests/conftest.py rename to tests/conftest.py diff --git a/src/diffpy/labpdfproc/tests/debug.py b/tests/debug.py similarity index 100% rename from src/diffpy/labpdfproc/tests/debug.py rename to tests/debug.py diff --git a/src/diffpy/labpdfproc/tests/run.py b/tests/run.py similarity index 100% rename from src/diffpy/labpdfproc/tests/run.py rename to tests/run.py diff --git a/src/diffpy/labpdfproc/tests/test_example.py b/tests/test_example.py similarity index 100% rename from src/diffpy/labpdfproc/tests/test_example.py rename to tests/test_example.py diff --git a/src/diffpy/labpdfproc/tests/test_fixtures.py b/tests/test_fixtures.py similarity index 100% rename from src/diffpy/labpdfproc/tests/test_fixtures.py rename to tests/test_fixtures.py diff --git a/src/diffpy/labpdfproc/tests/test_functions.py b/tests/test_functions.py similarity index 100% rename from src/diffpy/labpdfproc/tests/test_functions.py rename to tests/test_functions.py diff --git a/src/diffpy/labpdfproc/tests/test_tools.py b/tests/test_tools.py similarity index 100% rename from src/diffpy/labpdfproc/tests/test_tools.py rename to tests/test_tools.py From 126aa6b42d0e87a17f4fe35d63d5cb89a7448a5e Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Sep 2024 13:34:26 -0400 Subject: [PATCH 07/13] remove run.py and debug.py as we dont have unittest --- tests/debug.py | 35 ----------------------------------- tests/run.py | 34 ---------------------------------- 2 files changed, 69 deletions(-) delete mode 100644 tests/debug.py delete mode 100644 tests/run.py diff --git a/tests/debug.py b/tests/debug.py deleted file mode 100644 index 51881b9..0000000 --- a/tests/debug.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2024 The Trustees of Columbia University in the City of New York. -# All rights reserved. -# -# File coded by: Billinge Group members and community contributors. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.labpdfproc/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## - -""" -Convenience module for debugging the unit tests using - -python -m diffpy.labpdfproc.tests.debug - -Exceptions raised by failed tests or other errors are not caught. -""" - - -if __name__ == "__main__": - import sys - - from diffpy.labpdfproc.tests import testsuite - - pattern = sys.argv[1] if len(sys.argv) > 1 else "" - suite = testsuite(pattern) - suite.debug() - - -# End of file diff --git a/tests/run.py b/tests/run.py deleted file mode 100644 index d11095f..0000000 --- a/tests/run.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# (c) 2024 The Trustees of Columbia University in the City of New York. -# All rights reserved. -# -# File coded by: Billinge Group members and community contributors. -# -# See GitHub contributions for a more detailed list of contributors. -# https://github.com/diffpy/diffpy.labpdfproc/graphs/contributors -# -# See LICENSE.rst for license information. -# -############################################################################## -"""Convenience module for executing all unit tests with -python -m diffpy.labpdfproc.tests.run -""" - -import sys - -import pytest - -if __name__ == "__main__": - # show output results from every test function - args = ["-v"] - # show the message output for skipped and expected failure tests - if len(sys.argv) > 1: - args.extend(sys.argv[1:]) - print("pytest arguments: {}".format(args)) - # call pytest and exit with the return code from pytest - exit_res = pytest.main(args) - sys.exit(exit_res) - -# End of file From affababe3ef3cf6b0141e0008ca062be1852e43d Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Sep 2024 16:14:38 -0400 Subject: [PATCH 08/13] remove test_example as not needed --- tests/test_example.py | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 tests/test_example.py diff --git a/tests/test_example.py b/tests/test_example.py deleted file mode 100644 index 813df60..0000000 --- a/tests/test_example.py +++ /dev/null @@ -1,2 +0,0 @@ -def test_example(): - assert True From 3b196adc6a348a1b280aa94871dee9898ca753ee Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 9 Sep 2024 16:25:54 -0400 Subject: [PATCH 09/13] add a line in MANIFEST.in so test functions get access to data --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index a4ceb84..d00e802 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include README.rst include requirements.txt include doc/examples/* +recursive-include src/diffpy/labpdfproc/data * recursive-exclude * __pycache__ recursive-exclude * *.py[co] From 9e3130464515e0fdde9f8ad0957d8da769a1fc75 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 15:52:09 -0400 Subject: [PATCH 10/13] add back labpdfproc command --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index bb114ae..841e997 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ template = "{tag}" dev_template = "{tag}" dirty_template = "{tag}" +[project.scripts] +labpdfproc = "diffpy.labpdfproc.labpdfprocapp:main" + [tool.setuptools.packages.find] where = ["src"] # list of folders that contain the packages (["."] by default) include = ["*"] # package names should match these glob patterns (["*"] by default) From 2bf0e199945b33a3bcf551108b2a014e9eabfc6f Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Wed, 11 Sep 2024 18:06:25 -0400 Subject: [PATCH 11/13] delete init.py from tests folder --- tests/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 From 97ef5f52b28797299abfb10d6a852015b1ed9fbc Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Fri, 13 Sep 2024 13:46:47 -0400 Subject: [PATCH 12/13] tweak test conditions --- tests/test_mud_calculator.py | 2 +- tests/test_tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_mud_calculator.py b/tests/test_mud_calculator.py index 6c7351f..1b73213 100644 --- a/tests/test_mud_calculator.py +++ b/tests/test_mud_calculator.py @@ -19,4 +19,4 @@ def test_compute_mud(tmp_path): expected_mud = 3 actual_mud = compute_mud(file) - assert actual_mud == pytest.approx(expected_mud, rel=0.1, abs=0.1) + assert actual_mud == pytest.approx(expected_mud, rel=1e-4, abs=0.1) diff --git a/tests/test_tools.py b/tests/test_tools.py index 63fd952..67773e4 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -193,7 +193,7 @@ def test_set_mud(user_filesystem): cli_inputs = ["2.5", "data.xy"] actual_args = get_args(cli_inputs) actual_args = set_mud(actual_args) - assert actual_args.mud == pytest.approx(2.5, rel=0.1, abs=0.1) + assert actual_args.mud == pytest.approx(2.5, rel=1e-4, abs=0.1) assert actual_args.z_scan_file is None cwd = Path(user_filesystem) @@ -204,7 +204,7 @@ def test_set_mud(user_filesystem): cli_inputs = ["2.5", "data.xy"] + inputs actual_args = get_args(cli_inputs) actual_args = set_mud(actual_args) - assert actual_args.mud == pytest.approx(expected[0], rel=0.1, abs=0.1) + assert actual_args.mud == pytest.approx(expected[0], rel=1e-4, abs=0.1) assert actual_args.z_scan_file == expected[1] From 0013d35d647e1b132ecac80e3cc89768cdff964d Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sat, 14 Sep 2024 06:31:34 -0400 Subject: [PATCH 13/13] slight tweak to the argparse help message for z-scan file path