Skip to content

Commit 6f45baa

Browse files
committed
Add min_sep selection option
1 parent 90a8fad commit 6f45baa

2 files changed

Lines changed: 69 additions & 1 deletion

File tree

piff/select.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Select(object):
4141
# Parameters that derived classes should ignore if they appear in the config dict
4242
# (since they are handled by the base class).
4343
base_keys= ['min_snr', 'max_snr', 'hsm_size_reject', 'max_pixel_cut', 'reject_where',
44-
'max_edge_frac', 'stamp_center_size', 'max_mask_pixels', 'nstars',
44+
'max_edge_frac', 'stamp_center_size', 'max_mask_pixels', 'nstars', 'min_sep',
4545
'reserve_frac', 'seed']
4646

4747
def __init__(self, config, logger=None):
@@ -51,6 +51,7 @@ def __init__(self, config, logger=None):
5151
self.max_edge_frac = config.get('max_edge_frac', None)
5252
self.stamp_center_size = config.get('stamp_center_size', 13)
5353
self.max_mask_pixels = config.get('max_mask_pixels', None)
54+
self.min_sep = config.get('min_sep', None)
5455
self.hsm_size_reject = config.get('hsm_size_reject', 0.)
5556
self.max_pixel_cut = config.get('max_pixel_cut', None)
5657
self.reject_where = config.get('reject_where', None)
@@ -108,6 +109,9 @@ def process(cls, config_select, objects, logger=None, select_only=False):
108109
[default 13].
109110
:max_mask_pixels: If given, reject stars with more than this many masked pixels
110111
(i.e. those with w=0). [default: None]
112+
:min_sep: If given, reject stars that have any other selected star within
113+
this separation (in arcsec) on the same chip. Any star in such a
114+
close pair/group is rejected. [default: None]
111115
:hsm_size_reject: Whether to reject stars with a very different hsm-measured size than
112116
the other stars in the input catalog. (Used to reject objects with
113117
neighbors or other junk in the postage stamp.) [default: False]
@@ -352,6 +356,29 @@ def rejectStars(self, stars, logger=None):
352356
np.sum(flux>=flux_cut), flux_cut, self.max_pixel_cut)
353357
good_stars = [s for f,s in zip(flux,good_stars) if f < flux_cut]
354358

359+
if self.min_sep is not None and len(good_stars) > 1:
360+
if self.min_sep <= 0.:
361+
raise ValueError("min_sep must be positive")
362+
# Reject all stars that have a near neighbor within min_sep on the same chip.
363+
# Use (u,v), which are in arcsec, so min_sep is in natural angular units.
364+
remove = np.zeros(len(good_stars), dtype=bool)
365+
chipnums = np.array([int(star['chipnum']) for star in good_stars], dtype=int)
366+
for chipnum in np.unique(chipnums):
367+
chip_idx = np.where(chipnums == chipnum)[0]
368+
if len(chip_idx) < 2:
369+
continue
370+
uv = np.array([(good_stars[k]['u'], good_stars[k]['v']) for k in chip_idx])
371+
tree = scipy.spatial.cKDTree(uv)
372+
pairs = tree.query_pairs(self.min_sep)
373+
for i, j in pairs:
374+
remove[chip_idx[i]] = True
375+
remove[chip_idx[j]] = True
376+
nremove = int(np.sum(remove))
377+
if nremove > 0:
378+
logger.verbose("Rejected %d stars for having neighbors within min_sep=%s arcsec",
379+
nremove, self.min_sep)
380+
good_stars = [s for s, r in zip(good_stars, remove) if not r]
381+
355382
if self.nstars is not None and self.nstars < len(good_stars):
356383
if self.nstars < 0:
357384
raise ValueError("nstars must be non-negative")

tests/test_input.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1757,6 +1757,47 @@ def test_stars():
17571757
assert snr0 == 0.
17581758

17591759

1760+
@timer
1761+
def test_select_min_sep():
1762+
"""Test select.min_sep culling of close neighbors in arcsec."""
1763+
logger = None
1764+
1765+
stars = [
1766+
# Close pair on same chip: 3 pixels at 0.1 arcsec/pixel => 0.3 arcsec separation.
1767+
piff.Star.makeTarget(x=10, y=10, scale=0.1, properties={'chipnum': 0}).withFlux(
1768+
1.0, (0.0, 0.0)
1769+
),
1770+
piff.Star.makeTarget(x=13, y=10, scale=0.1, properties={'chipnum': 0}).withFlux(
1771+
1.0, (0.0, 0.0)
1772+
),
1773+
# Farther star on same chip.
1774+
piff.Star.makeTarget(x=30, y=10, scale=0.1, properties={'chipnum': 0}).withFlux(
1775+
1.0, (0.0, 0.0)
1776+
),
1777+
# Same position as first star, but different chip; should not be compared.
1778+
piff.Star.makeTarget(x=10, y=10, scale=0.1, properties={'chipnum': 1}).withFlux(
1779+
1.0, (0.0, 0.0)
1780+
),
1781+
]
1782+
1783+
# Disable SNR clipping so this test only exercises min_sep neighbor rejection behavior.
1784+
select = piff.FlagSelect({'max_snr': 0, 'min_sep': 0.5}, logger=logger)
1785+
kept = select.rejectStars(stars, logger=logger)
1786+
assert len(kept) == 2
1787+
assert sorted(int(s['chipnum']) for s in kept) == [0, 1]
1788+
assert any(np.isclose(s['x'], 30.0) and np.isclose(s['y'], 10.0) for s in kept)
1789+
1790+
# Smaller threshold should keep all stars.
1791+
select = piff.FlagSelect({'max_snr': 0, 'min_sep': 0.2}, logger=logger)
1792+
kept = select.rejectStars(stars, logger=logger)
1793+
assert len(kept) == 4
1794+
1795+
# Invalid negative min_sep should raise when min_sep logic is used.
1796+
with np.testing.assert_raises(ValueError):
1797+
select = piff.FlagSelect({'max_snr': 0, 'min_sep': -0.1}, logger=logger)
1798+
select.rejectStars(stars, logger=logger)
1799+
1800+
17601801
@timer
17611802
def test_pointing():
17621803
"""Test the input.setPointing function

0 commit comments

Comments
 (0)