changeset 1:2b3115342fef draft

planemo upload for repository https://github.com/MaterialsGalaxy/larch-tools/tree/main/larch_athena commit 1cf6d7160497ba58fe16a51f00d088a20934eba6
author muon-spectroscopy-computational-project
date Wed, 06 Dec 2023 13:03:55 +0000
parents ae2f265ecf8e
children a1e26990131c
files common.py larch_athena.py larch_athena.xml macros.xml test-data/ffi1.tabular test-data/multiple.prj
diffstat 6 files changed, 710 insertions(+), 169 deletions(-) [+]
line wrap: on
line diff
--- a/common.py	Tue Nov 14 15:34:40 2023 +0000
+++ b/common.py	Wed Dec 06 13:03:55 2023 +0000
@@ -7,38 +7,145 @@
 
 
 def get_group(athena_group: AthenaGroup, key: str = None) -> Group:
+    group_keys = list(athena_group._athena_groups.keys())
     if key is None:
-        group_keys = list(athena_group._athena_groups.keys())
         key = group_keys[0]
-    return extract_athenagroup(athena_group._athena_groups[key])
+    else:
+        key = key.replace("-", "_")
+
+    try:
+        return extract_athenagroup(athena_group._athena_groups[key])
+    except KeyError as e:
+        raise KeyError(f"{key} not in {group_keys}") from e
+
+
+def read_all_groups(dat_file: str, key: str = None) -> "dict[str, Group]":
+    # Cannot rely on do_ABC as _larch is None
+    athena_group = read_athena(
+        dat_file,
+        do_preedge=False,
+        do_bkg=False,
+        do_fft=False,
+    )
+    all_groups = {}
+    for key in athena_group._athena_groups.keys():
+        group = get_group(athena_group, key)
+        pre_edge_with_defaults(group=group)
+        xftf_with_defaults(group=group)
+        all_groups[key] = group
+
+    return all_groups
+
+
+def read_group(dat_file: str, key: str = None):
+    # Cannot rely on do_ABC as _larch is None
+    athena_group = read_athena(
+        dat_file,
+        do_preedge=False,
+        do_bkg=False,
+        do_fft=False,
+    )
+    group = get_group(athena_group, key)
+    pre_edge_with_defaults(group=group)
+    xftf_with_defaults(group=group)
+    return group
 
 
-def read_group(dat_file: str, key: str = None, xftf_params: dict = None):
-    athena_group = read_athena(dat_file)
-    group = get_group(athena_group, key)
-    bkg_parameters = group.athena_params.bkg
-    print(group.athena_params.fft)
-    print(group.athena_params.fft.__dict__)
-    pre_edge(
-        group,
-        e0=bkg_parameters.e0,
-        pre1=bkg_parameters.pre1,
-        pre2=bkg_parameters.pre2,
-        norm1=bkg_parameters.nor1,
-        norm2=bkg_parameters.nor2,
-        nnorm=bkg_parameters.nnorm,
-        make_flat=bkg_parameters.flatten,
+def pre_edge_with_defaults(group: Group, settings: dict = None):
+    merged_settings = {}
+    try:
+        bkg_parameters = group.athena_params.bkg
+    except AttributeError as e:
+        print(f"Cannot load group.athena_params.bkg from group:\n{e}")
+        bkg_parameters = None
+
+    keys = (
+        ("e0", "e0", None),
+        ("pre1", "pre1", None),
+        ("pre2", "pre2", None),
+        ("norm1", "nor1", None),
+        ("norm2", "nor2", None),
+        ("nnorm", "nnorm", None),
+        ("make_flat", "flatten", None),
+        ("step", "step", None),
+        # This cannot be read from file as it is not stored by Larch (0.9.71)
+        # ("nvict", "nvict", None),
     )
-    autobk(group)
-    if xftf_params is None:
-        xftf(group)
-    else:
-        print(xftf_params)
-        xftf(group, **xftf_params)
-        xftf_details = Group()
-        setattr(xftf_details, "call_args", xftf_params)
-        group.xftf_details = xftf_details
-    return group
+    for key, parameters_key, default in keys:
+        extract_attribute(
+            merged_settings, key, bkg_parameters, parameters_key, default
+        )
+
+    if settings:
+        for k, v in settings.items():
+            if k == "nvict":
+                print(
+                    "WARNING: `nvict` can be used for pre-edge but is not "
+                    "saved to file, so value used will not be accessible in "
+                    "future operations using this Athena .prj"
+                )
+            merged_settings[k] = v
+
+    print(f"Pre-edge normalization with {merged_settings}")
+    try:
+        pre_edge(group, **merged_settings)
+    except Warning as e:
+        raise Warning(
+            "Unable to perform pre-edge fitting with:\n\n"
+            f"energy:\n{group.energy}\n\nmu:{group.mu}\n\n"
+            "Consider checking the correct columns have been extracted"
+        ) from e
+    autobk(group, pre_edge_kws=merged_settings)
+
+
+def xftf_with_defaults(group: Group, settings: dict = None):
+    merged_settings = {}
+    try:
+        fft_parameters = group.athena_params.fft
+    except AttributeError as e:
+        print(f"Cannot load group.athena_params.fft from group:\n{e}")
+        fft_parameters = None
+
+    keys = (
+        ("kmin", "kmin", 0),
+        ("kmax", "kmax", 20),
+        ("dk", "dk", 1),
+        ("kweight", "kw", 2),
+        ("kweight", "kweight", 2),
+        ("window", "kwindow", "kaiser"),
+    )
+    for key, parameters_key, default in keys:
+        extract_attribute(
+            merged_settings, key, fft_parameters, parameters_key, default
+        )
+
+    if settings:
+        for k, v in settings.items():
+            merged_settings[k] = v
+
+    print(f"XFTF with {merged_settings}")
+    xftf(group, **merged_settings)
+    xftf_details = Group()
+    setattr(xftf_details, "call_args", merged_settings)
+    group.xftf_details = xftf_details
+
+
+def extract_attribute(
+    merged_settings: dict,
+    key: str,
+    parameters_group: Group,
+    parameters_key: str,
+    default: "str|int" = None,
+):
+    if parameters_group is not None:
+        try:
+            merged_settings[key] = getattr(parameters_group, parameters_key)
+            return
+        except AttributeError:
+            pass
+
+    if default is not None:
+        merged_settings[key] = default
 
 
 def read_groups(dat_files: "list[str]", key: str = None) -> Iterable[Group]:
--- a/larch_athena.py	Tue Nov 14 15:34:40 2023 +0000
+++ b/larch_athena.py	Wed Dec 06 13:03:55 2023 +0000
@@ -4,7 +4,9 @@
 import re
 import sys
 
-from common import read_group
+from common import (
+    pre_edge_with_defaults, read_all_groups, read_group, xftf_with_defaults
+)
 
 from larch.io import (
     create_athena,
@@ -14,7 +16,7 @@
     set_array_labels,
 )
 from larch.symboltable import Group
-from larch.xafs import autobk, pre_edge, rebin_xafs, xftf
+from larch.xafs import rebin_xafs
 
 import matplotlib
 import matplotlib.pyplot as plt
@@ -27,13 +29,11 @@
         self,
         energy_column: str,
         mu_column: str,
-        xftf_params: dict,
         data_format: str,
-        extract_group: str = None,
+        extract_group: "dict[str, str]" = None,
     ):
         self.energy_column = energy_column
         self.mu_column = mu_column
