Skip to content

Commit 1f00000

Browse files
authored
Merge pull request #31 from hstewart93/finder
Finder
2 parents 8ccccab + f807c49 commit 1f00000

16 files changed

Lines changed: 1536 additions & 90 deletions

.github/workflows/publish.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,3 @@ jobs:
3737
files: dist/*
3838
name: ${{ steps.branch_info.outputs.TAG }}
3939
tag_name: ${{ steps.branch_info.outputs.TAG }}
40-
41-

.github/workflows/pytest.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Pytest
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
pytest:
11+
name: Run tests
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: actions/setup-python@v5
16+
with:
17+
python-version: '3.12'
18+
- name: Install dependencies
19+
run: |
20+
python -m pip install --upgrade pip
21+
pip install -e .[ci]
22+
- name: Run tests
23+
run: |
24+
pytest

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include continunet/network/trained_model.h5

README.md

Lines changed: 103 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
# ContinUNet
2+
[![Pytest](https://github.com/hstewart93/continunet/actions/workflows/pytest.yml/badge.svg)](https://github.com/hstewart93/continunet/actions/workflows/pytest.yml)
3+
24
Source finding package for radio continuum data powered by U-Net segmentation algorithm.
35

6+
- [Paper](https://academic.oup.com/rasti/article/3/1/315/7685538?utm_source=advanceaccess&utm_campaign=rasti&utm_medium=email#supplementary-data)
7+
- [Installation](#installation)
8+
- [Developer Installation](#developer-installation)
9+
- [Quickstart](#quickstart)
10+
- [Example Notebook](https://github.com/hstewart93/continunet/tree/finder/continunet/user_example.ipynb)
11+
- [Training Dataset](https://www.kaggle.com/datasets/harrietstewart/continunet)
12+
- [Next Release](#development)
13+
414
## Installation
515
The project is available on [PyPI](https://pypi.org/project/continunet/), to install latest stable release use:
616

7-
```pip install continunet```
17+
```bash
18+
pip install continunet
19+
```
820

921
To install version in development, use:
1022

11-
```pip install git+https://github.com/hstewart93/continunet```
23+
```bash
24+
pip install git+https://github.com/hstewart93/continunet
25+
```
26+
27+
**ContinUNet has a `Python 3.9` minimum requirement.**
1228

1329
## Developer Installation
1430
If you want to contribute to the repository, install as follows:
1531

16-
Once you have cloned down this repository using `git clone` cd into the app directory eg.
32+
Once you have cloned down this repository using `git clone`, cd into the app directory:
1733

18-
```
34+
```bash
35+
git clone git@github.com:hstewart93/continunet.git
1936
cd continunet
2037
```
2138

2239
Create a virtual environment for development, if you are using bash:
2340

24-
```
41+
```bash
2542
python3 -m venv venv
2643
source venv/bin/activate
2744
pip install -e .[dev,ci]
@@ -31,10 +48,89 @@ To exit the virtual environment use `deactivate`.
3148

3249
This project used the black auto formatter which can be run on git commit along with flake8 if you install pre-commit. To do this run the following in your terminal from within your virtual environment.
3350

34-
```
51+
```bash
3552
pre-commit install
3653
```
3754

3855
Now pre-commit hooks should run on `git commit`.
3956

40-
The run the test suite use `pytest`.
57+
To run the test suite use `pytest`.
58+
59+
## Quickstart
60+
The package currently support `.FITS` type images. To perform source finding you can import the `finder` module:
61+
62+
```python
63+
from continunet.finder import Finder
64+
```
65+
66+
Load your image file:
67+
68+
```python
69+
finder = Finder("<filepath>")
70+
```
71+
72+
To produce a source catalogue and populate the `Finder` instance:
73+
74+
```python
75+
sources = finder.find()
76+
```
77+
78+
If you want to calculate the model map and residuals image as part of source finding, use the `Finder.find()` method with `generate_maps=True`:
79+
80+
```python
81+
sources = finder.find(generate_maps=True)
82+
model_map = finder.model_map
83+
residuals = finder.residuals
84+
```
85+
86+
Alternatively, manually calculate model map and residual images using:
87+
88+
```python
89+
model_map = finder.get_model_map()
90+
residuals = finder.get_residuals()
91+
```
92+
93+
Useful available attributes of the `Finder` object are:
94+
```python
95+
finder.sources # cleaned source catalogue
96+
finder.reconstructed_image # predicted image reconstructed by unet module
97+
finder.segmentation_map # predicted segmentation map
98+
finder.model_map # model map of cleaned predicted sources
99+
finder.residuals # residual image as numpy array
100+
finder.raw_sources # sources from labelled segmentation map before cleaning
101+
```
102+
103+
Export source catalogue using `finder.export_sources` as `.csv` by default or `.FITS` by setting `export_fits=True`:
104+
105+
```python
106+
finder.export_sources("<filepath>", export_fits=<Boolean>)
107+
```
108+
109+
Source parameters extracted are:
110+
111+
| **Parameter** | **Description** |
112+
|----------------------------|--------------------------------------------------------------------------------------------------------|
113+
| `x_location_original` | x coordinate of the source from the cutout used for inference |
114+
| `y_location_original` | y coordinate of the source from the cutout used for inference |
115+
| `orientation` | orientation of source ellipse in radians |
116+
| `major_axis` | major axis of source ellipse |
117+
| `minor_axis` | minor axis of source ellipse |
118+
| `flux_density_uncorrected` | total intensity of the segmented source region before beam corrections applied |
119+
| `label` | class label in predicted segmentation map |
120+
| `x_location` | x coordinate of the source in the original input image dimensions |
121+
| `y_location` | y coordinate of the source in the original input image dimensions |
122+
| `right_ascension` | RA coordinate of the source in the original input image dimensions |
123+
| `declination` | Dec coordinate of the source in the original input image dimensions |
124+
| `area` | area of source ellipse |
125+
| `position_angle` | position angle of source ellipse in degrees |
126+
| `correction_factor` | correction factor applied to flux density measurement to account for undersampling of synthesised beam |
127+
| `flux_density` | corrected flux density |
128+
129+
## Development
130+
ContinUNet is subject to ongoing development. To see the backlog of features and bug fixes please go to the [project board](https://github.com/users/hstewart93/projects/4/views/1). Please raise any feature requests or bugs as [issues](https://github.com/hstewart93/continunet/issues).
131+
132+
The following features will be added in the next release:
133+
134+
1. Exporting processed images to `.npy` and `.FTIS` [(#33)](https://github.com/hstewart93/continunet/issues/33)
135+
2. Inference for non-square images [(#27)](https://github.com/hstewart93/continunet/issues/27)
136+
3. Taking cutout of `ImageSquare` object before inference [(#28)](https://github.com/hstewart93/continunet/issues/28)

continunet/constants.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Constants for ContinuNet."""
2+
3+
4+
TRAINED_MODEL = "continunet/network/trained_model.h5"
5+
6+
# ANSI escape sequences
7+
RED = "\033[31m"
8+
GREEN = "\033[32m"
9+
YELLOW = "\033[33m"
10+
BLUE = "\033[34m"
11+
MAGENTA = "\033[35m"
12+
CYAN = "\033[36m"
13+
RESET = "\033[0m"

continunet/example_image.fits

759 KB
Binary file not shown.

continunet/finder.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Compile ContinUNet modules into Finder class for source finding."""
2+
3+
import importlib.resources
4+
import time
5+
6+
from astropy.table import Table
7+
8+
from continunet.constants import GREEN, RESET
9+
from continunet.image.fits import ImageSquare
10+
from continunet.image.processing import PreProcessor, PostProcessor
11+
from continunet.network.unet import Unet
12+
13+
14+
class Finder:
15+
"""Class for source finding in radio continuum images."""
16+
17+
def __init__(self, image: str, layers: int = 4):
18+
"""Initialise the Finder class.
19+
20+
Parameters
21+
----------
22+
image : str
23+
The path to the FITS image.
24+
layers : int
25+
The number of encoding and decoding layers in the U-Net model.
26+
Layers is set by default to 4, and cannot currently be changed.
27+
"""
28+
if not image.endswith(".fits"):
29+
raise ValueError("File must be a .fits file.")
30+
self.image = image
31+
if layers != 4:
32+
raise ValueError("Number of layers must be 4.")
33+
self.layers = layers
34+
self.image_object = None
35+
self.sources = None
36+
self.reconstructed_image = None
37+
self.post_processor = None
38+
self.segmentation_map = None
39+
self.model_map = None
40+
self.residuals = None
41+
self.raw_sources = None
42+
43+
def find(self, generate_maps=False, threshold="default"):
44+
"""Find sources in a continuum image."""
45+
start_time = time.time()
46+
# Load image
47+
self.image_object = ImageSquare(self.image)
48+
49+
# Pre-process image
50+
pre_processor = PreProcessor(self.image_object, self.layers)
51+
data = pre_processor.process()
52+
53+
# Run U-Net
54+
with importlib.resources.path("continunet.network", "trained_model.h5") as path:
55+
unet = Unet(data.shape[1:4], trained_model=path, image=data, layers=self.layers)
56+
self.reconstructed_image = unet.decode_image()
57+
58+
# Post-process reconstructed image
59+
self.post_processor = PostProcessor(unet.reconstructed, pre_processor, threshold=threshold)
60+
self.sources = self.post_processor.get_sources()
61+
self.segmentation_map = self.post_processor.segmentation_map
62+
self.raw_sources = self.post_processor.raw_sources
63+
64+
end_time = time.time()
65+
print(
66+
f"{GREEN}ContinUNet found {len(self.sources)} sources "
67+
f"in {(end_time - start_time):.2f} seconds.{RESET}"
68+
)
69+
70+
if generate_maps:
71+
self.model_map = self.post_processor.get_model_map()
72+
self.residuals = self.post_processor.get_residuals()
73+
self.segmentation_map = self.post_processor.segmentation_map
74+
75+
return self.sources
76+
77+
def export_sources(self, path: str, export_fits=False):
78+
"""Export source catalogue to a directory. Use export_fits=True to save as FITS."""
79+
if self.sources is None:
80+
raise ValueError("No sources to export.")
81+
if export_fits:
82+
table = Table.from_pandas(self.sources)
83+
table.write(path, format="fits", overwrite=True)
84+
return self
85+
86+
self.sources.to_csv(path)
87+
return self

continunet/image/fits.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from astropy.io import fits
77
from astropy.wcs import WCS
88

9+
from continunet.constants import CYAN, RESET
10+
911

1012
class FitsImage(ABC):
1113
"""Abstract model for an image imported from FITS format."""
@@ -22,6 +24,7 @@ def __init__(self, path):
2224

2325
def load(self):
2426
"""Load fits image from file and populate model args."""
27+
print(f"{CYAN}Loading FITS image from {self.path}...{RESET}")
2528
if not self.path:
2629
raise ValueError("Path to FITS file not provided.")
2730

continunet/image/pre_processing.py

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)