sdf-xarray 0.8.0__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/build_publish.yml +1 -1
  2. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/tests.yml +1 -1
  3. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/PKG-INFO +1 -1
  4. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/animation.md +8 -3
  5. sdf_xarray-0.9.0/docs/figures/voxel_plot_high_res.png +0 -0
  6. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/index.md +1 -0
  7. sdf_xarray-0.9.0/docs/plotting.md +95 -0
  8. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/understanding_datasets.md +51 -18
  9. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/why_sdf_xarray.md +1 -1
  10. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/pyproject.toml +8 -2
  11. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/_version.py +3 -3
  12. sdf_xarray-0.9.0/src/sdf_xarray/dataarray_accessor.py +226 -0
  13. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/plotting.py +247 -10
  14. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_epoch_dataarray_accessor.py +252 -29
  15. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/uv.lock +141 -0
  16. sdf_xarray-0.8.0/src/sdf_xarray/dataarray_accessor.py +0 -74
  17. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/lint.yml +0 -0
  18. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.gitignore +0 -0
  19. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.gitmodules +0 -0
  20. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.readthedocs.yaml +0 -0
  21. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CITATION.cff +0 -0
  22. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CMakeLists.txt +0 -0
  23. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CONTRIBUTING.md +0 -0
  24. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/LICENCE +0 -0
  25. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/README.md +0 -0
  26. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/.gitignore +0 -0
  27. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/BEAM.png +0 -0
  28. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/PlasmaFAIR.svg +0 -0
  29. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/force_render_dark_xarray_objects.css +0 -0
  30. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/sdf-xarray-logo-dark.svg +0 -0
  31. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/sdf-xarray-logo-light.svg +0 -0
  32. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_templates/custom-class-template.rst +0 -0
  33. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_templates/custom-module-template.rst +0 -0
  34. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/api.md +0 -0
  35. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/conf.py +0 -0
  36. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/contributing.md +0 -0
  37. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_1D_density.py +0 -0
  38. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_1D_poynting_flux.py +0 -0
  39. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies.py +0 -0
  40. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies_alternative.py +0 -0
  41. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_poynting_flux.py +0 -0
  42. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animations.md +0 -0
  43. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_ke.py +0 -0
  44. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_ke_probe.py +0 -0
  45. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_px.py +0 -0
  46. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms.md +0 -0
  47. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/live_demo.ipynb +0 -0
  48. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plots.md +0 -0
  49. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_1D_density.py +0 -0
  50. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_1D_poynting_flux.py +0 -0
  51. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_density.py +0 -0
  52. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_dist_fn_multispecies.py +0 -0
  53. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_poynting_flux.py +0 -0
  54. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_time_temperature.py +0 -0
  55. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_x_px_scatter.py +0 -0
  56. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/workshop_details.md +0 -0
  57. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/installation.md +0 -0
  58. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/known_issues.md +0 -0
  59. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/loading_data.md +0 -0
  60. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/make.bat +0 -0
  61. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/unit_conversion.md +0 -0
  62. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/__init__.py +0 -0
  63. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/csdf.pxd +0 -0
  64. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/dataset_accessor.py +0 -0
  65. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/download.py +0 -0
  66. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/sdf_interface.pyx +0 -0
  67. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_cython.py +0 -0
  68. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_dataset.py +0 -0
  69. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_datatree.py +0 -0
  70. {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_epoch_dataset_accessor.py +0 -0
@@ -20,7 +20,7 @@ jobs:
20
20
  submodules: "recursive"
21
21
 
22
22
  - name: Cache Zenodo datasets
23
- uses: actions/cache@v4
23
+ uses: actions/cache@v6
24
24
  with:
25
25
  enableCrossOsArchive: true
26
26
  path: ~/.cache/sdf_datasets
@@ -31,7 +31,7 @@ jobs:
31
31
  uv sync --python ${{ matrix.python-version }} --frozen
32
32
 
33
33
  - name: Cache Zenodo datasets
34
- uses: actions/cache@v4
34
+ uses: actions/cache@v6
35
35
  with:
36
36
  path: ~/.cache/sdf_datasets
37
37
  key: sdf-datasets-17991042
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sdf-xarray
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: Provides a backend for xarray to read SDF files as created by the EPOCH plasma PIC code.
5
5
  Author-Email: Peter Hill <peter.hill@york.ac.uk>, Joel Adams <joel.adams@york.ac.uk>, Shaun Doherty <shaun.doherty@york.ac.uk>, Chris Herdman <chris.herdman@york.ac.uk>, Liam Pattinson <liam.pattinson@york.ac.uk>, Sviatoslav Shekhanov <sviatoslav.shekhanov@york.ac.uk>
6
6
  License-Expression: BSD-3-Clause
@@ -35,9 +35,10 @@ resolved data has 2 dimensions.
35
35
 
36
36
  | Dimensions | Plotting function | Notes |
37
37
  | ---------- | ----------------------------- | --------------------- |
38
- | `2` | <inv:#xarray.plot.line> | |
39
- | `3` | <inv:#xarray.plot.pcolormesh> | |
40
- | `>3` | <inv:#xarray.plot.hist> | Not fully implemented |
38
+ | 2 | <inv:#xarray.plot.line> | |
39
+ | 3 | <inv:#xarray.plot.pcolormesh> | |
40
+ | 4 | <inv:#mpl_toolkits.mplot3d.axes3d.Axes3D.voxels>| WIP |
41
+ | >4 | <inv:#xarray.plot.hist> | Not fully implemented |
41
42
 
42
43
 
43
44
  ### 1D simulation
@@ -103,6 +104,10 @@ anim.show()
103
104
 
104
105
  ### 3D simulation
105
106
 
107
+ ```{warning}
108
+ [Voxel](./plotting.md#voxel-plots) animations are not currently implemented.
109
+ ```
110
+
106
111
  Opening a 3D simulation as a multi-file dataset and plotting it will
107
112
  return a <inv:#xarray.plot.hist>. However, this may not be
108
113
  desirable. We can plot a 3D simulation along a certain plane in the
@@ -21,6 +21,7 @@ why_sdf_xarray.md
21
21
  loading_data.md
22
22
  understanding_datasets.md
23
23
  unit_conversion.md
24
+ plotting.md
24
25
  animation.md
25
26
  ```
26
27
 
@@ -0,0 +1,95 @@
1
+ ---
2
+ file_format: mystnb
3
+ kernelspec:
4
+ name: python3
5
+ ---
6
+
7
+ # Plotting
8
+
9
+ You can plot datasets using
10
+ [`xarray.DataArray.epoch.plot`](project:#sdf_xarray.dataarray_accessor.EpochAccessor.plot).
11
+ This is a custom <project:#sdf_xarray> plotting routine that builds on top of
12
+ <inv:#xarray.DataArray.plot>, so you keep the familiar <inv:#xarray> plotting
13
+ behaviour while using <project:#sdf_xarray> conveniences (see
14
+ [here](project:#sdf_xarray.dataarray_accessor.EpochAccessor.plot) for details).
15
+ Under the hood, plotting is still handled by <inv:#matplotlib>, which means you
16
+ can use the full <inv:#matplotlib> API to customise your figure.
17
+
18
+ | Dimensions | Base plotting function | Notes |
19
+ | ---------- | ----------------------------- | --------------------- |
20
+ | 1 | <inv:#xarray.plot.line> | |
21
+ | 2 | <inv:#xarray.plot.pcolormesh> | |
22
+ | 3 | <inv:#mpl_toolkits.mplot3d.axes3d.Axes3D.voxels>| Custom functionality built for <project:#sdf_xarray> |
23
+ | >3 | <inv:#xarray.plot.hist> | Not fully implemented |
24
+
25
+ ## Line plots
26
+
27
+ One dimensional data will be plotted as a line. Multiple lines can be easily
28
+ plotted at the same time.
29
+
30
+ ```{code-cell} ipython3
31
+ import sdf_xarray as sdfxr
32
+ import matplotlib.pyplot as plt
33
+ ds = sdfxr.open_dataset("tutorial_dataset_1d/0010.sdf")
34
+ da_1 = ds["Derived_Number_Density_Electron"]
35
+ da_2 = ds["Derived_Number_Density_Ion"]
36
+ da_1.epoch.plot(label = "Electron")
37
+ da_2.epoch.plot(label = "Ion")
38
+ plt.legend()
39
+ plt.show()
40
+ ```
41
+
42
+ ## Mesh plots
43
+
44
+ Two dimensional data will be plotted as a mesh.
45
+
46
+ ```{code-cell} ipython3
47
+ ds = sdfxr.open_dataset("tutorial_dataset_2d/0010.sdf")
48
+ da = ds["Derived_Number_Density_Electron"]
49
+ da.epoch.plot()
50
+ plt.show()
51
+ ```
52
+
53
+ ## Voxel plots
54
+
55
+ Three dimensional data will be plotted using voxels. This behaviour is not native
56
+ to <inv:#xarray>, it has been custom built specifically for this package.
57
+
58
+ ```python
59
+ ds = sdfxr.open_dataset("tutorial_dataset_3d/0000.sdf")
60
+ da = ds["Derived_Number_Density"]
61
+ da.epoch.plot(vmin = 1e27)
62
+ plt.show()
63
+ ```
64
+
65
+ ![voxel_plot_high_res](./figures/voxel_plot_high_res.png)
66
+
67
+ ```{warning}
68
+ Voxel plots can be extremely computationally expensive and may take longer than
69
+ expected to plot.
70
+ ```
71
+
72
+ Because voxel plots are very expensive, even with relatively small data arrays,
73
+ plotting can be sped up by using [`xarray.DataArray.epoch.resize`](project:#sdf_xarray.dataarray_accessor.EpochAccessor.resize),
74
+ which uses interpolation to reduce the resolution.
75
+
76
+ ```{code-cell} ipython3
77
+ ds = sdfxr.open_dataset("tutorial_dataset_3d/0005.sdf")
78
+ da = ds["Derived_Number_Density"]
79
+ da_resized = da.epoch.resize((20, 20, 20))
80
+ da_resized.epoch.plot(vmin = 1e27)
81
+ plt.show()
82
+ ```
83
+
84
+ ## Histograms
85
+
86
+ When the data array has four or more dimensions (or is specified by the user), it will
87
+ be plotted as a histogram.
88
+
89
+ ```{code-cell} ipython3
90
+ ds = sdfxr.open_dataset("tutorial_dataset_3d/0005.sdf")
91
+ da = ds["Derived_Number_Density"]
92
+
93
+ da.epoch.plot(hist = True)
94
+ plt.show()
95
+ ```
@@ -29,23 +29,10 @@ ds = sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
29
29
  ds["Electric_Field_Ex"]
30
30
  ```
31
31
 
32
- ## Plotting
32
+ ## Visualisation
33
33
 
34
- You can plot datasets using
35
- [`xarray.DataArray.epoch.plot`](project:#sdf_xarray.dataarray_accessor.EpochAccessor.plot).
36
- This is a custom <project:#sdf_xarray> plotting routine that builds on top of
37
- <inv:#xarray.DataArray.plot>, so you keep the familiar <inv:#xarray> plotting
38
- behaviour while using <project:#sdf_xarray> conveniences (see
39
- [here](project:#sdf_xarray.dataarray_accessor.EpochAccessor.plot) for details).
40
- Under the hood, plotting is still handled by <inv:#matplotlib>, which means you
41
- can use the full <inv:#matplotlib> API to customise your figure.
42
-
43
- ```{code-cell} ipython3
44
- # This is discretized in both space and time
45
- ds["Electric_Field_Ex"].epoch.plot()
46
- plt.title("Electric field along the x-axis")
47
- plt.show()
48
- ```
34
+ `sdf-xarray` has built-in plotting and animation functionality, more details can
35
+ be found on [](./plotting.md) and [](./animation.md).
49
36
 
50
37
  ## Dimension slicing
51
38
 
@@ -56,6 +43,7 @@ for easy indexing. To quickly determine the number of time steps available,
56
43
  you can check the size of the time dimension.
57
44
 
58
45
  ```{code-cell} ipython3
46
+ ds = sdfxr.open_mfdataset("tutorial_dataset_1d/*.sdf")
59
47
  # This corresponds to the number of individual SDF files loaded
60
48
  print(f"There are a total of {ds['time'].size} time steps")
61
49
 
@@ -66,7 +54,7 @@ print(f"The time at the 20th simulation step is {sim_time:.2e} s")
66
54
 
67
55
  You can select and extract a single simulation snapshot using the integer
68
56
  index of the time step with the <inv:#xarray.Dataset.isel> function. This can be
69
- done by passsing the index to the `time` parameter (e.g., `time=0` for
57
+ done by passing the index to the `time` parameter (e.g., `time=0` for
70
58
  the first snapshot).
71
59
 
72
60
  ```{code-cell} ipython3
@@ -74,7 +62,7 @@ ds["Electric_Field_Ex"].isel(time=20)
74
62
  ```
75
63
 
76
64
  We can also use the <inv:#xarray.Dataset.sel> function if you wish to pass a
77
- value intead of an index.
65
+ value instead of an index.
78
66
 
79
67
  ```{tip}
80
68
  If you know roughly what time you wish to select but not the exact value
@@ -122,6 +110,51 @@ print(f"Total particle energy absorbed: {ds["Total_Particle_Energy_in_Simulation
122
110
  print(f"The laser absorption fraction: {ds["Laser_Absorption_Fraction_in_Simulation"][-1].values:.1f} %")
123
111
  ```
124
112
 
113
+ ## Limit
114
+
115
+ The `.epoch` accessor has the ability to "limit" data arrays. This is a helper
116
+ function which applies <inv:#xarray.DataArray.where> to the upper and lower bounds
117
+ of each dimension. You can use `None` to preserve the existing limit.
118
+
119
+ ```{code-cell} ipython3
120
+ ds = sdfxr.open_dataset("tutorial_dataset_2d/0020.sdf")
121
+ da = ds["Derived_Number_Density_Electron"]
122
+ da.epoch.plot()
123
+ print(f"Orignal shape: {da.shape}")
124
+ plt.show()
125
+ ```
126
+
127
+ ```{code-cell} ipython3
128
+ da_limit = da.epoch.limit(((-2e-6, 2e-6), (None, 0)))
129
+ da_limit.epoch.plot()
130
+ print(f"New shape: {da_limit.shape}")
131
+ plt.show()
132
+ ```
133
+
134
+ ## Resize
135
+
136
+ The data arrays can be interpolated with <project:#sdf_xarray.dataarray_accessor.EpochAccessor.resize>
137
+ to either increase or decrease the resolution while mantaining the general structure.
138
+ Decreasing the size may be useful to download large data from HPCs to local machines
139
+ or ploting with [](./plotting.md#voxel-plots). Be aware that the original
140
+ data will not be preserved.
141
+
142
+ ```{code-cell} ipython3
143
+ ds = sdfxr.open_dataset("tutorial_dataset_2d/0020.sdf")
144
+ da = ds["Derived_Number_Density_Electron"]
145
+
146
+ da.epoch.plot()
147
+ print(f"Orignal shape: {da.shape}")
148
+ plt.show()
149
+ ```
150
+
151
+ ```{code-cell} ipython3
152
+ da_resized = da.epoch.resize((50, 50))
153
+ da_resized.epoch.plot()
154
+ print(f"New shape: {da_resized.shape}")
155
+ plt.show()
156
+ ```
157
+
125
158
  ## Visualisation on HPC Machines
126
159
 
127
160
  In many cases you will be running EPOCH simulations via a HPC cluster and your
@@ -7,7 +7,7 @@ There are several benefits to using this package over the [`sdf_helper`](https:/
7
7
  - [data to numpy and pandas](https://docs.xarray.dev/en/stable/user-guide/pandas.html) - Data can be easily converted to [numpy](https://www.numpy.org/) or [pandas](https://pandas.pydata.org/) if you prefer to work with raw data.
8
8
  - [data labelling](project:understanding_datasets.md) - All SDF files come with a bunch of information about them such as the `dimensions`, `units` and `name` of the variable. In <inv:#xarray> these are added to each variable making it much easier to read.
9
9
  - [lazy/partial loading](https://docs.xarray.dev/en/stable/internals/internal-design.html#lazy-loading) - This reduces the RAM requirements when loading large SDF files as we only load the actual array values when they are needed.
10
- - [plotting](project:understanding_datasets.md#plotting) - You can easily plot variables across several dimensions including time using built in <inv:#matplotlib> support.
10
+ - [plotting](project:plotting.md) - You can easily plot variables across several dimensions including time using built in <inv:#matplotlib> support.
11
11
  - [animating](project:animation.md) - Visualise your data across time or slice through another dimension and make beautiful GIFs for your presentations.
12
12
  - [deck loading](loading-input-deck) - <project:#sdf_xarray> automatically loads in the associated `input.deck` utilised when creating the simulation so that you can access setup variables.
13
13
  - [easier unit conversion](project:unit_conversion.md) - Convert dimensions and variable units to other types with the help of [`pint`](https://pint.readthedocs.io/en/stable).
@@ -69,6 +69,7 @@ docs = [
69
69
  "sphinx-design>=0.7.0",
70
70
  "pooch>=1.8.2",
71
71
  "tqdm",
72
+ "scipy",
72
73
  ]
73
74
  build = ["cibuildwheel[uv]"]
74
75
  lint = ["ruff"]
@@ -80,6 +81,7 @@ test = [
80
81
  "tqdm",
81
82
  "pint",
82
83
  "pint-xarray",
84
+ "scipy",
83
85
  ]
84
86
 
85
87
  [project.entry-points."xarray.backends"]
@@ -133,6 +135,10 @@ ignore = [
133
135
  "PLR2004", # magic-comparison
134
136
  "B9", # flake8-bugbear opinionated warnings
135
137
  "PLR0913", # remove maximum number of arguments in a function
138
+ "PLR0917", # remove maximum number of positional arguments in a function
139
+ "BLE001", # allow capturing of base exception
136
140
  ]
137
- exclude = ["**/_version.py"]
138
- # Auto-generated file
141
+ exclude = ["**/_version.py"] # Auto-generated file
142
+
143
+ [tool.pytest.ini_options]
144
+ filterwarnings = ['ignore::UserWarning']
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '0.8.0'
22
- __version_tuple__ = version_tuple = (0, 8, 0)
21
+ __version__ = version = '0.9.0'
22
+ __version_tuple__ = version_tuple = (0, 9, 0)
23
23
 
24
- __commit_id__ = commit_id = 'gbd2a24a3a'
24
+ __commit_id__ = commit_id = 'g65bb865da'
@@ -0,0 +1,226 @@
1
+ from __future__ import annotations
2
+
3
+ from types import MethodType
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+ import xarray as xr
8
+ from xarray.plot.accessor import DataArrayPlotAccessor
9
+
10
+ from .plotting import _recover_vertex_coord, animate, show, voxel_plot
11
+
12
+ if TYPE_CHECKING:
13
+ from matplotlib.animation import FuncAnimation
14
+
15
+
16
+ def _resize_ndarray(
17
+ arr: np.ndarray,
18
+ new_shape: tuple | list | np.ndarray,
19
+ ) -> np.ndarray:
20
+ """
21
+ Resizes a `numpy.ndarray` to another shape. The returned array must have the
22
+ same dimensionality as the input array.
23
+ Parameters
24
+ ----------
25
+ arr
26
+ The input array.
27
+ new_shape
28
+ The shape of the new `xarray.DataArray`, must be the same length as arr.shape.
29
+ """
30
+
31
+ from scipy.interpolate import RegularGridInterpolator # noqa: PLC0415
32
+
33
+ if arr.ndim != len(new_shape):
34
+ raise ValueError(
35
+ f"The number of dimensions must match the input array. (original: {arr.ndim}, new: {len(new_shape)})"
36
+ )
37
+
38
+ old_grids = tuple(np.linspace(0, 1, size) for size in arr.shape)
39
+ new_grids = tuple(np.linspace(0, 1, size) for size in new_shape)
40
+ mesh = np.meshgrid(*new_grids, indexing="ij")
41
+ coords = np.stack(mesh, axis=-1)
42
+
43
+ return RegularGridInterpolator(old_grids, arr, bounds_error=False, fill_value=0)(
44
+ coords
45
+ )
46
+
47
+
48
+ @xr.register_dataarray_accessor("epoch")
49
+ class EpochAccessor:
50
+ def __init__(self, xarray_obj: xr.DataArray):
51
+ self._obj = xarray_obj
52
+
53
+ def plot(self, hist=False, *args, **kwargs) -> DataArrayPlotAccessor:
54
+ """
55
+ Builds upon `xarray.DataArray.plot` while changing some of its default behaviours.
56
+
57
+ These changes are:
58
+
59
+ - Flips the default axes order for 2D plots so that x and y are on the correct axes.
60
+ This exists because plotting of 2D data in xarray uses the `xarray.plot.pcolormesh`
61
+ function which takes assumes that ``x = dim[1]`` and ``y = dim[0]``.
62
+
63
+ Parameters
64
+ ----------
65
+ hist
66
+ If ``True``, will plot a histogram regardless of dimensionality (default = ``False``).
67
+ args
68
+ Positional arguments passed to `xarray.DataArray.plot`.
69
+ kwargs
70
+ Keyword arguments passed to `xarray.DataArray.plot`.
71
+ """
72
+ dims = self._obj.dims
73
+
74
+ if hist:
75
+ return self._obj.plot.hist(*args, **kwargs)
76
+
77
+ if len(dims) == 1:
78
+ return self._obj.plot(*args, **kwargs)
79
+
80
+ if len(dims) == 2:
81
+ updated_kwargs = dict(kwargs)
82
+ updated_kwargs.setdefault("x", dims[0])
83
+ updated_kwargs.setdefault("y", dims[1])
84
+ return self._obj.plot(*args, **updated_kwargs)
85
+
86
+ if len(dims) == 3:
87
+ return voxel_plot(self._obj, *args, **kwargs)
88
+
89
+ return self._obj.plot(*args, **kwargs)
90
+
91
+ def animate(self, *args, **kwargs) -> FuncAnimation:
92
+ """Generate animations of Epoch data.
93
+
94
+ Parameters
95
+ ----------
96
+ args
97
+ Positional arguments passed to :func:`animation`.
98
+ kwargs
99
+ Keyword arguments passed to :func:`animation`.
100
+
101
+ Examples
102
+ --------
103
+ >>> anim = ds["Electric_Field_Ey"].epoch.animate()
104
+ >>> anim.save("animation.gif")
105
+ >>> # Or in a jupyter notebook:
106
+ >>> anim.show()
107
+ """
108
+
109
+ # Add anim.show() functionality
110
+ # anim.show() will display the animation in a jupyter notebook
111
+ anim = animate(self._obj, *args, **kwargs)
112
+ anim.show = MethodType(show, anim)
113
+
114
+ return anim
115
+
116
+ def resize(
117
+ self,
118
+ new_shape,
119
+ ) -> xr.DataArray:
120
+ """
121
+ Resizes a `xarray.DataArray` to another shape. The returned array must have the
122
+ same dimensionality as the input array.
123
+
124
+ Parameters
125
+ ----------
126
+ new_shape
127
+ The shape of the new `xarray.DataArray`, must be the same length as self.shape.
128
+ """
129
+
130
+ da = self._obj
131
+
132
+ # Create resized data and Dataset
133
+ data_resized = _resize_ndarray(da.values, new_shape)
134
+ da_resized = da.copy()
135
+ da_resized = xr.DataArray(
136
+ data=data_resized,
137
+ dims=da.dims,
138
+ attrs=da.attrs,
139
+ )
140
+
141
+ original_cell_size_da = []
142
+
143
+ # Resize coordinates and add to DataArray
144
+ for i in range(len(da_resized.dims)):
145
+ coord = list(da_resized.dims)[i]
146
+
147
+ if coord.endswith("_mid"):
148
+ # If the coordinate is a midpoint coordinate, care must be taken to resize correctly
149
+ vertex_coord = _recover_vertex_coord(da[coord])
150
+ vertex_coord_resized = np.linspace(
151
+ vertex_coord[0], vertex_coord[-1], new_shape[i] + 1
152
+ )
153
+ # Turn the vertex coord back into a midpoint coord
154
+ da_resized[coord] = (
155
+ (np.roll(vertex_coord_resized, 1) + vertex_coord_resized) / 2
156
+ )[1:]
157
+ else:
158
+ # If not, the coordinate can be simply resized
159
+ coord_min = da[coord][0].values
160
+ coord_max = da[coord][-1].values
161
+ da_resized[coord] = np.linspace(coord_min, coord_max, new_shape[i])
162
+
163
+ da_resized[coord].attrs = da[coord].attrs
164
+
165
+ # Add original information as attributes
166
+ if "original_shape" not in da.attrs:
167
+ da_resized[coord].attrs["original_size"] = da.shape[i]
168
+ original_cell_size = float((da[coord][1] - da[coord][0]).values)
169
+ original_cell_size_da.append(original_cell_size)
170
+ da_resized[coord].attrs["original_cell_size"] = original_cell_size
171
+
172
+ if "original_shape" not in da.attrs:
173
+ da_resized.attrs["original_shape"] = da.shape
174
+ da_resized.attrs["original_cell_size"] = tuple(original_cell_size_da)
175
+
176
+ return da_resized
177
+
178
+ def limit(
179
+ self,
180
+ limits,
181
+ drop: bool = True,
182
+ ) -> xr.DataArray:
183
+ """
184
+ Drops values outside the specified limits.
185
+
186
+ Parameters
187
+ ----------
188
+ limits
189
+ Array-like list of limits.
190
+ drop
191
+ Bool specifying whether to drop the co-ordinates outside the limits (default = True).
192
+ """
193
+ da = self._obj
194
+ limits = list(limits)
195
+
196
+ # List of dimension names
197
+ dims = da.dims
198
+
199
+ original_lims_da = []
200
+ for i in range(len(dims)):
201
+ limits[i] = list(limits[i])
202
+
203
+ # Find the original limits of the dataarray
204
+ original_lims = (
205
+ float(da[dims[i]].values[0]),
206
+ float(da[dims[i]].values[-1]),
207
+ )
208
+ original_lims_da.append(original_lims)
209
+ if "original_lims" not in da[dims[i]].attrs:
210
+ da[dims[i]].attrs["original_lims"] = original_lims
211
+
212
+ # If None is passed into limit, will assume the existing limit
213
+ if limits[i][0] is None:
214
+ limits[i][0] = original_lims[0]
215
+ if limits[i][-1] is None:
216
+ limits[i][-1] = original_lims[1]
217
+
218
+ # Limit dataarray
219
+ da = da.where(da[dims[i]] >= limits[i][0], drop=drop).where(
220
+ da[dims[i]] <= limits[i][1], drop=drop
221
+ )
222
+
223
+ if "original_lims" not in da.attrs:
224
+ da.attrs["original_lims"] = tuple(original_lims_da)
225
+
226
+ return da