-        self.xftf_params = xftf_params
         self.data_format = data_format
         self.extract_group = extract_group
 
@@ -72,13 +72,24 @@
         self,
         filepath: str,
         is_zipped: bool = False,
-    ) -> "dict[str,Group]":
+    ) -> "tuple[dict, bool]":
         if is_zipped:
             return self.load_zipped_files()
 
         print(f"Attempting to read from {filepath}")
         if self.data_format == "athena":
-            group = read_group(filepath, self.extract_group, self.xftf_params)
+            if self.extract_group["extract_group"] == "single":
+                group = read_group(filepath, self.extract_group["group_name"])
+                return {"out": group}
+            elif self.extract_group["extract_group"] == "multiple":
+                groups = {}
+                for repeat in self.extract_group["multiple"]:
+                    name = repeat["group_name"]
+                    groups[name] = read_group(filepath, name)
+                return groups
+            else:
+                return read_all_groups(filepath)
+
         else:
             # Try ascii anyway
             try:
@@ -90,7 +101,9 @@
             except (UnicodeDecodeError, TypeError):
                 # Indicates this isn't plaintext, try h5
                 group = self.load_h5(filepath)
-        return {"out": group}
+            pre_edge_with_defaults(group)
+            xftf_with_defaults(group)
+            return {"out": group}
 
     def load_ascii(self, dat_file):
         with open(dat_file) as f:
@@ -156,27 +169,27 @@
 
         if "energy" in labels:
             print("'energy' present in column headers")
-        elif self.energy_column is not None:
+        elif self.energy_column:
             if self.energy_column.lower() in labels:
                 labels[labels.index(self.energy_column.lower())] = "energy"
             else:
                 raise ValueError(f"{self.energy_column} not found in {labels}")
         else:
             for i, label in enumerate(labels):
-                if label == "col1" or label.endswith("energy"):
+                if label in ("col1", "ef") or label.endswith("energy"):
                     labels[i] = "energy"
                     break
 
         if "mu" in labels:
             print("'mu' present in column headers")
-        elif self.mu_column is not None:
+        elif self.mu_column:
             if self.mu_column.lower() in labels:
                 labels[labels.index(self.mu_column.lower())] = "mu"
             else:
                 raise ValueError(f"{self.mu_column} not found in {labels}")
         else:
             for i, label in enumerate(labels):
-                if label in ["col2", "xmu", "lni0it", "ffi0"]:
+                if label in ["col2", "xmu", "lni0it", "ffi0", "ff/i1"]:
                     labels[i] = "mu"
                     break
 
@@ -189,29 +202,24 @@
 
 def calibrate_energy(
     xafs_group: Group,
-    energy_0: float,
-    energy_min: float,
-    energy_max: float,
-    energy_format: str,
+    calibration_e0: float = None,
+    energy_min: float = None,
+    energy_max: float = None,
 ):
-    if energy_0 is not None:
-        print(f"Recalibrating energy edge from {xafs_group.e0} to {energy_0}")
-        xafs_group.energy = xafs_group.energy + energy_0 - xafs_group.e0
-        xafs_group.e0 = energy_0
+    if calibration_e0 is not None:
+        print(f"Recalibrating edge from {xafs_group.e0} to {calibration_e0}")
+        xafs_group.energy = xafs_group.energy + calibration_e0 - xafs_group.e0
+        xafs_group.e0 = calibration_e0
 
     if not (energy_min or energy_max):
         return xafs_group
 
-    if energy_min:
-        if energy_format == "relative":
-            energy_min += xafs_group.e0
+    if energy_min is not None:
         index_min = np.searchsorted(xafs_group.energy, energy_min)
     else:
         index_min = 0
 
-    if energy_max:
-        if energy_format == "relative":
-            energy_max += xafs_group.e0
+    if energy_max is not None:
         index_max = np.searchsorted(xafs_group.energy, energy_max)
     else:
         index_max = len(xafs_group.energy)
@@ -240,81 +248,57 @@
 
 def main(
     xas_data: Group,
-    input_values: dict,
+    do_calibrate: bool,
+    calibrate_settings: dict,
+    do_rebin: bool,
+    do_pre_edge: bool,
+    pre_edge_settings: dict,
+    do_xftf: bool,
+    xftf_settings: dict,
+    plot_graph: bool,
+    annotation: str,
     path_key: str = "out",
 ):
-    energy_0 = input_values["variables"]["energy_0"]
-    if energy_0 is None and hasattr(xas_data, "e0"):
-        energy_0 = xas_data.e0
-
-    energy_format = input_values["variables"]["energy_format"]
-    pre1 = input_values["variables"]["pre1"]
-    pre2 = input_values["variables"]["pre2"]
-    pre1 = validate_pre(pre1, energy_0, energy_format)
-    pre2 = validate_pre(pre2, energy_0, energy_format)
-
-    pre_edge(
-        energy=xas_data.energy,
-        mu=xas_data.mu,
-        group=xas_data,
-        e0=energy_0,
-        pre1=pre1,
-        pre2=pre2,
-    )
+    if do_calibrate:
+        print(f"Calibrating energy with {calibrate_settings}")
+        xas_data = calibrate_energy(xas_data, **calibrate_settings)
+        # After re-calibrating, will need to redo pre-edge with new range
+        do_pre_edge = True
 
-    energy_min = input_values["variables"]["energy_min"]
-    energy_max = input_values["variables"]["energy_max"]
-    xas_data = calibrate_energy(
-        xas_data,
-        energy_0,
-        energy_min,
-        energy_max,
-        energy_format=energy_format,
-    )
+    if do_rebin:
+        print("Re-binning data")
+        rebin_xafs(
+            energy=xas_data.energy,
+            mu=xas_data.mu,
+            group=xas_data,
+            **pre_edge_settings,
+        )
+        xas_data = xas_data.rebinned
+        # After re-bin, will need to redo pre-edge
+        do_pre_edge = True
 
