diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4203e5d6..bea9f2da 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -23,7 +23,7 @@ jobs: PYTHON_VERSION: '3.8' # Set pillow and scikit-image version to be compatible with imageio and scipy # matplotlib needs 3.5 to support markers in hyperspy 2.0 (requires `collection.set_offset_transform`) - DEPENDENCIES: matplotlib==3.5 numpy==1.20.0 tifffile==2022.7.28 dask[array]==2021.3.1 numba==0.52 imageio==2.16 pillow==8.3.2 scikit-image==0.18.0 + DEPENDENCIES: matplotlib==3.5 numpy==1.20.0 tifffile==2022.7.28 dask[array]==2021.3.1 numba==0.52 imageio==2.16 pillow==8.3.2 scikit-image==0.18.0 python-box==6.0.0 LABEL: '-oldest' # test minimum requirement - os: ubuntu @@ -118,6 +118,13 @@ jobs: pip install git+https://github.com/hyperspy/hyperspy.git pip install git+https://github.com/hyperspy/exspy.git + - name: Install latest python-box + # When installing hyperspy, python-box 6 is installed (rosettasciio pinning) + # Remove when rosettasciio >0.4.0 is released + if: ${{ ! contains(matrix.LABEL, 'oldest') }} + run: | + pip install --upgrade python-box + - name: Install shell: bash run: | diff --git a/conda_environment.yml b/conda_environment.yml index 127b723f..b6549c6d 100644 --- a/conda_environment.yml +++ b/conda_environment.yml @@ -5,7 +5,7 @@ dependencies: - dask-core >=2021.3.1 - numpy >=1.20.0 - pint >=0.8 -- python-box >=6.0,<7.0 +- python-box >=6.0 - python-dateutil - pyyaml - scipy diff --git a/pyproject.toml b/pyproject.toml index 59e21b8e..a1fa6486 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "python-dateutil", "numpy>=1.20.0", "pint>=0.8", - "python-box>=6,<7", + "python-box>=6", "pyyaml", ] dynamic = ["version"] diff --git a/rsciio/pantarhei/_api.py b/rsciio/pantarhei/_api.py index d982b7b5..16020630 100644 --- a/rsciio/pantarhei/_api.py +++ b/rsciio/pantarhei/_api.py @@ -291,8 +291,8 @@ def export_pr(signal): ref_size = meta_data["ref_size"][::-1] # switch to numpy order pixel_factors = [ref_size[i] / data.shape[i] for i in range(data.ndim)] axes_meta_data = get_metadata_from_axes_info(axes_info, pixel_factors=pixel_factors) - for k in axes_meta_data: - meta_data[k] = axes_meta_data[k] + meta_data.update(axes_meta_data) + return data, meta_data @@ -331,17 +331,19 @@ def _metadata_converter_in(meta_data, axes, filename): if meta_data.get("filter.mode") == "EELS" and signal_dimensions == 1: mapped.set_item("Signal.signal_type", "EELS") - name = meta_data.get("repo_id").split(".")[0] - mapped.set_item("General.title", name) + name = meta_data.get("repo_id") + if name is not None: + mapped.set_item("General.title", name.split(".")[0]) if filename is not None: mapped.set_item("General.original_filename", os.path.split(filename)[1]) + timestamp = None if "acquisition.time" in meta_data: timestamp = meta_data["acquisition.time"] elif "camera.time" in meta_data: timestamp = meta_data["camera.time"] - if "timestamp" in locals(): + if timestamp is not None: timestamp = dt.fromisoformat(timestamp) mapped.set_item("General.date", timestamp.date().isoformat()) mapped.set_item("General.time", timestamp.time().isoformat()) @@ -384,9 +386,11 @@ def _metadata_converter_in(meta_data, axes, filename): def _metadata_converter_out(metadata, original_metadata=None): - metadata = DTBox(metadata, box_dots=True) - original_metadata = DTBox(original_metadata, box_dots=True) - original_fname = metadata.get("General.original_filename", "") + # Don't use `box_dots=True` to be able to use key containing period + # When a entry doesn't exist a empty DTBox is returned + metadata = DTBox(metadata, box_dots=False, default_box=True) + original_metadata = DTBox(original_metadata, box_dots=False, default_box=True) + original_fname = metadata.General.original_filename or "" original_extension = os.path.splitext(original_fname)[1] if original_metadata.get("ref_size"): PR_metadata_present = True @@ -394,7 +398,7 @@ def _metadata_converter_out(metadata, original_metadata=None): PR_metadata_present = False if original_extension == ".prz" and PR_metadata_present: - meta_data = original_metadata + meta_data = original_metadata.to_dict() meta_data["ref_size"] = meta_data["ref_size"][::-1] for key in ["content.types", "user.calib", "inherited.calib", "device.calib"]: if key in meta_data: @@ -407,43 +411,43 @@ def _metadata_converter_out(metadata, original_metadata=None): else: meta_data = {} - if metadata.get("Signal.signal_type") == "EELS": + if metadata.Signal.signal_type == "EELS": meta_data["filter.mode"] = "EELS" - name = metadata.get("General.title") - if name is not None: + name = metadata.General.title + if name: meta_data["repo_id"] = name + ".0" - date = metadata.get("General.date") - time = metadata.get("General.time") - if date is not None and time is not None: + date = metadata.General.date + time = metadata.General.time + if date and time: timestamp = date + "T" + time meta_data["acquisition.time"] = timestamp - md_TEM = metadata.get("Acquisition_instrument.TEM") - if md_TEM is not None: - beam_energy = md_TEM.get("beam_energy") - convergence_angle = md_TEM.get("convergence_angle") - collection_angle = md_TEM.get("Detector.EELS.collection_angle") - aperture = md_TEM.get("Detector.EELS.aperture") - acquisition_mode = md_TEM.get("acquisition_mode") - magnification = md_TEM.get("magnification") - camera_length = md_TEM.get("camera_length") - - if aperture is not None: + md_TEM = metadata.Acquisition_instrument.TEM + if md_TEM: + beam_energy = md_TEM.beam_energy + convergence_angle = md_TEM.convergence_angle + collection_angle = md_TEM.Detector.EELS.collection_angle + aperture = md_TEM.Detector.EELS.aperture + acquisition_mode = md_TEM.acquisition_mode + magnification = md_TEM.magnification + camera_length = md_TEM.camera_length + + if aperture: if isinstance(aperture, (float, int)): aperture = str(aperture) + " mm" meta_data["filter.aperture"] = aperture - if beam_energy is not None: + if beam_energy: beam_energy_ev = beam_energy * 1e3 meta_data["electron_gun.voltage"] = beam_energy_ev - if convergence_angle is not None: + if convergence_angle: convergence_angle_rad = convergence_angle / 1e3 meta_data["condenser.convergence_semi_angle"] = convergence_angle_rad - if collection_angle is not None: + if collection_angle: collection_angle_rad = collection_angle / 1e3 meta_data["filter.collection_semi_angle"] = collection_angle_rad - if camera_length is not None: + if camera_length: meta_data["projector.camera_length"] = camera_length if acquisition_mode == "STEM": key = "scan_driver" @@ -451,7 +455,7 @@ def _metadata_converter_out(metadata, original_metadata=None): else: key = "projector" meta_data["source.type"] = "camera" - if magnification is not None: + if magnification: meta_data[f"{key}.magnification"] = magnification return meta_data diff --git a/rsciio/tests/test_pantarhei.py b/rsciio/tests/test_pantarhei.py index 1a4d845d..e065079d 100644 --- a/rsciio/tests/test_pantarhei.py +++ b/rsciio/tests/test_pantarhei.py @@ -90,6 +90,7 @@ def test_save_load_cycle(tmp_path): s2 = hs.load(fname) np.testing.assert_allclose(s2.data, s.data) + assert s2.metadata.Signal.signal_type == s.metadata.Signal.signal_type def test_save_load_cycle_new_signal_1D_nav1(tmp_path): diff --git a/rsciio/utils/tools.py b/rsciio/utils/tools.py index 57b957c3..42870ab0 100644 --- a/rsciio/utils/tools.py +++ b/rsciio/utils/tools.py @@ -374,6 +374,15 @@ def xml2dtb(et, dictree): class DTBox(Box): + """ + Subclass of Box to help migration from hyperspy `DictionaryTreeBrowser` + to `Box` when splitting IO code from hyperspy to rosettasciio. + + When using `box_dots=True`, by default, period will be removed from keys. + To support period containing keys, use `box_dots=False, default_box=True`. + https://github.com/cdgriffith/Box/wiki/Types-of-Boxes#default-box + """ + def add_node(self, path): keys = path.split(".") for key in keys: diff --git a/upcoming_changes/263.maintenance.rst b/upcoming_changes/263.maintenance.rst new file mode 100644 index 00000000..bddd8ab6 --- /dev/null +++ b/upcoming_changes/263.maintenance.rst @@ -0,0 +1 @@ +Add support for ``python-box`` 7. \ No newline at end of file