Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 133 additions & 3 deletions nionswift_plugin/nion_eels_analysis/AlignZLP.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
# imports
import logging
import contextlib
import copy
import numpy
import gettext
import logging
import math
import typing
import contextlib

# third party libraries
import numpy
import scipy.ndimage

# local libraries
from nion.data import DataAndMetadata
from nion.eels_analysis import ZLP_Analysis
from nion.swift import Facade
from nion.swift.model import Symbolic
from nion.typeshed import API_1_0 as API
from nion.ui import Declarative
from nion.ui import Dialog
from nion.ui import Window
from nion.utils import Converter
from nion.utils import Event
from nion.utils import Registry


_ = gettext.gettext


DataArrayType = numpy.typing.NDArray[typing.Any]

Expand Down Expand Up @@ -337,3 +347,123 @@ def wc(w: Window.Window) -> None:

def calibrate_spectrum(api: Facade.API_1, window: Facade.DocumentWindow) -> None:
_calibrate_spectrum(api, window)


from nion.swift.model import Processing


class ProcessingAlignZLP(Processing.ProcessingBase):
def __init__(self, **kwargs: typing.Any) -> None:
super().__init__()
self.processing_id = "eels.align_zlp.a0"
self.title = _("Align ZLP")
self.sections = {"eels"}
self.sources = [
{"name": "src", "label": _("EELS Data"), "data_type": "xdata", "requirements": [{"type": "datum_rank", "values": (1,)}]}
]
self.parameters = [
{"name": "target_index", "label": _("Target Index"), "type": "integral", "value": 50, "value_default": 50, "value_min": 0}
]
self.outputs = [
{"name": "target", "label": _("Aligned EELS Data")},
{"name": "offset", "label": _("Applied Offset")}
]
self.is_mappable = True

def process(self, data_sources: typing.Mapping[str, DataAndMetadata.DataAndMetadata], **kwargs: typing.Any) -> typing.Mapping[str, Processing._ProcessingResult]:
eels_data_xdata = data_sources.get("src", None)
target_index = typing.cast(int, kwargs.get("target_index", 50))
assert eels_data_xdata

mx_pos = ZLP_Analysis.estimate_zlp_amplitude_position_width_com(eels_data_xdata.data)[1] or 0.0
# fallback to simple max
if not math.isfinite(mx_pos):
mx_pos = float(numpy.argmax(eels_data_xdata.data))
# determine the offset and apply it
interpolation_order = 1
offset = mx_pos - target_index
aligned_eels_data = scipy.ndimage.shift(eels_data_xdata.data, -offset, order=interpolation_order)

eels_data_calibration = eels_data_xdata.dimensional_calibrations[-1]
eels_data_calibration.offset = -(target_index + 0.5) * eels_data_calibration.scale

offset_calibration = copy.copy(eels_data_calibration)
offset_calibration.offset = 0

aligned_eels_xdata = DataAndMetadata.new_data_and_metadata(aligned_eels_data, eels_data_xdata.intensity_calibration, (eels_data_calibration, ))

return {
"target": aligned_eels_xdata,
"offset": DataAndMetadata.ScalarAndMetadata(lambda: offset, offset_calibration)
}


Registry.register_component(ProcessingAlignZLP(), {"processing-component"})