-    if input_values["rebin"]:
-        print(xas_data.energy, xas_data.mu)
-        rebin_xafs(energy=xas_data.energy, mu=xas_data.mu, group=xas_data)
-        xas_data = xas_data.rebinned
-        pre_edge(energy=xas_data.energy, mu=xas_data.mu, group=xas_data)
+    if do_pre_edge:
+        pre_edge_with_defaults(xas_data, pre_edge_settings)
 
-    try:
-        autobk(xas_data)
-    except ValueError as e:
-        raise ValueError(
-            f"autobk failed with energy={xas_data.energy}, mu={xas_data.mu}.\n"
-            "This may occur if the edge is not included in the above ranges."
-        ) from e
-    xftf(xas_data, **xftf_params)
+    if do_xftf:
+        xftf_with_defaults(xas_data, xftf_settings)
 
-    if input_values["plot_graph"]:
+    if plot_graph:
         plot_edge_fits(f"edge/{path_key}.png", xas_data)
         plot_flattened(f"flat/{path_key}.png", xas_data)
         plot_derivative(f"derivative/{path_key}.png", xas_data)
 
     xas_project = create_athena(f"prj/{path_key}.prj")
     xas_project.add_group(xas_data)
-    if input_values["annotation"]:
+    if annotation:
         group = next(iter(xas_project.groups.values()))
-        group.args["annotation"] = input_values["annotation"]
+        group.args["annotation"] = annotation
     xas_project.save()
 
     # Ensure that we do not run out of memory when running on large zips
     gc.collect()
 
 
-def validate_pre(pre, energy_0, energy_format):
-    if pre is not None and energy_format == "absolute":
-        if energy_0 is None:
-            raise ValueError(
-                "Edge energy must be set manually or be present in the "
-                "existing Athena project if using absolute format."
-            )
-        pre -= energy_0
-
-    return pre
-
-
 def plot_derivative(plot_path: str, xafs_group: Group):
     plt.figure()
     plt.plot(xafs_group.energy, xafs_group.dmude)
@@ -363,9 +347,8 @@
         )
     else:
         is_zipped = False
-    xftf_params = input_values["variables"]["xftf"]
+
     extract_group = None
-
     if "extract_group" in input_values["merge_inputs"]["format"]:
         extract_group = input_values["merge_inputs"]["format"]["extract_group"]
 
@@ -379,7 +362,6 @@
     reader = Reader(
         energy_column=energy_column,
         mu_column=mu_column,
-        xftf_params=xftf_params,
         data_format=data_format,
         extract_group=extract_group,
     )
@@ -388,9 +370,35 @@
         merge_inputs=merge_inputs,
         is_zipped=is_zipped,
     )
+
+    calibrate_items = input_values["processing"]["calibrate"].items()
+    calibrate_settings = {k: v for k, v in calibrate_items if v is not None}
+    do_calibrate = calibrate_settings.pop("calibrate") == "true"
+
+    do_rebin = input_values["processing"].pop("rebin")
+
+    pre_edge_items = input_values["processing"]["pre_edge"].items()
+    pre_edge_settings = {k: v for k, v in pre_edge_items if v is not None}
+    do_pre_edge = pre_edge_settings.pop("pre_edge") == "true"
+
+    xftf_items = input_values["processing"]["xftf"].items()
+    xftf_settings = {k: v for k, v in xftf_items if v is not None}
+    do_xftf = xftf_settings.pop("xftf") == "true"
+
+    plot_graph = input_values["plot_graph"]
+    annotation = input_values["annotation"]
+
     for key, group in keyed_data.items():
         main(
             group,
-            input_values=input_values,
+            do_calibrate=do_calibrate,
+            calibrate_settings=calibrate_settings,
+            do_rebin=do_rebin,
+            do_pre_edge=do_pre_edge,
+            pre_edge_settings=pre_edge_settings,
+            do_xftf=do_xftf,
+            xftf_settings=xftf_settings,
+            plot_graph=plot_graph,
+            annotation=annotation,
             path_key=key,
         )
--- a/larch_athena.xml	Tue Nov 14 15:34:40 2023 +0000
+++ b/larch_athena.xml	Wed Dec 06 13:03:55 2023 +0000
@@ -4,7 +4,7 @@
         <!-- version of underlying tool (PEP 440) -->
         <token name="@TOOL_VERSION@">0.9.71</token>
         <!-- version of this tool wrapper (integer) -->
-        <token name="@WRAPPER_VERSION@">0</token>
+        <token name="@WRAPPER_VERSION@">1</token>
         <!-- citation should be updated with every underlying tool version -->
         <!-- typical fields to update are version, month, year, and doi -->
         <token name="@TOOL_CITATION@">10.1088/1742-6596/430/1/012007</token>
@@ -15,11 +15,26 @@
             </param> 
         </xml>
         <xml name="extract_group">
-            <param name="extract_group" type="text" optional="true" label="Extract group" help="Which group to extract and process from the Athena project (will use first group in file if unset)"/>
+            <conditional name="extract_group">
+                <param name="extract_group" type="select" label="Group extraction" help="Method of handling group extraction. Extracting all or multiple named groups will result in multiple outputs, unless merging groups is also true.">
+                    <option value="single" selected="true">Extract single</option>
+                    <option value="multiple">Extract multiple</option>
+                    <option value="all">Extract all</option>
+                </param>
+                <when value="single">
+                    <param name="group_name" type="text" optional="true" label="Group label" help="Which group to extract and process from the Athena project (will use first group in file if unset)"/>
+                </when>
+                <when value="multiple">
+                    <repeat name="multiple" min="1" default="1" title="Groups">
+                        <param name="group_name" type="text" label="Group label" help="Which group to extract and process from the Athena project (will use first group in file if unset)"/>
+                    </repeat>
+                </when>
+                <when value="all"/>
+            </conditional>
         </xml>
         <xml name="columns">
-            <param name="energy_column" type="text" optional="true" label="Energy column" help="If set, this column we be used as 'energy'. Otherwise, will identify columns ending with 'energy' or labelled 'col1'."/>
-            <param name="mu_column" type="text" optional="true" label="μ column" help="If set, this column we be used as 'mu'. Otherwise, will identify the first column labelled as either 'col2', 'xmu', 'lni0it' or 'FFI0'."/>
+            <param name="energy_column" type="text" optional="true" label="Energy column" help="If set, this column we be used as 'energy'. Otherwise, will identify the first column ending with 'energy' or labelled 'col1' 'Ef'."/>
+            <param name="mu_column" type="text" optional="true" label="μ column" help="If set, this column we be used as 'mu'. Otherwise, will identify the first column labelled as either 'col2', 'xmu', 'lni0it', 'FFI0' or 'FF/I1'."/>
         </xml>
         <xml name="is_zipped">
             <param name="is_zipped" type="select" display="radio" label="Inputs Zipped" help="Whether plaintext input files are zipped together into one directory, or not.">
