Skip to content

Commit 154df7c

Browse files
committed
Mimic more closely the behaviour of Astrometry.net
1 parent 3cbcb07 commit 154df7c

File tree

11 files changed

+954
-410
lines changed

11 files changed

+954
-410
lines changed

README.md

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
- [Match](#match)
2525
- [Star](#star)
2626
- [Series](#series)
27+
- [batches\_generator](#batches_generator)
28+
- [SupportsFloatMapping](#supportsfloatmapping)
2729
- [Contribute](#contribute)
2830
- [Publish](#publish)
2931
- [MSVC compatibility (work in progress)](#msvc-compatibility-work-in-progress)
@@ -75,8 +77,7 @@ stars = [
7577
]
7678

7779
solution = solver.solve(
78-
stars_xs=[star[0] for star in stars],
79-
stars_ys=[star[1] for star in stars],
80+
stars=stars,
8081
size_hint=None,
8182
position_hint=None,
8283
solution_parameters=astrometry.SolutionParameters(),
@@ -99,8 +100,7 @@ import astrometry
99100

100101
solver = ...
101102
solution = solver.solve(
102-
stars_xs=...,
103-
stars_ys=...,
103+
stars=...,
104104
size_hint=astrometry.SizeHint(
105105
lower_arcsec_per_pixel=1.0,
106106
upper_arcsec_per_pixel=2.0,
@@ -179,8 +179,7 @@ import astrometry
179179

180180
solver = ...
181181
solution = solver.solve(
182-
stars_xs=...,
183-
stars_ys=...,
182+
stars=...,
184183
size_hint=...,
185184
position_hint=...,
186185
solution_parameters=astrometry.SolutionParameters(
@@ -199,8 +198,7 @@ import astrometry
199198

200199
solver = ...
201200
solution = solver.solve(
202-
stars_xs=...,
203-
stars_ys=...,
201+
stars=...,
204202
size_hint=...,
205203
position_hint=...,
206204
solution_parameters=astrometry.SolutionParameters(
@@ -216,8 +214,7 @@ import astrometry
216214

217215
solver = ...
218216
solution = solver.solve(
219-
stars_xs=...,
220-
stars_ys=...,
217+
stars=...,
221218
size_hint=...,
222219
position_hint=...,
223220
solution_parameters=astrometry.SolutionParameters(
@@ -237,8 +234,7 @@ import astrometry
237234

238235
solver = ...
239236
solution = solver.solve(
240-
stars_xs=...,
241-
stars_ys=...,
237+
stars=...,
242238
size_hint=...,
243239
position_hint=...,
244240
solution_parameters=astrometry.SolutionParameters(
@@ -266,8 +262,7 @@ def logodds_callback(logodds_list: list[float]) -> astrometry.Action:
266262

267263
solver = ...
268264
solution = solver.solve(
269-
stars_xs=...,
270-
stars_ys=...,
265+
stars=...,
271266
size_hint=...,
272267
position_hint=...,
273268
solution_parameters=astrometry.SolutionParameters(
@@ -341,8 +336,7 @@ class Solver:
341336

342337
def solve(
343338
self,
344-
stars_xs: typing.Iterable[float],
345-
stars_ys: typing.Iterable[float],
339+
stars: typing.Iterable[SupportsFloatMapping],
346340
size_hint: typing.Optional[SizeHint],
347341
position_hint: typing.Optional[PositionHint],
348342
solution_parameters: SolutionParameters,
@@ -352,8 +346,7 @@ class Solver:
352346
`solve` is thread-safe and can be called any number of times.
353347

354348
- `index_files`: List of index files to use for solving. The list need not come from a `Series` object. Series subsets and combinations are possible as well.
355-
- `star_xs`: First pixel coordinate of the input stars.
356-
- `star_ys`: Second pixel coordinate of the input stars, must have the same length as `star_xs`.
349+
- `star`: iterator over a list of pixel coordinates for the input stars.
357350
- `size_hint`: Optional angular pixel size range ([SizeHint](#sizehint)). Significantly speeds up `solve` when provided. If `size_hint` is `None`, the range `[0.1, 1000.0]` is used. This default range can be changed by setting `astrometry.DEFAULT_LOWER_ARCSEC_PER_PIXEL` and `astrometry.DEFAULT_UPPER_ARCSEC_PER_PIXEL` to other values.
358351
- `position_hint`: Optional field center Ra/Dec coordinates and error radius ([PositionHint](#positionhint)). Significantly speeds up `solve` when provided. If `position_hint` is None, the entire sky is used (`radius_deg = 180.0`).
359352
- `solution_parameters`: Advanced solver parameters ([SolutionParameters](#solutionparameters))
@@ -424,17 +417,18 @@ class SolutionParameters:
424417
parity: Parity = Parity.BOTH
425418
tune_up_logodds_threshold: typing.Optional[float] = 14.0
426419
output_logodds_threshold: float = 21.0
420+
slices_generator: typing.Callable[[int], typing.Iterable[tuple[int, int]]] = astrometry.batches_generator(25)
427421
logodds_callback: typing.Callable[[list[float]], Action] = lambda _: Action.CONTINUE
428422
```
429423

430424
- `solve_id`: Optional plate identifier used in logging messages. If `solve_id` is `None`, it is automatically assigned a unique integer. The value can be retrieved from the Solution object (`solution.solve_id`).
431425
- `uniformize_index`: Uniformize field stars at the matched index scale before verifying a match.
432426
- `deduplicate`: De-duplicate field stars before verifying a match.
433-
- `sip_order`: Polynomial order of the Simple Imaging Polynomial distortion (see https://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf). `0` disables SIP distortion.
427+
- `sip_order`: Polynomial order of the Simple Imaging Polynomial distortion (see https://irsa.ipac.caltech.edu/data/SPITZER/docs/files/spitzer/shupeADASS.pdf). `0` disables SIP distortion. `tune_up_logodds_threshold` must be `None` if `sip_order` is `0`.
434428
- `sip_inverse_order`: Polynomial order of the inversee Simple Polynomial distortion. Usually equal to `sip_order`. `0` means "equal to `sip_order`".
435429
- `distance_from_quad_bonus`: Assume that stars far from the matched quad will have larger positional variance.
436430
- `positional_noise_pixels`: Expected error on the positions of stars.
437-
- `distractor_ratio`: Fraction of distractors in the range `[0, 1]`.
431+
- `distractor_ratio`: Fraction of distractors in the range `]0, 1]`.
438432
- `code_tolerance_l2_distance`: Code tolerance in 4D codespace L2 distance.
439433
- `minimum_quad_size_pixels`: Minimum size of field quads to try, `None` calculates the size automatically as `minimum_quad_size_fraction * min(Δx, Δy)`, where `Δx` (resp. `Δy`) is the maximum `x` distance (resp. `y` distance) between stars in the field.
440434
- `minimum_quad_size_fraction`: Only used if `minimum_quad_size_pixels` is `None` (see above).
@@ -444,6 +438,7 @@ class SolutionParameters:
444438
- `parity`: Parity.NORMAL does not flip the axes, Parity.FLIP does, and Parity.BOTH tries flipped and non-flipped axes (at the cost of doubling computations).
445439
- `tune_up_logodds_threshold`: Matches whose log-odds are larger than this value are tuned-up (SIP distortion estimation) and accepted if their post-tune-up log-odds are larger than `output_logodds_threshold`. `None` disables tune-up and distortion estimation (SIP). The default Astrometry.net value is `math.log(1e6)`.
446440
- `output_logodds_threshold`: Matches whose log-odds are larger than this value are immediately accepted (added to the solution matches). The default Astrometry.net value is `math.log(1e9)`.
441+
- `slices_generator`: User-provided function that takes a number of stars as parameter and returns an iterable (such as list) of two-elements tuples representing ranges. The first tuple item (start) is included while the second tuple item (end) is not. The returned ranges can have a variable size and/or overlap. The algorithm compares each range with the star catalogue sequentially. Small ranges significantly speed up the algorithm but increase the odds of missing matches. `astrometry.batches_generator(n)` generates non-overlapping batches of `n` stars.
447442
- `logodds_callback`: User-provided function that takes a list of matches log-odds as parameter and returns an `astrometry.Action` object. `astrometry.Action.CONTINUE` tells the solver to keep searching for matches whereas `astrometry.Action.STOP` tells the solver to return the current matches immediately. The log-odds list is sorted from highest to lowest value and should not be modified by the callback function.
448443

449444
Accepted matches are always tuned up, even if they hit `tune_up_logodds_threshold` and were already tuned-up. Since log-odds are compared with the thresholds before the tune-up, the final log-odds are often significantly larger than `output_logodds_threshold`. Set `tune_up_logodds_threshold` to a value larger than or equal to `output_logodds_threshold` to disable the first tune-up, and `None` to disable tune-up altogether. Tune-up logic is equivalent to the following Python snippet:
@@ -558,6 +553,27 @@ class Series:
558553

559554
Change the constants `astrometry.CHUNK_SIZE`, `astrometry.DOWNLOAD_SUFFIX` and `astrometry.TIMEOUT` to configure the downloader parameters.
560555

556+
## batches_generator
557+
558+
```py
559+
def batches_generator(
560+
batch_size: int,
561+
) -> typing.Callable[[int], typing.Iterable[tuple[int, int]]]:
562+
...
563+
```
564+
565+
- `batch_size` sets the size of the generated batches.
566+
567+
`batches_generator` returns a slices generator compatible with `SolutionParameters.slices_generator`. The slices are non-overlapping and non-full slices are ignored. For instance, a batch size of `25` over `83` stars would generate the slices `(0, 25)`, `(25, 50)`, and `(50, 75)`.
568+
569+
## SupportsFloatMapping
570+
571+
```py
572+
class SupportsFloatMapping(typing.Protocol):
573+
def __getitem__(self, index: typing.SupportsIndex, /) -> typing.SupportsFloat:
574+
...
575+
```
576+
561577
# Contribute
562578

563579
Clone this repository and pull its submodule:

astrometry/__init__.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
DEFAULT_UPPER_ARCSEC_PER_PIXEL = 1000.0
2626

2727

28+
class SupportsFloatMapping(typing.Protocol):
29+
def __getitem__(self, index: typing.SupportsIndex, /) -> typing.SupportsFloat:
30+
...
31+
32+
2833
@dataclasses.dataclass
2934
class SizeHint:
3035
lower_arcsec_per_pixel: float
@@ -62,6 +67,18 @@ class Parity(enum.IntEnum):
6267
BOTH = 2
6368

6469

70+
def batches_generator(
71+
batch_size: int,
72+
) -> typing.Callable[[int], typing.Iterable[tuple[int, int]]]:
73+
def slices_generator(stars_count: int) -> typing.Iterable[tuple[int, int]]:
74+
if stars_count < batch_size:
75+
yield (0, stars_count)
76+
for start in range(0, stars_count - batch_size + 1, batch_size):
77+
yield (start, start + batch_size)
78+
79+
return slices_generator
80+
81+
6582
@dataclasses.dataclass
6683
class SolutionParameters:
6784
solve_id: typing.Optional[str] = None
@@ -83,20 +100,24 @@ class SolutionParameters:
83100
parity: Parity = Parity.BOTH
84101
tune_up_logodds_threshold: typing.Optional[float] = 14.0 # None means "no tune-up"
85102
output_logodds_threshold: float = 21.0
103+
slices_generator: typing.Callable[
104+
[int], typing.Iterable[tuple[int, int]]
105+
] = batches_generator(25)
86106
logodds_callback: typing.Callable[[list[float]], Action] = lambda _: Action.CONTINUE
87107

88108
def __post_init__(self):
89109
assert self.sip_order >= 0
90110
assert self.sip_inverse_order >= 0
91111
assert self.positional_noise_pixels >= 0.0
92-
assert self.distractor_ratio >= 0.0 and self.distractor_ratio <= 1.0
112+
assert self.distractor_ratio > 0.0 and self.distractor_ratio <= 1.0
93113
assert self.code_tolerance_l2_distance >= 0.0
94114
assert (
95115
self.minimum_quad_size_pixels is None
96116
or self.minimum_quad_size_pixels >= 0.0
97117
)
98118
assert self.minimum_quad_size_fraction >= 0.0
99119
assert self.maximum_quad_size_pixels >= 0.0
120+
assert self.sip_order > 0 or self.tune_up_logodds_threshold is None
100121

101122

102123
@dataclasses.dataclass
@@ -142,18 +163,28 @@ def __init__(self, index_files: list[pathlib.Path]):
142163

143164
def solve(
144165
self,
145-
stars_xs: typing.Iterable[float],
146-
stars_ys: typing.Iterable[float],
166+
stars: typing.Iterable[SupportsFloatMapping],
147167
size_hint: typing.Optional[SizeHint],
148168
position_hint: typing.Optional[PositionHint],
149169
solution_parameters: SolutionParameters,
150170
) -> Solution:
151171
with self.solve_id_lock:
152172
self.solve_id += 1
153-
if not isinstance(stars_xs, list):
154-
stars_xs = list(stars_xs)
155-
if not isinstance(stars_ys, list):
156-
stars_ys = list(stars_ys)
173+
174+
stars_xs: list[float] = []
175+
stars_ys: list[float] = []
176+
for star in stars:
177+
stars_xs.append(float(star[0]))
178+
stars_ys.append(float(star[1]))
179+
slices_starts: list[int] = []
180+
slices_ends: list[int] = []
181+
for star_slice in solution_parameters.slices_generator(len(stars_xs)):
182+
assert star_slice[0] >= 0
183+
assert star_slice[1] <= len(stars_xs)
184+
assert star_slice[0] < star_slice[1]
185+
slices_starts.append(star_slice[0])
186+
slices_ends.append(star_slice[1])
187+
assert len(slices_starts) > 0
157188
solve_id = (
158189
str(self.solve_id)
159190
if solution_parameters.solve_id is None
@@ -218,6 +249,8 @@ def solve(
218249
int(solution_parameters.parity),
219250
solution_parameters.tune_up_logodds_threshold,
220251
solution_parameters.output_logodds_threshold,
252+
slices_starts,
253+
slices_ends,
221254
solution_parameters.logodds_callback,
222255
)
223256
if raw_solution is None:

0 commit comments

Comments
 (0)