# class AlignZLPComputation(Symbolic.ComputationHandlerLike):
#
# def __init__(self, computation: Facade.Computation, **kwargs: typing.Any) -> None:
# self.computation = computation
# self.__aligned_eels_data: typing.Optional[DataAndMetadata.DataAndMetadata] = None
#
# def execute(self, **kwargs: typing.Any) -> None:
# eels_data_item = typing.cast(Facade.DataSource, kwargs["eels_data_item"])
# target_index = typing.cast(int, kwargs.get("target_index", 50))
# eels_data_xdata = eels_data_item.xdata
# assert eels_data_xdata
# mx_pos = ZLP_Analysis.estimate_zlp_amplitude_position_width_com(eels_data_xdata.data)[1] or 0.0
# # fallback to simple max
# if not math.isfinite(mx_pos):
# mx_pos = float(numpy.argmax(eels_data_xdata.data))
# # determine the offset and apply it
# interpolation_order = 1
# offset = mx_pos - target_index
# aligned_eels_data = scipy.ndimage.shift(eels_data_xdata.data, -offset, order=interpolation_order)
#
# eels_data_calibration = eels_data_xdata.dimensional_calibrations[-1]
# eels_data_calibration.offset = -(target_index + 0.5) * eels_data_calibration.scale
#
# offset_calibration = copy.copy(eels_data_calibration)
# offset_calibration.offset = 0
#
# self.__aligned_eels_data = DataAndMetadata.new_data_and_metadata(aligned_eels_data, eels_data_xdata.intensity_calibration, (eels_data_calibration, ))
#
# def commit(self) -> None:
# assert self.__aligned_eels_data
# self.computation.set_referenced_xdata("aligned_eels_data", self.__aligned_eels_data)


# def apply_align_zlp(api: Facade.API_1, window: Facade.DocumentWindow) -> None:
# target_display = window.target_display
# target_data_item_ = target_display._display_item.data_items[0] if target_display and len(target_display._display_item.data_items) > 0 else None
# if target_data_item_ and target_display:
# eels_data_item = Facade.DataItem(target_data_item_)
# if eels_data_item:
# assert eels_data_item.display_xdata
# if not eels_data_item.display_xdata.is_data_1d:
# logging.error("Failed: Data is not a sequence or collection of 1D spectra.")
# return
# aligned_eels_data_item = api.library.create_data_item_from_data(numpy.zeros_like(eels_data_item.display_xdata.data))
# api.library.create_computation("eels.align_zlp.a0", inputs={"src": eels_data_item, "target_index": 50}, outputs={"target": aligned_eels_data_item})
# window.display_data_item(aligned_eels_data_item)


# ComputationCallable = typing.Callable[[Symbolic._APIComputation], Symbolic.ComputationHandlerLike]
# Symbolic.register_computation_type("eels.align_zlp.a0", typing.cast(ComputationCallable, AlignZLPComputation))


# DocumentModel.DocumentModel.register_processing_descriptions({
# "eels.align_zlp.a0": {
# "title": _("Align ZLP"),
# "sources": [
# {"name": "eels_data_item", "label": _("EELS Data"), "data_type": "xdata", "requirements": [{"type": "datum_rank", "values": (1,)}]}
# ],
# "parameters": [
# {"name": "target_index", "label": _("Target Index"), "type": "integral", "value": 50, "value_default": 50, "value_min": 0}
# ],
# "outputs": [
# {"name": "aligned_eels_data", "label": _("Aligned EELS Data")}
# ]
# }
# })
15 changes: 14 additions & 1 deletion nionswift_plugin/nion_eels_analysis/ThicknessMap.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# local libraries
from nion.data import DataAndMetadata
from nion.swift import Facade
from nion.swift.model import DocumentModel
from nion.swift.model import Symbolic


Expand Down Expand Up @@ -51,7 +52,6 @@ def map_thickness_xdata(src_xdata: DataAndMetadata.DataAndMetadata) -> typing.Op


class EELSThicknessMapping:
label = _("Thickness Map")

def __init__(self, computation: Facade.Computation, **kwargs: typing.Any) -> None:
self.computation = computation
Expand Down Expand Up @@ -81,3 +81,16 @@ def map_thickness(api: Facade.API_1, window: Facade.DocumentWindow) -> None:

ComputationCallable = typing.Callable[[Symbolic._APIComputation], Symbolic.ComputationHandlerLike]
Symbolic.register_computation_type("eels.thickness_mapping", typing.cast(ComputationCallable, EELSThicknessMapping))


