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.
- {fitscube-2.3.0 → fitscube-2.3.1}/PKG-INFO +2 -1
- fitscube-2.3.1/fitscube/_version.py +24 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/bounding_box.py +7 -3
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/combine_fits.py +51 -12
- {fitscube-2.3.0 → fitscube-2.3.1}/pyproject.toml +1 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/conftest.py +29 -0
- fitscube-2.3.1/tests/test_bb.py +97 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_times.py +118 -0
- fitscube-2.3.0/fitscube/_version.py +0 -34
- {fitscube-2.3.0 → fitscube-2.3.1}/.github/CONTRIBUTING.md +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.github/dependabot.yml +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.github/release.yml +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.github/workflows/cd.yml +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.github/workflows/ci.yml +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.gitignore +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/.pre-commit-config.yaml +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/CHANGELOG.md +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/LICENSE +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/README.md +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/__init__.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/asyncio.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/cli.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/exceptions.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/extract.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/logging.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/fitscube/version.pyi +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/noxfile.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/__init__.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/cube.zip +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/images.zip +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/time_images.zip +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/data/timecube.zip +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_extract.py +0 -0
- {fitscube-2.3.0 → fitscube-2.3.1}/tests/test_frequencies.py +0 -0
- {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.
|
|
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
|
-
"""
|
|
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 (
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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:
|
|
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"
|
|
@@ -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
|
|
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
|