@@ -40,6 +55,7 @@
     </requirements>
     <required_files>
         <include type="literal" path="larch_athena.py"/>
+        <include type="literal" path="common.py"/>
     </required_files>
     <command detect_errors="exit_code"><![CDATA[
         mkdir prj edge flat derivative
@@ -111,16 +127,55 @@
             </when>
         </conditional>
         <param name="annotation" type="text" label="Annotation" optional="true" help="If set, will annotate the output project(s) with this string. This will be used to generate legends when plotting data."/>
-        <section name="variables" title="Processing Options">
-            <param name="energy_0" type="float" label="Edge energy (eV)" optional="true" help="If set, data will be calibrated so that the edge occurs at this energy (after merging, if relevant)."/>
-            <expand macro="energy_limits"/>
-            <param name="pre1" type="float" label="Pre-edge fit lower energy (eV)" optional="true" help="The lower edge of the region used for the pre-edge fitting, if unset will either use existing value or a default based on the data."/>
-            <param name="pre2" type="float" label="Pre-edge fit upper energy (eV)" optional="true" help="The upper edge of the region used for the pre-edge fitting, if unset will either use existing value or a default based on the data."/>
-            <section name="xftf" title="XFTF">
-                <expand macro="xftf_params"/>
-            </section>
+        <section name="processing" expanded="true" title="Processing Options" help="By default, the following processing steps will be performed either with default values, or those contained in the input Athena project (if used). If specified here, these values will be used instead for process in sequence.">
+            <conditional name="calibrate">
+                <param name="calibrate" type="select" label="Calibrate energy" help="If set, will shift the spectrum so that its (automatically determined) edge occurs at the specified value, and any values outside the range will be discarded.">
+                    <option value="" selected="true">False</option>
+                    <option value="true">True</option>
+                </param>
+                <when value=""/>
+                <when value="true">
+                    <param name="calibration_e0" type="float" label="Calibration energy (eV)" optional="true" help="If set, data will be calibrated so that the edge occurs at this energy (after merging, if relevant). Note that this is different from specifying E0 in the pre-edge normalization, as this will the x-axis of the data (so that the Nth x point will no longer align with the Nth y point)."/>
+                    <param name="energy_min" type="float" label="Minimum energy (eV)" optional="true" help="If set, data will be cropped below this value in electron volts (after re-calibrating)."/>
+                    <param name="energy_max" type="float" label="Maximum energy (eV)" optional="true" help="If set, data will be cropped above this value in electron volts (after re-calibrating)."/>
+                </when>
+            </conditional>
+            <param name="rebin" type="boolean" label="Re-bin data" help="Whether to re-bin along the energy axis to automatically ensure appropriate levels of precision in the pre-edge, near-edge and extended region of the spectrum."/>
+            <conditional name="pre_edge">
+                <param name="pre_edge" type="select" label="Pre-edge normalization" help="If set, will (re)perform forward pre-edge normalization using provided values.">
+                    <option value="" selected="true">False</option>
+                    <option value="true">True</option>
+                </param>
+                <when value=""/>
+                <when value="true">
+                    <param argument="e0" type="float" label="Edge energy (eV)" optional="true" help="If set, normalization will use this as the location of the edge rather than automatically determining it."/>
+                    <param argument="pre1" type="float" max="0" label="Pre-edge fit lower energy (eV)" optional="true" help="The lower end of the region used for the pre-edge fitting, relative to the edge energy (and therefore negative)."/>
+                    <param argument="pre2" type="float" max="0" label="Pre-edge fit upper energy (eV)" optional="true" help="The upper end of the region used for the pre-edge fitting, relative to the edge energy (and therefore negative)."/>
+                    <param argument="nvict" type="integer" label="Energy exponent" optional="true" help="Edge fitting will be performed against μ*E**n where n is defined here. This is 0 by default."/>
+                </when>
+            </conditional>
+            <conditional name="xftf">
+                <param name="xftf" type="select" label="XFTF" help="If set, will (re)perform forward Fourier Transform using provided values.">
+                    <option value="" selected="true">False</option>
+                    <option value="true">True</option>
+                </param>
+                <when value=""/>
+                <when value="true">
+                    <param argument="kmin" type="float" optional="true" min="0.0" help="Minimum k value."/>
+                    <param argument="kmax" type="float" optional="true" min="0.0" help="Maximum k value."/>
+                    <param argument="kweight" type="float" optional="true" help="Exponent for weighting spectra by raising k to this power."/>
+                    <param argument="dk" type="float" optional="true" help="Tapering parameter for Fourier Transform window."/>
+                    <param argument="window" type="select" optional="true" help="Fourier Transform window type.">
+                        <option value="hanning">Hanning (cosine-squared taper)</option>
+                        <option value="parzen">Parzen (linear taper)</option>
+                        <option value="welch">Welch (quadratic taper)</option>
+                        <option value="gaussian">Gaussian function window</option>
+                        <option value="sine">Sine function window</option>
+                        <option value="kaiser">Kaiser-Bessel function-derived window</option>
+                    </param>
+                </when>
+            </conditional>
         </section>
-        <param name="rebin" type="boolean" label="Re-bin data" help="Whether to re-bin along the energy axis to ensure appropriate levels of precision in the pre-edge, near-edge and extended region of the spectrum."/>
         <param name="plot_graph" type="boolean" label="Plot graph" help="Whether to plot the pre/post edge fitting and the normalised xμ data."/>
         <param name="zip_outputs" type="boolean" label="Zip outputs" help="Whether to zip all outputs into one dataset."/>
     </inputs>
@@ -129,53 +184,53 @@
         <data name="out_zip" format="zip" from_work_dir="out_zip.zip" label="Zipped Athena project(s) ${annotation} ${on_string}">
             <filter>zip_outputs</filter>
         </data>
-        <!-- Single outputs of differnt types if merging, or not using a zip -->
+        <!-- Single outputs of different types if merging, or not using a zip -->
         <data name="athena_project_file" format="prj" from_work_dir="prj/out.prj" label="Athena project ${annotation} ${on_string}">
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] or (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"] == "")</filter>
+            <filter>not (merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single"))</filter>
         </data>
         <data name="edge_plot" format="png" from_work_dir="edge/out.png" label="Edge fitting ${annotation} ${on_string}">
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] or (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"] == "")</filter>
+            <filter>not (merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single"))</filter>
         </data>
         <data name="flat_plot" format="png" from_work_dir="flat/out.png" label="Flattened plot ${annotation} ${on_string}">
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] or (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"] == "")</filter>
+            <filter>not (merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single"))</filter>
         </data>
         <data name="derivative_plot" format="png" from_work_dir="derivative/out.png" label="Derivative plot ${annotation} ${on_string}">
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] or merge_inputs["format"]["dat_file"].extension != "zip"</filter>
-            <filter>merge_inputs["merge_inputs"] or (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"] == "")</filter>
+            <filter>not (merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single"))</filter>
         </data>
-        <!-- Directories of outputs if using single, non-merged zip as input -->
+        <!-- Directories of outputs if using single, non-merged zip as input or extracting multiple/all Athena groups -->
         <collection name="athena_project_file_collection" format="prj" type="list" label="Athena projects ${annotation} ${on_string}">
             <discover_datasets pattern="__name_and_ext__" directory="prj"/>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] == "" and merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]</filter>
+            <filter>merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single")</filter>
         </collection>
         <collection name="edge_plot_collection" format="png" type="list" label="Edge fittings ${annotation} ${on_string}">
             <discover_datasets pattern="__name_and_ext__" directory="edge"/>
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] == "" and merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]</filter>
+            <filter>merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single")</filter>
         </collection>
         <collection name="flat_plot_collection" format="png" type="list" label="Flattened plots ${annotation} ${on_string}">
             <discover_datasets pattern="__name_and_ext__" directory="flat"/>
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] == "" and merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]</filter>
+            <filter>merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single")</filter>
         </collection>
         <collection name="derivative_plot_collection" format="png" type="list" label="Derivative plots ${annotation} ${on_string}">
             <discover_datasets pattern="__name_and_ext__" directory="derivative"/>
             <filter>plot_graph</filter>
             <filter>not zip_outputs</filter>