DocumentModel.DocumentModel.register_processing_descriptions({
"eels.thickness_mapping": {
"title": _("Thickness Map"),
"sources": [
{"name": "spectrum_image_data_item", "label": _("Spectrum Image"), "data_type": "xdata"}
],
"outputs": [
{"name": "map", "label": _("Thickness Map")}
]
}
})
2 changes: 2 additions & 0 deletions nionswift_plugin/nion_eels_analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ def __build_menus(self, document_window: DocumentController.DocumentController)
eels_menu.add_menu_item(_("Align ZLP (com method)"), functools.partial(AlignZLP.align_zlp_com, api, window))
eels_menu.add_menu_item(_("Align ZLP (peak fit method)"), functools.partial(AlignZLP.align_zlp_fit, api, window))
eels_menu.add_separator()
eels_menu.add_menu_item(_("Align ZLP"), lambda: document_window.perform_action("processing.eels.align_zlp.a0"))
eels_menu.add_separator()
eels_menu.add_menu_item(_("Show Live Thickness Measurement"), functools.partial(LiveThickness.attach_measure_thickness, api, window))
eels_menu.add_menu_item(_("Show Live ZLP Measurement"), functools.partial(LiveZLP.attach_measure_zlp, api, window))
eels_menu.add_separator()
Expand Down
42 changes: 39 additions & 3 deletions nionswift_plugin/nion_eels_analysis/test/AlignZLP_test.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import numpy
import typing
import unittest

import numpy
import scipy

from nion.data import Calibration
from nion.data import DataAndMetadata
from nion.swift import Application
from nion.swift import Facade
from nion.swift.model import DataItem
from nion.swift.test import TestContext
from nion.ui import TestUI

from .. import AlignZLP


Facade.initialize()


def create_memory_profile_context() -> TestContext.MemoryProfileContext:
return TestContext.MemoryProfileContext()


def generate_peak_data(*, range_ev: float = 100.0, length: int = 1000, add_noise: bool = False, is_biased: bool = False) -> DataAndMetadata.DataAndMetadata:
x_axis = numpy.arange(-range_ev / 10, range_ev, range_ev / length)
if is_biased:
x_axis[length // 10:] = numpy.arange(0, range_ev / 2.5, range_ev / 2.5 / length)
data = 1e6 * scipy.stats.norm.pdf(x_axis, 0, 1)
if add_noise:
data += numpy.abs(numpy.random.normal(0, 5, data.shape))
intensity_calibration = Calibration.Calibration(units="counts")
dimensional_calibrations = [Calibration.Calibration(scale=range_ev / length, offset=-range_ev / 10, units="eV")]
return DataAndMetadata.new_data_and_metadata(data, intensity_calibration=intensity_calibration, dimensional_calibrations=dimensional_calibrations)


class TestBackgroundSubtraction(unittest.TestCase):

def setUp(self) -> None:
Expand Down Expand Up @@ -67,3 +83,23 @@ def test_calibrate_spectrum_for_single_spectrum(self) -> None:
self.assertEqual(0, len(document_model.data_items))
self.assertEqual(0, len(document_model.display_items))
self.assertEqual(0, len(document_model.data_structures))

def test_align_zlp_computation(self) -> None:
with create_memory_profile_context() as test_context:
document_controller = test_context.create_document_controller_with_application()
document_model = document_controller.document_model
peak_xdata = generate_peak_data()
eels_data_item = DataItem.new_data_item(peak_xdata)
document_model.append_data_item(eels_data_item)
display_panel = document_controller.selected_display_panel
display_item = document_model.get_display_item_for_data_item(eels_data_item)
display_panel.set_display_panel_display_item(display_item)
api = Facade.get_api("~1.0", "~1.0")
AlignZLP.apply_align_zlp(api, Facade.DocumentWindow(document_controller))
document_model.recompute_all()
document_controller.periodic()
self.assertFalse(any(computation.error_text for computation in document_model.computations))
self.assertEqual(2, len(document_model.data_items))
self.assertIn("(Align ZLP)", document_model.data_items[1].title)
self.assertAlmostEqual(50.0, numpy.argmax(document_model.data_items[1].xdata))
self.assertAlmostEqual(0.0, document_model.data_items[1].dimensional_calibrations[-1].convert_to_calibrated_value(50.5))
Loading