1extern crate mapproj;
2#[macro_use]
3extern crate quick_error;
4
5#[doc = include_str!("../readme.md")]
6pub mod error;
7
8use coo_system::CooSystem;
9use error::Error;
10pub mod coo_system;
11pub mod params;
12mod projection;
13mod sip;
14mod utils;
15
16use crate::projection::WCSCanonicalProjection;
17pub use params::WCSParams;
18
19use mapproj::{
21 conic::{cod::Cod, coe::Coe, coo::Coo, cop::Cop},
22 cylindrical::{car::Car, cea::Cea, cyp::Cyp, mer::Mer},
23 hybrid::hpx::Hpx,
24 img2celestial::Img2Celestial,
25 img2proj::{WcsImgXY2ProjXY, WcsWithSipImgXY2ProjXY},
26 pseudocyl::{ait::Ait, mol::Mol, par::Par, sfl::Sfl},
27 zenithal::{
28 air::Air, arc::Arc, azp::Azp, ncp::Ncp, sin::Sin, stg::Stg, szp::Szp, tan::Tan, zea::Zea,
29 zpn::Zpn,
30 },
31 XYZ,
32};
33
34use paste::paste;
35macro_rules! create_specific_proj {
37 ( $proj_name:ident, $params:expr, $ctype1:expr, $naxis1:expr, $naxis2:expr, $crpix1:expr, $crpix2:expr, $img2proj:expr ) => {{
38 let (positional_angle, proj) = $proj_name::parse_proj(&$params)?;
39
40 let is_sip_found = &$ctype1[($ctype1.len() - 3)..] == "SIP";
41 if is_sip_found {
42 let sip = sip::parse_sip($params, $naxis1, $naxis2, $crpix1, $crpix2)?;
43 let img2proj = WcsWithSipImgXY2ProjXY::new($img2proj, sip);
44
45 paste! {
46 Ok((WCSCelestialProj::[ <$proj_name Sip> ](Img2Celestial::new(img2proj, proj)), positional_angle))
47 }
48 } else {
49 Ok((
50 WCSCelestialProj::$proj_name(Img2Celestial::new($img2proj, proj)),
51 positional_angle,
52 ))
53 }
54 }};
55}
56
57pub type ImgXY = mapproj::ImgXY;
60pub type LonLat = mapproj::LonLat;
63
64#[derive(Debug)]
65pub struct WCS {
66 naxisi: Box<[i64]>,
69 fov1: f64,
71 fov2: f64,
73 proj: WCSProj,
75}
76
77impl WCS {
84 pub fn new(params: &WCSParams) -> Result<Self, Error> {
85 let naxisi = match params.naxis {
87 2 => {
88 let naxis1 = params
89 .znaxis1
90 .or(params.naxis1)
91 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS1"))?;
92
93 let naxis2 = params
94 .znaxis2
95 .or(params.naxis2)
96 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS2"))?;
97
98 Ok(vec![naxis1, naxis2].into_boxed_slice())
99 }
100 3 => {
101 let naxis1 = params
102 .znaxis1
103 .or(params.naxis1)
104 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS1"))?;
105 let naxis2 = params
106 .znaxis2
107 .or(params.naxis2)
108 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS2"))?;
109 let naxis3 = params
110 .znaxis3
111 .or(params.naxis3)
112 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS3"))?;
113
114 Ok(vec![naxis1, naxis2, naxis3].into_boxed_slice())
115 }
116 4 => {
117 let naxis1 = params
118 .znaxis1
119 .or(params.naxis1)
120 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS1"))?;
121 let naxis2 = params
122 .znaxis2
123 .or(params.naxis2)
124 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS2"))?;
125 let naxis3 = params
126 .znaxis3
127 .or(params.naxis3)
128 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS3"))?;
129 let naxis4 = params
130 .znaxis4
131 .or(params.naxis4)
132 .ok_or(Error::MandatoryWCSKeywordsMissing("NAXIS4"))?;
133
134 Ok(vec![naxis1, naxis2, naxis3, naxis4].into_boxed_slice())
135 }
136 _ => Err(Error::NotSupportedNaxis(params.naxis)),
137 }?;
138
139 let proj = WCSProj::new(naxisi[0], naxisi[1], params)?;
141
142 let fov1 = proj.s_lon * (naxisi[0] as f64);
143 let fov2 = proj.s_lat * (naxisi[1] as f64);
144
145 Ok(WCS {
146 naxisi,
147 fov1,
148 fov2,
149 proj,
150 })
151 }
152
153 pub fn img_dimensions(&self) -> &[i64] {
155 &self.naxisi[..]
156 }
157
158 pub fn field_of_view(&self) -> (f64, f64) {
159 (self.fov1, self.fov2)
160 }
161
162 pub fn proj(&self, lonlat: &LonLat) -> Option<ImgXY> {
170 self.proj.proj_lonlat(lonlat)
171 }
172
173 pub fn unproj(&self, img_pos: &ImgXY) -> Option<LonLat> {
179 self.proj.unproj_lonlat(img_pos)
180 }
181
182 pub fn coo_system(&self) -> &CooSystem {
184 self.proj.coo_system()
185 }
186}
187
188use std::ops::Deref;
189impl Deref for WCS {
190 type Target = WCSProj;
191
192 fn deref(&self) -> &Self::Target {
193 &self.proj
194 }
195}
196
197pub struct WCSProj {
198 proj: WCSCelestialProj,
201 coo_system: CooSystem,
204 pos_angle: f64,
205 s_lon: f64, s_lat: f64, }
208
209impl std::fmt::Debug for WCSProj {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("WCS")
212 .field("coosys", &self.coo_system)
213 .field("euler angle", &self.pos_angle)
214 .finish()
215 }
216}
217
218pub enum WCSCelestialProj {
225 Azp(Img2Celestial<Azp, WcsImgXY2ProjXY>),
227 Szp(Img2Celestial<Szp, WcsImgXY2ProjXY>),
228 Tan(Img2Celestial<Tan, WcsImgXY2ProjXY>),
229 Stg(Img2Celestial<Stg, WcsImgXY2ProjXY>),
230 Sin(Img2Celestial<Sin, WcsImgXY2ProjXY>),
231 Arc(Img2Celestial<Arc, WcsImgXY2ProjXY>),
232 Zpn(Img2Celestial<Zpn, WcsImgXY2ProjXY>),
233 Zea(Img2Celestial<Zea, WcsImgXY2ProjXY>),
234 Air(Img2Celestial<Air, WcsImgXY2ProjXY>),
235 Ncp(Img2Celestial<Ncp, WcsImgXY2ProjXY>),
236 Cyp(Img2Celestial<Cyp, WcsImgXY2ProjXY>),
238 Cea(Img2Celestial<Cea, WcsImgXY2ProjXY>),
239 Car(Img2Celestial<Car, WcsImgXY2ProjXY>),
240 Mer(Img2Celestial<Mer, WcsImgXY2ProjXY>),
241 Sfl(Img2Celestial<Sfl, WcsImgXY2ProjXY>),
243 Par(Img2Celestial<Par, WcsImgXY2ProjXY>),
244 Mol(Img2Celestial<Mol, WcsImgXY2ProjXY>),
245 Ait(Img2Celestial<Ait, WcsImgXY2ProjXY>),
246 Cop(Img2Celestial<Cop, WcsImgXY2ProjXY>),
248 Cod(Img2Celestial<Cod, WcsImgXY2ProjXY>),
249 Coe(Img2Celestial<Coe, WcsImgXY2ProjXY>),
250 Coo(Img2Celestial<Coo, WcsImgXY2ProjXY>),
251 Hpx(Img2Celestial<Hpx, WcsImgXY2ProjXY>),
253
254 AzpSip(Img2Celestial<Azp, WcsWithSipImgXY2ProjXY>),
257 SzpSip(Img2Celestial<Szp, WcsWithSipImgXY2ProjXY>),
258 TanSip(Img2Celestial<Tan, WcsWithSipImgXY2ProjXY>),
259 StgSip(Img2Celestial<Stg, WcsWithSipImgXY2ProjXY>),
260 SinSip(Img2Celestial<Sin, WcsWithSipImgXY2ProjXY>),
261 ArcSip(Img2Celestial<Arc, WcsWithSipImgXY2ProjXY>),
262 ZpnSip(Img2Celestial<Zpn, WcsWithSipImgXY2ProjXY>),
263 ZeaSip(Img2Celestial<Zea, WcsWithSipImgXY2ProjXY>),
264 AirSip(Img2Celestial<Air, WcsWithSipImgXY2ProjXY>),
265 NcpSip(Img2Celestial<Ncp, WcsWithSipImgXY2ProjXY>),
266 CypSip(Img2Celestial<Cyp, WcsWithSipImgXY2ProjXY>),
268 CeaSip(Img2Celestial<Cea, WcsWithSipImgXY2ProjXY>),
269 CarSip(Img2Celestial<Car, WcsWithSipImgXY2ProjXY>),
270 MerSip(Img2Celestial<Mer, WcsWithSipImgXY2ProjXY>),
271 SflSip(Img2Celestial<Sfl, WcsWithSipImgXY2ProjXY>),
273 ParSip(Img2Celestial<Par, WcsWithSipImgXY2ProjXY>),
274 MolSip(Img2Celestial<Mol, WcsWithSipImgXY2ProjXY>),
275 AitSip(Img2Celestial<Ait, WcsWithSipImgXY2ProjXY>),
276 CopSip(Img2Celestial<Cop, WcsWithSipImgXY2ProjXY>),
278 CodSip(Img2Celestial<Cod, WcsWithSipImgXY2ProjXY>),
279 CoeSip(Img2Celestial<Coe, WcsWithSipImgXY2ProjXY>),
280 CooSip(Img2Celestial<Coo, WcsWithSipImgXY2ProjXY>),
281 HpxSip(Img2Celestial<Hpx, WcsWithSipImgXY2ProjXY>),
283}
284
285fn parse_pc_matrix(params: &WCSParams) -> Option<(f64, f64, f64, f64)> {
286 let pc11 = params.pc1_1;
287 let pc12 = params.pc1_2;
288 let pc21 = params.pc2_1;
289 let pc22 = params.pc2_2;
290
291 let pc_matrix_found = match (&pc11, &pc12, &pc21, &pc22) {
292 (None, None, None, None) => false,
293 _ => true,
296 };
297
298 if pc_matrix_found {
299 let pc11 = pc11.unwrap_or(1.0);
300 let pc12 = pc12.unwrap_or(0.0);
301 let pc21 = pc21.unwrap_or(0.0);
302 let pc22 = pc22.unwrap_or(1.0);
303
304 Some((pc11, pc12, pc21, pc22))
305 } else {
306 None
307 }
308}
309
310fn parse_cd_matrix(params: &WCSParams) -> Option<(f64, f64, f64, f64)> {
311 let cd11 = params.cd1_1;
312 let cd12 = params.cd1_2;
313 let cd21 = params.cd2_1;
314 let cd22 = params.cd2_2;
315
316 let cd_matrix_found = match (&cd11, &cd12, &cd21, &cd22) {
317 (None, None, None, None) => false,
318 _ => true,
321 };
322
323 if cd_matrix_found {
324 let cd11 = cd11.unwrap_or(1.0);
325 let cd12 = cd12.unwrap_or(0.0);
326 let cd21 = cd21.unwrap_or(0.0);
327 let cd22 = cd22.unwrap_or(1.0);
328
329 Some((cd11, cd12, cd21, cd22))
330 } else {
331 None
332 }
333}
334
335impl WCSProj {
336 pub fn new(naxis1: i64, naxis2: i64, params: &WCSParams) -> Result<Self, Error> {
343 let crpix1 = params.crpix1.unwrap_or(0.0);
349 let crpix2 = params.crpix2.unwrap_or(0.0);
350
351 let (img2proj, s_lon, s_lat) =
356 if let Some((cd11, cd12, cd21, cd22)) = parse_cd_matrix(params) {
357 (
359 WcsImgXY2ProjXY::from_cd(crpix1, crpix2, cd11, cd12, cd21, cd22),
360 cd11.abs(),
361 cd22.abs(),
362 )
363 } else {
364 let cdelt1 = params.cdelt1.unwrap_or(1.0);
366 let cdelt2 = params.cdelt2.unwrap_or(1.0);
367
368 if let Some((pc11, pc12, pc21, pc22)) = parse_pc_matrix(params) {
369 (
371 WcsImgXY2ProjXY::from_pc(
372 crpix1, crpix2, pc11, pc12, pc21, pc22, cdelt1, cdelt2,
373 ),
374 (cdelt1 * pc11).abs(),
375 (cdelt2 * pc22).abs(),
376 )
377 } else {
378 let crota2 = params.crota2.unwrap_or(0.0);
380 let cosc = crota2.to_radians().cos();
381
382 (
383 WcsImgXY2ProjXY::from_cr(crpix1, crpix2, crota2, cdelt1, cdelt2),
384 (cdelt1 * cosc).abs(),
385 (cdelt2 * cosc).abs(),
386 )
387 }
388 };
389
390 let ctype1 = ¶ms.ctype1;
392 let proj_name = &ctype1[5..=7];
393
394 let (proj, pos_angle) = match proj_name.as_bytes() {
395 b"AZP" => {
397 create_specific_proj!(Azp, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
398 }
399 b"SZP" => {
400 create_specific_proj!(Szp, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
401 }
402 b"TAN" => {
403 create_specific_proj!(Tan, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
404 }
405 b"STG" => {
406 create_specific_proj!(Stg, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
407 }
408 b"SIN" => {
409 create_specific_proj!(Sin, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
410 }
411 b"ARC" => {
412 create_specific_proj!(Arc, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
413 }
414 b"ZPN" => {
415 create_specific_proj!(Zpn, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
416 }
417 b"ZEA" => {
418 create_specific_proj!(Zea, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
419 }
420 b"AIR" => {
421 create_specific_proj!(Air, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
422 }
423 b"NCP" => {
424 create_specific_proj!(Ncp, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
425 }
426 b"CYP" => {
428 create_specific_proj!(Cyp, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
429 }
430 b"CEA" => {
431 create_specific_proj!(Cea, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
432 }
433 b"CAR" => {
434 create_specific_proj!(Car, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
435 }
436 b"MER" => {
437 create_specific_proj!(Mer, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
438 }
439 b"SFL" => {
441 create_specific_proj!(Sfl, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
442 }
443 b"PAR" => {
444 create_specific_proj!(Par, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
445 }
446 b"MOL" => {
447 create_specific_proj!(Mol, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
448 }
449 b"AIT" => {
450 create_specific_proj!(Ait, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
451 }
452 b"COP" => {
454 create_specific_proj!(Cop, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
455 }
456 b"COD" => {
457 create_specific_proj!(Cod, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
458 }
459 b"COE" => {
460 create_specific_proj!(Coe, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
461 }
462 b"COO" => {
463 create_specific_proj!(Coo, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
464 }
465 b"HPX" => {
467 create_specific_proj!(Hpx, params, ctype1, naxis1, naxis2, crpix1, crpix2, img2proj)
468 }
469 _ => Err(Error::NotImplementedProjection(proj_name.to_string())),
470 }?;
471
472 let coo_system = CooSystem::parse(¶ms)?;
473
474 Ok(WCSProj {
475 proj,
476 coo_system,
477 pos_angle,
478 s_lon,
479 s_lat,
480 })
481 }
482
483 pub fn proj_lonlat(&self, lonlat: &LonLat) -> Option<ImgXY> {
491 let lonlat = &self.coo_system.from_icrs(lonlat.clone());
492
493 match &self.proj {
494 WCSCelestialProj::Azp(wcs) => wcs.lonlat2img(lonlat),
496 WCSCelestialProj::Szp(wcs) => wcs.lonlat2img(lonlat),
497 WCSCelestialProj::Tan(wcs) => wcs.lonlat2img(lonlat),
498 WCSCelestialProj::Stg(wcs) => wcs.lonlat2img(lonlat),
499 WCSCelestialProj::Sin(wcs) => wcs.lonlat2img(lonlat),
500 WCSCelestialProj::Arc(wcs) => wcs.lonlat2img(lonlat),
501 WCSCelestialProj::Zpn(wcs) => wcs.lonlat2img(lonlat),
502 WCSCelestialProj::Zea(wcs) => wcs.lonlat2img(lonlat),
503 WCSCelestialProj::Air(wcs) => wcs.lonlat2img(lonlat),
504 WCSCelestialProj::Ncp(wcs) => wcs.lonlat2img(lonlat),
505 WCSCelestialProj::Cyp(wcs) => wcs.lonlat2img(lonlat),
507 WCSCelestialProj::Cea(wcs) => wcs.lonlat2img(lonlat),
508 WCSCelestialProj::Car(wcs) => wcs.lonlat2img(lonlat),
509 WCSCelestialProj::Mer(wcs) => wcs.lonlat2img(lonlat),
510 WCSCelestialProj::Sfl(wcs) => wcs.lonlat2img(lonlat),
512 WCSCelestialProj::Par(wcs) => wcs.lonlat2img(lonlat),
513 WCSCelestialProj::Mol(wcs) => wcs.lonlat2img(lonlat),
514 WCSCelestialProj::Ait(wcs) => wcs.lonlat2img(lonlat),
515 WCSCelestialProj::Cop(wcs) => wcs.lonlat2img(lonlat),
517 WCSCelestialProj::Cod(wcs) => wcs.lonlat2img(lonlat),
518 WCSCelestialProj::Coe(wcs) => wcs.lonlat2img(lonlat),
519 WCSCelestialProj::Coo(wcs) => wcs.lonlat2img(lonlat),
520 WCSCelestialProj::Hpx(wcs) => wcs.lonlat2img(lonlat),
522
523 WCSCelestialProj::AzpSip(wcs) => wcs.lonlat2img(lonlat),
526 WCSCelestialProj::SzpSip(wcs) => wcs.lonlat2img(lonlat),
527 WCSCelestialProj::TanSip(wcs) => wcs.lonlat2img(lonlat),
528 WCSCelestialProj::StgSip(wcs) => wcs.lonlat2img(lonlat),
529 WCSCelestialProj::SinSip(wcs) => wcs.lonlat2img(lonlat),
530 WCSCelestialProj::ArcSip(wcs) => wcs.lonlat2img(lonlat),
531 WCSCelestialProj::ZpnSip(wcs) => wcs.lonlat2img(lonlat),
532 WCSCelestialProj::ZeaSip(wcs) => wcs.lonlat2img(lonlat),
533 WCSCelestialProj::AirSip(wcs) => wcs.lonlat2img(lonlat),
534 WCSCelestialProj::NcpSip(wcs) => wcs.lonlat2img(lonlat),
535 WCSCelestialProj::CypSip(wcs) => wcs.lonlat2img(lonlat),
537 WCSCelestialProj::CeaSip(wcs) => wcs.lonlat2img(lonlat),
538 WCSCelestialProj::CarSip(wcs) => wcs.lonlat2img(lonlat),
539 WCSCelestialProj::MerSip(wcs) => wcs.lonlat2img(lonlat),
540 WCSCelestialProj::SflSip(wcs) => wcs.lonlat2img(lonlat),
542 WCSCelestialProj::ParSip(wcs) => wcs.lonlat2img(lonlat),
543 WCSCelestialProj::MolSip(wcs) => wcs.lonlat2img(lonlat),
544 WCSCelestialProj::AitSip(wcs) => wcs.lonlat2img(lonlat),
545 WCSCelestialProj::CopSip(wcs) => wcs.lonlat2img(lonlat),
547 WCSCelestialProj::CodSip(wcs) => wcs.lonlat2img(lonlat),
548 WCSCelestialProj::CoeSip(wcs) => wcs.lonlat2img(lonlat),
549 WCSCelestialProj::CooSip(wcs) => wcs.lonlat2img(lonlat),
550 WCSCelestialProj::HpxSip(wcs) => wcs.lonlat2img(lonlat),
552 }
553 }
554
555 pub fn proj_xyz(&self, xyz: &(f64, f64, f64)) -> Option<ImgXY> {
556 let xyz = &self.coo_system.from_icrs_xyz(XYZ::new(xyz.0, xyz.1, xyz.2));
557
558 match &self.proj {
559 WCSCelestialProj::Azp(wcs) => wcs.xyz2img(xyz),
561 WCSCelestialProj::Szp(wcs) => wcs.xyz2img(xyz),
562 WCSCelestialProj::Tan(wcs) => wcs.xyz2img(xyz),
563 WCSCelestialProj::Stg(wcs) => wcs.xyz2img(xyz),
564 WCSCelestialProj::Sin(wcs) => wcs.xyz2img(xyz),
565 WCSCelestialProj::Arc(wcs) => wcs.xyz2img(xyz),
566 WCSCelestialProj::Zpn(wcs) => wcs.xyz2img(xyz),
567 WCSCelestialProj::Zea(wcs) => wcs.xyz2img(xyz),
568 WCSCelestialProj::Air(wcs) => wcs.xyz2img(xyz),
569 WCSCelestialProj::Ncp(wcs) => wcs.xyz2img(xyz),
570 WCSCelestialProj::Cyp(wcs) => wcs.xyz2img(xyz),
572 WCSCelestialProj::Cea(wcs) => wcs.xyz2img(xyz),
573 WCSCelestialProj::Car(wcs) => wcs.xyz2img(xyz),
574 WCSCelestialProj::Mer(wcs) => wcs.xyz2img(xyz),
575 WCSCelestialProj::Sfl(wcs) => wcs.xyz2img(xyz),
577 WCSCelestialProj::Par(wcs) => wcs.xyz2img(xyz),
578 WCSCelestialProj::Mol(wcs) => wcs.xyz2img(xyz),
579 WCSCelestialProj::Ait(wcs) => wcs.xyz2img(xyz),
580 WCSCelestialProj::Cop(wcs) => wcs.xyz2img(xyz),
582 WCSCelestialProj::Cod(wcs) => wcs.xyz2img(xyz),
583 WCSCelestialProj::Coe(wcs) => wcs.xyz2img(xyz),
584 WCSCelestialProj::Coo(wcs) => wcs.xyz2img(xyz),
585 WCSCelestialProj::Hpx(wcs) => wcs.xyz2img(xyz),
587
588 WCSCelestialProj::AzpSip(wcs) => wcs.xyz2img(xyz),
591 WCSCelestialProj::SzpSip(wcs) => wcs.xyz2img(xyz),
592 WCSCelestialProj::TanSip(wcs) => wcs.xyz2img(xyz),
593 WCSCelestialProj::StgSip(wcs) => wcs.xyz2img(xyz),
594 WCSCelestialProj::SinSip(wcs) => wcs.xyz2img(xyz),
595 WCSCelestialProj::ArcSip(wcs) => wcs.xyz2img(xyz),
596 WCSCelestialProj::ZpnSip(wcs) => wcs.xyz2img(xyz),
597 WCSCelestialProj::ZeaSip(wcs) => wcs.xyz2img(xyz),
598 WCSCelestialProj::AirSip(wcs) => wcs.xyz2img(xyz),
599 WCSCelestialProj::NcpSip(wcs) => wcs.xyz2img(xyz),
600 WCSCelestialProj::CypSip(wcs) => wcs.xyz2img(xyz),
602 WCSCelestialProj::CeaSip(wcs) => wcs.xyz2img(xyz),
603 WCSCelestialProj::CarSip(wcs) => wcs.xyz2img(xyz),
604 WCSCelestialProj::MerSip(wcs) => wcs.xyz2img(xyz),
605 WCSCelestialProj::SflSip(wcs) => wcs.xyz2img(xyz),
607 WCSCelestialProj::ParSip(wcs) => wcs.xyz2img(xyz),
608 WCSCelestialProj::MolSip(wcs) => wcs.xyz2img(xyz),
609 WCSCelestialProj::AitSip(wcs) => wcs.xyz2img(xyz),
610 WCSCelestialProj::CopSip(wcs) => wcs.xyz2img(xyz),
612 WCSCelestialProj::CodSip(wcs) => wcs.xyz2img(xyz),
613 WCSCelestialProj::CoeSip(wcs) => wcs.xyz2img(xyz),
614 WCSCelestialProj::CooSip(wcs) => wcs.xyz2img(xyz),
615 WCSCelestialProj::HpxSip(wcs) => wcs.xyz2img(xyz),
617 }
618 }
619
620 pub fn unproj_xyz(&self, img_pos: &ImgXY) -> Option<XYZ> {
621 let xyz = match &self.proj {
622 WCSCelestialProj::Azp(wcs) => wcs.img2xyz(&img_pos),
624 WCSCelestialProj::Szp(wcs) => wcs.img2xyz(&img_pos),
625 WCSCelestialProj::Tan(wcs) => wcs.img2xyz(&img_pos),
626 WCSCelestialProj::Stg(wcs) => wcs.img2xyz(&img_pos),
627 WCSCelestialProj::Sin(wcs) => wcs.img2xyz(&img_pos),
628 WCSCelestialProj::Arc(wcs) => wcs.img2xyz(&img_pos),
629 WCSCelestialProj::Zpn(wcs) => wcs.img2xyz(&img_pos),
630 WCSCelestialProj::Zea(wcs) => wcs.img2xyz(&img_pos),
631 WCSCelestialProj::Air(wcs) => wcs.img2xyz(&img_pos),
632 WCSCelestialProj::Ncp(wcs) => wcs.img2xyz(&img_pos),
633 WCSCelestialProj::Cyp(wcs) => wcs.img2xyz(&img_pos),
635 WCSCelestialProj::Cea(wcs) => wcs.img2xyz(&img_pos),
636 WCSCelestialProj::Car(wcs) => wcs.img2xyz(&img_pos),
637 WCSCelestialProj::Mer(wcs) => wcs.img2xyz(&img_pos),
638 WCSCelestialProj::Sfl(wcs) => wcs.img2xyz(&img_pos),
640 WCSCelestialProj::Par(wcs) => wcs.img2xyz(&img_pos),
641 WCSCelestialProj::Mol(wcs) => wcs.img2xyz(&img_pos),
642 WCSCelestialProj::Ait(wcs) => wcs.img2xyz(&img_pos),
643 WCSCelestialProj::Cop(wcs) => wcs.img2xyz(&img_pos),
645 WCSCelestialProj::Cod(wcs) => wcs.img2xyz(&img_pos),
646 WCSCelestialProj::Coe(wcs) => wcs.img2xyz(&img_pos),
647 WCSCelestialProj::Coo(wcs) => wcs.img2xyz(&img_pos),
648 WCSCelestialProj::Hpx(wcs) => wcs.img2xyz(&img_pos),
650
651 WCSCelestialProj::AzpSip(wcs) => wcs.img2xyz(&img_pos),
654 WCSCelestialProj::SzpSip(wcs) => wcs.img2xyz(&img_pos),
655 WCSCelestialProj::TanSip(wcs) => wcs.img2xyz(&img_pos),
656 WCSCelestialProj::StgSip(wcs) => wcs.img2xyz(&img_pos),
657 WCSCelestialProj::SinSip(wcs) => wcs.img2xyz(&img_pos),
658 WCSCelestialProj::ArcSip(wcs) => wcs.img2xyz(&img_pos),
659 WCSCelestialProj::ZpnSip(wcs) => wcs.img2xyz(&img_pos),
660 WCSCelestialProj::ZeaSip(wcs) => wcs.img2xyz(&img_pos),
661 WCSCelestialProj::AirSip(wcs) => wcs.img2xyz(&img_pos),
662 WCSCelestialProj::NcpSip(wcs) => wcs.img2xyz(&img_pos),
663 WCSCelestialProj::CypSip(wcs) => wcs.img2xyz(&img_pos),
665 WCSCelestialProj::CeaSip(wcs) => wcs.img2xyz(&img_pos),
666 WCSCelestialProj::CarSip(wcs) => wcs.img2xyz(&img_pos),
667 WCSCelestialProj::MerSip(wcs) => wcs.img2xyz(&img_pos),
668 WCSCelestialProj::SflSip(wcs) => wcs.img2xyz(&img_pos),
670 WCSCelestialProj::ParSip(wcs) => wcs.img2xyz(&img_pos),
671 WCSCelestialProj::MolSip(wcs) => wcs.img2xyz(&img_pos),
672 WCSCelestialProj::AitSip(wcs) => wcs.img2xyz(&img_pos),
673 WCSCelestialProj::CopSip(wcs) => wcs.img2xyz(&img_pos),
675 WCSCelestialProj::CodSip(wcs) => wcs.img2xyz(&img_pos),
676 WCSCelestialProj::CoeSip(wcs) => wcs.img2xyz(&img_pos),
677 WCSCelestialProj::CooSip(wcs) => wcs.img2xyz(&img_pos),
678 WCSCelestialProj::HpxSip(wcs) => wcs.img2xyz(&img_pos),
680 };
681 xyz.map(|v| self.coo_system.to_icrs_xyz(v))
682 }
683
684 pub fn unproj_lonlat(&self, img_pos: &ImgXY) -> Option<LonLat> {
692 let lonlat = match &self.proj {
693 WCSCelestialProj::Azp(wcs) => wcs.img2lonlat(&img_pos),
695 WCSCelestialProj::Szp(wcs) => wcs.img2lonlat(&img_pos),
696 WCSCelestialProj::Tan(wcs) => wcs.img2lonlat(&img_pos),
697 WCSCelestialProj::Stg(wcs) => wcs.img2lonlat(&img_pos),
698 WCSCelestialProj::Sin(wcs) => wcs.img2lonlat(&img_pos),
699 WCSCelestialProj::Arc(wcs) => wcs.img2lonlat(&img_pos),
700 WCSCelestialProj::Zpn(wcs) => wcs.img2lonlat(&img_pos),
701 WCSCelestialProj::Zea(wcs) => wcs.img2lonlat(&img_pos),
702 WCSCelestialProj::Air(wcs) => wcs.img2lonlat(&img_pos),
703 WCSCelestialProj::Ncp(wcs) => wcs.img2lonlat(&img_pos),
704 WCSCelestialProj::Cyp(wcs) => wcs.img2lonlat(&img_pos),
706 WCSCelestialProj::Cea(wcs) => wcs.img2lonlat(&img_pos),
707 WCSCelestialProj::Car(wcs) => wcs.img2lonlat(&img_pos),
708 WCSCelestialProj::Mer(wcs) => wcs.img2lonlat(&img_pos),
709 WCSCelestialProj::Sfl(wcs) => wcs.img2lonlat(&img_pos),
711 WCSCelestialProj::Par(wcs) => wcs.img2lonlat(&img_pos),
712 WCSCelestialProj::Mol(wcs) => wcs.img2lonlat(&img_pos),
713 WCSCelestialProj::Ait(wcs) => wcs.img2lonlat(&img_pos),
714 WCSCelestialProj::Cop(wcs) => wcs.img2lonlat(&img_pos),
716 WCSCelestialProj::Cod(wcs) => wcs.img2lonlat(&img_pos),
717 WCSCelestialProj::Coe(wcs) => wcs.img2lonlat(&img_pos),
718 WCSCelestialProj::Coo(wcs) => wcs.img2lonlat(&img_pos),
719 WCSCelestialProj::Hpx(wcs) => wcs.img2lonlat(&img_pos),
721
722 WCSCelestialProj::AzpSip(wcs) => wcs.img2lonlat(&img_pos),
725 WCSCelestialProj::SzpSip(wcs) => wcs.img2lonlat(&img_pos),
726 WCSCelestialProj::TanSip(wcs) => wcs.img2lonlat(&img_pos),
727 WCSCelestialProj::StgSip(wcs) => wcs.img2lonlat(&img_pos),
728 WCSCelestialProj::SinSip(wcs) => wcs.img2lonlat(&img_pos),
729 WCSCelestialProj::ArcSip(wcs) => wcs.img2lonlat(&img_pos),
730 WCSCelestialProj::ZpnSip(wcs) => wcs.img2lonlat(&img_pos),
731 WCSCelestialProj::ZeaSip(wcs) => wcs.img2lonlat(&img_pos),
732 WCSCelestialProj::AirSip(wcs) => wcs.img2lonlat(&img_pos),
733 WCSCelestialProj::NcpSip(wcs) => wcs.img2lonlat(&img_pos),
734 WCSCelestialProj::CypSip(wcs) => wcs.img2lonlat(&img_pos),
736 WCSCelestialProj::CeaSip(wcs) => wcs.img2lonlat(&img_pos),
737 WCSCelestialProj::CarSip(wcs) => wcs.img2lonlat(&img_pos),
738 WCSCelestialProj::MerSip(wcs) => wcs.img2lonlat(&img_pos),
739 WCSCelestialProj::SflSip(wcs) => wcs.img2lonlat(&img_pos),
741 WCSCelestialProj::ParSip(wcs) => wcs.img2lonlat(&img_pos),
742 WCSCelestialProj::MolSip(wcs) => wcs.img2lonlat(&img_pos),
743 WCSCelestialProj::AitSip(wcs) => wcs.img2lonlat(&img_pos),
744 WCSCelestialProj::CopSip(wcs) => wcs.img2lonlat(&img_pos),
746 WCSCelestialProj::CodSip(wcs) => wcs.img2lonlat(&img_pos),
747 WCSCelestialProj::CoeSip(wcs) => wcs.img2lonlat(&img_pos),
748 WCSCelestialProj::CooSip(wcs) => wcs.img2lonlat(&img_pos),
749 WCSCelestialProj::HpxSip(wcs) => wcs.img2lonlat(&img_pos),
751 };
752
753 lonlat.map(|ll| self.coo_system.to_icrs(ll))
754 }
755
756 pub fn coo_system(&self) -> &CooSystem {
758 &self.coo_system
759 }
760}
761
762#[cfg(test)]
763mod tests {
764 use super::WCS;
765 use crate::Error;
766 use crate::WCSParams;
767
768 use crate::mapproj::Projection;
769
770 use fitsrs::card::Value;
771 use fitsrs::fits::Fits;
772 use fitsrs::hdu::header::{extension::image::Image, Header};
773 use fitsrs::Pixels;
774
775 use glob::glob;
776 use mapproj::{CanonicalProjection, ImgXY, LonLat};
777 use serde::Deserialize;
778 use std::f64::consts::PI;
779 use std::fs::File;
780 use std::io::BufReader;
781
782 use std::convert::TryFrom;
783
784 use crate::Error::MandatoryWCSKeywordsMissing;
785 fn parse_card<'de, T: Deserialize<'de>>(
786 header: &'de Header<Image>,
787 key: &'static str,
788 ) -> Result<T, Error> {
789 header
790 .get_parsed::<T>(key)
791 .map_err(|_| MandatoryWCSKeywordsMissing("Card cannot be parsed"))
792 }
793
794 fn parse_opt_card<'de, T: Deserialize<'de>>(
795 header: &'de Header<Image>,
796 key: &'static str,
797 ) -> Option<T> {
798 header.get_parsed::<T>(key).ok()
799 }
800
801 impl<'a> TryFrom<&'a Header<Image>> for WCS {
802 type Error = Error;
803
804 fn try_from(h: &'a Header<Image>) -> Result<Self, Self::Error> {
805 let params = WCSParams {
806 naxis: parse_card::<i64>(h, "NAXIS").unwrap(),
807 ctype1: parse_card::<String>(h, "CTYPE1").unwrap(),
808
809 naxis1: parse_opt_card::<i64>(h, "NAXIS1"),
810 naxis2: parse_opt_card::<i64>(h, "NAXIS2"),
811
812 znaxis1: parse_opt_card::<i64>(h, "ZNAXIS1"),
813 znaxis2: parse_opt_card::<i64>(h, "ZNAXIS2"),
814 znaxis3: parse_opt_card::<i64>(h, "ZNAXIS3"),
815 znaxis4: parse_opt_card::<i64>(h, "ZNAXIS4"),
816
817 ctype2: parse_opt_card::<String>(h, "CTYPE2"),
818 ctype3: parse_opt_card::<String>(h, "CTYPE3"),
819
820 a_order: parse_opt_card::<i64>(h, "A_ORDER"),
821 b_order: parse_opt_card::<i64>(h, "B_ORDER"),
822 ap_order: parse_opt_card::<i64>(h, "AP_ORDER"),
823 bp_order: parse_opt_card::<i64>(h, "BP_ORDER"),
824 crpix1: parse_opt_card::<f64>(h, "CRPIX1"),
825 crpix2: parse_opt_card::<f64>(h, "CRPIX2"),
826 crpix3: parse_opt_card::<f64>(h, "CRPIX3"),
827 crval1: parse_opt_card::<f64>(h, "CRVAL1"),
828 crval2: parse_opt_card::<f64>(h, "CRVAL2"),
829 crval3: parse_opt_card::<f64>(h, "CRVAL3"),
830 crota1: parse_opt_card::<f64>(h, "CROTA1"),
831 crota2: parse_opt_card::<f64>(h, "CROTA2"),
832 crota3: parse_opt_card::<f64>(h, "CROTA3"),
833 cdelt1: parse_opt_card::<f64>(h, "CDELT1"),
834 cdelt2: parse_opt_card::<f64>(h, "CDELT2"),
835 cdelt3: parse_opt_card::<f64>(h, "CDELT3"),
836 naxis3: parse_opt_card::<i64>(h, "NAXIS3"),
837 naxis4: parse_opt_card::<i64>(h, "NAXIS4"),
838 lonpole: parse_opt_card::<f64>(h, "LONPOLE"),
839 latpole: parse_opt_card::<f64>(h, "LATPOLE"),
840 equinox: parse_opt_card::<f64>(h, "EQUINOX"),
841 epoch: parse_opt_card::<f64>(h, "EPOCH"),
842 radesys: parse_opt_card::<String>(h, "RADESYS"),
843 pv1_0: parse_opt_card::<f64>(h, "PV1_0"),
844 pv1_1: parse_opt_card::<f64>(h, "PV1_1"),
845 pv1_2: parse_opt_card::<f64>(h, "PV1_2"),
846 pv2_0: parse_opt_card::<f64>(h, "PV2_0"),
847 pv2_1: parse_opt_card::<f64>(h, "PV2_1"),
848 pv2_2: parse_opt_card::<f64>(h, "PV2_2"),
849 pv2_3: parse_opt_card::<f64>(h, "PV2_3"),
850 pv2_4: parse_opt_card::<f64>(h, "PV2_4"),
851 pv2_5: parse_opt_card::<f64>(h, "PV2_5"),
852 pv2_6: parse_opt_card::<f64>(h, "PV2_6"),
853 pv2_7: parse_opt_card::<f64>(h, "PV2_7"),
854 pv2_8: parse_opt_card::<f64>(h, "PV2_8"),
855 pv2_9: parse_opt_card::<f64>(h, "PV2_9"),
856 pv2_10: parse_opt_card::<f64>(h, "PV2_10"),
857 pv2_11: parse_opt_card::<f64>(h, "PV2_11"),
858 pv2_12: parse_opt_card::<f64>(h, "PV2_12"),
859 pv2_13: parse_opt_card::<f64>(h, "PV2_13"),
860 pv2_14: parse_opt_card::<f64>(h, "PV2_14"),
861 pv2_15: parse_opt_card::<f64>(h, "PV2_15"),
862 pv2_16: parse_opt_card::<f64>(h, "PV2_16"),
863 pv2_17: parse_opt_card::<f64>(h, "PV2_17"),
864 pv2_18: parse_opt_card::<f64>(h, "PV2_18"),
865 pv2_19: parse_opt_card::<f64>(h, "PV2_19"),
866 pv2_20: parse_opt_card::<f64>(h, "PV2_20"),
867 cd1_1: parse_opt_card::<f64>(h, "CD1_1"),
868 cd1_2: parse_opt_card::<f64>(h, "CD1_2"),
869 cd1_3: parse_opt_card::<f64>(h, "CD1_3"),
870 cd2_1: parse_opt_card::<f64>(h, "CD2_1"),
871 cd2_2: parse_opt_card::<f64>(h, "CD2_2"),
872 cd2_3: parse_opt_card::<f64>(h, "CD2_3"),
873 cd3_1: parse_opt_card::<f64>(h, "CD3_1"),
874 cd3_2: parse_opt_card::<f64>(h, "CD3_2"),
875 cd3_3: parse_opt_card::<f64>(h, "CD3_3"),
876 pc1_1: parse_opt_card::<f64>(h, "PC1_1"),
877 pc1_2: parse_opt_card::<f64>(h, "PC1_2"),
878 pc1_3: parse_opt_card::<f64>(h, "PC1_3"),
879 pc2_1: parse_opt_card::<f64>(h, "PC2_1"),
880 pc2_2: parse_opt_card::<f64>(h, "PC2_2"),
881 pc2_3: parse_opt_card::<f64>(h, "PC2_3"),
882 pc3_1: parse_opt_card::<f64>(h, "PC3_1"),
883 pc3_2: parse_opt_card::<f64>(h, "PC3_2"),
884 pc3_3: parse_opt_card::<f64>(h, "PC3_3"),
885 a_0_0: parse_opt_card::<f64>(h, "A_0_0"),
886 a_1_0: parse_opt_card::<f64>(h, "A_1_0"),
887 a_2_0: parse_opt_card::<f64>(h, "A_2_0"),
888 a_3_0: parse_opt_card::<f64>(h, "A_3_0"),
889 a_4_0: parse_opt_card::<f64>(h, "A_4_0"),
890 a_5_0: parse_opt_card::<f64>(h, "A_5_0"),
891 a_6_0: parse_opt_card::<f64>(h, "A_6_0"),
892 a_0_1: parse_opt_card::<f64>(h, "A_0_1"),
893 a_1_1: parse_opt_card::<f64>(h, "A_1_1"),
894 a_2_1: parse_opt_card::<f64>(h, "A_2_1"),
895 a_3_1: parse_opt_card::<f64>(h, "A_3_1"),
896 a_4_1: parse_opt_card::<f64>(h, "A_4_1"),
897 a_5_1: parse_opt_card::<f64>(h, "A_5_1"),
898 a_0_2: parse_opt_card::<f64>(h, "A_0_2"),
899 a_1_2: parse_opt_card::<f64>(h, "A_1_2"),
900 a_2_2: parse_opt_card::<f64>(h, "A_2_2"),
901 a_3_2: parse_opt_card::<f64>(h, "A_3_2"),
902 a_4_2: parse_opt_card::<f64>(h, "A_4_2"),
903 a_0_3: parse_opt_card::<f64>(h, "A_0_3"),
904 a_1_3: parse_opt_card::<f64>(h, "A_1_3"),
905 a_2_3: parse_opt_card::<f64>(h, "A_2_3"),
906 a_3_3: parse_opt_card::<f64>(h, "A_3_3"),
907 a_0_4: parse_opt_card::<f64>(h, "A_0_4"),
908 a_1_4: parse_opt_card::<f64>(h, "A_1_4"),
909 a_2_4: parse_opt_card::<f64>(h, "A_2_4"),
910 a_0_5: parse_opt_card::<f64>(h, "A_0_5"),
911 a_1_5: parse_opt_card::<f64>(h, "A_1_5"),
912 a_0_6: parse_opt_card::<f64>(h, "A_0_6"),
913 ap_0_0: parse_opt_card::<f64>(h, "AP_0_0"),
914 ap_1_0: parse_opt_card::<f64>(h, "AP_1_0"),
915 ap_2_0: parse_opt_card::<f64>(h, "AP_2_0"),
916 ap_3_0: parse_opt_card::<f64>(h, "AP_3_0"),
917 ap_4_0: parse_opt_card::<f64>(h, "AP_4_0"),
918 ap_5_0: parse_opt_card::<f64>(h, "AP_5_0"),
919 ap_6_0: parse_opt_card::<f64>(h, "AP_6_0"),
920 ap_0_1: parse_opt_card::<f64>(h, "AP_0_1"),
921 ap_1_1: parse_opt_card::<f64>(h, "AP_1_1"),
922 ap_2_1: parse_opt_card::<f64>(h, "AP_2_1"),
923 ap_3_1: parse_opt_card::<f64>(h, "AP_3_1"),
924 ap_4_1: parse_opt_card::<f64>(h, "AP_4_1"),
925 ap_5_1: parse_opt_card::<f64>(h, "AP_5_1"),
926 ap_0_2: parse_opt_card::<f64>(h, "AP_0_2"),
927 ap_1_2: parse_opt_card::<f64>(h, "AP_1_2"),
928 ap_2_2: parse_opt_card::<f64>(h, "AP_2_2"),
929 ap_3_2: parse_opt_card::<f64>(h, "AP_3_2"),
930 ap_4_2: parse_opt_card::<f64>(h, "AP_4_2"),
931 ap_0_3: parse_opt_card::<f64>(h, "AP_0_3"),
932 ap_1_3: parse_opt_card::<f64>(h, "AP_1_3"),
933 ap_2_3: parse_opt_card::<f64>(h, "AP_2_3"),
934 ap_3_3: parse_opt_card::<f64>(h, "AP_3_3"),
935 ap_0_4: parse_opt_card::<f64>(h, "AP_0_4"),
936 ap_1_4: parse_opt_card::<f64>(h, "AP_1_4"),
937 ap_2_4: parse_opt_card::<f64>(h, "AP_2_4"),
938 ap_0_5: parse_opt_card::<f64>(h, "AP_0_5"),
939 ap_1_5: parse_opt_card::<f64>(h, "AP_1_5"),
940 ap_0_6: parse_opt_card::<f64>(h, "AP_0_6"),
941 b_0_0: parse_opt_card::<f64>(h, "B_0_0"),
942 b_1_0: parse_opt_card::<f64>(h, "B_1_0"),
943 b_2_0: parse_opt_card::<f64>(h, "B_2_0"),
944 b_3_0: parse_opt_card::<f64>(h, "B_3_0"),
945 b_4_0: parse_opt_card::<f64>(h, "B_4_0"),
946 b_5_0: parse_opt_card::<f64>(h, "B_5_0"),
947 b_6_0: parse_opt_card::<f64>(h, "B_6_0"),
948 b_0_1: parse_opt_card::<f64>(h, "B_0_1"),
949 b_1_1: parse_opt_card::<f64>(h, "B_1_1"),
950 b_2_1: parse_opt_card::<f64>(h, "B_2_1"),
951 b_3_1: parse_opt_card::<f64>(h, "B_3_1"),
952 b_4_1: parse_opt_card::<f64>(h, "B_4_1"),
953 b_5_1: parse_opt_card::<f64>(h, "B_5_1"),
954 b_0_2: parse_opt_card::<f64>(h, "B_0_2"),
955 b_1_2: parse_opt_card::<f64>(h, "B_1_2"),
956 b_2_2: parse_opt_card::<f64>(h, "B_2_2"),
957 b_3_2: parse_opt_card::<f64>(h, "B_3_2"),
958 b_4_2: parse_opt_card::<f64>(h, "B_4_2"),
959 b_0_3: parse_opt_card::<f64>(h, "B_0_3"),
960 b_1_3: parse_opt_card::<f64>(h, "B_1_3"),
961 b_2_3: parse_opt_card::<f64>(h, "B_2_3"),
962 b_3_3: parse_opt_card::<f64>(h, "B_3_3"),
963 b_0_4: parse_opt_card::<f64>(h, "B_0_4"),
964 b_1_4: parse_opt_card::<f64>(h, "B_1_4"),
965 b_2_4: parse_opt_card::<f64>(h, "B_2_4"),
966 b_0_5: parse_opt_card::<f64>(h, "B_0_5"),
967 b_1_5: parse_opt_card::<f64>(h, "B_1_5"),
968 b_0_6: parse_opt_card::<f64>(h, "B_0_6"),
969 bp_0_0: parse_opt_card::<f64>(h, "BP_0_0"),
970 bp_1_0: parse_opt_card::<f64>(h, "BP_1_0"),
971 bp_2_0: parse_opt_card::<f64>(h, "BP_2_0"),
972 bp_3_0: parse_opt_card::<f64>(h, "BP_3_0"),
973 bp_4_0: parse_opt_card::<f64>(h, "BP_4_0"),
974 bp_5_0: parse_opt_card::<f64>(h, "BP_5_0"),
975 bp_6_0: parse_opt_card::<f64>(h, "BP_6_0"),
976 bp_0_1: parse_opt_card::<f64>(h, "BP_0_1"),
977 bp_1_1: parse_opt_card::<f64>(h, "BP_1_1"),
978 bp_2_1: parse_opt_card::<f64>(h, "BP_2_1"),
979 bp_3_1: parse_opt_card::<f64>(h, "BP_3_1"),
980 bp_4_1: parse_opt_card::<f64>(h, "BP_4_1"),
981 bp_5_1: parse_opt_card::<f64>(h, "BP_5_1"),
982 bp_0_2: parse_opt_card::<f64>(h, "BP_0_2"),
983 bp_1_2: parse_opt_card::<f64>(h, "BP_1_2"),
984 bp_2_2: parse_opt_card::<f64>(h, "BP_2_2"),
985 bp_3_2: parse_opt_card::<f64>(h, "BP_3_2"),
986 bp_4_2: parse_opt_card::<f64>(h, "BP_4_2"),
987 bp_0_3: parse_opt_card::<f64>(h, "BP_0_3"),
988 bp_1_3: parse_opt_card::<f64>(h, "BP_1_3"),
989 bp_2_3: parse_opt_card::<f64>(h, "BP_2_3"),
990 bp_3_3: parse_opt_card::<f64>(h, "BP_3_3"),
991 bp_0_4: parse_opt_card::<f64>(h, "BP_0_4"),
992 bp_1_4: parse_opt_card::<f64>(h, "BP_1_4"),
993 bp_2_4: parse_opt_card::<f64>(h, "BP_2_4"),
994 bp_0_5: parse_opt_card::<f64>(h, "BP_0_5"),
995 bp_1_5: parse_opt_card::<f64>(h, "BP_1_5"),
996 bp_0_6: parse_opt_card::<f64>(h, "BP_0_6"),
997 };
998
999 WCS::new(¶ms)
1000 }
1001 }
1002
1003 fn wcs_from_fits_header(header: &Header<Image>) -> Result<WCS, Error> {
1004 header.try_into()
1005 }
1006
1007 #[test]
1008 fn test_visualize() {
1009 let f = File::open("examples/panstarrs-rotated-around-orion.fits").unwrap();
1010
1011 let reader = BufReader::new(f);
1012 let mut fits = Fits::from_reader(reader);
1013 let hdu = fits.next().unwrap().unwrap();
1014
1015 match hdu {
1016 HDU::XImage(hdu) | HDU::Primary(hdu) => {
1017 let header = hdu.get_header();
1018
1019 let data = match fits.get_data(&hdu).pixels() {
1021 Pixels::F32(it) => it.collect::<Vec<_>>(),
1022 _ => unreachable!(),
1023 };
1024
1025 let wcs = wcs_from_fits_header(&header).unwrap();
1026 reproject_fits_image(mapproj::zenithal::azp::Azp::new(), &wcs, &header, &data);
1027 reproject_fits_image(mapproj::zenithal::szp::Szp::new(), &wcs, &header, &data);
1028 reproject_fits_image(mapproj::zenithal::tan::Tan::new(), &wcs, &header, &data);
1029 reproject_fits_image(mapproj::zenithal::stg::Stg::new(), &wcs, &header, &data);
1030 reproject_fits_image(mapproj::zenithal::sin::Sin::new(), &wcs, &header, &data);
1031 reproject_fits_image(mapproj::zenithal::arc::Arc::new(), &wcs, &header, &data);
1032 reproject_fits_image(mapproj::zenithal::zea::Zea::new(), &wcs, &header, &data);
1033 reproject_fits_image(mapproj::zenithal::air::Air::new(), &wcs, &header, &data);
1034 reproject_fits_image(mapproj::zenithal::ncp::Ncp::new(), &wcs, &header, &data);
1035
1036 reproject_fits_image(mapproj::pseudocyl::mol::Mol::new(), &wcs, &header, &data);
1037 reproject_fits_image(mapproj::pseudocyl::ait::Ait::new(), &wcs, &header, &data);
1038 reproject_fits_image(mapproj::pseudocyl::par::Par::new(), &wcs, &header, &data);
1039 reproject_fits_image(mapproj::pseudocyl::sfl::Sfl::new(), &wcs, &header, &data);
1040
1041 reproject_fits_image(mapproj::cylindrical::cyp::Cyp::new(), &wcs, &header, &data);
1042 reproject_fits_image(mapproj::cylindrical::cea::Cea::new(), &wcs, &header, &data);
1043 reproject_fits_image(mapproj::cylindrical::car::Car::new(), &wcs, &header, &data);
1044 reproject_fits_image(mapproj::cylindrical::mer::Mer::new(), &wcs, &header, &data);
1045
1046 reproject_fits_image(mapproj::conic::cod::Cod::new(), &wcs, &header, &data);
1047 reproject_fits_image(mapproj::conic::cop::Cop::new(), &wcs, &header, &data);
1048 reproject_fits_image(mapproj::conic::coo::Coo::new(), &wcs, &header, &data);
1049 reproject_fits_image(mapproj::conic::coe::Coe::new(), &wcs, &header, &data);
1050
1051 reproject_fits_image(mapproj::hybrid::hpx::Hpx::new(), &wcs, &header, &data);
1052 }
1053 _ => unreachable!(),
1054 }
1055 }
1056
1057 fn reproject_fits_image<'a, T: CanonicalProjection>(
1058 proj: T,
1059 wcs: &WCS,
1060 header: &Header<Image>,
1061 data: &[f32],
1062 ) {
1063 let scale = header.get_parsed::<f32>("BSCALE").unwrap_or(1.0);
1064 let offset = header.get_parsed::<f32>("BZERO").unwrap_or(0.0);
1065
1066 let xtension = header.get_xtension();
1067 let naxis = xtension.get_naxis();
1068 let width = naxis[0];
1069 let height = naxis[1];
1070
1071 let bounds = proj.bounds();
1073 let x_bounds = bounds.x_bounds().as_ref().unwrap_or(&((-PI)..=PI));
1074 let y_bounds = bounds.y_bounds().as_ref().unwrap_or(&((-PI)..=PI));
1075
1076 let x_off = x_bounds.start();
1077 let x_len = x_bounds.end() - x_bounds.start();
1078
1079 let y_off = y_bounds.start();
1080 let y_len = y_bounds.end() - y_bounds.start();
1081
1082 const WIDTH_IMAGE: usize = 1024;
1083 const HEIGHT_IMAGE: usize = 1024;
1084 let mut imgbuf = image::ImageBuffer::new(WIDTH_IMAGE as u32, HEIGHT_IMAGE as u32);
1086
1087 for y in 0..height {
1088 for x in 0..width {
1089 let grayscale_val = (data[(y * width + x) as usize] * scale + offset) as u8;
1090
1091 let img_xy = ImgXY::new(x as f64, y as f64);
1092 if let Some(lonlat) = wcs.unproj(&img_xy) {
1093 if let Some(proj_xy) = proj.proj_lonlat(&lonlat) {
1094 let proj_x = ((proj_xy.x() as f64) - x_off) / x_len; let proj_y = ((proj_xy.y() as f64) - y_off) / y_len; if (0.0..1.0).contains(&proj_x) && (0.0..1.0).contains(&proj_y) {
1098 let ix = (proj_x * (WIDTH_IMAGE as f64)) as usize;
1099 let iy = (proj_y * (HEIGHT_IMAGE as f64)) as usize;
1100
1101 let pixel = imgbuf
1102 .get_pixel_mut(ix as u32, (HEIGHT_IMAGE as u32) - iy as u32 - 1);
1103 *pixel = image::Rgb([grayscale_val, grayscale_val, grayscale_val]);
1104 }
1105 }
1106 }
1107 }
1108 }
1109
1110 let filename = &format!(
1111 "tests/reproj/pans-{}.jpeg",
1112 <T as CanonicalProjection>::WCS_NAME
1113 );
1114 imgbuf.save(filename).unwrap();
1115 }
1116
1117 macro_rules! assert_delta {
1118 ($x:expr, $y:expr, $d:expr) => {
1119 if ($x - $y).abs() > $d {
1120 panic!();
1121 }
1122 };
1123 }
1124
1125 #[test]
1126 fn astropy_comparison() {
1127 use std::fs;
1128
1129 let fits_file_paths = fs::read_dir("./examples")
1130 .unwrap()
1131 .filter_map(|res| res.ok())
1132 .map(|dir_entry| dir_entry.path())
1134 .filter_map(|path| {
1136 if path.extension().map_or(false, |ext| ext == "fits") {
1137 Some(path)
1138 } else {
1139 None
1140 }
1141 });
1142
1143 for path in fits_file_paths {
1144 println!("Test {:?}", path.display());
1145
1146 let f = File::open(path.clone()).unwrap();
1147
1148 let reader = BufReader::new(f);
1149 let mut fits = Fits::from_reader(reader);
1150 let hdu = fits.next().unwrap().unwrap();
1151 match hdu {
1152 HDU::XImage(hdu) | HDU::Primary(hdu) => {
1153 let header = hdu.get_header();
1154 let wcs = wcs_from_fits_header(header).unwrap();
1155
1156 let path_astropy = path.with_extension("fits.csv");
1158 let f = File::open(path_astropy).unwrap();
1159
1160 let mut rdr = csv::Reader::from_reader(BufReader::new(f));
1161 for result in rdr.records() {
1162 let record = result.unwrap();
1163
1164 let ra: f64 = record[0].parse().unwrap();
1165 let dec: f64 = record[1].parse().unwrap();
1166 let x: f64 = record[2].parse().unwrap();
1167 let y: f64 = record[3].parse().unwrap();
1168
1169 if ra.is_finite() && dec.is_finite() {
1170 if let Some(img_xy) = wcs.proj(&LonLat::new(ra, dec)) {
1171 assert_delta!(img_xy.x(), x, 1e-4);
1175 assert_delta!(img_xy.y(), y, 1e-4);
1176 }
1177 }
1178 }
1179 }
1180 _ => unreachable!(),
1181 };
1182 }
1183 }
1184
1185 #[test]
1186 fn crval_to_crpix() {
1187 for entry in glob("examples/*.fits").unwrap() {
1188 if let Ok(path) = dbg!(entry) {
1189 let f = File::open(path).unwrap();
1190 let reader = BufReader::new(f);
1191 let mut fits = Fits::from_reader(reader);
1192 let hdu = fits.next().unwrap().unwrap();
1193
1194 match hdu {
1195 HDU::XImage(hdu) | HDU::Primary(hdu) => {
1196 let header = hdu.get_header();
1197 let crval1 = header.get_parsed::<f64>("CRVAL1").unwrap_or(0.0);
1198 let crval2 = header.get_parsed::<f64>("CRVAL2").unwrap_or(0.0);
1199 let crpix1 =
1200 if let Some(Value::Integer { value, .. }) = header.get("CRPIX1") {
1201 *value as f64
1202 } else if let Some(Value::Float { value, .. }) = header.get("CRPIX1") {
1203 *value
1204 } else {
1205 0.0
1206 };
1207
1208 let crpix2 =
1209 if let Some(Value::Integer { value, .. }) = header.get("CRPIX2") {
1210 *value as f64
1211 } else if let Some(Value::Float { value, .. }) = header.get("CRPIX2") {
1212 *value
1213 } else {
1214 0.0
1215 };
1216
1217 let wcs = wcs_from_fits_header(&header).unwrap();
1218
1219 let proj_px = wcs
1221 .proj(&LonLat::new(
1222 dbg!(crval1).to_radians(),
1223 dbg!(crval2).to_radians(),
1224 ))
1225 .unwrap();
1226 assert_delta!(proj_px.x(), crpix1, 1e-6);
1227 assert_delta!(proj_px.y(), crpix2, 1e-6);
1228
1229 let lonlat = wcs.unproj_lonlat(&ImgXY::new(crpix1, crpix2)).unwrap();
1231 assert_delta!(lonlat.lon(), crval1.to_radians(), 1e-6);
1232 assert_delta!(lonlat.lat(), crval2.to_radians(), 1e-6);
1233 }
1234 _ => unreachable!(),
1235 };
1236 }
1237 }
1238 }
1239
1240 use fitsrs::hdu::HDU;
1241 #[test]
1242 fn open_fits() {
1243 let f = File::open("examples/neowise.fits").unwrap();
1244
1245 let reader = BufReader::new(f);
1246 let mut fits = Fits::from_reader(reader);
1247 let hdu = fits.next().unwrap().unwrap();
1248 match hdu {
1249 HDU::XImage(hdu) | HDU::Primary(hdu) => {
1250 let header = hdu.get_header();
1251 let wcs = wcs_from_fits_header(header).unwrap();
1252 dbg!(wcs.unproj(&ImgXY::new(0.0, 1200.0)));
1253 }
1254 _ => unreachable!(),
1255 }
1256 }
1257}