-            <filter>merge_inputs["merge_inputs"] == "" and merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]</filter>
+            <filter>merge_inputs["merge_inputs"] == "" and (merge_inputs["format"]["format"] == "plaintext" and merge_inputs["format"]["is_zipped"]["is_zipped"]) or (merge_inputs["format"]["format"] == "athena" and merge_inputs["format"]["extract_group"]["extract_group"] != "single")</filter>
         </collection>
     </outputs>
     <tests>
+        <!-- 1 -->
         <test expect_num_outputs="1">
             <param name="dat_file" value="test.xmu"/>
             <output name="athena_project_file">
@@ -184,6 +239,7 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 2 -->
         <test expect_num_outputs="1">
             <param name="dat_file" value="ffi0.tabular"/>
             <output name="athena_project_file">
@@ -192,12 +248,22 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 3 -->
+        <test expect_num_outputs="1">
+            <param name="dat_file" value="ffi1.tabular"/>
+            <output name="athena_project_file">
+                <assert_contents>
+                    <has_size value="4400" delta="100"/>
+                </assert_contents>
+            </output>
+        </test>
+        <!-- 4 -->
         <test expect_num_outputs="4">
             <param name="dat_file" value="test.xmu"/>
             <param name="plot_graph" value="true"/>
             <output name="athena_project_file">
                 <assert_contents>
-                    <has_size value="5405" delta="10"/>
+                    <has_size value="5400" delta="100"/>
                 </assert_contents>
             </output>
             <output name="edge_plot">
@@ -216,6 +282,7 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 5 -->
         <test expect_num_outputs="4">
             <param name="is_zipped" value="true"/>
             <param name="dat_file" value="test.zip"/>
@@ -225,6 +292,7 @@
             <output_collection name="flat_plot_collection" type="list" count="2"/>
             <output_collection name="derivative_plot_collection" type="list" count="2"/>
         </test>
+        <!-- 6 -->
         <test expect_num_outputs="1">
             <param name="is_zipped" value="true"/>
             <param name="dat_file" value="h5.zip"/>
@@ -236,9 +304,10 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 7 -->
         <test expect_num_outputs="1">
             <param name="dat_file" value="test.xmu"/>
-            <param name="energy_format" value="absolute"/>
+            <param name="calibrate" value="true"/>
             <param name="energy_min" value="7000"/>
             <output name="athena_project_file">
                 <assert_contents>
@@ -246,9 +315,10 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 8 -->
         <test expect_num_outputs="4">
             <param name="dat_file" value="test.xmu"/>
-            <param name="energy_format" value="absolute"/>
+            <param name="calibrate" value="true"/>
             <param name="energy_min" value="7000"/>
             <param name="energy_max" value="7200"/>
             <param name="plot_graph" value="true"/>
@@ -259,32 +329,34 @@
             </output>
             <output name="edge_plot">
                 <assert_contents>
-                    <has_size value="44430" delta="10"/>
+                    <has_size value="44900" delta="100"/>
                 </assert_contents>
             </output>
             <output name="flat_plot">
                 <assert_contents>
-                    <has_size value="37310" delta="10"/>
+                    <has_size value="39400" delta="100"/>
                 </assert_contents>
             </output>
             <output name="derivative_plot">
                 <assert_contents>
-                    <has_size value="46390" delta="10"/>
+                    <has_size value="45900" delta="100"/>
                 </assert_contents>
             </output>
         </test>
+        <!-- 9 -->
         <test expect_num_outputs="1">
             <param name="dat_file" value="test.xmu"/>
-            <param name="energy_format" value="absolute"/>
-            <param name="energy_0" value="7050"/>
+            <param name="calibrate" value="true"/>
+            <param name="calibration_e0" value="7050"/>
             <param name="energy_min" value="7000"/>
             <param name="energy_max" value="7200"/>
             <output name="athena_project_file">
                 <assert_contents>
-                    <has_size value="3300" delta="50"/>
+                    <has_size value="3600" delta="100"/>
                 </assert_contents>
             </output>
         </test>
+        <!-- 10 -->
         <test expect_num_outputs="1">
             <param name="dat_file" value="test.xmu"/>
             <param name="rebin" value="true"/>
@@ -294,6 +366,7 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 11 -->
         <test expect_num_outputs="1">
             <param name="merge_inputs" value="true"/>
             <param name="dat_file" value="262875_PtSn_OCO_Abu_1.nxs,262876_PtSn_OCO_Abu_2.nxs"/>
@@ -303,6 +376,7 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 12 -->
         <test expect_num_outputs="1">
             <param name="merge_inputs" value="true"/>
             <param name="is_zipped" value="true"/>
@@ -313,6 +387,7 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 13 -->
         <test expect_num_outputs="1">
             <param name="format" value="athena"/>
             <param name="dat_file" value="test.prj"/>
@@ -322,6 +397,22 @@
                 </assert_contents>
             </output>
         </test>
