Skip to content

Commit 1039574

Browse files
nabobalisCadair
andauthored
Added unit tests that mimics a dynamic spectra (#931)
Co-authored-by: Stuart Mumford <stuart@cadair.com>
1 parent 0a837d1 commit 1039574

13 files changed

Lines changed: 284 additions & 43 deletions

.circleci/codecov_upload.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/bin/bash -e
2-
curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import
2+
curl https://keybase.io/codecovsecops/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import
33
curl -Os https://uploader.codecov.io/latest/linux/codecov
44
curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM
55
curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig

.circleci/config.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
jobname:
3333
type: string
3434
docker:
35-
- image: cimg/python:3.12
35+
- image: cimg/python:3.14
3636
environment:
3737
TOXENV=<< parameters.jobname >>
3838
steps:
@@ -57,7 +57,7 @@ jobs:
5757
jobname:
5858
type: string
5959
docker:
60-
- image: cimg/python:3.12
60+
- image: cimg/python:3.14
6161
environment:
6262
TOXENV: << parameters.jobname >>
6363
GIT_SSH_COMMAND: ssh -i ~/.ssh/id_rsa_b1c8b094a8ec67162b0f18a949a6b1db
@@ -99,16 +99,16 @@ workflows:
9999
matrix:
100100
parameters:
101101
jobname:
102-
- "py312-figure"
103-
- "py312-figure-devdeps"
102+
- "py314-figure"
103+
- "py314-figure-devdeps"
104104

105105
- deploy-reference-images:
106106
name: baseline-<< matrix.jobname >>
107107
matrix:
108108
parameters:
109109
jobname:
110-
- "py312-figure"
111-
- "py312-figure-devdeps"
110+
- "py314-figure"
111+
- "py314-figure-devdeps"
112112
requires:
113113
- << matrix.jobname >>
114114
filters:

changelog/931.bugfix.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix compatibility with gwcs >=1.0.
2+
3+
This required bumping minimum supported gwcs to 0.24, and concequenty asdf to >=3.3.0 and scipy to >=1.14.1.

ndcube/conftest.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,57 @@ def gwcs_2d_lt_ln():
224224

225225
return (wcs.WCS(forward_transform=cel_model, output_frame=sky_frame, input_frame=input_frame))
226226

227+
228+
@pytest.fixture
229+
def gwcs_2d_t_f_linear():
230+
"""
231+
2D gWCS for a dynamic spectrum: uniform time (array axis 1 / X) and linear
232+
frequency (array axis 0 / Y).
233+
"""
234+
time_model = models.Scale(14.0)
235+
freq_model = models.Scale(1e6)
236+
237+
time_frame = cf.TemporalFrame(axes_order=(0,), unit=u.s,
238+
reference_frame=Time("2024-03-23T00:03:23"))
239+
freq_frame = cf.SpectralFrame(axes_order=(1,), unit=u.Hz, axes_names=('frequency',))
240+
241+
transform = time_model & freq_model
242+
frame = cf.CompositeFrame([time_frame, freq_frame])
243+
detector_frame = cf.CoordinateFrame(name="detector", naxes=2,
244+
axes_order=(0, 1),
245+
axes_type=("pixel", "pixel"),
246+
unit=(u.pix, u.pix))
247+
return wcs.WCS(forward_transform=transform, output_frame=frame,
248+
input_frame=detector_frame)
249+
250+
251+
@pytest.fixture
252+
def gwcs_2d_t_f_log():
253+
"""
254+
2D gWCS for a dynamic spectrum: irregularly-spaced time (array axis 1 / X)
255+
and log-spaced frequency (array axis 0 / Y) via Tabular1D lookup tables.
256+
"""
257+
times_s = np.array([0.0, 14.0, 27.4, 41.1, 55.2, 67.8, 82.3, 95.9, 109.1, 122.5])
258+
freqs_hz = np.logspace(np.log10(3.992e6), np.log10(978.572e6), 16)
259+
260+
time_model = models.Tabular1D(points=np.arange(10), lookup_table=times_s,
261+
method='linear', bounds_error=False)
262+
freq_model = models.Tabular1D(points=np.arange(16), lookup_table=freqs_hz,
263+
method='linear', bounds_error=False)
264+
265+
time_frame = cf.TemporalFrame(axes_order=(0,), unit=u.s,
266+
reference_frame=Time("2024-03-23T00:03:23"))
267+
freq_frame = cf.SpectralFrame(axes_order=(1,), unit=u.Hz, axes_names=('frequency',))
268+
269+
transform = time_model & freq_model
270+
frame = cf.CompositeFrame([time_frame, freq_frame])
271+
detector_frame = cf.CoordinateFrame(name="detector", naxes=2,
272+
axes_order=(0, 1),
273+
axes_type=("pixel", "pixel"),
274+
unit=(u.pix, u.pix))
275+
return wcs.WCS(forward_transform=transform, output_frame=frame,
276+
input_frame=detector_frame)
277+
227278
@pytest.fixture
228279
def wcs_4d_t_l_lt_ln():
229280
header = {
@@ -564,6 +615,20 @@ def extra_coords_sharing_axis():
564615
# NOTE: If you add more fixtures please add to the all_ndcubes fixture
565616
################################################################################
566617

618+
@pytest.fixture
619+
def ndcube_gwcs_2d_t_f_linear(gwcs_2d_t_f_linear):
620+
shape = (16, 10) # (n_freq, n_time): freq on Y axis, time on X axis
621+
gwcs_2d_t_f_linear.array_shape = shape
622+
return NDCube(data_nd(shape), wcs=gwcs_2d_t_f_linear)
623+
624+
625+
@pytest.fixture
626+
def ndcube_gwcs_2d_t_f_log(gwcs_2d_t_f_log):
627+
shape = (16, 10) # (n_freq, n_time): freq on Y axis, time on X axis
628+
gwcs_2d_t_f_log.array_shape = shape
629+
return NDCube(data_nd(shape), wcs=gwcs_2d_t_f_log)
630+
631+
567632
@pytest.fixture
568633
def ndcube_gwcs_4d_ln_lt_l_t(gwcs_4d_t_l_lt_ln):
569634
shape = (5, 8, 10, 12)
@@ -1074,6 +1139,8 @@ def ndcube_1d_l(wcs_1d_l):
10741139

10751140

10761141
@pytest.fixture(params=[
1142+
"ndcube_gwcs_2d_t_f_linear",
1143+
"ndcube_gwcs_2d_t_f_log",
10771144
"ndcube_gwcs_4d_ln_lt_l_t",
10781145
"ndcube_gwcs_4d_ln_lt_l_t_unit",
10791146
"ndcube_gwcs_3d_ln_lt_l",
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
"""
2+
Tests to simulate dynamic spectrum WCSes (frequency x time).
3+
"""
4+
import pytest
5+
from numpy.testing import assert_allclose
6+
7+
import astropy.units as u
8+
9+
from ndcube.wcs.wrappers import ResampledLowLevelWCS
10+
11+
12+
def _world_at(cube, time_pixel, freq_pixel):
13+
return cube.wcs.low_level_wcs.pixel_to_world_values(time_pixel, freq_pixel)
14+
15+
16+
@pytest.mark.parametrize("ndc", [
17+
"ndcube_gwcs_2d_t_f_linear",
18+
"ndcube_gwcs_2d_t_f_log",
19+
], indirect=True)
20+
def test_dynspec_array_axis_physical_types(ndc):
21+
types = ndc.array_axis_physical_types
22+
assert "em.freq" in types[0]
23+
assert "time" in types[1]
24+
25+
26+
def test_linear_dynspec_pixel_to_world(ndcube_gwcs_2d_t_f_linear):
27+
time, freq = ndcube_gwcs_2d_t_f_linear.wcs.low_level_wcs.pixel_to_world_values(3, 2)
28+
assert_allclose(time, 42.0)
29+
assert_allclose(freq, 2e6)
30+
31+
32+
def test_linear_dynspec_world_to_pixel(ndcube_gwcs_2d_t_f_linear):
33+
pix_t, pix_f = ndcube_gwcs_2d_t_f_linear.wcs.low_level_wcs.world_to_pixel_values(28.0, 4e6)
34+
assert_allclose(pix_t, 2.0)
35+
assert_allclose(pix_f, 4.0)
36+
37+
38+
@pytest.mark.parametrize(("bin_shape", "expected_shape", "expected_time", "expected_freq"), [
39+
((2, 1), (8, 10), 0.0, 0.5e6),
40+
((1, 2), (16, 5), 7.0, 0.0),
41+
])
42+
def test_linear_dynspec_rebin_wcs(ndcube_gwcs_2d_t_f_linear, bin_shape,
43+
expected_shape, expected_time, expected_freq):
44+
rebinned = ndcube_gwcs_2d_t_f_linear.rebin(bin_shape)
45+
time0, freq0 = rebinned.wcs.low_level_wcs.pixel_to_world_values(0, 0)
46+
47+
assert rebinned.shape == expected_shape
48+
assert isinstance(rebinned.wcs.low_level_wcs, ResampledLowLevelWCS)
49+
assert_allclose(time0, expected_time)
50+
assert_allclose(freq0, expected_freq)
51+
52+
53+
@pytest.mark.parametrize(("lower_corner", "upper_corner", "expected_shape"), [
54+
([None, 3e6 * u.Hz], [None, 7e6 * u.Hz], (5, 10)),
55+
([14 * u.s, None], [56 * u.s, None], (16, 4)),
56+
])
57+
def test_linear_dynspec_crop_by_values_shape(ndcube_gwcs_2d_t_f_linear,
58+
lower_corner, upper_corner,
59+
expected_shape):
60+
cropped = ndcube_gwcs_2d_t_f_linear.crop_by_values(lower_corner, upper_corner)
61+
assert cropped.shape == expected_shape
62+
63+
64+
def test_log_dynspec_world_axis_units(ndcube_gwcs_2d_t_f_log):
65+
assert ndcube_gwcs_2d_t_f_log.wcs.world_axis_units == ("s", "Hz")
66+
67+
68+
@pytest.mark.parametrize(("time_pixel", "freq_pixel", "expected_time", "expected_freq"), [
69+
(0, 0, 0.0, 3.992e6),
70+
(9, 15, 122.5, 978.572e6),
71+
])
72+
def test_log_dynspec_pixel_to_world_endpoints(ndcube_gwcs_2d_t_f_log,
73+
time_pixel, freq_pixel,
74+
expected_time, expected_freq):
75+
time, freq = ndcube_gwcs_2d_t_f_log.wcs.low_level_wcs.pixel_to_world_values(
76+
time_pixel, freq_pixel)
77+
assert_allclose(time, expected_time)
78+
assert_allclose(freq, expected_freq, rtol=1e-6)
79+
80+
81+
def test_log_dynspec_world_to_pixel_roundtrip(ndcube_gwcs_2d_t_f_log):
82+
time, freq = _world_at(ndcube_gwcs_2d_t_f_log, 3, 7)
83+
pix_t, pix_f = ndcube_gwcs_2d_t_f_log.wcs.low_level_wcs.world_to_pixel_values(
84+
time, freq)
85+
assert_allclose(pix_t, 3.0, atol=1e-10)
86+
assert_allclose(pix_f, 7.0, atol=1e-10)
87+
88+
89+
@pytest.mark.parametrize(("bin_shape", "expected_shape", "axis"), [
90+
((2, 1), (8, 10), "freq"),
91+
((1, 2), (16, 5), "time"),
92+
])
93+
def test_log_dynspec_rebin_wcs_midpoint(ndcube_gwcs_2d_t_f_log, bin_shape,
94+
expected_shape, axis):
95+
rebinned = ndcube_gwcs_2d_t_f_log.rebin(bin_shape)
96+
time0, freq0 = rebinned.wcs.low_level_wcs.pixel_to_world_values(0, 0)
97+
98+
assert rebinned.shape == expected_shape
99+
assert isinstance(rebinned.wcs.low_level_wcs, ResampledLowLevelWCS)
100+
if axis == "freq":
101+
_, freq_left = _world_at(ndcube_gwcs_2d_t_f_log, 0, 0)
102+
_, freq_right = _world_at(ndcube_gwcs_2d_t_f_log, 0, 1)
103+
assert_allclose(freq0, (freq_left + freq_right) / 2, rtol=1e-6)
104+
else:
105+
time_left, _ = _world_at(ndcube_gwcs_2d_t_f_log, 0, 0)
106+
time_right, _ = _world_at(ndcube_gwcs_2d_t_f_log, 1, 0)
107+
assert_allclose(time0, (time_left + time_right) / 2, rtol=1e-6)
108+
109+
110+
@pytest.mark.parametrize(("lower_corner", "upper_corner", "expected_shape",
111+
"axis", "bounds"), [
112+
([None, 10e6 * u.Hz], [None, 100e6 * u.Hz], (8, 10), "freq", (10e6, 100e6)),
113+
([20 * u.s, None], [80 * u.s, None], (16, 6), "time", (20.0, 80.0)),
114+
])
115+
def test_log_dynspec_crop_by_values_single_axis(ndcube_gwcs_2d_t_f_log,
116+
lower_corner, upper_corner,
117+
expected_shape, axis, bounds):
118+
cropped = ndcube_gwcs_2d_t_f_log.crop_by_values(lower_corner, upper_corner)
119+
assert cropped.shape == expected_shape
120+
121+
if axis == "freq":
122+
values = [cropped.wcs.low_level_wcs.pixel_to_world_values(0, i)[1]
123+
for i in range(cropped.shape[0])]
124+
else:
125+
values = [cropped.wcs.low_level_wcs.pixel_to_world_values(i, 0)[0]
126+
for i in range(cropped.shape[1])]
127+
128+
assert values[0] <= bounds[0]
129+
assert values[-1] >= bounds[1]
130+
131+
132+
def test_log_dynspec_crop_by_freq_and_time(ndcube_gwcs_2d_t_f_log):
133+
cropped = ndcube_gwcs_2d_t_f_log.crop_by_values(
134+
[20 * u.s, 10e6 * u.Hz], [80 * u.s, 100e6 * u.Hz])
135+
assert cropped.shape == (8, 6)

ndcube/tests/test_ndcube_slice_and_crop.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,3 +615,25 @@ def test_crop_all_points_beyond_cube_extent_error(points):
615615

616616
with pytest.raises(ValueError, match="are outside the range of the NDCube being cropped"):
617617
cube.crop(*points, keepdims=True)
618+
619+
620+
def test_crop_by_values_quantity_table_coordinate():
621+
# Regression: QuantityTableCoordinate-based WCS raised
622+
# "High Level objects are not supported with the native API" because
623+
# world_to_pixel_values received Quantity objects instead of plain values.
624+
freqs_hz = np.logspace(np.log10(4e6), np.log10(200e6), 16)
625+
times_s = np.linspace(0, 140, 10)
626+
wcs2d = astropy.wcs.WCS(naxis=2)
627+
wcs2d.wcs.ctype = ["PIXEL", "PIXEL"]
628+
wcs2d.wcs.crpix = [1, 1]
629+
wcs2d.wcs.cdelt = [1, 1]
630+
wcs2d.wcs.crval = [0, 0]
631+
data = np.arange(16 * 10).reshape(16, 10)
632+
cube = NDCube(data, wcs=wcs2d)
633+
cube.extra_coords.add("frequency", (0,), freqs_hz * u.Hz)
634+
cube.extra_coords.add("time", (1,), times_s * u.s)
635+
636+
cropped = cube.crop_by_values([10e6 * u.Hz, 20 * u.s], [100e6 * u.Hz, 80 * u.s],
637+
wcs=cube.extra_coords)
638+
assert cropped.shape == (10, 5)
639+
np.testing.assert_array_equal(cropped.data, data[3:13, 1:6])

ndcube/utils/cube.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,20 @@ def get_crop_item_from_points(points, wcs, crop_by_values, keepdims, original_sh
181181
# Derive the pixel indices of the input point and place each index
182182
# in the list corresponding to its axis.
183183
# Use the to_pixel methods to preserve fractional indices for future rounding.
184-
point_pixel_indices = (sliced_wcs.world_to_pixel_values(*sliced_point) if crop_by_values
185-
else HighLevelWCSWrapper(sliced_wcs).world_to_pixel(*sliced_point))
184+
if crop_by_values:
185+
# world_to_pixel_values is APE14 low-level API and expects plain
186+
# floats, not Quantity objects. So we need to strip units here; the values are
187+
# already in the correct units because _get_crop_by_values_item called
188+
# .to(wcs.world_axis_units[j]) before reaching this point.
189+
#
190+
# Passing Quantity objects raises TypeError in gWCS when the WCS's
191+
# declared high-level type is itself Quantity (e.g., a WCS built from
192+
# QuantityTableCoordinate), because gWCS cannot distinguish such inputs
193+
# from an accidental high-level API call.
194+
stripped_point = [p.value if hasattr(p, "value") else p for p in sliced_point]
195+
point_pixel_indices = sliced_wcs.world_to_pixel_values(*stripped_point)
196+
else:
197+
point_pixel_indices = HighLevelWCSWrapper(sliced_wcs).world_to_pixel(*sliced_point)
186198
# For each pixel axis associated with this point, place the pixel coords for
187199
# that pixel axis into the corresponding list within combined_points_pixel_idx.
188200
if sliced_wcs.pixel_n_dim == 1:

ndcube/visualization/tests/figure_hashes_mpl_3100_ft_261_astropy_710_animators_124.json renamed to ndcube/visualization/tests/figure_hashes_mpl_3109_ft_261_astropy_720_animators_124.json

File renamed without changes.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube": "61b743e90986d7c624d2cbd78ca78cfdd6c46e0f7726aed10af178f9f8d7d1e3",
3+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "ea9da0f97ceb0538ff7acd017aa63a475a2648b575c0005cd9a38693791f0092",
4+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "e5922ae535968af5833b5d10ebe2ed5870e01cbeccece83212bfccf6999700c7",
5+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "c525c9d2e51d348dca04383444710261d9b46b71b36b6af770a0cf12972aae61",
6+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[ln_lt_l_t-cslice3-kwargs3]": "d318ec88e5515e61c8e53525734b1e424f58559bc2ccc7af154e3ce3f5405387",
7+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[uncertainty-cslice4-kwargs4]": "855cd9c8ec77dddd092ca6d4506f9bc5bf309869e502a0256b542b2cbbccc8dd",
8+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[unit_uncertainty-cslice5-kwargs5]": "e44c87f05655e7c11aa5e1947d1506033acb4cde165616118055a063ebc5c9df",
9+
"ndcube.visualization.tests.test_plotting.test_plot_1D_cube_from_slice[mask-cslice6-kwargs6]": "ec0b8fdb355f47945d893858abd61a1fd097e7cea210148a7699a05fd3f14e2d",
10+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube": "8dacf131e153015efc66fccf4f475d17fc9b87d2be9ca6d2f9cd01a24a2a5a18",
11+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_colorbar": "378f0b34d69ba8ad4caa76df93b9fb57d1f0a9c540b6c5afe238737970e0e854",
12+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis": "8dacf131e153015efc66fccf4f475d17fc9b87d2be9ca6d2f9cd01a24a2a5a18",
13+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_custom_axis_plot_axes": "f0913c14ae37080b56bc16a1ef3129b69922932c31849102c49ae4ab91b9aaca",
14+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "43534b4bf0a1d901ed410188f26e29a8067ec6c0adb621e6623c3a68b8fa3afb",
15+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "24f65c026fd64b24f416db69c3fa4a7607191f3f5596268c00af5794d7123aa2",
16+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[ln_lt_l_t-cslice2-kwargs2]": "520ba0ba241cbad9b042ace302c1c25c0b5b1c6d4652ee8f76dba63fcc691929",
17+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[unit_uncertainty-cslice3-kwargs3]": "43534b4bf0a1d901ed410188f26e29a8067ec6c0adb621e6623c3a68b8fa3afb",
18+
"ndcube.visualization.tests.test_plotting.test_plot_2D_cube_from_slice[mask-cslice4-kwargs4]": "1b9495133636c27923ac7701dcd77fec51239a6509c0e198274e40529fbca3b8",
19+
"ndcube.visualization.tests.test_plotting.test_animate_2D_cube": "cfad6d4794b50026abea9de223ae97f3f5dfbd28b9e75a14e955a0b45fc1ecce",
20+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice0-kwargs0]": "628751fc2fc5cdcacf42a88e9152bbdce69a3fb6dd2f46e1e2c00d82f051843c",
21+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice1-kwargs1]": "61e991fb1a45005720e60faab9f0f41adad8f8ac827d3dc018c44c8d7c422207",
22+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs2]": "5f2a07be30fcff768f458c6a2d18e6cbbadcec605af15cf2977421556bbb5bda",
23+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs3]": "62b6fa3af726115e7f4b1579744564fdfff4efde3f5612cf651a8fd161f7661d",
24+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-None-kwargs4]": "1b24cbbe4da25b222754e96a547c9f9bd1905df30bc742033babebc093dbe2a0",
25+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice5-kwargs5]": "d34b83c14da1348cf1d9ba20fb0c929c061d922303465daf46f6bb67f8ee2d6e",
26+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[ln_lt_l_t-cslice6-kwargs6]": "5f2a07be30fcff768f458c6a2d18e6cbbadcec605af15cf2977421556bbb5bda",
27+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[unit_uncertainty-cslice7-kwargs7]": "d34b83c14da1348cf1d9ba20fb0c929c061d922303465daf46f6bb67f8ee2d6e",
28+
"ndcube.visualization.tests.test_plotting.test_animate_cube_from_slice[mask-cslice8-kwargs8]": "86d89a4034798c01a016ec8756b353a5b8d9d9332664fd1162c8fb2dc1f201c1"
29+
}

0 commit comments

Comments
 (0)