fitscube 2.3.0__tar.gz → 2.3.1__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 (35) hide show
  1. {fitscube-2.3.0 → fitscube-2.3.1}/PKG-INFO +2 -1
  2. fitscube-2.3.1/fitscube/_version.py +24 -0
  3. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/bounding_box.py +7 -3
  4. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/combine_fits.py +51 -12
  5. {fitscube-2.3.0 → fitscube-2.3.1}/pyproject.toml +1 -0
  6. {fitscube-2.3.0 → fitscube-2.3.1}/tests/conftest.py +29 -0
  7. fitscube-2.3.1/tests/test_bb.py +97 -0
  8. {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_times.py +118 -0
  9. fitscube-2.3.0/fitscube/_version.py +0 -34
  10. {fitscube-2.3.0 → fitscube-2.3.1}/.github/CONTRIBUTING.md +0 -0
  11. {fitscube-2.3.0 → fitscube-2.3.1}/.github/dependabot.yml +0 -0
  12. {fitscube-2.3.0 → fitscube-2.3.1}/.github/release.yml +0 -0
  13. {fitscube-2.3.0 → fitscube-2.3.1}/.github/workflows/cd.yml +0 -0
  14. {fitscube-2.3.0 → fitscube-2.3.1}/.github/workflows/ci.yml +0 -0
  15. {fitscube-2.3.0 → fitscube-2.3.1}/.gitignore +0 -0
  16. {fitscube-2.3.0 → fitscube-2.3.1}/.pre-commit-config.yaml +0 -0
  17. {fitscube-2.3.0 → fitscube-2.3.1}/CHANGELOG.md +0 -0
  18. {fitscube-2.3.0 → fitscube-2.3.1}/LICENSE +0 -0
  19. {fitscube-2.3.0 → fitscube-2.3.1}/README.md +0 -0
  20. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/__init__.py +0 -0
  21. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/asyncio.py +0 -0
  22. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/cli.py +0 -0
  23. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/exceptions.py +0 -0
  24. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/extract.py +0 -0
  25. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/logging.py +0 -0
  26. {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/version.pyi +0 -0
  27. {fitscube-2.3.0 → fitscube-2.3.1}/noxfile.py +0 -0
  28. {fitscube-2.3.0 → fitscube-2.3.1}/tests/__init__.py +0 -0
  29. {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/cube.zip +0 -0
  30. {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/images.zip +0 -0
  31. {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/time_images.zip +0 -0
  32. {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/timecube.zip +0 -0
  33. {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_extract.py +0 -0
  34. {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_frequencies.py +0 -0
  35. {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_package.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fitscube
3
- Version: 2.3.0
3
+ Version: 2.3.1
4
4
  Summary: A package to produce produce FITS cubes.
5
5
  Project-URL: Homepage, https://github.com/AlecThomson/fitscube
6
6
  Project-URL: Bug Tracker, https://github.com/AlecThomson/fitscube/issues
@@ -58,6 +58,7 @@ Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
58
58
  Requires-Dist: sphinx-copybutton; extra == 'docs'
59
59
  Requires-Dist: sphinx>=7.0; extra == 'docs'
60
60
  Provides-Extra: test
61
+ Requires-Dist: pytest-asyncio; extra == 'test'
61
62
  Requires-Dist: pytest-cov>=3; extra == 'test'
62
63
  Requires-Dist: pytest>=6; extra == 'test'
63
64
  Provides-Extra: uvloop
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '2.3.1'
22
+ __version_tuple__ = version_tuple = (2, 3, 1)
23
+
24
+ __commit_id__ = commit_id = None
@@ -15,16 +15,17 @@ from fitscube.logging import logger
15
15
 
16
16
  @dataclass(frozen=True)
17
17
  class BoundingBox:
18
- """Simple container to represent a bounding box"""
18
+ """Simple container to represent a bounding box. Maximum values can be
19
+ used as is when slicing."""
19
20
 
20
21
  xmin: int
21
22
  """Minimum x pixel"""
22
23
  xmax: int
23
24
  """Maximum x pixel"""
24
25
  ymin: int
25
- """Minimum y pixel"""
26
+ """Minimum y pixel. Can be used as is in slice (e.g. is exclusive). """
26
27
  ymax: int
27
- """Maximum y pixel"""
28
+ """Maximum y pixel Can be used as is in slice (e.g. is exclusive)."""
28
29
  original_shape: tuple[int, int]
29
30
  """The original shape of the image. If constructed against a cube this is the shape of a single plane."""
30
31
  y_span: int
@@ -62,6 +63,9 @@ def create_bound_box_plane(image_data: np.ndarray) -> BoundingBox | None:
62
63
  xmin, xmax = np.where(x_valid)[0][[0, -1]]
63
64
  ymin, ymax = np.where(y_valid)[0][[0, -1]]
64
65
 
66
+ xmax += 1
67
+ ymax += 1
68
+
65
69
  y_span = ymax - ymin
66
70
  x_span = xmax - xmin
67
71
 
@@ -16,10 +16,7 @@ import asyncio
16
16
  import warnings
17
17
  from io import BufferedRandom
18
18
  from pathlib import Path
19
- from typing import (
20
- NamedTuple,
21
- TypeVar,
22
- )
19
+ from typing import Literal, NamedTuple, TypeVar
23
20
 
24
21
  import astropy.units as u
25
22
  import numpy as np
@@ -50,6 +47,8 @@ BIT_DICT = {
50
47
  16: 2,
51
48
  8: 1,
52
49
  }
50
+ FLOAT_LENGTH = Literal[16, 32, 64]
51
+ FLOAT_TYPE = {64: ">f8", 32: ">f4"}
53
52
 
54
53
  warnings.filterwarnings("ignore", category=UserWarning, module="astropy.io.fits")
55
54
  warnings.filterwarnings("ignore", category=VerifyWarning)
@@ -247,19 +246,26 @@ async def create_output_cube_coro(
247
246
  overwrite: bool = False,
248
247
  time_domain_mode: bool = False,
249
248
  bounding_box: BoundingBox | None = None,
249
+ float_length: FLOAT_LENGTH | None = None,
250
250
  ) -> InitResult:
251
- """Initialize the data cube.
251
+ """Generate the output header and write a dummy cube to disk based on properties of the
252
+ inpute data. The output cube written here has a correctly formed header and a pre-zerod
253
+ data cube written as output.
252
254
 
253
255
  Args:
254
- old_name (str): Old FITS file name
255
- n_chan (int): Number of channels
256
-
257
- Raises:
258
- KeyError: If 2D and REFFREQ is not in header
259
- ValueError: If not 2D and FREQ is not in header
256
+ old_name (Path): The path to a representative image to draw the base fits header from
257
+ out_cube (Path): Path of the output cube to create
258
+ specs (u.Quantity): Specification of the unit that denotes the 'cube' axis
259
+ ignore_spec (bool, optional): Whether the provided `specs` axis should be ignored. If True dummy placeholder fields added. Defaults to False.
260
+ has_beams (bool, optional): Indicates whether a CASA Beam table will also be generated and added to the output cube. Defaults to False.
261
+ single_beam (bool, optional): Indicates whether a constant restoring beam has been used among all input images. If so only the beam fields are need. Defaults to False.
262
+ overwrite (bool, optional): If True the out the output cube will overwrite any existing file. Defaults to False.
263
+ time_domain_mode (bool, optional): Whether to join images via the DATE-OBS (e.g. time) axis. If False cube joined along frequency axis. Defaults to False.
264
+ bounding_box (BoundingBox | None, optional): Whether a trimming operation should be applied to input images. If a BoundingBox is supplied this will be used to update the reference pixel position and data shape indicators. Defaults to None.
265
+ float_length (Literal[16, 32, 64] | None, optional): The precision of the output data. If None drawn from input data. Otherwise values accepted are 16, 32 and 64. Defaults to None.
260
266
 
261
267
  Returns:
262
- InitResult: header, spec_idx, spec_fits_idx, is_2d
268
+ InitResult: Details of the output cube, including the output header
263
269
  """
264
270
 
265
271
  # define units if in time or freq domain
@@ -380,6 +386,23 @@ async def create_output_cube_coro(
380
386
  else:
381
387
  cube_shape[idx] = n_chan
382
388
 
389
+ logger.critical(f"{float_length=} {new_header['BITPIX']=}")
390
+ if float_length is not None:
391
+ # Per astropy docs
392
+ # bITPIX numpy data type
393
+ # 8 numpy.uint8 (note it is UNsigned integer)
394
+ # 16 numpy.int16
395
+ # 32 numpy.int32
396
+ # 64 numpy.int64
397
+ # -32 numpy.float32
398
+ # -64 numpy.float64
399
+ assert float_length in list(BIT_DICT.keys()), (
400
+ f"{float_length=} not in {BIT_DICT=}"
401
+ )
402
+ bit_pix = int(float_length)
403
+ logger.info(f"Specified {float_length=}, corresponding to {bit_pix=}")
404
+ new_header["BITPIX"] = -abs(bit_pix)
405
+
383
406
  output_header = await create_cube_from_scratch_coro(
384
407
  output_file=out_cube, output_header=new_header, overwrite=overwrite
385
408
  )
@@ -657,6 +680,11 @@ async def process_channel(
657
680
  if invalidate_zeros:
658
681
  plane[plane == 0.0] = np.nan
659
682
 
683
+ if "BITPIX" in new_header:
684
+ bit_pix = abs(new_header["BITPIX"])
685
+ float_type = FLOAT_TYPE[bit_pix]
686
+ plane = plane.astype(float_type)
687
+
660
688
  await write_channel_to_cube_coro(
661
689
  file_handle=file_handle,
662
690
  plane=plane,
@@ -678,6 +706,7 @@ async def combine_fits_coro(
678
706
  time_domain_mode: bool = False,
679
707
  bounding_box: bool = False,
680
708
  invalidate_zeros: bool = False,
709
+ float_length: FLOAT_LENGTH | None = None,
681
710
  ) -> u.Quantity:
682
711
  """Combine FITS files into a cube.
683
712
  Can handle either frequency or time dimensions agnostically
@@ -690,6 +719,7 @@ async def combine_fits_coro(
690
719
  time_domain_mode (bool, optional): Work in time domain mode - make a time-cube. Default = False.
691
720
  bounding_box (bool, optional): Clip invalid/padded pixels when crafting the fits cube. When True an extra read of the input daata is needed, but output cube is smaller. Defaults to False.
692
721
  invalidate_zeros (bool, optionals): Set pixels whose values are exactly zero to NaNs. Defaults to False.
722
+ float_length (Literal[16, 32, 64] | None, optional): The floating point precision in bits to use when creating the output cube. If None the size of the input data are used. Defaults to None.
693
723
 
694
724
  Returns:
695
725
  tuple[fits.HDUList, u.Quantity]: The combined FITS cube and frequencies
@@ -760,6 +790,7 @@ async def combine_fits_coro(
760
790
  overwrite=overwrite,
761
791
  time_domain_mode=time_domain_mode,
762
792
  bounding_box=final_bounding_box,
793
+ float_length=float_length,
763
794
  )
764
795
 
765
796
  new_channels = np.arange(len(specs))
@@ -881,6 +912,13 @@ def get_parser(
881
912
  action="store_true",
882
913
  help="Set pixels whose values are exactly zero to NaNs",
883
914
  )
915
+ parser.add_argument(
916
+ "--floating",
917
+ type=int,
918
+ choices=(8, 16, 32, 64),
919
+ default=None,
920
+ help="The number of floating point bits to use in the out cube. If None the input data precision is used.",
921
+ )
884
922
 
885
923
  return parser
886
924
 
@@ -923,6 +961,7 @@ def cli(args: argparse.Namespace | None = None) -> None:
923
961
  time_domain_mode=time_domain_mode,
924
962
  bounding_box=args.bounding_box,
925
963
  invalidate_zeros=args.invalidate_zeros,
964
+ float_length=args.floating,
926
965
  )
927
966
 
928
967
  spequency = "times" if time_domain_mode else "frequencies"
@@ -38,6 +38,7 @@ dependencies = [
38
38
  [project.optional-dependencies]
39
39
  test = [
40
40
  "pytest >=6",
41
+ "pytest-asyncio",
41
42
  "pytest-cov >=3",
42
43
  ]
43
44
  dev = [
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
  from shutil import unpack_archive
5
5
 
6
6
  import pytest
7
+ from astropy.io import fits
7
8
 
8
9
  EXAMPLE_HEADER = "SIMPLE = T / conforms to FITS standard BITPIX = -32 / array data type NAXIS = 4 / number of array dimensions NAXIS1 = 6192 NAXIS2 = 6192 NAXIS3 = 1 NAXIS4 = 72 EXTEND = T BSCALE = 1.0 BZERO = 0.0 BUNIT = 'JY/BEAM ' BMAJ = 0.00398184964207042 BMIN = 0.00332268385509243 BPA = 77.3858939868987 EQUINOX = 2000.0 LONPOLE = 180.0 BTYPE = 'Intensity' TELESCOP= 'ASKAP ' OBJECT = 'EMU_1141-55' ORIGIN = 'WSClean ' CTYPE1 = 'RA---SIN' CRPIX1 = 3097.0 CRVAL1 = 173.522555019647 CDELT1 = -0.000625 CUNIT1 = 'deg ' CTYPE2 = 'DEC--SIN' CRPIX2 = 3097.0 CRVAL2 = -55.3190947400628 CDELT2 = 0.000625 CUNIT2 = 'deg ' CTYPE3 = 'STOKES ' CRPIX3 = 1.0 CRVAL3 = 1.0 CDELT3 = 1.0 CUNIT3 = '' CTYPE4 = 'FREQ ' CRPIX4 = 1 CRVAL4 = 801490740.740741 CDELT4 = 4000000.0 CUNIT4 = 'Hz ' SPECSYS = 'TOPOCENT' DATE-OBS= '2023-01-08T15:36:40.9' WSCDATAC= 'DATA ' WSCVDATE= '2022-10-21' WSCVERSI= '3.2 ' WSCWEIGH= 'Briggs''(-0)' WSCENVIS= 1026544.24656423 WSCFIELD= 0.0 WSCGAIN = 0.1 WSCGKRNL= 7.0 WSCIMGWG= 4052355.35920529 WSCMAJOR= 9.0 WSCMGAIN= 0.9 WSCMINOR= 62860.0 WSCNEGCM= 1.0 WSCNEGST= 0.0 WSCNITER= 5000000.0 WSCNORMF= 4052355.35920529 WSCNVIS = 7648518.0 WSCNWLAY= 1.0 WSCTHRES= 0.0 WSCVWSUM= 30594072.0 COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H HISTORY wsclean -abs-mem 200 -local-rms-window 20 -size 6192 6192 -local-rms -foHISTORY rce-mask-rounds 4 -auto-mask 8.0 -auto-threshold 3.0 -channels-out 72 -mHISTORY gain 0.9 -nmiter 10 -niter 5000000 -multiscale-scale-bias 0.8 -multiscalHISTORY e-scales 0,4,8,16,24,32,48,64,92,128,196,512,796,1025 -fit-spectral-pol HISTORY 4 -weight briggs -0 -data-column DATA -scale 2.25asec -gridder wgridder HISTORY -wgridder-accuracy 0.0001 -join-channels -minuv-l 50.0 -beam-fitting-sizHISTORY e 1.25 -deconvolution-channels 8 -parallel-gridding 18 -temp-dir /dev/shHISTORY m/gal16b.8974844 -pol i -save-source-list -name /dev/shm/gal16b.8974844/HISTORY SB47138.EMU_1141-55.beam19.i /scratch3/gal16b/emu_download/flint_jollytrHISTORY actor/47138/SB47138.EMU_1141-55.beam19.ms END "
9
10
 
@@ -65,3 +66,31 @@ def time_image_paths(tmpdir) -> list[Path]:
65
66
  image_paths.sort()
66
67
 
67
68
  return image_paths
69
+
70
+
71
+ @pytest.fixture
72
+ def time_image_paths_withzeroborder(tmpdir) -> list[Path]:
73
+ """Same as above but zero out border pixels in the data"""
74
+ tmp_dir = Path(tmpdir) / "time_images"
75
+ tmp_dir.mkdir(exist_ok=True, parents=True)
76
+ images_zip = Path(__file__).parent / "data" / "time_images.zip"
77
+
78
+ unpack_archive(images_zip, tmp_dir)
79
+ image_paths = list(tmp_dir.glob("*fits"))
80
+ image_paths.sort()
81
+
82
+ output_paths = []
83
+ for image in image_paths:
84
+ with fits.open(image, memmap=False, lazy_load_hdus=False) as a:
85
+ data = a[0].data
86
+ data[..., :5, :] = 0
87
+ data[..., -5:, :] = 0
88
+ data[..., :, :5] = 0
89
+ data[..., :, -5:] = 0
90
+
91
+ a[0].data = data
92
+ image_output = image.with_suffix(".zero.fits")
93
+ fits.writeto(image_output, data=data, header=a[0].header)
94
+ output_paths.append(image_output)
95
+
96
+ return output_paths
@@ -0,0 +1,97 @@
1
+ # Tests to ensure the bounding box functionality is working
2
+ from __future__ import annotations
3
+
4
+ import numpy as np
5
+ import pytest
6
+ from fitscube.bounding_box import (
7
+ BoundingBox,
8
+ create_bound_box_plane,
9
+ extract_common_bounding_box,
10
+ )
11
+
12
+
13
+ def test_bound_box_plane() -> None:
14
+ """See if we can make the bounding box correctly"""
15
+
16
+ image = np.zeros((100, 100))
17
+
18
+ bb = create_bound_box_plane(image_data=image)
19
+ assert isinstance(bb, BoundingBox)
20
+ assert bb.xmin == 0
21
+ assert bb.ymin == 0
22
+ assert bb.xmax == 100
23
+ assert bb.ymax == 100
24
+ assert bb.x_span == 100
25
+ assert bb.y_span == 100
26
+
27
+ image[:, :4] = np.nan
28
+ image[96:, :] = np.nan
29
+
30
+ bb = create_bound_box_plane(image_data=image)
31
+ assert isinstance(bb, BoundingBox)
32
+ assert bb.xmin == 0
33
+ assert bb.ymin == 4
34
+ assert bb.xmax == 96
35
+ assert bb.ymax == 100
36
+ assert bb.x_span == 96
37
+ assert bb.y_span == 96
38
+
39
+
40
+ def test_extract_common_bounding_box() -> None:
41
+ """Construct a set of bounding boxes to ensure a largest fits all
42
+ one can be created"""
43
+
44
+ bbs = []
45
+ for i in range(2, 8, 1):
46
+ image = np.zeros((100, 100))
47
+
48
+ print(i)
49
+
50
+ image[:i, :] = np.nan
51
+ image[:, -i:] = np.nan
52
+ assert np.sum(np.isnan(image)) > 1
53
+ bbs.append(create_bound_box_plane(image_data=image))
54
+
55
+ assert len(bbs) == 6
56
+
57
+ bb = extract_common_bounding_box(bounding_boxes=bbs)
58
+ assert bb.xmin == 2
59
+ assert bb.ymin == 0
60
+ assert bb.xmax == 100
61
+ assert bb.ymax == 98
62
+
63
+
64
+ def test_extract_common_bounding_box_error() -> None:
65
+ """See if the correct errors are raised"""
66
+
67
+ with pytest.raises(ValueError, match="No valid"):
68
+ extract_common_bounding_box(bounding_boxes=[None, None, None])
69
+
70
+ bbs = []
71
+ for i in range(2, 8, 1):
72
+ image = np.zeros((100 + i, 100))
73
+ bbs.append(create_bound_box_plane(image_data=image))
74
+
75
+ with pytest.raises(ValueError, match="Different shapes"):
76
+ extract_common_bounding_box(bounding_boxes=bbs)
77
+
78
+
79
+ # @pytest.mark.asyncio
80
+ # async def test_get_bounding_box_from_fits(time_image_paths) -> None:
81
+ # """!!! NOTE: Sometimes this test can raise the following error:
82
+
83
+ # > Exception ignored in: <socket.socket fd=-1, family=1, type=1, proto=0>
84
+
85
+ # Not sure why it is capable of such a thing.
86
+ # """
87
+ # futures = [
88
+ # await get_bounding_box_for_fits_coro(fits_path=fits_path)
89
+ # for fits_path in time_image_paths
90
+ # ]
91
+ # assert all(isinstance(bb, BoundingBox) for bb in futures)
92
+
93
+ # common_bb = extract_common_bounding_box(bounding_boxes=futures)
94
+ # assert common_bb.xmin == 0
95
+ # assert common_bb.ymin == 0
96
+ # assert common_bb.xmax == 100
97
+ # assert common_bb.ymax == 100
@@ -92,6 +92,14 @@ def test_even_combine(file_list: list[Path], even_specs: u.Quantity, output_file
92
92
  def test_uneven_combine(
93
93
  file_list: list[Path], even_specs: u.Quantity, output_file: Path
94
94
  ):
95
+ """!!!! NOTE: For some unknown reason thios test *SOMETIMES* raises the
96
+ following error:
97
+
98
+ > FAILED tests/test_times.py::test_uneven - ValueError: operands could not be broadcast together with shapes (5,) (6,)
99
+
100
+ Completely unclear to me why
101
+ """
102
+
95
103
  # uneven_specs = np.concatenate([even_specs[0:1], even_specs[3:]])
96
104
  file_array = np.array(file_list)
97
105
  uneven_files = np.concatenate([file_array[0:1], file_array[3:]]).tolist()
@@ -149,3 +157,113 @@ def test_wsclean_images_create_axis(time_image_paths, tmpdir) -> None:
149
157
  # The TIME axis will be appended as a new dimension
150
158
  cube_image_data = cube_data[i]
151
159
  assert np.allclose(image_data.squeeze(), cube_image_data.squeeze())
160
+
161
+
162
+ @pytest.mark.filterwarnings("ignore:'datfix' made the change")
163
+ def test_wsclean_images_create_axis_floating(time_image_paths, tmpdir) -> None:
164
+ """Ensure that the combined cube conforms to the input data"""
165
+
166
+ tmpdir = Path(tmpdir) / "time_cube_combine"
167
+ tmpdir.mkdir(parents=True, exist_ok=True)
168
+ out_cube = tmpdir / "time_cube_mate.fits"
169
+
170
+ for float_length, float_rep in ((32, ">f4"), (64, ">f8")):
171
+ combine_fits(
172
+ file_list=time_image_paths,
173
+ out_cube=out_cube,
174
+ overwrite=True,
175
+ time_domain_mode=True,
176
+ float_length=float_length, # type: ignore [arg-type]
177
+ )
178
+
179
+ cube_data = fits.getdata(out_cube)
180
+ assert cube_data.dtype == float_rep
181
+ for i, time_image_path in enumerate(time_image_paths):
182
+ image_data = fits.getdata(time_image_path)
183
+ # The TIME axis will be appended as a new dimension
184
+ cube_image_data = cube_data[i]
185
+ assert np.allclose(image_data.squeeze(), cube_image_data.squeeze())
186
+
187
+
188
+ @pytest.mark.filterwarnings("ignore:'datfix' made the change")
189
+ def test_wsclean_images_create_axis_withbb(time_image_paths, tmpdir) -> None:
190
+ """Ensure that the combined cube conforms to the input data. This will use the
191
+ bounding box option. This should led to no change."""
192
+
193
+ tmpdir = Path(tmpdir) / "time_cube_combine"
194
+ tmpdir.mkdir(parents=True, exist_ok=True)
195
+ out_cube = tmpdir / "time_cube_mate.fits"
196
+
197
+ combine_fits(
198
+ file_list=time_image_paths,
199
+ out_cube=out_cube,
200
+ overwrite=True,
201
+ time_domain_mode=True,
202
+ bounding_box=True,
203
+ )
204
+
205
+ cube_data = fits.getdata(out_cube)
206
+ for i, time_image_path in enumerate(time_image_paths):
207
+ image_data = fits.getdata(time_image_path)
208
+ # The TIME axis will be appended as a new dimension
209
+ cube_image_data = cube_data[i]
210
+ assert np.allclose(image_data.squeeze(), cube_image_data.squeeze())
211
+
212
+
213
+ @pytest.mark.filterwarnings("ignore:'datfix' made the change")
214
+ def test_wsclean_images_create_axis_withbb_noinvalidate0(
215
+ time_image_paths_withzeroborder, tmpdir
216
+ ) -> None:
217
+ """Ensure that the combined cube conforms to the input data. This will use the
218
+ bounding box option. This should led to no change."""
219
+
220
+ tmpdir = Path(tmpdir) / "time_cube_combine_zeros"
221
+ tmpdir.mkdir(parents=True, exist_ok=True)
222
+ out_cube = tmpdir / "time_cube_mate.fits"
223
+
224
+ combine_fits(
225
+ file_list=time_image_paths_withzeroborder,
226
+ out_cube=out_cube,
227
+ overwrite=True,
228
+ time_domain_mode=True,
229
+ bounding_box=True,
230
+ invalidate_zeros=False,
231
+ )
232
+
233
+ cube_data = fits.getdata(out_cube)
234
+ assert cube_data.squeeze().shape[-2:] == (100, 100)
235
+ for i, time_image_path in enumerate(time_image_paths_withzeroborder):
236
+ image_data = fits.getdata(time_image_path)
237
+ # The TIME axis will be appended as a new dimension
238
+ cube_image_data = cube_data[i]
239
+ assert np.allclose(image_data.squeeze(), cube_image_data.squeeze())
240
+
241
+
242
+ @pytest.mark.filterwarnings("ignore:'datfix' made the change")
243
+ def test_wsclean_images_create_axis_withbb_invalidatezeros(
244
+ time_image_paths_withzeroborder, tmpdir
245
+ ) -> None:
246
+ """Ensure that the combined cube conforms to the input data. This will use the
247
+ bounding box option. This should led to no change. Here we also get fitscube to
248
+ mark pixels that are exactly 0 to be nans"""
249
+
250
+ tmpdir = Path(tmpdir) / "time_cube_combine_zeros"
251
+ tmpdir.mkdir(parents=True, exist_ok=True)
252
+ out_cube = tmpdir / "time_cube_mate.fits"
253
+
254
+ combine_fits(
255
+ file_list=time_image_paths_withzeroborder,
256
+ out_cube=out_cube,
257
+ overwrite=True,
258
+ time_domain_mode=True,
259
+ bounding_box=True,
260
+ invalidate_zeros=True,
261
+ )
262
+
263
+ cube_data = fits.getdata(out_cube)
264
+ assert cube_data.squeeze().shape[-2:] == (90, 90)
265
+ for i, time_image_path in enumerate(time_image_paths_withzeroborder):
266
+ image_data = fits.getdata(time_image_path)
267
+ # The TIME axis will be appended as a new dimension
268
+ cube_image_data = cube_data[i]
269
+ assert np.allclose(image_data.squeeze()[5:-5, 5:-5], cube_image_data.squeeze())
@@ -1,34 +0,0 @@
1
- # file generated by setuptools-scm
2
- # don't change, don't track in version control
3
-
4
- __all__ = [
5
- "__version__",
6
- "__version_tuple__",
7
- "version",
8
- "version_tuple",
9
- "__commit_id__",
10
- "commit_id",
11
- ]
12
-
13
- TYPE_CHECKING = False
14
- if TYPE_CHECKING:
15
- from typing import Tuple
16
- from typing import Union
17
-
18
- VERSION_TUPLE = Tuple[Union[int, str], ...]
19
- COMMIT_ID = Union[str, None]
20
- else:
21
- VERSION_TUPLE = object
22
- COMMIT_ID = object
23
-
24
- version: str
25
- __version__: str
26
- __version_tuple__: VERSION_TUPLE
27
- version_tuple: VERSION_TUPLE
28
- commit_id: COMMIT_ID
29
- __commit_id__: COMMIT_ID
30
-
31
- __version__ = version = '2.3.0'
32
- __version_tuple__ = version_tuple = (2, 3, 0)
33
-
34
- __commit_id__ = commit_id = None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes