Skip to content

Commit 3ccd31b

Browse files
committed
Bugfixes to sourcefinder internals for Python-2 compatibility.
* Fixed tuple-unpacking as function arguments - it appears Python-3 is a bit more flexible in this regard. * Fixed incorrect assignment of array.data instead of array * Refactored validator declarations a bit. * Added a mask-shape check to the Island constructor.
1 parent bc817d4 commit 3ccd31b

File tree

8 files changed

+59
-39
lines changed

8 files changed

+59
-39
lines changed

docs/source/notebooks/gaussian_parameterizations.ipynb

+7-7
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,10 @@
184184
"metadata": {},
185185
"outputs": [],
186186
"source": [
187-
"from fastimgproto.sourcefind.fit import Gaussian2dFit\n",
187+
"from fastimgproto.sourcefind.fit import Gaussian2dParams\n",
188188
"import numpy as np\n",
189-
"import astropy.units as u"
189+
"import astropy.units as u\n",
190+
"import attr"
190191
]
191192
},
192193
{
@@ -204,7 +205,7 @@
204205
"metadata": {},
205206
"outputs": [],
206207
"source": [
207-
"g1 = Gaussian2dFit(x_centre=0,\n",
208+
"g1 = Gaussian2dParams(x_centre=0,\n",
208209
" y_centre=0,\n",
209210
" amplitude=1,\n",
210211
" semimajor=1.5,\n",
@@ -239,7 +240,7 @@
239240
"metadata": {},
240241
"outputs": [],
241242
"source": [
242-
"g1.theta = -np.pi/4\n",
243+
"g1 = attr.evolve(g1, theta = -np.pi/4)\n",
243244
"g1.correlation"
244245
]
245246
},
@@ -249,8 +250,7 @@
249250
"metadata": {},
250251
"outputs": [],
251252
"source": [
252-
"g1.theta = np.pi/4\n",
253-
"g1.semimajor = 1000\n",
253+
"g1 = attr.evolve(g1, theta = np.pi/4, semimajor=1000)\n",
254254
"g1.correlation"
255255
]
256256
},
@@ -260,7 +260,7 @@
260260
"metadata": {},
261261
"outputs": [],
262262
"source": [
263-
"g1.semimajor = 1.0001\n",
263+
"g1 = attr.evolve(g1, semimajor=1.0001)\n",
264264
"g1.correlation"
265265
]
266266
}

docs/source/notebooks/simulate_and_image.ipynb

+2-2
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@
241241
"ax1.set_xlim(axlims)\n",
242242
"ax1.set_ylim(axlims)\n",
243243
"for src in sfimage.islands:\n",
244-
" ax1.axvline(src.xbar, ls=':')\n",
245-
" ax1.axhline(src.ybar, ls=':')"
244+
" ax1.axvline(src.params.moments_fit.x_centre, ls=':')\n",
245+
" ax1.axhline(src.params.moments_fit.y_centre, ls=':')"
246246
]
247247
}
248248
],

docs/source/notebooks/sourcefinding.ipynb

