fitscube 2.2.0__tar.gz → 2.3.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.
- {fitscube-2.2.0 → fitscube-2.3.0}/.github/workflows/cd.yml +2 -2
- {fitscube-2.2.0 → fitscube-2.3.0}/.github/workflows/ci.yml +3 -3
- {fitscube-2.2.0 → fitscube-2.3.0}/.pre-commit-config.yaml +4 -4
- {fitscube-2.2.0 → fitscube-2.3.0}/PKG-INFO +1 -1
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/_version.py +16 -3
- fitscube-2.3.0/fitscube/bounding_box.py +149 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/combine_fits.py +60 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/.github/CONTRIBUTING.md +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/.github/dependabot.yml +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/.github/release.yml +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/.gitignore +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/CHANGELOG.md +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/LICENSE +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/README.md +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/__init__.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/asyncio.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/cli.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/exceptions.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/extract.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/logging.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/fitscube/version.pyi +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/noxfile.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/pyproject.toml +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/__init__.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/conftest.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/data/cube.zip +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/data/images.zip +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/data/time_images.zip +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/data/timecube.zip +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/test_extract.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/test_frequencies.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/test_package.py +0 -0
- {fitscube-2.2.0 → fitscube-2.3.0}/tests/test_times.py +0 -0
|
@@ -25,7 +25,7 @@ jobs:
|
|
|
25
25
|
runs-on: ubuntu-latest
|
|
26
26
|
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v5
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0
|
|
31
31
|
|
|
@@ -43,7 +43,7 @@ jobs:
|
|
|
43
43
|
if: github.event_name == 'release' && github.event.action == 'published'
|
|
44
44
|
|
|
45
45
|
steps:
|
|
46
|
-
- uses: actions/download-artifact@
|
|
46
|
+
- uses: actions/download-artifact@v5
|
|
47
47
|
with:
|
|
48
48
|
name: Packages
|
|
49
49
|
path: dist
|
|
@@ -21,7 +21,7 @@ jobs:
|
|
|
21
21
|
name: Format
|
|
22
22
|
runs-on: ubuntu-latest
|
|
23
23
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
24
|
+
- uses: actions/checkout@v5
|
|
25
25
|
with:
|
|
26
26
|
fetch-depth: 0
|
|
27
27
|
- uses: actions/setup-python@v5
|
|
@@ -42,7 +42,7 @@ jobs:
|
|
|
42
42
|
runs-on: [ubuntu-latest]
|
|
43
43
|
|
|
44
44
|
steps:
|
|
45
|
-
- uses: actions/checkout@
|
|
45
|
+
- uses: actions/checkout@v5
|
|
46
46
|
with:
|
|
47
47
|
fetch-depth: 0
|
|
48
48
|
|
|
@@ -62,6 +62,6 @@ jobs:
|
|
|
62
62
|
--durations=20
|
|
63
63
|
|
|
64
64
|
- name: Upload coverage report
|
|
65
|
-
uses: codecov/codecov-action@v5.
|
|
65
|
+
uses: codecov/codecov-action@v5.5.0
|
|
66
66
|
with:
|
|
67
67
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -10,7 +10,7 @@ repos:
|
|
|
10
10
|
# additional_dependencies: [black==24.*]
|
|
11
11
|
|
|
12
12
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
13
|
-
rev: "
|
|
13
|
+
rev: "v6.0.0"
|
|
14
14
|
hooks:
|
|
15
15
|
- id: check-added-large-files
|
|
16
16
|
- id: check-case-conflict
|
|
@@ -33,7 +33,7 @@ repos:
|
|
|
33
33
|
- id: rst-inline-touching-normal
|
|
34
34
|
|
|
35
35
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
36
|
-
rev: "v0.12.
|
|
36
|
+
rev: "v0.12.11"
|
|
37
37
|
hooks:
|
|
38
38
|
- id: ruff
|
|
39
39
|
args: ["--fix", "--show-fixes"]
|
|
@@ -56,7 +56,7 @@ repos:
|
|
|
56
56
|
- tomli
|
|
57
57
|
|
|
58
58
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
|
59
|
-
rev: "v0.
|
|
59
|
+
rev: "v0.11.0.1"
|
|
60
60
|
hooks:
|
|
61
61
|
- id: shellcheck
|
|
62
62
|
|
|
@@ -75,7 +75,7 @@ repos:
|
|
|
75
75
|
additional_dependencies: ["validate-pyproject-schema-store[all]"]
|
|
76
76
|
|
|
77
77
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
78
|
-
rev: "0.33.
|
|
78
|
+
rev: "0.33.3"
|
|
79
79
|
hooks:
|
|
80
80
|
- id: check-dependabot
|
|
81
81
|
- id: check-github-workflows
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
# file generated by setuptools-scm
|
|
2
2
|
# don't change, don't track in version control
|
|
3
3
|
|
|
4
|
-
__all__ = [
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
5
12
|
|
|
6
13
|
TYPE_CHECKING = False
|
|
7
14
|
if TYPE_CHECKING:
|
|
@@ -9,13 +16,19 @@ if TYPE_CHECKING:
|
|
|
9
16
|
from typing import Union
|
|
10
17
|
|
|
11
18
|
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
12
20
|
else:
|
|
13
21
|
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
14
23
|
|
|
15
24
|
version: str
|
|
16
25
|
__version__: str
|
|
17
26
|
__version_tuple__: VERSION_TUPLE
|
|
18
27
|
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
19
30
|
|
|
20
|
-
__version__ = version = '2.
|
|
21
|
-
__version_tuple__ = version_tuple = (2,
|
|
31
|
+
__version__ = version = '2.3.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (2, 3, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Some basic utilities to help with the creating of
|
|
2
|
+
bounding boxes to use in fitscube"""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
from astropy.io import fits
|
|
12
|
+
|
|
13
|
+
from fitscube.logging import logger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class BoundingBox:
|
|
18
|
+
"""Simple container to represent a bounding box"""
|
|
19
|
+
|
|
20
|
+
xmin: int
|
|
21
|
+
"""Minimum x pixel"""
|
|
22
|
+
xmax: int
|
|
23
|
+
"""Maximum x pixel"""
|
|
24
|
+
ymin: int
|
|
25
|
+
"""Minimum y pixel"""
|
|
26
|
+
ymax: int
|
|
27
|
+
"""Maximum y pixel"""
|
|
28
|
+
original_shape: tuple[int, int]
|
|
29
|
+
"""The original shape of the image. If constructed against a cube this is the shape of a single plane."""
|
|
30
|
+
y_span: int
|
|
31
|
+
"""The span between ymax and ymin"""
|
|
32
|
+
x_span: int
|
|
33
|
+
"""The span between xmax and xmin"""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_bound_box_plane(image_data: np.ndarray) -> BoundingBox | None:
|
|
37
|
+
"""Create a bounding box around pixels in a 2D image. If all
|
|
38
|
+
pixels are not valid, then ``None`` is returned.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
image_data (np.ndarray): The 2D image to construct a bounding box around
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Optional[BoundingBox]: None if no valid pixels, a bounding box with the (xmin,xmax,ymin,ymax) of valid pixels
|
|
45
|
+
"""
|
|
46
|
+
assert len(image_data.shape) == 2, (
|
|
47
|
+
f"Only two-dimensional arrays supported, received {image_data.shape}"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# First convert to a boolean array
|
|
51
|
+
image_valid = np.isfinite(image_data)
|
|
52
|
+
|
|
53
|
+
if not any(image_valid.reshape(-1)):
|
|
54
|
+
logger.info("No pixels to creating bounding box for")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
# Then make them 1D arrays
|
|
58
|
+
x_valid = np.any(image_valid, axis=1)
|
|
59
|
+
y_valid = np.any(image_valid, axis=0)
|
|
60
|
+
|
|
61
|
+
# Now get the first and last index
|
|
62
|
+
xmin, xmax = np.where(x_valid)[0][[0, -1]]
|
|
63
|
+
ymin, ymax = np.where(y_valid)[0][[0, -1]]
|
|
64
|
+
|
|
65
|
+
y_span = ymax - ymin
|
|
66
|
+
x_span = xmax - xmin
|
|
67
|
+
|
|
68
|
+
return BoundingBox(
|
|
69
|
+
xmin=xmin,
|
|
70
|
+
xmax=xmax,
|
|
71
|
+
ymin=ymin,
|
|
72
|
+
ymax=ymax,
|
|
73
|
+
y_span=y_span,
|
|
74
|
+
x_span=x_span,
|
|
75
|
+
original_shape=image_data.shape[-2:],
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def extract_common_bounding_box(
|
|
80
|
+
bounding_boxes: list[BoundingBox | None],
|
|
81
|
+
) -> BoundingBox:
|
|
82
|
+
"""Get the smallest bounding box that encompasses all bounding boxes
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
bounding_boxes (list[BoundingBox | None]): A list of bounding boxes. If None (returned for invalid images) skip it.
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If all input bounding boxes are invalid
|
|
89
|
+
ValueError: If there is an `original_shape` mismatch
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
BoundingBox: The smallest bounding box
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
# Step 1: filter out all Nones
|
|
96
|
+
valid_boxes: list[BoundingBox] = [bb for bb in bounding_boxes if bb is not None]
|
|
97
|
+
|
|
98
|
+
if len(valid_boxes) == 0:
|
|
99
|
+
msg = "No valid input boxes to consider"
|
|
100
|
+
raise ValueError(msg)
|
|
101
|
+
|
|
102
|
+
if not all(
|
|
103
|
+
valid_boxes[0].original_shape == bb.original_shape for bb in valid_boxes
|
|
104
|
+
):
|
|
105
|
+
msg = "Different shapes, and not sure this is really supported or meaningful"
|
|
106
|
+
raise ValueError(msg)
|
|
107
|
+
|
|
108
|
+
xmin = int(np.min([bb.xmin for bb in valid_boxes]))
|
|
109
|
+
xmax = int(np.max([bb.xmax for bb in valid_boxes]))
|
|
110
|
+
ymin = int(np.min([bb.ymin for bb in valid_boxes]))
|
|
111
|
+
ymax = int(np.max([bb.ymax for bb in valid_boxes]))
|
|
112
|
+
|
|
113
|
+
y_span = ymax - ymin
|
|
114
|
+
x_span = xmax - xmin
|
|
115
|
+
|
|
116
|
+
return BoundingBox(
|
|
117
|
+
xmin=xmin,
|
|
118
|
+
xmax=xmax,
|
|
119
|
+
ymin=ymin,
|
|
120
|
+
ymax=ymax,
|
|
121
|
+
y_span=y_span,
|
|
122
|
+
x_span=x_span,
|
|
123
|
+
original_shape=valid_boxes[0].original_shape,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
async def get_bounding_box_for_fits_coro(
|
|
128
|
+
fits_path: Path, invalidate_zeros: bool = False
|
|
129
|
+
) -> BoundingBox | None:
|
|
130
|
+
"""Create a bounding box for an image contained in a FITS file.
|
|
131
|
+
|
|
132
|
+
The assumption is that the FITS file contains an image, not a cube.
|
|
133
|
+
If the cube can bot be reshapped to an image without losing data
|
|
134
|
+
the underlying bounding box creation will fail.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
fits_path (Path): The fits image to call
|
|
138
|
+
invalidate_zeros (bool, optional): Mark pixels that are exactly 0.0 as invalid (NaN them). Defaults to False.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
BoundingBox | None: The bounding box that describes the bounds of valid data. If all data are invalid (and not bounding box possible) None is returned.
|
|
142
|
+
"""
|
|
143
|
+
data = await asyncio.to_thread(fits.getdata, fits_path, memmap=False)
|
|
144
|
+
data = np.squeeze(data)
|
|
145
|
+
|
|
146
|
+
if invalidate_zeros:
|
|
147
|
+
data[data == 0.0] = np.nan
|
|
148
|
+
|
|
149
|
+
return await asyncio.to_thread(create_bound_box_plane, image_data=data)
|
|
@@ -34,6 +34,11 @@ from radio_beam.beam import NoBeamException
|
|
|
34
34
|
from tqdm.asyncio import tqdm
|
|
35
35
|
|
|
36
36
|
from fitscube.asyncio import gather_with_limit, sync_wrapper
|
|
37
|
+
from fitscube.bounding_box import (
|
|
38
|
+
BoundingBox,
|
|
39
|
+
extract_common_bounding_box,
|
|
40
|
+
get_bounding_box_for_fits_coro,
|
|
41
|
+
)
|
|
37
42
|
from fitscube.logging import TQDM_OUT, logger, set_verbosity
|
|
38
43
|
|
|
39
44
|
T = TypeVar("T")
|
|
@@ -170,6 +175,7 @@ async def create_cube_from_scratch_coro(
|
|
|
170
175
|
output_shape = output_wcs.array_shape
|
|
171
176
|
msg = f"Creating a new FITS file with shape {output_shape}"
|
|
172
177
|
logger.info(msg)
|
|
178
|
+
|
|
173
179
|
# If the output shape is less than 1801, we can create a blank array
|
|
174
180
|
# in memory and write it to disk
|
|
175
181
|
if np.prod(output_shape) < 1801:
|
|
@@ -240,6 +246,7 @@ async def create_output_cube_coro(
|
|
|
240
246
|
single_beam: bool = False,
|
|
241
247
|
overwrite: bool = False,
|
|
242
248
|
time_domain_mode: bool = False,
|
|
249
|
+
bounding_box: BoundingBox | None = None,
|
|
243
250
|
) -> InitResult:
|
|
244
251
|
"""Initialize the data cube.
|
|
245
252
|
|
|
@@ -358,6 +365,14 @@ async def create_output_cube_coro(
|
|
|
358
365
|
)
|
|
359
366
|
del new_header["BMAJ"], new_header["BMIN"], new_header["BPA"]
|
|
360
367
|
|
|
368
|
+
if bounding_box:
|
|
369
|
+
logger.info("Updating CRPIX1 and CRPIX2 header values to reflect bounding box")
|
|
370
|
+
new_header["CRPIX1"] -= bounding_box.ymin
|
|
371
|
+
new_header["CRPIX2"] -= bounding_box.xmin
|
|
372
|
+
logger.info("Updating NAXIS1 and NAXIS2 ro reflect bounding box")
|
|
373
|
+
new_header["NAXIS1"] = bounding_box.y_span
|
|
374
|
+
new_header["NAXIS2"] = bounding_box.x_span
|
|
375
|
+
|
|
361
376
|
plane_shape = list(old_data.shape)
|
|
362
377
|
cube_shape = plane_shape.copy()
|
|
363
378
|
if is_2d:
|
|
@@ -619,6 +634,8 @@ async def process_channel(
|
|
|
619
634
|
old_channel: int,
|
|
620
635
|
is_missing: bool,
|
|
621
636
|
file_list: list[Path],
|
|
637
|
+
bounding_box: BoundingBox | None = None,
|
|
638
|
+
invalidate_zeros: bool = False,
|
|
622
639
|
) -> None:
|
|
623
640
|
msg = f"Processing channel {new_channel}"
|
|
624
641
|
logger.info(msg)
|
|
@@ -631,6 +648,15 @@ async def process_channel(
|
|
|
631
648
|
fits.getdata, file_list[old_channel], memmap=False
|
|
632
649
|
)
|
|
633
650
|
|
|
651
|
+
if bounding_box is not None:
|
|
652
|
+
plane = plane[
|
|
653
|
+
...,
|
|
654
|
+
bounding_box.xmin : bounding_box.xmax,
|
|
655
|
+
bounding_box.ymin : bounding_box.ymax,
|
|
656
|
+
]
|
|
657
|
+
if invalidate_zeros:
|
|
658
|
+
plane[plane == 0.0] = np.nan
|
|
659
|
+
|
|
634
660
|
await write_channel_to_cube_coro(
|
|
635
661
|
file_handle=file_handle,
|
|
636
662
|
plane=plane,
|
|
@@ -650,6 +676,8 @@ async def combine_fits_coro(
|
|
|
650
676
|
overwrite: bool = False,
|
|
651
677
|
max_workers: int | None = None,
|
|
652
678
|
time_domain_mode: bool = False,
|
|
679
|
+
bounding_box: bool = False,
|
|
680
|
+
invalidate_zeros: bool = False,
|
|
653
681
|
) -> u.Quantity:
|
|
654
682
|
"""Combine FITS files into a cube.
|
|
655
683
|
Can handle either frequency or time dimensions agnostically
|
|
@@ -660,6 +688,8 @@ async def combine_fits_coro(
|
|
|
660
688
|
ignore_spec (bool, optional): Ignore frequency/time information. Defaults to False.
|
|
661
689
|
create_blanks (bool, optional): Attempt to create even frequency spacing. Defaults to False.
|
|
662
690
|
time_domain_mode (bool, optional): Work in time domain mode - make a time-cube. Default = False.
|
|
691
|
+
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
|
+
invalidate_zeros (bool, optionals): Set pixels whose values are exactly zero to NaNs. Defaults to False.
|
|
663
693
|
|
|
664
694
|
Returns:
|
|
665
695
|
tuple[fits.HDUList, u.Quantity]: The combined FITS cube and frequencies
|
|
@@ -704,6 +734,21 @@ async def combine_fits_coro(
|
|
|
704
734
|
specs = specs[new_sort_idx]
|
|
705
735
|
missing_chan_idx = missing_chan_idx[new_sort_idx]
|
|
706
736
|
|
|
737
|
+
# Get the bounding box, if requested
|
|
738
|
+
final_bounding_box = None
|
|
739
|
+
if bounding_box:
|
|
740
|
+
boxes_futures = [
|
|
741
|
+
get_bounding_box_for_fits_coro(
|
|
742
|
+
fits_path=fits_path, invalidate_zeros=invalidate_zeros
|
|
743
|
+
)
|
|
744
|
+
for fits_path in file_list
|
|
745
|
+
]
|
|
746
|
+
boxes = await gather_with_limit(
|
|
747
|
+
max_workers, *boxes_futures, desc="Bounding boxes"
|
|
748
|
+
)
|
|
749
|
+
final_bounding_box = extract_common_bounding_box(bounding_boxes=boxes)
|
|
750
|
+
logger.info(f"The final bounding box is: {final_bounding_box=}")
|
|
751
|
+
|
|
707
752
|
# Initialize the data cube
|
|
708
753
|
new_header, _, _, _ = await create_output_cube_coro(
|
|
709
754
|
old_name=file_list[0],
|
|
@@ -714,6 +759,7 @@ async def combine_fits_coro(
|
|
|
714
759
|
single_beam=single_beam,
|
|
715
760
|
overwrite=overwrite,
|
|
716
761
|
time_domain_mode=time_domain_mode,
|
|
762
|
+
bounding_box=final_bounding_box,
|
|
717
763
|
)
|
|
718
764
|
|
|
719
765
|
new_channels = np.arange(len(specs))
|
|
@@ -741,6 +787,8 @@ async def combine_fits_coro(
|
|
|
741
787
|
old_channel=old_channel,
|
|
742
788
|
is_missing=is_missing,
|
|
743
789
|
file_list=file_list,
|
|
790
|
+
bounding_box=final_bounding_box,
|
|
791
|
+
invalidate_zeros=invalidate_zeros,
|
|
744
792
|
)
|
|
745
793
|
coros.append(coro)
|
|
746
794
|
|
|
@@ -823,6 +871,16 @@ def get_parser(
|
|
|
823
871
|
default=None,
|
|
824
872
|
help="Maximum number of workers to use for concurrent processing",
|
|
825
873
|
)
|
|
874
|
+
parser.add_argument(
|
|
875
|
+
"--bounding-box",
|
|
876
|
+
action="store_true",
|
|
877
|
+
help="Attempt to consider padded images when creating the cube. Requires an extract read of the input data.",
|
|
878
|
+
)
|
|
879
|
+
parser.add_argument(
|
|
880
|
+
"--invalidate-zeros",
|
|
881
|
+
action="store_true",
|
|
882
|
+
help="Set pixels whose values are exactly zero to NaNs",
|
|
883
|
+
)
|
|
826
884
|
|
|
827
885
|
return parser
|
|
828
886
|
|
|
@@ -863,6 +921,8 @@ def cli(args: argparse.Namespace | None = None) -> None:
|
|
|
863
921
|
overwrite=overwrite,
|
|
864
922
|
max_workers=args.max_workers,
|
|
865
923
|
time_domain_mode=time_domain_mode,
|
|
924
|
+
bounding_box=args.bounding_box,
|
|
925
|
+
invalidate_zeros=args.invalidate_zeros,
|
|
866
926
|
)
|
|
867
927
|
|
|
868
928
|
spequency = "times" if time_domain_mode else "frequencies"
|
|
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
|