-
Notifications
You must be signed in to change notification settings - Fork 1
Experiment single dataset #58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
henrikjacobsenfys
wants to merge
9
commits into
develop
Choose a base branch
from
experiment_single_dataset
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+469
−0
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
a0e7c24
Add basic class and example
henrikjacobsenfys 6b3c0cc
Tests
henrikjacobsenfys fac0bfa
Added some tests
henrikjacobsenfys 658314e
Comment tests
henrikjacobsenfys d616c78
Fix a test
henrikjacobsenfys cd4464a
update example
henrikjacobsenfys 02bf7af
add tests as suggested in review
henrikjacobsenfys 30a39a3
Update tests
henrikjacobsenfys 8b241bb
Add tests of _in_notebook
henrikjacobsenfys File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| { | ||
| "cells": [ | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "51b7b8be", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "from easydynamics.experiment import Experiment\n" | ||
| ] | ||
| }, | ||
| { | ||
| "cell_type": "code", | ||
| "execution_count": null, | ||
| "id": "eb91e49a", | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": [ | ||
| "vanadium_experiment=Experiment(\"Vanadium\")\n", | ||
| "vanadium_experiment.load_hdf5(filename=\"example_data/vanadium_data_example.h5\")\n", | ||
| "\n", | ||
| "vanadium_experiment.plot_data()\n" | ||
| ] | ||
| } | ||
| ], | ||
| "metadata": { | ||
| "kernelspec": { | ||
| "display_name": "newdynamics", | ||
| "language": "python", | ||
| "name": "python3" | ||
| }, | ||
| "language_info": { | ||
| "codemirror_mode": { | ||
| "name": "ipython", | ||
| "version": 3 | ||
| }, | ||
| "file_extension": ".py", | ||
| "mimetype": "text/x-python", | ||
| "name": "python", | ||
| "nbconvert_exporter": "python", | ||
| "pygments_lexer": "ipython3", | ||
| "version": "3.11.13" | ||
| } | ||
| }, | ||
| "nbformat": 4, | ||
| "nbformat_minor": 5 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| from .experiment import ( | ||
| Experiment, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "Experiment", | ||
| ] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| from typing import Optional, Union | ||
|
|
||
| import plopp as pp | ||
| import scipp as sc | ||
| from easyscience.job.experiment import ExperimentBase | ||
| from scipp.io import load_hdf5 as sc_load_hdf5 | ||
| from scipp.io import save_hdf5 as sc_save_hdf5 | ||
|
|
||
|
|
||
| class Experiment(ExperimentBase): | ||
| """ | ||
| Holds data from an experiment as a sc.DataArray along with metadata. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| name: str, | ||
| data: Optional[Union[sc.DataArray, str]] = None, | ||
| *args, | ||
| **kwargs, | ||
| ): | ||
| super().__init__(name, *args, **kwargs) | ||
|
|
||
| if data is None: | ||
| self._data: Optional[sc.DataArray] = None | ||
| elif isinstance(data, str): | ||
| self.load_hdf5(filename=data) | ||
| elif isinstance(data, sc.DataArray): | ||
| self._data = data | ||
| else: | ||
| raise TypeError( | ||
| f"Data must be a sc.DataArray or a filename string, not {type(data).__name__}" | ||
| ) | ||
|
|
||
| def load_hdf5(self, filename: str, name: Optional[str] = None): | ||
| """ | ||
| Load data from an HDF5 file. | ||
|
|
||
| Args: | ||
| filename (str): Path to the HDF5 file. | ||
| """ | ||
| if not isinstance(filename, str): | ||
| raise TypeError(f"Filename must be a string, not {type(filename).__name__}") | ||
|
|
||
| if name is not None: | ||
| if not isinstance(name, str): | ||
| raise TypeError(f"Name must be a string, not {type(name).__name__}") | ||
| self.name = name | ||
|
|
||
| # TODO: Add checks of dimensions etc. I'm not yet sure what dimensions I want to allow, so for now I trust that the data is valid. | ||
| loaded_data = sc_load_hdf5(filename) | ||
| if not isinstance(loaded_data, sc.DataArray): | ||
| raise TypeError( | ||
| f"Loaded data must be a sc.DataArray, not {type(loaded_data).__name__}" | ||
| ) | ||
| self._data = loaded_data | ||
|
|
||
| def save_hdf5(self, filename: Optional[str] = None): | ||
| """Save the dataset to HDF5. | ||
|
|
||
| Args: | ||
| filename (str): Path to the output HDF5 file. | ||
| """ | ||
|
|
||
| if filename is None: | ||
| filename = f"{self.name}.h5" | ||
|
|
||
| if not isinstance(filename, str): | ||
| raise TypeError(f"Filename must be a string, not {type(filename).__name__}") | ||
|
|
||
| if self._data is None: | ||
| raise ValueError("No data to save.") | ||
|
|
||
| import os | ||
|
|
||
| dir_name = os.path.dirname(filename) | ||
| if dir_name: | ||
| os.makedirs(dir_name, exist_ok=True) | ||
|
|
||
| sc_save_hdf5(self._data, filename) | ||
|
|
||
| def remove_data(self): | ||
| """Remove the dataset from the experiment.""" | ||
| self._data = None | ||
|
|
||
| @property | ||
| def data(self) -> Optional[sc.DataArray]: | ||
| """Get the dataset associated with this experiment.""" | ||
| return self._data | ||
|
|
||
| @data.setter | ||
| def data(self, value: sc.DataArray): | ||
| """Set the dataset associated with this experiment.""" | ||
| if not isinstance(value, sc.DataArray): | ||
| raise TypeError(f"Data must be a sc.DataArray, not {type(value).__name__}") | ||
| self._data = value | ||
|
|
||
| def plot_data(self): | ||
| """Plot the dataset using plopp.""" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
use mocking like there's no tomorrow ;) e.g. def test_plot_data_success(self, experiment):
"Test plotting data successfully when in notebook environment"
# GIVEN
with patch.object(Experiment, '_in_notebook', return_value=True), \
patch('plopp.plot') as mock_plot, \
patch('IPython.display.display') as mock_display:
mock_fig = MagicMock()
mock_plot.return_value = mock_fig
# WHEN
experiment.plot_data()
# THEN
mock_plot.assert_called_once()
args, kwargs = mock_plot.call_args
assert sc.identical(args[0], experiment._data.transpose())
assert kwargs['title'] == f"{experiment.name}"
mock_display.assert_called_once_with(mock_fig)
def test_plot_data_no_data_raises(self):
"Test plotting data raises ValueError when no data is present"
# GIVEN
experiment = Experiment(name="empty_experiment")
# WHEN / THEN
with pytest.raises(ValueError, match="No data to plot"):
experiment.plot_data()
def test_plot_data_not_in_notebook_raises(self, experiment):
"Test plotting data raises RuntimeError when not in notebook environment"
# GIVEN
with patch.object(Experiment, '_in_notebook', return_value=False):
# WHEN / THEN
with pytest.raises(RuntimeError, match="plot_data\\(\\) can only be used in a Jupyter notebook environment"):
experiment.plot_data()
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks! I'll look into it |
||
|
|
||
| if self._data is None: | ||
| raise ValueError("No data to plot. Please load data first.") | ||
|
|
||
| if not self._in_notebook(): | ||
| raise RuntimeError( | ||
| "plot_data() can only be used in a Jupyter notebook environment." | ||
| ) | ||
|
|
||
| from IPython.display import display | ||
|
|
||
| fig = pp.plot(self._data.transpose(), title=f"{self.name}") | ||
| display(fig) | ||
|
|
||
| @staticmethod | ||
| def _in_notebook(): | ||
| try: | ||
| from IPython import get_ipython | ||
|
|
||
| shell = get_ipython().__class__.__name__ | ||
| if shell == "ZMQInteractiveShell": | ||
| return True # Jupyter notebook or JupyterLab | ||
| elif shell == "TerminalInteractiveShell": | ||
| return False # Terminal IPython | ||
| else: | ||
| return False | ||
| except (NameError, ImportError): | ||
| return False # Standard Python (no IPython) | ||
|
|
||
| def __repr__(self) -> str: | ||
| return f"Experiment `{self.name}` with data: {self._data}" | ||
rozyczko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def __copy__(self) -> "Experiment": | ||
| """Return a copy of the object.""" | ||
| temp = self.as_dict(skip=["unique_name"]) | ||
| new_obj = self.__class__.from_dict(temp) | ||
| new_obj.data = self.data.copy() if self.data is not None else None | ||
| return new_obj | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calling
experiment.save_hdf5()with no arguments (or any filename without a directory component) raises FileNotFoundError becauseos.path.dirname('test.h5')returns '', soos.makedirs('', exist_ok=True)fails on Windows. The defaultfilename = f"{self.name}.h5"therefore breaks out of the box.Guard the directory creation with something like