eye-patch 1.0.0a0__tar.gz → 1.1.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.
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.github/workflows/cd.yml +2 -2
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.github/workflows/ci.yml +9 -7
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.pre-commit-config.yaml +5 -4
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/PKG-INFO +9 -9
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/README.md +7 -6
- eye_patch-1.1.0/docs/_static/adaptive_mac_dark.png +0 -0
- eye_patch-1.1.0/docs/_static/adaptive_mac_light.png +0 -0
- eye_patch-1.1.0/docs/_static/open_filter.mp4 +0 -0
- eye_patch-1.1.0/docs/masking.md +387 -0
- eye_patch-1.1.0/docs/scripts/adaptive_mac_figure.py +304 -0
- eye_patch-1.1.0/eye_patch/__main__.py +8 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/_version.py +2 -2
- eye_patch-1.1.0/eye_patch/cli.py +49 -0
- eye_patch-1.1.0/eye_patch/convolve.py +219 -0
- eye_patch-1.1.0/eye_patch/erosion.py +325 -0
- eye_patch-1.1.0/eye_patch/extract.py +185 -0
- eye_patch-1.1.0/eye_patch/flood_fill.py +198 -0
- eye_patch-1.1.0/eye_patch/logging.py +48 -0
- eye_patch-1.1.0/eye_patch/mac.py +213 -0
- eye_patch-1.1.0/eye_patch/masking.py +313 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/naming.py +7 -5
- eye_patch-1.1.0/eye_patch/options.py +49 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/pixel_utils.py +15 -5
- eye_patch-1.1.0/eye_patch/snr.py +116 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/pyproject.toml +4 -4
- eye_patch-1.1.0/tests/conftest.py +42 -0
- eye_patch-1.1.0/tests/test_cli.py +122 -0
- eye_patch-1.1.0/tests/test_convolve.py +118 -0
- eye_patch-1.1.0/tests/test_erosion.py +337 -0
- eye_patch-1.1.0/tests/test_extract.py +180 -0
- eye_patch-1.1.0/tests/test_flood_fill.py +68 -0
- eye_patch-1.1.0/tests/test_logging.py +45 -0
- eye_patch-1.1.0/tests/test_mac.py +97 -0
- eye_patch-1.1.0/tests/test_masking.py +410 -0
- eye_patch-1.1.0/tests/test_snr.py +41 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/uv.lock +135 -0
- eye_patch-1.0.0a0/docs/_static/open_filter.mp4 +0 -0
- eye_patch-1.0.0a0/docs/masking.md +0 -214
- eye_patch-1.0.0a0/eye_patch/masking.py +0 -1174
- eye_patch-1.0.0a0/tests/test_masking.py +0 -514
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.copier-answers.yml +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.git_archival.txt +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.gitattributes +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.github/CONTRIBUTING.md +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.github/dependabot.yml +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.github/release.yml +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.gitignore +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/.readthedocs.yaml +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/LICENSE +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/docs/Makefile +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/docs/_static/multi_scale_erode.jpg +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/docs/conf.py +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/docs/index.md +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/__init__.py +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/_version.pyi +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/eye_patch/py.typed +0 -0
- {eye_patch-1.0.0a0 → eye_patch-1.1.0}/tests/test_package.py +0 -0
|
@@ -25,11 +25,11 @@ jobs:
|
|
|
25
25
|
runs-on: ubuntu-latest
|
|
26
26
|
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v7
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0
|
|
31
31
|
|
|
32
|
-
- uses: hynek/build-and-inspect-python-package@
|
|
32
|
+
- uses: hynek/build-and-inspect-python-package@v3.0.1
|
|
33
33
|
|
|
34
34
|
publish:
|
|
35
35
|
needs: [dist]
|
|
@@ -22,12 +22,14 @@ jobs:
|
|
|
22
22
|
name: Format
|
|
23
23
|
runs-on: ubuntu-latest
|
|
24
24
|
steps:
|
|
25
|
-
- uses: actions/checkout@
|
|
25
|
+
- uses: actions/checkout@v7
|
|
26
26
|
with:
|
|
27
27
|
fetch-depth: 0
|
|
28
|
-
- uses: actions/setup-python@
|
|
28
|
+
- uses: actions/setup-python@v7
|
|
29
29
|
with:
|
|
30
|
-
python-version:
|
|
30
|
+
python-version: |
|
|
31
|
+
3.11
|
|
32
|
+
3.x
|
|
31
33
|
|
|
32
34
|
- uses: astral-sh/setup-uv@v7
|
|
33
35
|
|
|
@@ -42,15 +44,15 @@ jobs:
|
|
|
42
44
|
strategy:
|
|
43
45
|
fail-fast: false
|
|
44
46
|
matrix:
|
|
45
|
-
python-version: ["3.
|
|
47
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
46
48
|
runs-on: [ubuntu-latest]
|
|
47
49
|
|
|
48
50
|
steps:
|
|
49
|
-
- uses: actions/checkout@
|
|
51
|
+
- uses: actions/checkout@v7
|
|
50
52
|
with:
|
|
51
53
|
fetch-depth: 0
|
|
52
54
|
|
|
53
|
-
- uses: actions/setup-python@
|
|
55
|
+
- uses: actions/setup-python@v7
|
|
54
56
|
with:
|
|
55
57
|
python-version: ${{ matrix.python-version }}
|
|
56
58
|
allow-prereleases: true
|
|
@@ -66,6 +68,6 @@ jobs:
|
|
|
66
68
|
--durations=20
|
|
67
69
|
|
|
68
70
|
- name: Upload coverage report
|
|
69
|
-
uses: codecov/codecov-action@
|
|
71
|
+
uses: codecov/codecov-action@v7
|
|
70
72
|
with:
|
|
71
73
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -36,25 +36,26 @@ repos:
|
|
|
36
36
|
- id: rst-inline-touching-normal
|
|
37
37
|
|
|
38
38
|
- repo: https://github.com/rbubley/mirrors-prettier
|
|
39
|
-
rev: "v3.
|
|
39
|
+
rev: "v3.9.4"
|
|
40
40
|
hooks:
|
|
41
41
|
- id: prettier
|
|
42
42
|
types_or: [yaml, markdown, html, css, scss, javascript, json]
|
|
43
43
|
args: [--prose-wrap=always]
|
|
44
44
|
|
|
45
45
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
46
|
-
rev: "v0.15.
|
|
46
|
+
rev: "v0.15.20"
|
|
47
47
|
hooks:
|
|
48
48
|
- id: ruff-check
|
|
49
49
|
args: ["--fix", "--show-fixes"]
|
|
50
50
|
- id: ruff-format
|
|
51
51
|
|
|
52
52
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
53
|
-
rev: "v2.
|
|
53
|
+
rev: "v2.1.0"
|
|
54
54
|
hooks:
|
|
55
55
|
- id: mypy
|
|
56
56
|
files: eye_patch|tests
|
|
57
57
|
args: ["--ignore-missing-imports"]
|
|
58
|
+
language_version: python3.11
|
|
58
59
|
additional_dependencies:
|
|
59
60
|
- numpy
|
|
60
61
|
- capn-crunch
|
|
@@ -87,7 +88,7 @@ repos:
|
|
|
87
88
|
additional_dependencies: ["validate-pyproject-schema-store[all]"]
|
|
88
89
|
|
|
89
90
|
- repo: https://github.com/python-jsonschema/check-jsonschema
|
|
90
|
-
rev: "0.37.
|
|
91
|
+
rev: "0.37.4"
|
|
91
92
|
hooks:
|
|
92
93
|
- id: check-dependabot
|
|
93
94
|
- id: check-github-workflows
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: eye-patch
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: The pirate mask.
|
|
5
5
|
Project-URL: Homepage, https://github.com/flint-crew/eye-patch
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/flint-crew/eye-patch/issues
|
|
@@ -16,7 +16,6 @@ Classifier: Operating System :: OS Independent
|
|
|
16
16
|
Classifier: Programming Language :: Python
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
@@ -57,7 +56,7 @@ Description-Content-Type: text/markdown
|
|
|
57
56
|
|
|
58
57
|
The pirate mask!
|
|
59
58
|
|
|
60
|
-
A
|
|
59
|
+
A Python library and CLI tools for creating masks to assist deconvolution of
|
|
61
60
|
interferometric data.
|
|
62
61
|
|
|
63
62
|
This library was originally the masking module of the
|
|
@@ -69,7 +68,7 @@ separated out for flexible use.
|
|
|
69
68
|
## Documentation
|
|
70
69
|
|
|
71
70
|
Full documentation is provided on
|
|
72
|
-
[
|
|
71
|
+
[Read the Docs](https://eye-patch.readthedocs.io/).
|
|
73
72
|
|
|
74
73
|
## Installation
|
|
75
74
|
|
|
@@ -91,7 +90,7 @@ Or, from a local clone:
|
|
|
91
90
|
|
|
92
91
|
```bash
|
|
93
92
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
94
|
-
cd
|
|
93
|
+
cd eye-patch
|
|
95
94
|
pip install -e .
|
|
96
95
|
```
|
|
97
96
|
|
|
@@ -100,13 +99,14 @@ installations. Reproducible builds can be created using `uv sync`:
|
|
|
100
99
|
|
|
101
100
|
```bash
|
|
102
101
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
102
|
+
cd eye-patch
|
|
103
103
|
uv venv
|
|
104
104
|
uv sync
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
## Contributions
|
|
108
108
|
|
|
109
|
-
Contributions are welcome! Please do submit a pull
|
|
109
|
+
Contributions are welcome! Please do submit a pull request or issue if you spot
|
|
110
110
|
something you would like to address.
|
|
111
111
|
|
|
112
112
|
The full set of dev tooling can be installed via:
|
|
@@ -114,13 +114,13 @@ The full set of dev tooling can be installed via:
|
|
|
114
114
|
```bash
|
|
115
115
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
116
116
|
cd eye-patch
|
|
117
|
-
pip install
|
|
117
|
+
pip install -e . --group dev # --group needs pip 25.1 or newer
|
|
118
118
|
```
|
|
119
119
|
|
|
120
120
|
Or, using `uv`:
|
|
121
121
|
|
|
122
122
|
```bash
|
|
123
123
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
124
|
-
cd
|
|
124
|
+
cd eye-patch
|
|
125
125
|
uv sync --group dev
|
|
126
126
|
```
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
The pirate mask!
|
|
25
25
|
|
|
26
|
-
A
|
|
26
|
+
A Python library and CLI tools for creating masks to assist deconvolution of
|
|
27
27
|
interferometric data.
|
|
28
28
|
|
|
29
29
|
This library was originally the masking module of the
|
|
@@ -35,7 +35,7 @@ separated out for flexible use.
|
|
|
35
35
|
## Documentation
|
|
36
36
|
|
|
37
37
|
Full documentation is provided on
|
|
38
|
-
[
|
|
38
|
+
[Read the Docs](https://eye-patch.readthedocs.io/).
|
|
39
39
|
|
|
40
40
|
## Installation
|
|
41
41
|
|
|
@@ -57,7 +57,7 @@ Or, from a local clone:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
60
|
-
cd
|
|
60
|
+
cd eye-patch
|
|
61
61
|
pip install -e .
|
|
62
62
|
```
|
|
63
63
|
|
|
@@ -66,13 +66,14 @@ installations. Reproducible builds can be created using `uv sync`:
|
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
68
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
69
|
+
cd eye-patch
|
|
69
70
|
uv venv
|
|
70
71
|
uv sync
|
|
71
72
|
```
|
|
72
73
|
|
|
73
74
|
## Contributions
|
|
74
75
|
|
|
75
|
-
Contributions are welcome! Please do submit a pull
|
|
76
|
+
Contributions are welcome! Please do submit a pull request or issue if you spot
|
|
76
77
|
something you would like to address.
|
|
77
78
|
|
|
78
79
|
The full set of dev tooling can be installed via:
|
|
@@ -80,13 +81,13 @@ The full set of dev tooling can be installed via:
|
|
|
80
81
|
```bash
|
|
81
82
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
82
83
|
cd eye-patch
|
|
83
|
-
pip install
|
|
84
|
+
pip install -e . --group dev # --group needs pip 25.1 or newer
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
Or, using `uv`:
|
|
87
88
|
|
|
88
89
|
```bash
|
|
89
90
|
git clone https://github.com/flint-crew/eye-patch.git
|
|
90
|
-
cd
|
|
91
|
+
cd eye-patch
|
|
91
92
|
uv sync --group dev
|
|
92
93
|
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
# Creating Clean Masks
|
|
2
|
+
|
|
3
|
+
The imaging performance of CLEAN and its derivatives can be improved through the
|
|
4
|
+
construction of reliable clean mask regions. These regions restrict the set of
|
|
5
|
+
pixels that are involved during the peak finding stage of CLEAN. Here the aim of
|
|
6
|
+
the game is to optimally select pixels that contain genuine emission. Issues
|
|
7
|
+
such as clean bias, sub-optimal self-calibration and clean divergence can be
|
|
8
|
+
minimised or outright eliminated provided a clean mask has been reliably
|
|
9
|
+
constructed.
|
|
10
|
+
|
|
11
|
+
`eye-patch` provides functionality to construct such clean masks, where the
|
|
12
|
+
output is a pixel-wise mask of arbitrary shapes that either allow or deny
|
|
13
|
+
cleaning to occur at certain pixel locations. These are intended to be evaluated
|
|
14
|
+
against a restored FITS image. Pixels in the output clean mask are either `0`
|
|
15
|
+
(cleaning is not allowed here) or `1` (cleaning is allowed here), apart from the
|
|
16
|
+
per-scale masks described below.
|
|
17
|
+
|
|
18
|
+
## Available statistics
|
|
19
|
+
|
|
20
|
+
`eye-patch` currently supports two statistics to identify pixels of significance
|
|
21
|
+
that should be cleaned.
|
|
22
|
+
|
|
23
|
+
### Signal-to-noise (SNR)
|
|
24
|
+
|
|
25
|
+
The obvious method is based on signal-to-noise (SNR). After constructing
|
|
26
|
+
background ({math}`\mu`) and noise ({math}`\sigma`) measures across an image, a
|
|
27
|
+
direction-dependent SNR ({math}`\mathrm{SNR}`) across the image
|
|
28
|
+
({math}`\mathrm{img}`) can be expressed as:
|
|
29
|
+
|
|
30
|
+
{math}`\mathrm{SNR}(x,y) = \frac{\left(\mathrm{img}-\mu\right)}{\sigma}.`
|
|
31
|
+
|
|
32
|
+
In the simplest case, constant values may be set across the extent of the image,
|
|
33
|
+
with {math}`\mu=0` and {math}`\sigma=\mathrm{SD}(\mathrm{img})`. These can be
|
|
34
|
+
replaced with more sophisticated schemes that compute position-dependent metrics
|
|
35
|
+
that also incorporate iterative outlier clipping (e.g.
|
|
36
|
+
[Background and Noise Estimator](https://github.com/PaulHancock/Aegean)) or
|
|
37
|
+
robust statistics (e.g.
|
|
38
|
+
[Selavy](https://www.atnf.csiro.au/computing/software/askapsoft/sdp/docs/current/analysis/selavy.html)).
|
|
39
|
+
|
|
40
|
+
At their heart, these SNR-based processes assume Gaussian-distributed, zero-mean
|
|
41
|
+
noise, where pixel intensities that are unlikely to occur by chance are assumed
|
|
42
|
+
to be genuine emission. That is to say, pixels that are {math}`\gg5\sigma` are
|
|
43
|
+
likely to be real and should be included in a mask for cleaning. Without the
|
|
44
|
+
flood fill (see below), the pixels with an SNR above `MaskingOptions.base_clip`
|
|
45
|
+
form the mask.
|
|
46
|
+
|
|
47
|
+
### Minimum absolute clip
|
|
48
|
+
|
|
49
|
+
SNR-based measures have a clear statistical foundation when identifying bright
|
|
50
|
+
pixels. However, there are situations when such a metric could be perturbed or
|
|
51
|
+
otherwise corrupted, including:
|
|
52
|
+
|
|
53
|
+
- Calibration errors that produce imaging artefacts,
|
|
54
|
+
- Deconvolution errors that accumulate over minor/major iterations,
|
|
55
|
+
- Mishandling of the w-term so that the 2D Fourier transform approximation
|
|
56
|
+
breaks down,
|
|
57
|
+
- Extended diffuse structures that represent a large fraction of the region used
|
|
58
|
+
to calculate the local {math}`\mu` or {math}`\sigma` quantities,
|
|
59
|
+
- Combinations of the above, or
|
|
60
|
+
- Gremlins lurking around in the data.
|
|
61
|
+
|
|
62
|
+
Ultimately, if the regions being considered in the derivation of the noise do
|
|
63
|
+
not appear Gaussian-like, the robustness of methods to calculate the local noise
|
|
64
|
+
level is suspect. Additionally, it is unclear whether an accurate noise
|
|
65
|
+
estimation is even an appropriate basis for such regions. For instance,
|
|
66
|
+
artefacts around a bright source produced by phase error could be
|
|
67
|
+
{math}`\gg\sigma` if the genuine source is sufficiently bright.
|
|
68
|
+
|
|
69
|
+
We introduce the Minimum Absolute Clip ({math}`\mathrm{MAC}`) as an alternative
|
|
70
|
+
metric. It assumes:
|
|
71
|
+
|
|
72
|
+
1. approximately zero-mean Gaussian noise,
|
|
73
|
+
2. the sky brightness is positive definite, and
|
|
74
|
+
3. should there be a significantly bright negative component, there will be a
|
|
75
|
+
positive one of comparable brightness nearby.
|
|
76
|
+
|
|
77
|
+
These assumptions are fair for Stokes-I images. A masking function can therefore
|
|
78
|
+
be constructed, where:
|
|
79
|
+
|
|
80
|
+
{math}`\mathrm{MAC}(x, y) = \mathrm{img} > P \times |\mathrm{RollingMinimum}\left(\mathrm{img}, \mathrm{BoxSize}(\mathrm{img}, x, y)\right)|`
|
|
81
|
+
|
|
82
|
+
where {math}`\mathrm{RollingMinimum}` is a minimum boxcar filter whose size, in
|
|
83
|
+
pixels, is returned by {math}`\mathrm{BoxSize}`, which could be tuned in a
|
|
84
|
+
position-dependent way. The absolute value of the
|
|
85
|
+
{math}`\mathrm{RollingMinimum}` function is multiplied by an increase factor
|
|
86
|
+
{math}`P` to raise the minimum positive threshold. The {math}`\mathrm{MAC}` is a
|
|
87
|
+
simple and efficient statistic to compute.
|
|
88
|
+
|
|
89
|
+
The choice of an appropriate boxcar size is an important consideration. A larger
|
|
90
|
+
size enforces more conservative behaviour from the {math}`\mathrm{MAC}` process.
|
|
91
|
+
Should too small a region be supplied, then large multi-scale features are
|
|
92
|
+
typically less reliably assessed. Extensions can be made to detect when the
|
|
93
|
+
rolling box filter size is too small (e.g. by an imbalance of positive to
|
|
94
|
+
negative pixels). A basic adaptive boxcar size is implemented via the
|
|
95
|
+
`MaskingOptions.mac_adaptive_max_depth`,
|
|
96
|
+
`MaskingOptions.mac_adaptive_step_factor` and
|
|
97
|
+
`MaskingOptions.mac_adaptive_skew_delta` parameters.
|
|
98
|
+
|
|
99
|
+
<!-- The figures are made with docs/scripts/adaptive_mac_figure.py -->
|
|
100
|
+
|
|
101
|
+
```{figure} _static/adaptive_mac_light.png
|
|
102
|
+
:alt: A cut through an image, where a fixed box gives a clip level that rises far above a wide, bright source and leaves its middle unmasked, while the adaptive box keeps the clip level at the noise and masks the whole source.
|
|
103
|
+
:figclass: only-light
|
|
104
|
+
:width: 100%
|
|
105
|
+
|
|
106
|
+
The top panel is a cut through an image of unit noise (grey), with a compact
|
|
107
|
+
source and a bright source that is wider than the default 75-pixel box. The blue
|
|
108
|
+
and orange lines are the clip levels of the {math}`\mathrm{MAC}`, with an
|
|
109
|
+
increase factor of 4, from a fixed box and from the adaptive box
|
|
110
|
+
(`mac_adaptive_max_depth=3`). A pixel is included in the clean mask wherever the
|
|
111
|
+
image is above the clip level, and the bottom panel shows the resulting masks along the cut, where
|
|
112
|
+
the bars are True. With a fixed box, the rolling minimum in the middle of the
|
|
113
|
+
wide source only sees the source, so the clip level rises above it and the
|
|
114
|
+
middle of the source is missed, leaving a hole. The adaptive box doubles in size
|
|
115
|
+
wherever positive pixels dominate it, so its clip level stays at the noise and
|
|
116
|
+
the whole source is masked. Away from the wide source the two clip levels are
|
|
117
|
+
the same, and their lines overlap.
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```{figure} _static/adaptive_mac_dark.png
|
|
121
|
+
:alt: A cut through an image, where a fixed box gives a clip level that rises far above a wide, bright source and leaves its middle unmasked, while the adaptive box keeps the clip level at the noise and masks the whole source.
|
|
122
|
+
:figclass: only-dark
|
|
123
|
+
:width: 100%
|
|
124
|
+
|
|
125
|
+
The top panel is a cut through an image of unit noise (grey), with a compact
|
|
126
|
+
source and a bright source that is wider than the default 75-pixel box. The blue
|
|
127
|
+
and orange lines are the clip levels of the {math}`\mathrm{MAC}`, with an
|
|
128
|
+
increase factor of 4, from a fixed box and from the adaptive box
|
|
129
|
+
(`mac_adaptive_max_depth=3`). A pixel is included in the clean mask wherever the
|
|
130
|
+
image is above the clip level, and the bottom panel shows the resulting masks along the cut, where
|
|
131
|
+
the bars are True. With a fixed box, the rolling minimum in the middle of the
|
|
132
|
+
wide source only sees the source, so the clip level rises above it and the
|
|
133
|
+
middle of the source is missed, leaving a hole. The adaptive box doubles in size
|
|
134
|
+
wherever positive pixels dominate it, so its clip level stays at the noise and
|
|
135
|
+
the whole source is masked. Away from the wide source the two clip levels are
|
|
136
|
+
the same, and their lines overlap.
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The {math}`\mathrm{MAC}` is selected with `MaskingOptions.use_mac`, and may be
|
|
140
|
+
used with or without the flood fill (see below). Without the flood fill, the
|
|
141
|
+
increase factor {math}`P` is `MaskingOptions.base_clip`, and with it, the seed
|
|
142
|
+
and flood clips take its place. As the {math}`\mathrm{MAC}` operates on the
|
|
143
|
+
image itself, no background or noise images are needed.
|
|
144
|
+
|
|
145
|
+
## Reverse flood filling
|
|
146
|
+
|
|
147
|
+
Cleaning sources with features that are both faint and diffuse is difficult. On
|
|
148
|
+
a per-pixel basis it is often a reality that a minimum signal-to-noise threshold
|
|
149
|
+
is not met. Should the cleaning thresholds (set in either absolute
|
|
150
|
+
{math}`\mathrm{Jy}/\mathrm{beam}` units or in terms of {math}`\sigma` units) be
|
|
151
|
+
too high, this diffuse emission is never cleaned. In `eye-patch` we include a
|
|
152
|
+
reverse flood fill procedure that grows islands of pixels above an initial
|
|
153
|
+
criterion out to adjacent pixels that meet a secondary, lower level. This
|
|
154
|
+
process is the initial step of most source finders.
|
|
155
|
+
|
|
156
|
+
There are two steps to this process:
|
|
157
|
+
|
|
158
|
+
1. _Initial significant island detection_: The statistic of choice (see above)
|
|
159
|
+
is computed, and all pixels above the threshold are marked as pixels to
|
|
160
|
+
clean. This threshold is set via `MaskingOptions.seed_clip`.
|
|
161
|
+
2. _Growing the initial set of islands_: Islands that are identified as
|
|
162
|
+
significant go through a dilation process, which is restricted to the pixels
|
|
163
|
+
above a lower threshold. This lower clip is set via
|
|
164
|
+
`MaskingOptions.flood_clip`.
|
|
165
|
+
|
|
166
|
+
The flood fill procedure is 'opt in', and is activated via
|
|
167
|
+
`MaskingOptions.flood_fill`. Either statistic may be used to seed and grow
|
|
168
|
+
islands. With the {math}`\mathrm{MAC}`, the seed and flood clips are increase
|
|
169
|
+
factors {math}`P` of the rolling minimum, rather than SNR levels.
|
|
170
|
+
|
|
171
|
+
## Islands of faint and diffuse emission
|
|
172
|
+
|
|
173
|
+
Islands of faint and diffuse emission that would otherwise be missed may also be
|
|
174
|
+
added via `MaskingOptions.grow_low_snr_island`, with or without the flood fill.
|
|
175
|
+
Pixels above `MaskingOptions.grow_low_snr_island_clip` are joined together, and
|
|
176
|
+
islands made up of at least `MaskingOptions.grow_low_snr_island_min_pixels`
|
|
177
|
+
pixels are added to the mask. When the {math}`\mathrm{MAC}` statistic is used,
|
|
178
|
+
the clip is an increase factor of the same clipping level as the other clips.
|
|
179
|
+
For a 75-pixel box over noise, this clipping level is around {math}`3.7\sigma`,
|
|
180
|
+
so a factor near `0.5` is comparable to a {math}`1.75\sigma` clip of a signal
|
|
181
|
+
map.
|
|
182
|
+
|
|
183
|
+
This option is 'opt in', and complements rather than replaces the flood fill or
|
|
184
|
+
`MaskingOptions.base_clip`. Its islands are formed separately from the rest of
|
|
185
|
+
the mask, using only `MaskingOptions.grow_low_snr_island_clip` and
|
|
186
|
+
`MaskingOptions.grow_low_snr_island_min_pixels` rather than the seed and flood
|
|
187
|
+
clips, and are then added to the mask. As it only ever adds pixels, it can go
|
|
188
|
+
awry. Anything it picks up in error, such as a large region of noise, artefacts
|
|
189
|
+
or poorly subtracted background above its clip, stays in the mask, as nothing
|
|
190
|
+
later removes such an island. The beam erosion (see below), should it be used,
|
|
191
|
+
only trims the edges of islands. Care should be taken with a low clip or a small
|
|
192
|
+
minimum island size, as both make such errors more likely.
|
|
193
|
+
|
|
194
|
+
<!-- TODO: Need to include some other image here -->
|
|
195
|
+
|
|
196
|
+
## Eroding output islands
|
|
197
|
+
|
|
198
|
+
Output binary clean masks are naturally at the resolution of the input image (at
|
|
199
|
+
least in `eye-patch`). All pixels that reside in an island are candidate pixels
|
|
200
|
+
where cleaning is allowed to occur. Consider an unresolved source that is
|
|
201
|
+
perfectly aligned to the discrete pixel grid. In principle, its clean component
|
|
202
|
+
should be confined to a single pixel. However, if the source is sufficiently
|
|
203
|
+
bright, the resulting mask is made up of many pixels (i.e. the shape of the
|
|
204
|
+
restoring beam). Should deep cleaning be employed, it is possible that small
|
|
205
|
+
perturbations from the noise underlying a source would also be cleaned.
|
|
206
|
+
|
|
207
|
+
A binary erosion process with the erosion structure set to the shape of the
|
|
208
|
+
restoring beam at a particular power level can be used to 'contract' all pixel
|
|
209
|
+
masks. The output should better reflect where clean components ought to be
|
|
210
|
+
placed. See the `MaskingOptions.beam_shape_erode` and
|
|
211
|
+
`MaskingOptions.beam_shape_erode_minimum_response` options to activate and
|
|
212
|
+
control this process. The minimum response should be set between 0 and 1
|
|
213
|
+
(exclusive), where numbers closer to 0 represent a larger binary erosion
|
|
214
|
+
structure shape. That is to say, islands need to be _larger_ for any pixels to
|
|
215
|
+
remain as `MaskingOptions.beam_shape_erode_minimum_response` approaches 0.
|
|
216
|
+
|
|
217
|
+
### Multi-scale beam erosion
|
|
218
|
+
|
|
219
|
+
Basic routines to enable per-scale beam shape erosion are provided via
|
|
220
|
+
`MaskingOptions.multiscale_scales`, together with
|
|
221
|
+
`MaskingOptions.beam_shape_erode`. For each specified scale the restoring beam
|
|
222
|
+
(the base shape in this erosion) is convolved with the circular Gaussian that
|
|
223
|
+
wsclean uses for that scale. The resulting beam is then evaluated in the basic
|
|
224
|
+
process outlined above, eroding the clean islands at the native resolution to
|
|
225
|
+
the clean islands at the specified scale.
|
|
226
|
+
|
|
227
|
+
The scales are wsclean multi-scale scales in pixels, and are not the FWHM of the
|
|
228
|
+
Gaussian. wsclean (via radler) forms the Gaussian of a scale with
|
|
229
|
+
{math}`\sigma = 3/16` of the scale, which is a FWHM of about `0.44` of the
|
|
230
|
+
scale. A scale of 16 pixels is therefore a Gaussian with a FWHM of about 7
|
|
231
|
+
pixels. These masks are intended for wsclean's Gaussian scales
|
|
232
|
+
(`-multiscale-shape gaussian`).
|
|
233
|
+
|
|
234
|
+
```{note}
|
|
235
|
+
Versions of `eye-patch` up to v1.0.1 used each scale as the FWHM of the Gaussian.
|
|
236
|
+
This made the erosion structure of a scale about 2.3 times wider than the
|
|
237
|
+
wsclean Gaussian of the same scale, so larger scales were eroded more than
|
|
238
|
+
intended. Masks at larger scales are now larger than before, and settings tuned
|
|
239
|
+
against the earlier behaviour may need to be revisited. The example image below
|
|
240
|
+
was made with the earlier behaviour.
|
|
241
|
+
|
|
242
|
+
To compare against the earlier behaviour, set
|
|
243
|
+
`MaskingOptions.beam_shape_erode_scales_as_fwhm` (or
|
|
244
|
+
`--beam-shape-erode-scales-as-fwhm` on the CLI). Only the width of each scale is
|
|
245
|
+
restored. Other fixes, such as centring the erosion structure on a pixel, still
|
|
246
|
+
apply.
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The clean mask of each scale is stored as a 'bitmapped' value. As the masks are
|
|
250
|
+
floats, a pixel within the mask of the `n`'th scale has `2**n` added to it.
|
|
251
|
+
Scales are first sorted into ascending order, as wsclean reads the `n`'th bit as
|
|
252
|
+
its `n`'th smallest scale.
|
|
253
|
+
|
|
254
|
+
When more than one scale is specified, this bitmapped mask is written to
|
|
255
|
+
`<image>.scalemask.fits`. With a single scale, or without erosion, the mask is
|
|
256
|
+
written to `<image>.mask.fits`. Should the image header be missing the beam
|
|
257
|
+
information, no erosion is performed, and the mask is also written to
|
|
258
|
+
`<image>.mask.fits`.
|
|
259
|
+
|
|
260
|
+
This is intended to provide a mechanism to ensure cleaning at larger scales
|
|
261
|
+
occurs in well-defined regions. An example of the erosion process for
|
|
262
|
+
`0 1 2 4 8 16 32 64 128 256 512 1024 2056` is shown below. Point source islands
|
|
263
|
+
are still in the mask. As they are only present at the first scale, they are
|
|
264
|
+
stored as `2**0 = 1`.
|
|
265
|
+
|
|
266
|
+
```{image} _static/multi_scale_erode.jpg
|
|
267
|
+
:alt: Example of multi-scale beam erode
|
|
268
|
+
:width: 100%
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## `--convolve-first` option
|
|
272
|
+
|
|
273
|
+
Historically, for extragalactic fields the reverse flood fill process described
|
|
274
|
+
above, using a minimum absolute clip as the threshold statistic, produced
|
|
275
|
+
reliable clean masks. A subsequent binary erosion process to attempt to
|
|
276
|
+
characterise per-scale clean masks produced meaningful results. For regions like
|
|
277
|
+
the Galactic Plane, however, the reverse flood fill process would often miss
|
|
278
|
+
diffuse emission that was both extended and below the flood fill clip level.
|
|
279
|
+
Reducing this lower level clip would not consistently capture such faint
|
|
280
|
+
extended structures, and pixels boosted by noise would often leak into the clean
|
|
281
|
+
mask from legitimate islands (flying spaghetti monsters).
|
|
282
|
+
|
|
283
|
+
The `--convolve-first` option activates a routine where the base image is first
|
|
284
|
+
put through an `open` filter before being convolved to some desired scale, using
|
|
285
|
+
the same Gaussian that wsclean uses for that scale. Significance thresholding
|
|
286
|
+
operations are then applied to this filtered image. Importantly, the `open`
|
|
287
|
+
filter attempts to isolate small structures from larger ones. This is
|
|
288
|
+
implemented through the combined usage of a minimum filter followed by a maximum
|
|
289
|
+
filter (could be considered a high-pass filter). The per-scale clean masks are
|
|
290
|
+
always written to `<image>.scalemask.fits`, using the same bitmapped
|
|
291
|
+
representation as the multi-scale beam erosion.
|
|
292
|
+
|
|
293
|
+
```{raw} html
|
|
294
|
+
<video width="100%" controls>
|
|
295
|
+
<source src="_static/open_filter.mp4" type="video/mp4">
|
|
296
|
+
Your browser does not support the video tag.
|
|
297
|
+
</video>
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Should this option be activated, be aware that the seed and flood clipping
|
|
301
|
+
thresholds change, and it should be possible to use lower limits. For example,
|
|
302
|
+
`seed_clip=1.02` and `flood_clip=0.15` may be used, as these are increase
|
|
303
|
+
factors of the minimum absolute clip, which this option always uses. At the
|
|
304
|
+
native scale (a scale of `0`), the flood clip is raised to at least `0.8`.
|
|
305
|
+
|
|
306
|
+
## Accessing via the CLI
|
|
307
|
+
|
|
308
|
+
The masking utility may be accessed via the `eye-patch` CLI entry point:
|
|
309
|
+
|
|
310
|
+
```{argparse}
|
|
311
|
+
:ref: eye_patch.cli.get_parser
|
|
312
|
+
:prog: eye_patch_masking
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
## The `MaskingOptions` class
|
|
316
|
+
|
|
317
|
+
Embedded below is the `MaskingOptions` class used to construct clean masks.
|
|
318
|
+
Input values are validated by `pydantic` to ensure they are appropriately typed.
|
|
319
|
+
|
|
320
|
+
```{literalinclude} ../eye_patch/options.py
|
|
321
|
+
:pyobject: MaskingOptions
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Changes from v1.0.1 and earlier versions
|
|
325
|
+
|
|
326
|
+
### Renamed options
|
|
327
|
+
|
|
328
|
+
Several `MaskingOptions` attributes were renamed so that each name describes
|
|
329
|
+
what the option does, rather than where it was first used. The table below maps
|
|
330
|
+
each earlier name to its new name.
|
|
331
|
+
|
|
332
|
+
| Earlier name | New name |
|
|
333
|
+
| ----------------------------------------- | -------------------------------- |
|
|
334
|
+
| `base_snr_clip` | `base_clip` |
|
|
335
|
+
| `flood_fill_positive_seed_clip` | `seed_clip` |
|
|
336
|
+
| `flood_fill_positive_flood_clip` | `flood_clip` |
|
|
337
|
+
| `flood_fill_use_mac` | `use_mac` |
|
|
338
|
+
| `flood_fill_use_mac_box_size` | `mac_box_size` |
|
|
339
|
+
| `flood_fill_use_mac_adaptive_step_factor` | `mac_adaptive_step_factor` |
|
|
340
|
+
| `flood_fill_use_mac_adaptive_skew_delta` | `mac_adaptive_skew_delta` |
|
|
341
|
+
| `flood_fill_use_mac_adaptive_max_depth` | `mac_adaptive_max_depth` |
|
|
342
|
+
| `beam_shape_erode_scales` | `multiscale_scales` |
|
|
343
|
+
| `grow_low_snr_island_size` | `grow_low_snr_island_min_pixels` |
|
|
344
|
+
|
|
345
|
+
The CLI flags follow the same mapping, with dashes in place of underscores. For
|
|
346
|
+
example, `--flood-fill-positive-seed-clip` is now `--seed-clip`.
|
|
347
|
+
|
|
348
|
+
Earlier names are no longer accepted. An earlier name, whether in a strategy
|
|
349
|
+
file or passed to `MaskingOptions`, raises a validation error rather than being
|
|
350
|
+
silently ignored, so a stale setting is caught when it is loaded. The masking
|
|
351
|
+
options of [Flint](https://github.com/flint-crew/flint) use the earlier names,
|
|
352
|
+
so this table also maps a Flint strategy file onto `eye-patch`.
|
|
353
|
+
|
|
354
|
+
### Changes in behaviour
|
|
355
|
+
|
|
356
|
+
- **The {math}`\mathrm{MAC}` no longer needs the flood fill.** `use_mac`
|
|
357
|
+
(formerly `flood_fill_use_mac`) applies with or without `flood_fill`. Without
|
|
358
|
+
the flood fill, the mask is the pixels above `base_clip` times the
|
|
359
|
+
{math}`\mathrm{MAC}` clipping level. Previously `flood_fill_use_mac` was
|
|
360
|
+
ignored unless `flood_fill` was set, and `base_snr_clip` was applied to the
|
|
361
|
+
signal image instead. As the {math}`\mathrm{MAC}` operates on the image
|
|
362
|
+
itself, no signal image is formed, so the RMS and background images are not
|
|
363
|
+
needed.
|
|
364
|
+
- **Islands of faint and diffuse emission no longer need the flood fill.**
|
|
365
|
+
`grow_low_snr_island` adds its islands to the mask whether or not `flood_fill`
|
|
366
|
+
is set. Previously it was silently ignored without the flood fill. It remains
|
|
367
|
+
opt in, and complements rather than replaces the flood fill or `base_clip`.
|
|
368
|
+
Its islands are formed separately, from `grow_low_snr_island_clip` and
|
|
369
|
+
`grow_low_snr_island_min_pixels` rather than the seed and flood clips, and are
|
|
370
|
+
then added to the mask. As it only ever adds pixels, it can go awry: anything
|
|
371
|
+
it picks up in error, such as a large region of noise, artefacts or poorly
|
|
372
|
+
subtracted background above its clip, stays in the mask. Nothing later removes
|
|
373
|
+
such an island, as the beam erosion, should it be used, only trims the edges
|
|
374
|
+
of islands.
|
|
375
|
+
- **`base_clip` follows the statistic.** With `use_mac`, it is an increase
|
|
376
|
+
factor of the {math}`\mathrm{MAC}` clipping level, as `seed_clip` and
|
|
377
|
+
`flood_clip` already were.
|
|
378
|
+
- **Masks made without the flood fill are written as 32-bit floats**, rather
|
|
379
|
+
than 64-bit floats. Their values are unchanged.
|
|
380
|
+
- **Masks extracted from a mosaic take the shape of their template image.**
|
|
381
|
+
Should the template carry degenerate frequency and Stokes axes, as images
|
|
382
|
+
written by wsclean do, the extracted masks now carry them too, rather than
|
|
383
|
+
always being two-dimensional. Their values are unchanged.
|
|
384
|
+
|
|
385
|
+
Settings that set `flood_fill_use_mac` or `grow_low_snr_island` without
|
|
386
|
+
`flood_fill` now produce a different mask, and should be revisited. The
|
|
387
|
+
`convolve_first` option is unaffected.
|