Skip to content

Conversation

@damskii9992
Copy link
Collaborator

No description provided.

@damskii9992 damskii9992 added [scope] significant Breaking or major changes (MAJOR.minor.patch) [priority] high Should be prioritized soon [area] Measurement The data-container and viewer labels Feb 9, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pull request does not contain a valid label. Please add one of the following labels: ['chore', 'fix', 'bugfix', 'bug', 'enhancement', 'feature', 'dependencies', 'documentation']

@damskii9992 damskii9992 added the chore temporary label Feb 9, 2026
Copy link
Member

@henrikjacobsenfys henrikjacobsenfys left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only minor nitpicks and questions.

It may be nice to provide an example notebook to see how it all works in practice.

self._data_array.coords['y'] = y_positions
self._has_physical_coords = True

def delete_physical_coord_positions(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a restore_physical_coord_positions method make sense?

if not isinstance(dimensions, dict):
raise TypeError('dimensions must be a dictionary mapping dimension names to rebin factors.')
if all(isinstance(value, Numeric) and value == 1 for value in dimensions.values()): # Reverts to original data
# self.revert_rebin() # If we want secondary rebins to work on the original data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should presumably not be outcommented?

return
sizes = dimensions.copy()
if 't' in dimensions:
raise ValueError("Rebinning of the time-of-flight ('t') dimension is yet not supported.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet

'title': self.display_name + title_suffix,
'clabel': 'Transmission',
'cmin': 0.0,
'cmax': 3.0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should perhaps be something like 1.1*max(self._data_array.values)?

f"'{coord_name}' coordinate must have a unit of {expected_dim_string}, such as ('{expected_unit}')."
) from None # noqa: E501

# Does this need to be moved somewhere else? Maybe Corelib?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to have it here, at least for now. Perhaps in a utils file so it doesn't get lost?

assert measurement.regions_of_interest[0] is valid_roi
assert measurement.regions_of_interest[roi_name] is valid_roi

def test_regions_of_interest_removal_by_index(self, valid_data_array, valid_roi):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in the following test: May be good to add at least another roi and check that only the requested roi is removed As it is, the test would pass if the removal removed all roi, the first in the list, the last in the list or a random one.


monkeypatch.setattr(measurement, '_is_notebook', mock_is_notebook)
# Then Expect
measurement.plot(time_of_flight=time_of_flight) # Just ensure no exception is raised
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you mock plot and check that it gets the right kwargs?

# Then Expect
measurement.slicer_plot() # Just ensure no exception is raised

def test_spectrum_inspector_fails_outside_notebook(self, valid_data_array):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned that some tests don't work yet, perhaps this is one of them. In any case, it fails for me with the following error, just posting in case it's useful:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src\easyimaging\measurement\measurement.py:524: in spectrum_inspector
    return pp.inspector(self._data_array, dim='t', orientation='vertical', **inspector_kwargs_defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.pixi\envs\dev\Lib\site-packages\plopp\plotting\inspector.py:206: in inspector
    f1d = linefigure(
.pixi\envs\dev\Lib\site-packages\plopp\graphics\figures.py:31: in linefigure
    return backends.get(group='2d', name='figure')(view_maker, *nodes, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.pixi\envs\dev\Lib\site-packages\plopp\backends\matplotlib\figure.py:195: in Figure
    return InteractiveFigure(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.pixi\envs\dev\Lib\site-packages\plopp\backends\matplotlib\figure.py:136: in __init__
    self.__init_figure__(View, *args, **kwargs)
.pixi\envs\dev\Lib\site-packages\plopp\backends\matplotlib\figure.py:24: in __init_figure__
    self.view = View(*args, **kwargs)
                ^^^^^^^^^^^^^^^^^^^^^
.pixi\envs\dev\Lib\site-packages\plopp\graphics\graphicalview.py:100: in __init__
    self.canvas = canvas_maker(
.pixi\envs\dev\Lib\site-packages\plopp\backends\matplotlib\canvas.py:157: in __init__
    self.fig = make_figure(figsize=(6.0, 4.0) if figsize is None else figsize)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (), kwargs = {'figsize': (6.0, 4.0)}
fig = <Figure size 600x400 with 0 Axes>

    def make_figure(*args, **kwargs) -> plt.Figure:
        """
        Create a new figure.
    
        If we use ``plt.figure()`` directly, the figures auto-show in the notebooks.
        We want to display the figures when the figure repr is requested.
    
        When using the static backend, we can return the ``plt.Figure`` (note the uppercase
        F) directly.
        When using the interactive backend, we need to do more work. The ``plt.Figure``
        will not have a toolbar nor will it be interactive, as opposed to what
        ``plt.figure`` returns. To fix this, we need to create a manager for the figure
        (see https://stackoverflow.com/a/75477367).
        """
        fig = plt.Figure(*args, **kwargs)
        if is_interactive_backend():
            # Create a manager for the figure, which makes it interactive, as well as
            # making it possible to show the figure from the terminal.
>           plt._backend_mod.new_figure_manager_given_figure(1, fig)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           AttributeError: 'NoneType' object has no attribute 'new_figure_manager_given_figure'

.pixi\envs\dev\Lib\site-packages\plopp\backends\matplotlib\utils.py:71: AttributeError

): # noqa: E501
measurement.spectrum_inspector()

def test_spectrum_inspector_runs_in_notebook_with_widget_backend(self, valid_data_array, monkeypatch, _use_ipympl):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_use_ipympl not found

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need to have the same file in two different places in the project?

else:
raise ValueError('Both x_range and y_range must be provided together or not at all.')

def set_pixel_coord_range(self, x_pixel_range: Sequence[int, int], y_pixel_range: Sequence[int, int]) -> None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and similar lines are quite long and therefore not so easy to read:

def set_pixel_coord_range(self, x_pixel_range: Sequence[int, int], y_pixel_range: Sequence[int, int]) -> None:

Maybe it would be better to format them like this:

def set_pixel_coord_range(
        self, 
        x_pixel_range: Sequence[int, int], 
        y_pixel_range: Sequence[int, int]
) -> None:

Yes, this uses more lines, but it is much easier to read. It is faster to see what the arguments are, their types, and the return value.

Copy link
Member

@rozyczko rozyczko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few issues, nothing blocking.

requires-python = ">=3.11,<3.14"
dependencies = [
"easyscience>=2.0.0",
#"easyscience@git+https://github.com/easyscience/corelib#egg=easylist2",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You rely on EasyList but use the release version. The current code doesn't work when installed

Revert any rebinning applied to the measurement data, restoring it to its original resolution.
"""
if self._rebinned_data_array is not None:
del self._rebinned_data_array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will call AttributeError if called before any rebinning (_rebinned_data_array is not defined!)
Consider guards

[dependencies]
python = ">=3.11,<3.13"
python = ">=3.14.3,<3.15"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inconsistent python version between pixi.toml and pyproject.toml!

Rebin the measurement image stack. This operation reduces the resolution of the data by combining adjacent pixels or time bins.
The rebinned dimensions must be evenly divisible by their specific rebin factor.

To revert to the original data, provide a dictionary with all rebin factors set to 1.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The above statement is not true, as the revert path is commented out.

@staticmethod
def _validate_provided_coord(
data_array: sc.DataArray,
coord: sc.Variable | np.array,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

np.array - should this not be np.ndarray?

np.array is a function, np.ndarray is a type.

Comment on lines +190 to +192
@_data_array.setter
def _data_array(self, value: sc.DataArray) -> None:
raise AttributeError('Cannot set _data_array, it is a read-only property.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pytohn will raise AttributeError even without the setter - this is a private property.

plot_kwargs_defaults.update(kwargs)

if time_of_flight is None:
plot = self._data_array.mean('t').plot(**plot_kwargs_defaults)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you just said in the docstring, that
If no time-of-flight is provided, the plot will sum over all time-of-flight values.
mean =/= sum ...

Comment on lines +597 to +610
def _is_notebook(self) -> bool:
"""
Check if the code is running in a Jupyter notebook environment.
"""
try:
shell = get_ipython().__class__.__name__ # pyright: ignore[reportUndefinedVariable]
if shell == 'ZMQInteractiveShell':
return True # Jupyter notebook or qtconsole
elif shell == 'TerminalInteractiveShell':
return False # Terminal running IPython
else:
return False # Other type (possibly other IDE)
except NameError:
return False # Probably standard Python interpreter
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a @staticmethod

Comment on lines +23 to +24
if TYPE_CHECKING:
pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or just remove this

Comment on lines +198 to +200
raise TypeError(f'{name} indice must be an integer.')
if value < 0:
raise ValueError(f'{name} indice must be non-negative.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indice -> index

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[area] Measurement The data-container and viewer chore temporary [priority] high Should be prioritized soon [scope] significant Breaking or major changes (MAJOR.minor.patch)

Projects

4 participants