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.
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/build_publish.yml +1 -1
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/tests.yml +1 -1
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/PKG-INFO +1 -1
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/animation.md +8 -3
- sdf_xarray-0.9.0/docs/figures/voxel_plot_high_res.png +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/index.md +1 -0
- sdf_xarray-0.9.0/docs/plotting.md +95 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/understanding_datasets.md +51 -18
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/why_sdf_xarray.md +1 -1
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/pyproject.toml +8 -2
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/_version.py +3 -3
- sdf_xarray-0.9.0/src/sdf_xarray/dataarray_accessor.py +226 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/plotting.py +247 -10
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_epoch_dataarray_accessor.py +252 -29
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/uv.lock +141 -0
- sdf_xarray-0.8.0/src/sdf_xarray/dataarray_accessor.py +0 -74
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.github/workflows/lint.yml +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.gitignore +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.gitmodules +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/.readthedocs.yaml +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CITATION.cff +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CMakeLists.txt +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/CONTRIBUTING.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/LICENCE +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/README.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/.gitignore +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/BEAM.png +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/PlasmaFAIR.svg +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/force_render_dark_xarray_objects.css +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/sdf-xarray-logo-dark.svg +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_static/sdf-xarray-logo-light.svg +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_templates/custom-class-template.rst +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/_templates/custom-module-template.rst +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/api.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/conf.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/contributing.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_1D_density.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_1D_poynting_flux.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_dist_fn_multispecies_alternative.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animating/animate_2D_poynting_flux.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/animations.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_ke.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_ke_probe.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms/plot_hist_px.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/histograms.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/live_demo.ipynb +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plots.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_1D_density.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_1D_poynting_flux.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_density.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_dist_fn_multispecies.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_2D_poynting_flux.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_time_temperature.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/plotting/plot_x_px_scatter.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/epoch_workshop_2026/workshop_details.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/installation.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/known_issues.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/loading_data.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/make.bat +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/docs/unit_conversion.md +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/__init__.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/csdf.pxd +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/dataset_accessor.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/download.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/src/sdf_xarray/sdf_interface.pyx +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_cython.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_dataset.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_datatree.py +0 -0
- {sdf_xarray-0.8.0 → sdf_xarray-0.9.0}/tests/test_epoch_dataset_accessor.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sdf-xarray
|
|
3
|
-
Version: 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
|
-
|
|
|
39
|
-
|
|
|
40
|
-
|
|
|
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
|
|
Binary file
|
|
@@ -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
|
+

|
|
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
|
-
##
|
|
32
|
+
## Visualisation
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
[
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.9.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 9, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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
|