+        <!-- 14: Extract multiple groups from Athena .prj -->
+        <test expect_num_outputs="1">
+            <param name="format" value="athena"/>
+            <param name="extract_group" value="multiple"/>
+            <param name="group_name" value="merge"/>
+            <param name="group_name" value="d__Ref_PtSn_OC_MERGE_CALIBRATE"/>
+            <param name="dat_file" value="multiple.prj"/>
+            <output_collection name="athena_project_file_collection" type="list" count="2"/>
+        </test>
+        <!-- 15: Extract all groups from Athena .prj -->
+        <test expect_num_outputs="1">
+            <param name="format" value="athena"/>
+            <param name="extract_group" value="all"/>
+            <param name="dat_file" value="multiple.prj"/>
+            <output_collection name="athena_project_file_collection" type="list" count="9"/>
+        </test>
     </tests>
     <help><![CDATA[
         Using Larch, create an Athena project file from the input X-ray Absorption Fine Structure (XAFS) data file.
--- a/macros.xml	Tue Nov 14 15:34:40 2023 +0000
+++ b/macros.xml	Wed Dec 06 13:03:55 2023 +0000
@@ -1,26 +1,6 @@
 <macros>
-    <xml name="energy_limits">
-        <param name="energy_format" type="select" display="radio" label="Energy limits" help="Whether to limit the energy relative to the absorption edge or with absolute values.">
-            <option value="relative" selected="true">Relative</option>
-            <option value="absolute">Absolute</option>
-        </param>
-        <param name="energy_min" type="float" label="Minimum energy (eV)" optional="true" help="If set, data will be cropped below this value in electron volts."/>
-        <param name="energy_max" type="float" label="Maximum energy (eV)" optional="true" help="If set, data will be cropped above this value in electron volts."/>
-    </xml>
-    <xml name="xftf_params">
-        <param argument="kmin" type="float" value="0" min="0.0" help="Minimum k value."/>
-        <param argument="kmax" type="float" value="20" min="0.0" help="Maximum k value."/>
-        <param argument="kweight" type="float" value="2" help="Exponent for weighting spectra by raising k to this power."/>
-        <param argument="dk" type="float" value="4" help="Tapering parameter for Fourier Transform window."/>
-        <param argument="window" type="select" help="Fourier Transform window type.">
-            <option value="hanning">Hanning (cosine-squared taper)</option>
-            <option value="parzen">Parzen (linear taper)</option>
-            <option value="welch">Welch (quadratic taper)</option>
-            <option value="gaussian">Gaussian function window</option>
-            <option value="sine">Sine function window</option>
-            <option value="kaiser" selected="true">Kaiser-Bessel function-derived window</option>
-        </param>
-        <param argument="rmin" type="float" value="0.0" min="0.0" help="Minimum radial distance."/>
-        <param argument="rmax" type="float" value="10.0" min="0.0" help="Maximum radial distance."/>
+    <xml name="plot_limits_energy">
+        <param name="x_limit_min" type="float" label="Minimum plot energy (eV)" optional="true" help="If set, plot will be limited to this value on the x axis."/>
+        <param name="x_limit_max" type="float" label="Maximum plot energy (eV)" optional="true" help="If set, plot will be limited to this value on the x axis."/>
     </xml>
 </macros>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-data/ffi1.tabular	Wed Dec 06 13:03:55 2023 +0000
@@ -0,0 +1,355 @@
+# Energy        	FF/I1       	Time
+  6911.8277       0.80926541        272002.00
+  6916.9236       0.80418730        270032.00
+  6921.7638       0.79959074        268827.00
+  6926.8750       0.79459529        267514.00
+  6931.7907       0.79004613        255214.00
+
+  6962.0000       0.78791063        305150.00
+  6972.0000       0.77475191        306445.00
+  6982.0000       0.76169076        306446.00
+  6992.0000       0.74992551        306744.00
+  7002.0000       0.73660315        306246.00
+  7012.0000       0.72423404        306358.00
+  7022.0000       0.71196509        306309.00
+  7032.0000       0.70002723        306019.00
+  7042.0000       0.68735767        306529.00
+  7052.0000       0.67439453        306124.00
+  7062.0000       0.66235517        306188.00
+  7072.0000       0.65022361        306119.00
+  7082.0000       0.63942705        306143.00
+  7092.0000       0.62824650        306098.00
+  7092.5000       0.62753795        305825.00
+  7093.0000       0.62748380        305756.00
+  7093.5000       0.62785219        305745.00
+  7094.0000       0.62636441        305717.00
+  7094.5000       0.62575793        305670.00
+  7095.0000       0.62551244        305666.00
+  7095.5000       0.62515947        305631.00
+  7096.0000       0.62492947        305585.00
+  7096.5000       0.62367544        305536.00
+  7097.0000       0.62332504        305539.00
+  7097.5000       0.62259547        305510.00
+  7098.0000       0.62252464        305466.00
+  7098.5000       0.62240279        305494.00
+  7099.0000       0.62172416        305432.00
+  7099.5000       0.62098768        305503.00
+  7100.0000       0.62118707        305402.00
+  7100.5000       0.62045272        305414.00
+  7101.0000       0.62040785        305391.00
+  7101.5000       0.62002332        305400.00
+  7102.0000       0.61932445        305952.00
+  7102.5000       0.61954721        305420.00
+  7103.0000       0.61934375        304860.00
+  7103.5000       0.61907141        305180.00
+  7104.0000       0.61843263        305180.00
+  7104.5000       0.61879967        305435.00
+  7105.0000       0.61854579        305144.00
+  7105.5000       0.61837948        305045.00
+  7106.0000       0.61887282        304943.00
+  7106.5000       0.61837848        305015.00
+  7107.0000       0.61844121        305023.00
+  7107.5000       0.61908174        304990.00
+  7108.0000       0.62025344        305117.00
+  7108.5000       0.62083284        305305.00
+  7109.0000       0.62279082        304852.00
+  7109.5000       0.62484776        304938.00
+  7110.0000       0.63029578        305214.00
+  7110.5000       0.63827614        304831.00
+  7111.0000       0.65354125        304668.00
+  7111.5000       0.67171017        304489.00
+  7112.0000       0.68465719        304804.00
+  7112.5000       0.68977450        304821.00
+  7113.0000       0.68982223        304700.00
+  7113.5000       0.68880150        304652.00
+  7114.0000       0.68631667        304907.00
+  7114.5000       0.68636382        304536.00
+  7115.0000       0.69345881        305540.00
+  7115.5000       0.71368054        306249.00
+  7116.0000       0.75470012        306100.00
+  7116.5000       0.82334644        305917.00
+  7117.0000       0.91897755        306536.00
+  7117.5000        1.0048919        306925.00
+  7118.0000        1.0613446        306741.00
+  7118.5000        1.1171460        306475.00
+  7119.0000        1.1598287        306428.00
+  7119.5000        1.1845153        306460.00
+  7120.0000        1.1839175        306450.00
+  7120.5000        1.1705836        306582.00
+  7121.0000        1.1532435        306359.00
+  7121.5000        1.1455536        306364.00
+  7122.0000        1.1474359        306349.00
+  7122.5000        1.1522733        306255.00
+  7123.0000        1.1570854        306266.00
+  7123.5000        1.1609012        306230.00
+  7124.0000        1.1633654        306227.00
+  7124.5000        1.1626865        306371.00
+  7125.0000        1.1609214        306051.00
+  7125.5000        1.1630846        306141.00
+  7126.0000        1.1694208        306113.00
+  7126.5000        1.1791744        306099.00
+  7127.0000        1.1920499        306090.00
+  7127.5000        1.2050365        306010.00
+  7128.0000        1.2176110        306003.00
+  7128.5000        1.2307236        305954.00
+  7129.0000        1.2447142        305942.00
+  7129.5000        1.2590796        306047.00
+  7130.0000        1.2750382        305942.00
+  7130.5000        1.2916155        305940.00
+  7131.0000        1.3069910        305777.00
+  7131.5000        1.3214558        305771.00
+  7132.0000        1.3316754        305988.00
+  7132.8830        1.3507457        305946.00
+  7133.7850        1.3673679        305893.00
+  7134.7070        1.3796873        305719.00
+  7135.6470        1.3878931        305734.00
+  7136.6060        1.3912805        305686.00
+  7137.5840        1.3911931        305808.00
+  7138.5820        1.3880279        305655.00
+  7139.5990        1.3853474        305680.00
+  7140.6340        1.3831500        305655.00
+  7141.6890        1.3775299        305647.00
+  7142.7630        1.3673370        305546.00
+  7143.8560        1.3502223        305620.00
+  7144.9680        1.3336271        305565.00
+  7146.0990        1.3195536        305942.00
+  7147.2490        1.3074943        305369.00
+  7148.4180        1.2960234        305508.00
+  7149.6060        1.2858296        305329.00
+  7150.8140        1.2749358        305424.00
+  7152.0410        1.2649495        305227.00
+  7153.2860        1.2568554        305483.00
+  7154.5510        1.2512398        305317.00
+  7155.8340        1.2450083        305327.00
+  7157.1370        1.2358616        305362.00
+  7158.4590        1.2228915        305483.00
+  7159.8000        1.2062374        305185.00
+  7161.1600        1.1928682        305200.00
+  7162.5390        1.1842737        305144.00
+  7163.9370        1.1854668        305132.00
+  7165.3540        1.1935051        305025.00
+  7166.7910        1.2026287        305071.00
+  7168.2460        1.2093646        304988.00
+  7169.7200        1.2123004        304957.00
+  7171.2140        1.2128140        303977.00
+  7172.7260        1.2152670        306335.00
+  7174.2580        1.2206383        305331.00
+  7175.8090        1.2263235        305180.00
+  7177.3780        1.2348007        304691.00
+  7178.9670        1.2429421        304825.00
+  7180.5750        1.2486921        304819.00
+  7182.2030        1.2524190        304876.00
+  7183.8490        1.2555733        304881.00
+  7185.5140        1.2605700        304902.00
+  7187.1980        1.2658377        304750.00
+  7188.9010        1.2695672        304618.00
+  7190.6240        1.2740295        304729.00
+  7192.3650        1.2803476        304624.00
+  7194.1260        1.2881666        304796.00
+  7195.9060        1.2964693        304913.00
+  7197.7050        1.3028079        304714.00
+  7199.5220        1.3047468        304745.00
+  7201.3590        1.2989422        304572.00
+  7203.2150        1.2811419        304614.00
+  7205.0900        1.2574551        304650.00
+  7206.9840        1.2349254        304423.00
+  7208.8970        1.2149769        304251.00
+  7210.8300        1.1972301        304339.00
+  7212.7810        1.1791903        304465.00
+  7214.7510        1.1614710        304344.00
+  7216.7410        1.1469665        304357.00
+  7218.7500        1.1337012        304283.00
+  7220.7770        1.1251983        304310.00
+  7222.8240        1.1212105        304357.00
+  7224.8900        1.1226870        304315.00
+  7226.9750        1.1282596        304319.00
+  7229.0790        1.1361879        304265.00
+  7231.2020        1.1424448        304222.00
+  7233.3440        1.1466808        304141.00
+  7235.5050        1.1511667        304231.00
+  7237.6850        1.1551194        304163.00
+  7239.8850        1.1601517        304198.00
+  7242.1030        1.1647366        304019.00
+  7244.3410        1.1695266        304007.00
+  7246.5970        1.1755328        304050.00
+  7248.8730        1.1793338        303881.00
+  7251.1670        1.1781583        304021.00
+  7253.4810        1.1690464        303919.00
+  7255.8140        1.1563788        303971.00
+  7258.1660        1.1441121        303951.00
+  7260.5370        1.1370528        304123.00
+  7262.9270        1.1358182        303754.00
+  7265.3360        1.1375476        303422.00
+  7267.7650        1.1418590        304213.00
+  7270.2120        1.1460955        304372.00
+  7272.6790        1.1475066        303800.00
+  7275.1640        1.1464166        303768.00
+  7277.6680        1.1414081        303769.00
+  7280.1920        1.1335102        303936.00
+  7282.7350        1.1245373        303644.00
+  7285.2970        1.1166967        304254.00
+  7287.8770        1.1106422        305223.00
+  7290.4780        1.1053995        305548.00
+  7293.0970        1.0989324        305571.00
+  7295.7350        1.0898640        305599.00
+  7298.3920        1.0760697        305554.00
+  7301.0680        1.0625112        305441.00
+  7303.7640        1.0518639        304737.00
+  7306.4780        1.0452685        306215.00
+  7309.2110        1.0417105        305425.00
+  7311.9640        1.0391516        305631.00
+  7314.7360        1.0350223        305492.00
+  7317.5260        1.0307148        305331.00
+  7320.3360        1.0290354        305591.00
+  7323.1650        1.0291306        305413.00
+  7326.0130        1.0334814        305373.00
+  7328.8800        1.0392331        305099.00
+  7331.7660        1.0438424        305523.00
+  7334.6710        1.0471145        305074.00
+  7337.5960        1.0476086        305182.00
+  7340.5390        1.0452525        305300.00
+  7343.5010        1.0386325        305317.00
+  7346.4830        1.0312056        305293.00
+  7349.4830        1.0234727        305162.00
+  7352.5030        1.0164420        304986.00
+  7355.5420        1.0108031        304917.00
+  7358.6000        1.0079450        305066.00
+  7361.6760        1.0071789        305087.00
+  7364.7720        1.0058498        304731.00
+  7367.8870        1.0018487        304884.00
+  7371.0210       0.99480756        305111.00
+  7374.1750       0.98681002        304985.00
+  7377.3470       0.97984890        304910.00
+  7380.5380       0.97353699        304699.00
+  7383.7490       0.96852948        304984.00
+  7386.9780       0.96429334        304841.00
+  7390.2270       0.96072809        304467.00
+  7393.4940       0.95802094        304650.00
+  7396.7810       0.95230605        304697.00
+  7400.0870       0.94351175        304742.00
+  7403.4120       0.93363091        304816.00
+  7406.7550       0.92545595        304658.00
+  7410.1190       0.92119926        304605.00
+  7413.5000       0.91918832        303795.00
+  7416.9020       0.91651349        305304.00
+  7420.3220       0.91338736        304548.00
+  7423.7610       0.90989200        304397.00
+  7427.2200       0.90467023        304707.00
+  7430.6970       0.89954758        304458.00
+  7434.1940       0.89577218        304310.00
+  7437.7090       0.89366937        304338.00
+  7441.2440       0.89233424        304342.00
+  7444.7980       0.89109983        304576.00
+  7448.3710       0.88991984        304397.00
+  7451.9620       0.89021992        304464.00
+  7455.5740       0.88787942        304333.00
+  7459.2040       0.88208656        304131.00
+  7462.8530       0.87531841        304376.00
+  7466.5210       0.87040248        304297.00
+  7470.2080       0.86516782        304048.00
+  7473.9150       0.85785170        304975.00
+  7477.6400       0.85054938        305708.00
+  7481.3850       0.84453901        306082.00
+  7485.1480       0.83863201        305961.00
+  7488.9310       0.83371670        306201.00
+  7492.7330       0.82904991        306134.00
+  7496.5540       0.82459446        306039.00
+  7500.3940       0.82079844        305977.00
+  7504.2530       0.81677576        305156.00
+  7508.1310       0.81178911        306748.00
+  7512.0280       0.80667240        306054.00
+  7515.9440       0.80196850        305949.00
+  7519.8790       0.79709432        305906.00
+  7523.8340       0.79034294        305935.00
+  7527.8070       0.78287009        305891.00
+  7531.8000       0.77566164        305816.00
+  7535.8120       0.77172412        305895.00
+  7539.8420       0.76918155        305839.00
+  7543.8920       0.76588805        305840.00
+  7547.9610       0.76149338        305863.00
+  7552.0490       0.75668300        305740.00
+  7556.1560       0.75093523        305560.00
+  7560.2820       0.74539760        305668.00
+  7564.4270       0.74088376        305785.00
+  7568.5910       0.73831944        305684.00
+  7572.7740       0.73529922        305582.00
+  7576.9770       0.73187551        305523.00
+  7581.1980       0.72922440        305558.00
+  7585.4390       0.72676436        305647.00
+  7589.6980       0.72359612        305208.00
+  7593.9770       0.71807714        306038.00
+  7598.2750       0.71252104        305315.00
+  7602.5920       0.70549579        305573.00
+  7606.9280       0.69954396        305378.00
+  7611.2830       0.69265029        305425.00
+  7615.6570       0.68689223        305488.00
+  7620.0500       0.68092659        305297.00
+  7624.4620       0.67499882        305001.00
+  7628.8940       0.66889046        305246.00
+  7633.3440       0.66341014        305391.00
+  7637.8130       0.65798296        305277.00
+  7642.3020       0.65352747        305129.00
+  7646.8100       0.64842668        305185.00
+  7651.3360       0.64433064        305107.00
+  7655.8820       0.64130624        304968.00
+  7660.4470       0.63726602        303292.00
+  7665.0300       0.63282645        306701.00
+  7669.6340       0.62771419        304931.00
+  7674.2550       0.62136047        304917.00
+  7678.8960       0.61572914        304978.00
+  7683.5570       0.61094428        305007.00
+  7688.2360       0.60604922        304909.00
+  7692.9350       0.60062272        304867.00
+  7697.6520       0.59482509        304843.00
+  7702.3890       0.58954937        304732.00
+  7707.1440       0.58505148        304761.00
+  7711.9190       0.58034243        304780.00
+  7716.7120       0.57641028        304730.00
+  7721.5250       0.57240859        304752.00
+  7726.3570       0.56757710        304652.00
+  7731.2080       0.56298530        304949.00
+  7736.0780       0.55790407        301749.00
+  7740.9670       0.55415072        309843.00
+  7745.8750       0.55025596        306494.00
+  7750.8030       0.54458907        306086.00
+  7755.7500       0.53968840        306137.00
+  7760.7150       0.53400917        306150.00
+  7765.6990       0.52848485        306231.00
+  7770.7030       0.52243506        306178.00
+  7775.7260       0.51730921        305604.00
+  7780.7670       0.51119667        306396.00
+  7785.8280       0.50502129        306122.00
+  7790.9080       0.49915279        306044.00
+  7796.0070       0.49353357        305959.00
+  7801.1250       0.48866156        305944.00
+  7806.2620       0.48323724        305753.00
+  7811.4180       0.47782882        305897.00
+  7816.5940       0.47305934        305849.00
+  7821.7880       0.46837192        305834.00
+  7827.0010       0.46392860        305216.00
+  7832.2340       0.45913400        306347.00
+  7837.4850       0.45441121        305718.00
+  7842.7560       0.44961359        305672.00
+  7848.0460       0.44433501        305591.00
+  7853.3540       0.43874661        305721.00
+  7858.6830       0.43362602        305761.00
+  7864.0300       0.42833655        305543.00
+  7869.3960       0.42287776        305580.00
+  7874.7810       0.41790268        305561.00
+  7880.1850       0.41331540        305567.00
+  7885.6080       0.40736809        305548.00
+  7891.0510       0.40162419        305485.00
+  7896.5120       0.39641156        305410.00
+  7901.9920       0.39158402        305454.00
+  7907.4920       0.38664929        305471.00
+  7913.0110       0.38183248        305389.00
+  7918.5480       0.37664824        305311.00
+  7924.1050       0.37186542        305383.00
+  7929.6810       0.36622348        305224.00
+  7935.2760       0.36059857        305309.00
+  7940.8900       0.35529935        304801.00
+  7946.5230       0.34983333        305709.00
+  7952.1750       0.34434706        305185.00
+  7957.8470       0.33897801        305134.00
+  7963.5370       0.33392445        305037.00
+  7969.2470       0.32835579        305340.00
\ No newline at end of file
Binary file test-data/multiple.prj has changed