Skip to content

Commit 531b804

Browse files
committed
Add convenience methods to load json data and generate WCS objects.
1 parent 66aceee commit 531b804

File tree

5 files changed

+166
-8
lines changed

5 files changed

+166
-8
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ astrometry_cache
44
build
55
wheelhouse
66
__pycache__
7+
test/solution.json

README.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,7 @@ solver = ...
151151
solution = ...
152152

153153
if solution.has_match():
154-
wcs = astropy.wcs.WCS(
155-
astropy.io.fits.Header(
156-
astropy.io.fits.Card(key, value[0], value[1])
157-
for key, value in wcs_fields.items()
158-
)
159-
)
154+
wcs = solution.best_match().astropy_wcs()
160155
pixels = wcs.all_world2pix(
161156
[[star.ra_deg, star.dec_deg] for star in solution.best_match().stars],
162157
0,
@@ -485,9 +480,15 @@ class Solution:
485480
def has_match(self) -> bool: ...
486481

487482
def best_match(self) -> Match: ...
483+
484+
def to_json(self) -> str: ...
485+
486+
@classmethod
487+
def from_json(cls, solution_as_json: str) -> Solution: ...
488+
488489
```
489490

490-
`matches` are sorted in descending log-odds order. `best_match` returns the first match in the list.
491+
`matches` are sorted in descending log-odds order. `best_match` returns the first match in the list. `to_json` and `from_json` may be used to save and load solutions.
491492

492493
## Match
493494

@@ -502,6 +503,8 @@ class Match:
502503
stars: tuple[Star, ...]
503504
quad_stars: tuple[Star, ...]
504505
wcs_fields: dict[str, tuple[typing.Any, str]]
506+
507+
def astropy_wcs(self) -> astropy.wcs.WCS: ...
505508
```
506509

507510
- `logodds`: Log-odds (https://en.wikipedia.org/wiki/Logit) of the match.
@@ -513,6 +516,8 @@ class Match:
513516
- `quad_stars`: The index stars subset (usually 4 but can be 3 or 5) used in the hash code search step (see https://arxiv.org/pdf/0910.2233.pdf, 2. Methods).
514517
- `wcs_fields`: WCS fields describing the transformation between pixel coordinates and world coordinates. This dictionary can be passed directly to `astropy.wcs.WCS`.
515518

519+
`astropy_wcs` generates an Astropy WCS object. See [Calculate field stars pixel positions with astropy](#calculate-field-stars-pixel-positions-with-astropy) for details.
520+
516521
## Star
517522

518523
```py
@@ -567,7 +572,7 @@ def batches_generator(
567572
...
568573
```
569574

570-
- `batch_size` sets the size of the generated batches.
575+
- `batch_size` sets the size of the generated batches.
571576

572577
`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)`.
573578

astrometry/__init__.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ class Match:
139139
quad_stars: tuple[Star, ...]
140140
wcs_fields: dict[str, tuple[typing.Any, str]]
141141

142+
def astropy_wcs(self):
143+
import astropy.wcs
144+
import astropy.io.fits
145+
146+
return astropy.wcs.WCS(
147+
astropy.io.fits.Header(
148+
astropy.io.fits.Card(key, value[0], value[1])
149+
for key, value in self.wcs_fields.items()
150+
)
151+
)
152+
142153

143154
@dataclasses.dataclass
144155
class Solution:
@@ -161,6 +172,40 @@ def to_json(self):
161172
match["index_path"] = str(match["index_path"])
162173
return json.dumps(solution_as_dict)
163174

175+
@classmethod
176+
def from_json(cls, solution_as_json: str):
177+
solution_as_dict = json.loads(solution_as_json)
178+
return cls(
179+
solve_id=solution_as_dict["solve_id"],
180+
matches=[
181+
Match(
182+
logodds=match_as_dict["logodds"],
183+
center_ra_deg=match_as_dict["center_ra_deg"],
184+
center_dec_deg=match_as_dict["center_dec_deg"],
185+
scale_arcsec_per_pixel=match_as_dict["scale_arcsec_per_pixel"],
186+
index_path=pathlib.Path(match_as_dict["index_path"]),
187+
stars=tuple(
188+
Star(
189+
ra_deg=star_as_dict["ra_deg"],
190+
dec_deg=star_as_dict["dec_deg"],
191+
metadata=star_as_dict["metadata"],
192+
)
193+
for star_as_dict in match_as_dict["stars"]
194+
),
195+
quad_stars=tuple(
196+
Star(
197+
ra_deg=star_as_dict["ra_deg"],
198+
dec_deg=star_as_dict["dec_deg"],
199+
metadata=star_as_dict["metadata"],
200+
)
201+
for star_as_dict in match_as_dict["quad_stars"]
202+
),
203+
wcs_fields=match_as_dict["wcs_fields"],
204+
)
205+
for match_as_dict in solution_as_dict["matches"]
206+
],
207+
)
208+
164209

165210
class Solver(astrometry_extension.Solver):
166211
def __init__(self, index_files: list[pathlib.Path]):

test/test_astropy.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import astrometry
2+
3+
solver = astrometry.Solver(
4+
astrometry.series_5200.index_files(
5+
cache_directory="astrometry_cache",
6+
scales={6},
7+
)
8+
)
9+
10+
stars = [
11+
[388.9140568247906, 656.5003281719216],
12+
[732.9210858972549, 473.66395545775106],
13+
[401.03459504299843, 253.788113189415],
14+
[312.6591868096163, 624.7527729425295],
15+
[694.6844564647456, 606.8371776658344],
16+
[741.7233477959561, 344.41284826261443],
17+
[867.3574610200455, 672.014835980283],
18+
[1063.546651153479, 593.7844603550848],
19+
[286.69070190952704, 422.170016812049],
20+
[401.12779619355155, 16.13543616977013],
21+
[205.12103484692776, 698.1847350789413],
22+
[202.88444768690894, 111.24830187635557],
23+
[339.1627757703069, 86.60739435924549],
24+
]
25+
26+
solution = solver.solve(
27+
stars=stars,
28+
size_hint=None,
29+
position_hint=None,
30+
solution_parameters=astrometry.SolutionParameters(),
31+
)
32+
33+
print(solution.best_match().astropy_wcs())

test/test_json.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import astrometry
2+
import logging
3+
import pathlib
4+
5+
dirname = pathlib.Path(__file__).resolve().parent
6+
7+
logging.getLogger().setLevel(logging.INFO)
8+
9+
solver = astrometry.Solver(
10+
astrometry.series_5200_heavy.index_files(
11+
cache_directory=dirname / "astrometry_cache",
12+
scales={6},
13+
)
14+
)
15+
16+
stars = [
17+
[283.7181611390329, 250.9437436782978],
18+
[388.9140568247906, 656.5003281719216],
19+
[732.9210858972549, 473.66395545775106],
20+
[401.03459504299843, 253.788113189415],
21+
[312.6591868096163, 624.7527729425295],
22+
[694.6844564647456, 606.8371776658344],
23+
[741.7233477959561, 344.41284826261443],
24+
[867.3574610200455, 672.014835980283],
25+
[1178.0701325872994, 120.66335820053426],
26+
[1063.546651153479, 593.7844603550848],
27+
[1266.479933601124, 478.6594707983611],
28+
[286.69070190952704, 422.170016812049],
29+
[401.12779619355155, 16.13543616977013],
30+
[393.1902113796317, 485.8601927863897],
31+
[865.3547639559572, 614.3599340062373],
32+
[205.12103484692776, 698.1847350789413],
33+
[504.2664247977979, 214.23557044789035],
34+
[-67.78648235582016, 646.7413890177716],
35+
[202.88444768690894, 111.24830187635557],
36+
[747.2580778813443, 116.51880571011176],
37+
[339.1627757703069, 86.60739435924549],
38+
[592.1438288540525, 508.6376406353861],
39+
]
40+
41+
print("Solve with default parameters")
42+
solution = solver.solve(
43+
stars=stars,
44+
size_hint=astrometry.SizeHint(
45+
lower_arcsec_per_pixel=1.0,
46+
upper_arcsec_per_pixel=2.0,
47+
),
48+
position_hint=astrometry.PositionHint(ra_deg=65.7, dec_deg=36.2, radius_deg=1.0),
49+
solution_parameters=astrometry.SolutionParameters(),
50+
)
51+
52+
print(f"Write the solution to {dirname / 'solution.json'}")
53+
with open(dirname / "solution.json", "w") as solution_file:
54+
solution_file.write(solution.to_json())
55+
56+
print(f"Read back the solution file")
57+
with open(dirname / "solution.json") as solution_file:
58+
read_solution = astrometry.Solution.from_json(solution_file.read())
59+
60+
if solution != read_solution:
61+
assert solution.solve_id == read_solution.solve_id
62+
for match, read_match in zip(solution.matches, read_solution.matches):
63+
if match != read_match:
64+
assert match.logodds == read_match.logodds
65+
assert match.center_ra_deg == read_match.center_ra_deg
66+
assert match.center_dec_deg == read_match.center_dec_deg
67+
assert match.scale_arcsec_per_pixel == read_match.scale_arcsec_per_pixel
68+
assert match.index_path == read_match.index_path
69+
for star, read_star in zip(match.stars, read_match.stars):
70+
assert star.ra_deg == read_star.ra_deg
71+
assert star.dec_deg == read_star.dec_deg
72+
assert star.metadata.keys() == read_star.metadata.keys()
73+
for key in star.metadata.keys():
74+
assert star.metadata[key] == read_star.metadata[key]

0 commit comments

Comments
 (0)