+6-4
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,10 @@
185185
"outputs": [],
186186
"source": [
187187
"plt.imshow(island.data)\n",
188-
"plt.xlim(island.extremum_x_idx-10,island.extremum_x_idx+10,)\n",
189-
"plt.ylim(island.extremum_y_idx-10,island.extremum_y_idx+10,)\n",
190-
"plt.scatter(island.xbar,island.ybar, marker='*', s=200, c='y',)"
188+
"plt.xlim(island.extremum.index.x-10,island.extremum.index.x+10,)\n",
189+
"plt.ylim(island.extremum.index.y-10,island.extremum.index.y+10,)\n",
190+
"moments_fit = island.params.moments_fit\n",
191+
"plt.scatter(moments_fit.x_centre,moments_fit.y_centre, marker='*', s=200, c='y',)"
191192
]
192193
},
193194
{
@@ -201,7 +202,8 @@
201202
"print()\n",
202203
"print(\"Island barycentres:\")\n",
203204
"for i in sfimage.islands:\n",
204-
" print(i.xbar, i.ybar)"
205+
" moments = i.params.moments_fit\n",
206+
" print(moments.x_centre, moments.y_centre)"
205207
]
206208
}
207209
],

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
install_requires = [
88
'astropy',
9-
'attrs>=16.3.0',
9+
'attrs>=17.2.0',
1010
'click',
1111
'drive-casa>=0.7.6',
1212
'pandas',

src/fastimgproto/sourcefind/image.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,8 @@
88
from scipy import ndimage
99
from scipy.optimize import OptimizeResult, least_squares
1010

11-
from fastimgproto.sourcefind.fit import (
12-
Gaussian2dParams,
13-
gaussian2d,
14-
gaussian2d_jac,
15-
)
16-
from fastimgproto.utils import (
17-
nonzero_bounding_slice_2d,
18-
positive_negative_sign_validator,
19-
)
11+
from fastimgproto.sourcefind.fit import Gaussian2dParams, gaussian2d
12+
from fastimgproto.utils import nonzero_bounding_slice_2d
2013

2114
_STRUCTURE_ELEMENT = np.array([[1, 1, 1],
2215
[1, 1, 1],
@@ -98,7 +91,12 @@ class IslandParams(object):
9891
"""
9992

10093
# Required for initialization
101-
sign = attrib(validator=positive_negative_sign_validator)
94+
sign = attrib()
95+
@sign.validator
96+
def check_sign(instance, attribute, value):
97+
if value not in (-1, 1):
98+
raise ValueError("'sign' should be +1 or -1")
99+
102100
extremum = attrib(validator=attr.validators.instance_of(Pixel))
103101

104102
# Optional
@@ -141,6 +139,11 @@ class Island(object):
141139
validator=attr.validators.instance_of(np.ndarray))
142140
mask = attrib(cmp=False,
143141
validator=attr.validators.instance_of(np.ndarray))
142+
@mask.validator
143+
def check_mask_shape(instance, attribute, value):
144+
if not value.shape == instance.parent_data.shape:
145+
raise ValueError("Mask-shape does not match parent-shape")
146+
144147
params = attrib(validator=attr.validators.instance_of(IslandParams))
145148

146149
# xbar = attrib(default=None)
@@ -158,7 +161,7 @@ def data(self):
158161
Return a MaskedArray view of the parent-array.
159162
"""
160163
return np.ma.MaskedArray(
161-
self.parent_data.data,
164+
self.parent_data,
162165
mask=self.mask,
163166
)
164167

src/fastimgproto/utils.py

-9
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,6 @@ def reset_progress_bar(progress_bar, total, description="Doing something"):
2727
return
2828

2929

30-
31-
def positive_negative_sign_validator(instance, attribute, value):
32-
"""
33-
Attrs validator for verifying ``value in (-1,1)``.
34-
"""
35-
if value not in (-1, 1):
36-
raise ValueError("'sign' should be +1 or -1")
37-
38-
3930
def nonzero_bounding_slice_2d(input):
4031
"""
4132
Get slices defining the bounding box for any nonzero / True-valued subarray.

tests/test_sourcefind/test_detection.py

+27-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import attr
44
import numpy as np
5+
import pytest
56
import scipy.ndimage
67
from pytest import approx
78

@@ -15,6 +16,10 @@
1516
)
1617
from fastimgproto.sourcefind.fit import Gaussian2dParams
1718
from fastimgproto.sourcefind.image import (
19+
Island,
20+
IslandParams,
21+
Pixel,
22+
PixelIndex,
1823
SourceFindImage,
1924
estimate_rms,
2025
extremum_pixel_index,
@@ -41,6 +46,26 @@ def test_rms_estimation():
4146
assert np.abs((rms_est - rms) / rms) < 0.05
4247

4348

49+
def test_island_constructor_validation():
50+
img = np.zeros((ydim, xdim), dtype=np.float_)
51+
bright_pixel = Pixel(index=PixelIndex(y=int(ydim / 2), x=int(xdim / 2)),
52+
value = 1.0)
53+
island_params = IslandParams(sign=1,
54+
extremum = bright_pixel)
55+
56+
bad_mask = np.ones((2,2), dtype=bool)
57+
with pytest.raises(ValueError):
58+
island = Island(parent_data = img,
59+
mask=bad_mask,
60+
params = island_params)
61+
62+
mask = np.ones_like(img, dtype=bool)
63+
mask[attr.astuple(bright_pixel.index)] = False
64+
island = Island(parent_data = img,
65+
mask=mask,
66+
params = island_params)
67+
68+
4469
def test_basic_source_detection():
4570
"""
4671
We use a flat background (rather than noisy) to avoid random-noise fluctuations
@@ -50,7 +75,7 @@ def test_basic_source_detection():
5075
start adding secondary sources and check we get multiple finds as
5176
expected...
5277
"""
53-
img = np.zeros((ydim, xdim))
78+
img = np.zeros((ydim, xdim), dtype=np.float_)
5479
add_gaussian2d_to_image(bright_src, img)
5580

5681
sf = SourceFindImage(img, detection_n_sigma=4,
@@ -127,7 +152,6 @@ def test_negative_source_detection():
127152
neg_island = negative_islands[0]
128153
pos_island = positive_islands[0]
129154

130-
131155
min_pixel_index = extremum_pixel_index(img, -1)
132156
assert attr.astuple(neg_island.extremum.index) == min_pixel_index
133157

@@ -137,7 +161,7 @@ def test_negative_source_detection():
137161
# Sanity check that the island masks look sensible
138162
# Check that the mask==False regions are disjoint - taking the boolean OR
139163
# on both masks should result in a fully `True` mask-array.
140-
assert (np.logical_or(neg_island.data.mask,pos_island.data.mask).all())
164+
assert (np.logical_or(neg_island.data.mask, pos_island.data.mask).all())
141165

142166
# And that the true/false regions look sensible for the extremum pixels:
143167
assert neg_island.data.mask[min_pixel_index] == False

tests/test_sourcefind/test_fitting.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ def test_gauss2d_func_and_jacobian():
5454
xy_pairs = np.column_stack((x_samples, y_samples))
5555

5656
looped_g2d = np.array(
57-
[gaussian2d(*tuple(pair), *attr.astuple(profile))
57+
[gaussian2d(*tuple(list(pair) + list(attr.astuple(profile))))
5858
for pair in xy_pairs])
5959
vectored_g2d = gaussian2d(x_samples, y_samples, *attr.astuple(profile))
6060
assert (looped_g2d == vectored_g2d).all()
6161

6262
looped_jac = np.array(
63-
[gaussian2d_jac(*tuple(pair), *attr.astuple(profile))
63+
[gaussian2d_jac(*tuple(list(pair) + list(attr.astuple(profile))))
6464
for pair in xy_pairs])
6565
vectored_jac = gaussian2d_jac(x_samples, y_samples, *attr.astuple(profile))
6666

0 commit comments

Comments
 (0)