-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathastrogaia-python.py
More file actions
3187 lines (2863 loc) · 176 KB
/
astrogaia-python.py
File metadata and controls
3187 lines (2863 loc) · 176 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python3
import argparse
import sys
import logging
import astropy.units as u
from astropy.coordinates import SkyCoord
from astroquery.gaia import Gaia
from astropy.coordinates.name_resolve import NameResolveError
from astropy.units.core import UnitsError
from astropy.coordinates import Angle
from astropy.table import Table
from pwn import log
import shutil
from tabulate import tabulate
import random
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.offsetbox import AnchoredText
import matplotlib
import re
from typing import List
import time
import os
from dataclasses import dataclass, field
import requests
from pathlib import Path
import signal
import copy
import numpy as np
from tqdm import tqdm
import warnings
# ANSI escape codes dictionary
colors = {
"BLACK": '\033[30m',
"RED": '\033[31m',
"GREEN": '\033[32m',
"BROWN": '\033[33m',
"BLUE": '\033[34m',
"PURPLE": '\033[35m',
"CYAN": '\033[36m',
"WHITE": '\033[37m',
"GRAY": '\033[1;30m',
"L_RED": '\033[1;31m',
"L_GREEN": '\033[1;32m',
"YELLOW": '\033[1;33m',
"L_BLUE": '\033[1;34m',
"PINK": '\033[1;35m',
"L_CYAN": '\033[1;36m',
"NC": '\033[0m'
}
script_version = 'v1.0.0'
# Define simple characters
sb: str = f'{colors["L_CYAN"]}[{colors["YELLOW"]}*{colors["L_CYAN"]}]{colors["NC"]}' # [*]
sb_v2: str = f'{colors["RED"]}[{colors["YELLOW"]}+{colors["RED"]}]{colors["NC"]}' # [*]
whitespaces: str = " "*(len(sb)+1) # ' '
warning: str = f'{colors["YELLOW"]}[{colors["RED"]}!{colors["YELLOW"]}]{colors["NC"]}' # [!]
# Ctrl-C
def signal_handler(signal, frame):
print(f"{warning} {colors['L_RED']}Ctrl-C. Exiting...{colors['NC']}")
sys.exit(1)
# Redirect the signal handler to trigger Ctrl-C custom function
signal.signal(signal.SIGINT, signal_handler)
# Filter warnings thrown by numpy
# Since not always all the magnitudes will be measured for all the filters, this will throw a warning when
# when attempting to pass them as an array
warnings.filterwarnings('ignore')
# Get user flags
def parseArgs():
"""
Get commands and flags provided by the user
"""
# General description / contact info
general_description = f"{colors['L_CYAN']}Gaia DR3 tool written in Python 💫{colors['NC']} -- "
general_description += f"{colors['L_GREEN']}Contact: {colors['GREEN']}Francisco Carrasco Varela \
(ffcarrasco@uc.cl) ⭐{colors['NC']}"
parser = argparse.ArgumentParser(description=f"{general_description}", epilog=f"example: {sys.argv[0]} extract")
# Define commands
commands = parser.add_subparsers(dest='command')
### 'extract' command
str_extract_command: str = 'extract'
extract_command = commands.add_parser(str_extract_command, help=f'{colors["RED"]}Different modes to extract data{colors["NC"]}',
description=f'{colors["L_RED"]}Extract data from Gaia{colors["NC"]}', epilog=f"example: {sys.argv[0]} extract raw")
parser_sub_extract = extract_command.add_subparsers(dest='subcommand',
help=f"{colors['RED']}Select the source/method to extract data{colors['NC']}")
# Sub-command extract - raw
str_extract_subcommand_raw: str = 'raw'
extract_raw_subcommand_help = f"{colors['L_RED']}Extract raw Gaia data directly from Archive{colors['NC']}"
extract_subcommand_raw = parser_sub_extract.add_parser(str_extract_subcommand_raw, description=extract_raw_subcommand_help,
help=f"{colors['RED']}Extract raw Gaia data directly from Archive{colors['NC']}",
epilog=f"example: {sys.argv[0]} extract raw rectangle")
# Sub-subcommand: extract - raw - cone
extract_raw_cone_subsubcommand_help = f"{colors['RED']}Extract data in 'cone search' mode{colors['NC']}"
parser_sub_extract_raw = extract_subcommand_raw.add_subparsers(dest='subsubcommand', help=f"{colors['RED']}Shape to extract data{colors['NC']}")
str_extract_subcommand_raw_subsubcommand_cone = 'cone'
epilog_str_extract_raw_cone_example = rf'''examples: {sys.argv[0]} extract raw cone -n "47 Tuc" -r 2.1 {colors["GRAY"]}# Extract data for "47 Tucanae" or "NGC104"{colors["NC"]}
{sys.argv[0]} extract raw cone --right-ascension "210" --declination "-60" -r 1.2 -n "myObject" {colors["GRAY"]}# Use a custom name/object, but you have to provide coords{colors["NC"]}
{sys.argv[0]} extract raw cone --right-ascension="20h50m45.7s" --declination="-5d23m33.3s" -r=3.3 {colors["GRAY"]}# Search for negative coordinates{colors["NC"]}
'''
extract_subcommand_raw_subsubcommand_cone = parser_sub_extract_raw.add_parser(str_extract_subcommand_raw_subsubcommand_cone,
help=f"{colors['RED']}Extract data in 'cone search' mode{colors['NC']}",
description=extract_raw_cone_subsubcommand_help,
epilog=epilog_str_extract_raw_cone_example, formatter_class=argparse.RawTextHelpFormatter)
extract_subcommand_raw_subsubcommand_cone.add_argument('-n', '--name', type=str, required=True,
help="Object name. Ideally how it is found in catalogs and no spaces. Examples: 'NGC104', 'NGC_6121', 'Omega_Cen', 'myObject'")
extract_subcommand_raw_subsubcommand_cone.add_argument('-r', '--radii', type=float, required=True,
help="Radius to extract data. Default units: arcmin (see '--radius-units' to change this)")
extract_subcommand_raw_subsubcommand_cone.add_argument('--right-ascension', type=str,
help="Right ascension J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_cone.add_argument('--declination', type=str,
help="Declination J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_cone.add_argument('-o', '--outfile', type=str,
help="Output filename to save data. File extension is automatically added, so '-o example' creates 'example.dat' file")
extract_subcommand_raw_subsubcommand_cone.add_argument('--skip-extra-data', action="store_true", help='Skip online Gaia-based extra data for your object')
extract_subcommand_raw_subsubcommand_cone.add_argument('--gaia-release', default='gdr3', type=str,
help="Select the Gaia Data Release you want to display what type of data contains\nValid options: {gdr3, gaiadr3, g3dr3, gaia3dr3, gdr2, gaiadr2} (Default: Gaia DR3)")
extract_subcommand_raw_subsubcommand_cone.add_argument('--radius-units', default='arcmin', type=str,
help="Units for radius in Cone Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_raw_subsubcommand_cone.add_argument('--row-limit', type=int, default=-1,
help='Limit of rows/data to retrieve from Archive. Default = -1 (which means "NO LIMIT")')
extract_subcommand_raw_subsubcommand_cone.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_raw_subsubcommand_cone.add_argument('--no-print-data-requested', action="store_true", help='Print requested data to Archive')
extract_subcommand_raw_subsubcommand_cone.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
extract_subcommand_raw_subsubcommand_cone.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_raw_subsubcommand_cone.add_argument('--no-save-raw-data', action="store_true", help="Do not save raw data")
# Sub-subcommand: extract - raw - rectangle
str_extract_subcommand_raw_subsubcommand_rect = 'rectangle'
extract_subcommand_raw_subsubcommand_rect_example = f"example: {sys.argv[0]} extract raw rectangle -ra '210' -dec '-60' -w 6.5 -ht 5"
extract_subcommand_raw_subsubcommand_rect = parser_sub_extract_raw.add_parser(str_extract_subcommand_raw_subsubcommand_rect,
help=f"{colors['RED']}Extract data in 'rectangle search' mode{colors['NC']}",
description=f"{colors['L_RED']}Extract data in rectangle shape/mode{colors['NC']}",
epilog=extract_subcommand_raw_subsubcommand_rect_example)
extract_subcommand_raw_subsubcommand_rect.add_argument('-n', '--name', type=str, required=True,
help="Object name. Ideally how it is found in catalogs and no spaces. Examples: 'NGC104', 'NGC_6121', 'Omega_Cen', 'myObject'")
extract_subcommand_raw_subsubcommand_rect.add_argument('-w', '--width', type=float, required=True,
help="Width to extract data. Default units: arcmin (see '--width-units' to change this)")
extract_subcommand_raw_subsubcommand_rect.add_argument('-ht', '--height', type=float, required=True,
help="Height to extract data. Default units: arcmin (see '--height-units' to change this)")
extract_subcommand_raw_subsubcommand_rect.add_argument('--right-ascension', type=str,
help="Right ascension J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_rect.add_argument('--declination', type=str,
help="Declination J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_rect.add_argument('-o', '--outfile', help="output file")
extract_subcommand_raw_subsubcommand_rect.add_argument('--skip-extra-data', action="store_true", help='Skip online Gaia-based extra data for your object')
extract_subcommand_raw_subsubcommand_rect.add_argument('--gaia-release', default='gdr3', type=str,
help="Select the Gaia Data Release you want to display what type of data contains\nValid options: {gdr3, gaiadr3, g3dr3, gaia3dr3, gdr2, gaiadr2} (Default: Gaia DR3)")
extract_subcommand_raw_subsubcommand_rect.add_argument('--width-units', default='arcmin', type=str,
help="Units for width in Rectanguñar Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_raw_subsubcommand_rect.add_argument('--height-units', default='arcmin', type=str,
help="Units for height in Rectangular Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_raw_subsubcommand_rect.add_argument('--row-limit', type=int, default=-1,
help='Limit of rows/data to retrieve from Archive. Default = -1 (which means "NO LIMIT")')
extract_subcommand_raw_subsubcommand_rect.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_raw_subsubcommand_rect.add_argument('--no-print-data-requested', action="store_true", help='Print requested data to Archive')
extract_subcommand_raw_subsubcommand_rect.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
extract_subcommand_raw_subsubcommand_rect.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_raw_subsubcommand_rect.add_argument('--no-save-raw-data', action="store_true", help="Do not save raw data")
# Sub-subcommand: extract - raw - annulus
str_extract_subcommand_raw_subsubcommand_ring = 'ring'
extract_subcommand_raw_subsubcommand_ring_example = f"example: {sys.argv[0]} extract raw ring -ra '210' -dec '-60.5' -i 7.0 -e 6.5"
extract_subcommand_raw_subsubcommand_ring = parser_sub_extract_raw.add_parser(str_extract_subcommand_raw_subsubcommand_ring,
help=f"{colors['RED']}Extract data in 'Annulus/Ring Search' mode{colors['NC']}",
description=f"{colors['L_RED']}Extract data in annulus/ring shape/mode using 2 Cones with different radius{colors['NC']}",
epilog=f"example: {extract_subcommand_raw_subsubcommand_ring_example}")
extract_subcommand_raw_subsubcommand_ring.add_argument('-n', '--name', type=str, required=True,
help="Object name. Ideally how it is found in catalogs and no spaces. Examples: 'NGC104', 'NGC_6121', 'Omega_Cen', 'myObject'")
extract_subcommand_raw_subsubcommand_ring.add_argument('-i', '--inner-radius', type=float, required=True,
help="Inner radius cone to extract data. Default units: arcmin (see '--internal-units' to change this)")
extract_subcommand_raw_subsubcommand_ring.add_argument('-e', '--external-radius', type=float, required=True,
help="External/outer radius cone to extract data. Default units: arcmin (see '--external-units' to change this)")
extract_subcommand_raw_subsubcommand_ring.add_argument('--right-ascension', type=str,
help="Right ascension J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_ring.add_argument('--declination', type=str,
help="Declination J2000 coordinates center. Default units: degrees. Not required if you provide a name found in catalogs.")
extract_subcommand_raw_subsubcommand_ring.add_argument('-o', '--outfile', help="output file")
extract_subcommand_raw_subsubcommand_ring.add_argument('--skip-extra-data', action="store_true", help='Skip online Gaia-based extra data for your object')
extract_subcommand_raw_subsubcommand_ring.add_argument('--gaia-release', default='gdr3', type=str,
help="Select the Gaia Data Release you want to display what type of data contains\nValid options: {gdr3, gaiadr3, g3dr3, gaia3dr3, gdr2, gaiadr2} (Default: Gaia DR3)")
extract_subcommand_raw_subsubcommand_ring.add_argument('--inner-rad-units', default='arcmin', type=str,
help="Units for Inner Radius in Cone Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_raw_subsubcommand_ring.add_argument('--external-rad-units', default='arcmin', type=str,
help="Units for External Radius in Cone Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_raw_subsubcommand_ring.add_argument('--row-limit', type=int, default=-1,
help='Limit of rows/data to retrieve from Archive. Default = -1 (which means "NO LIMIT")')
extract_subcommand_raw_subsubcommand_ring.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_raw_subsubcommand_ring.add_argument('--no-print-data-requested', action="store_true", help='Print requested data to Archive')
extract_subcommand_raw_subsubcommand_ring.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
extract_subcommand_raw_subsubcommand_ring.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_raw_subsubcommand_ring.add_argument('--no-save-raw-data', action="store_true", help="Do not save raw data")
# Sub-command extract - filter
str_extract_subcommand_filter: str = 'filter'
extract_filter_subcommand_help = f"{colors['L_BLUE']}Filter Gaia data applying different methods{colors['NC']}"
extract_subcommand_filter = parser_sub_extract.add_parser(str_extract_subcommand_filter, description=extract_filter_subcommand_help,
help=f"{colors['BLUE']}Filter Gaia data applying different methods{colors['NC']}",
epilog=f"example: {sys.argv[0]} extract filter parameters")
extract_filter_subsubcommand_help = f"{colors['BLUE']}Filter data from Gaia{colors['NC']}"
parser_sub_filter = extract_subcommand_filter.add_subparsers(dest='subsubcommand', help=f"{extract_filter_subsubcommand_help}")
# Sub-subcommand: extract - filter - parameters
str_extract_subcommand_filter_subsubcommand_parameters = 'parameters'
extract_filter_parameters_subsubcommand_help = f"{colors['PURPLE']}Filter Gaia data based on its parameters such as errors, magnitudes, etc{colors['NC']}"
epilog_str_extract_filter_parameters_example = rf'''examples: {sys.argv[0]} extract filter cone -n "47 Tuc" -r 2.1 {colors["GRAY"]}# Extract data for "47 Tucanae" or "NGC104"{colors["NC"]}
{sys.argv[0]} extract raw cone --right-ascension "210" --declination "-60" -r 1.2 -n "myObject" {colors["GRAY"]}# Use a custom name/object, but you have to provide coords{colors["NC"]}
{sys.argv[0]} extract raw cone --right-ascension="20h50m45.7s" --declination="-5d23m33.3s" -r=3.3 {colors["GRAY"]}# Search for negative coordinates{colors["NC"]}
'''
extract_subcommand_filter_subsubcommand_parameters = parser_sub_filter.add_parser(str_extract_subcommand_filter_subsubcommand_parameters,
help=extract_filter_parameters_subsubcommand_help,
description=f"{colors['RED']}Filter Gaia data based on parameters returned in data{colors['NC']}",
epilog=epilog_str_extract_filter_parameters_example,
formatter_class=argparse.RawTextHelpFormatter)
extract_subcommand_filter_subsubcommand_parameters.add_argument('-f', '--file', type=str,
help="File containing data to read, extract and filter.\nIf not provided, name, radius, RA, and DEC parameters are required.\nData will be directly extracted from Archive in 'Cone' Search mode.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('-n', '--name', type=str,
help="Object name. Ideally how it is found in catalogs and no spaces. Examples: 'NGC104', 'NGC_6121', 'Omega_Cen', 'myObject'.\nNot required if you provide a file containing data.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('-r', '--radii', type=float,
help="Radius to extract data. Default units: arcmin (see '--radius-units' to change this).\nNot required if you provide a file containing data.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--right-ascension', type=str,
help="Right ascension J2000 coordinates center. Default units: degrees.\nNot required if you provide a file containing data.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--declination', type=str,
help="Declination J2000 coordinates center. Default units: degrees.\nNot required if you provide a file containing data.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('-d', '--file-format', type=str, default='ascii.ecsv',
help="File format to read file containing data. Default: 'asci.ecsv'")
extract_subcommand_filter_subsubcommand_parameters.add_argument('-o', '--outfile', type=str,
help="Output filename to save data output.")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--filter-by-ruwe', type=float, default=1.4,
help="Filter by Renormalised unit weight error (RUWE).\nOnly keep lower values than the filter value. Default: 1.4")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--filter-by-pm-error', type=float, default=0.35,
help="Filter by Proper Motions errors (RA and DEC components).\nOnly keep lower values than the filter value. Default: 0.35 mas/yr")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--filter-by-g-rp-max', type=float, default=19.5,
help="Filter by max G_RP allowed magnitude.\nOnly keep lower values than the filter value. Default: 19.5 mag")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--filter-by-g-rp-min', type=float, default=10.5,
help="Filter by min G_RP allowed magnitude.\nOnly keep higher values than the filter value. Default: 10.5 mag")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--skip-extra-data', action="store_true",
help='Skip online Gaia-based extra data for your object')
extract_subcommand_filter_subsubcommand_parameters.add_argument('--gaia-release', default='gdr3', type=str,
help="Select the Gaia Data Release you want to display what type of data contains\nValid options: {gdr3, gaiadr3, g3dr3, gaia3dr3, gdr2, gaiadr2} (Default: Gaia DR3)")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--radius-units', default='arcmin', type=str,
help="Units for radius in Cone Search. Options: {arcsec, arcmin, degree} (Default: arcmin)")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--row-limit', type=int, default=-1,
help='Limit of rows/data to retrieve from Archive. Default = -1 (which means "NO LIMIT")')
extract_subcommand_filter_subsubcommand_parameters.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--no-print-data-requested', action="store_true", help='Print requested data to Archive')
extract_subcommand_filter_subsubcommand_parameters.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
extract_subcommand_filter_subsubcommand_parameters.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_filter_subsubcommand_parameters.add_argument('--no-save-output', action="store_true", help="Do not save data output")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--no-filter-ruwe', action="store_true",
help="Do not apply filter by Renormalised unit weight error (RUWE)")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--no-filter-pm-error', action="store_true",
help="Do not apply filter by Proper Motion errors (both components, RA and DEC)")
extract_subcommand_filter_subsubcommand_parameters.add_argument('--no-filter-g-rp', action="store_true",
help="Do not apply filter by G_RP magnitude")
# Sub-subcommand: extract - filter - ellipse
str_extract_subcommand_filter_subsubcommand_ellipse = 'ellipse'
extract_subcommand_filter_subsubcommand_ellipse_example = f"example: {sys.argv[0]} extract filter ellipse -f ngc104_raw.dat --width 5.5 10.2 --height 6.6 7.2"
extract_subcommand_filter_subsubcommand_ellipse = parser_sub_filter.add_parser(str_extract_subcommand_filter_subsubcommand_ellipse,
help=f"{colors['RED']}Extract data within an ellipse in Vector Point Diagram{colors['NC']}",
description=f"{colors['L_RED']}Extract data in annulus/ring shape/mode using 2 Cones with different radius{colors['NC']}",
epilog=extract_subcommand_filter_subsubcommand_ellipse_example,
formatter_class=argparse.RawTextHelpFormatter)
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-f', '--file', type=str, required=True,
help="File containing data to read, extract and filter.")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--pmra', type=float,
help="Coordinate in Proper Motion along RA (mas/yr) where the ellipse is centered in VPD.\nRequired if you are using a \"custom\" object not obtained in GCs or OCs catalogues.")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--pmdec', type=float,
help="Coordinate in Proper Motion along DEC (mas/yr) where the ellipse is centered in VPD.\nRequired if you are using a \"custom\" object not obtained in GCs or OCs catalogues.")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-w', '--width', type=float, nargs='+', required=True,
help="Minimum and maximum width to create ellipses. Example: '--width 15. 25.'\n'Width' is the axis along PMRA coordinate in VPD in 'mas/yr' units")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-ht', '--height', type=float, nargs='+', required=True,
help="Minimum and maximum height to create ellipses. Example: '--height 15. 30.'\n'Height' is the axis along PMDEC coordinate in VPD in 'mas/yr' units")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-i', '--inclination', type=float, nargs='+', default=[-90.0, 90.0],
help="Minimum and maximum angle inclination to create ellipses. Example: '--inclination -89. 89.'\n'Inclination' is the angle between the Y-axis and the width axis counterclockwise in VPD")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-d', '--file-format', type=str, default='ascii.ecsv',
help="File format to read file containing data. Default: 'asci.ecsv'")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('-o', '--outfile', type=str,
help="Output filename to save data output.")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--n-divisions-in-width', type=int, default=10,
help="The program will create an ellipse bewteen the minimum and maximum value of width N times. Default=10")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--n-divisions-in-height', type=int, default=10,
help="The program will create an ellipse bewteen the minimum and maximum value of height N times. Default=10")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--n-divisions-in-inclination', type=int, default=360,
help="The program will create an ellipse bewteen the minimum and maximum value of inclination N times. Default=360")
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--no-print-data-requested', action="store_true", help='Print requested data to Archive')
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_filter_subsubcommand_ellipse.add_argument('--no-save-output', action="store_true", help="Do not save data output")
# Sub-subcommand: extract - filter - cordoni
str_extract_subcommand_filter_subsubcommand_cordoni = 'cordoni'
extract_subcommand_filter_subsubcommand_cordoni_example = f"example: {sys.argv[0]} extract filter cordoni -f ngc104_filter_ellipse.dat"
extract_subcommand_filter_subsubcommand_cordoni = parser_sub_filter.add_parser(str_extract_subcommand_filter_subsubcommand_cordoni,
help=f"{colors['CYAN']}Apply Cordoni et al. (2018, ApJ, 869, 139C) filtering algorithm to data{colors['NC']}",
description=f"{colors['CYAN']}Apply Cordoni et al. (2018, ApJ, 869, 139C) filtering algorithm to Gaia data{colors['NC']}",
epilog=extract_subcommand_filter_subsubcommand_cordoni_example,
formatter_class=argparse.RawTextHelpFormatter)
extract_subcommand_filter_subsubcommand_cordoni.add_argument('-f', '--file', type=str, required=True,
help="File containing data to read, extract and filter.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--n-divisions', type=int, default=20,
help="Number of bins to divide the data into. Default=20")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--n-iterations', type=int, default=3,
help="Number of times to apply Cordoni et al. algorithm. Default=3")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--sigma', type=float, default=3.0,
help='Number of times to multiply standard dev. to median value for every bin. Default=3.0')
extract_subcommand_filter_subsubcommand_cordoni.add_argument('-d', '--file-format', type=str, default='ascii.ecsv',
help="File format to read file containing data. Default: 'asci.ecsv'")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('-o', '--outfile', type=str,
help="Output filename to save data output.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--data-outfile-format', type=str, default='ascii.ecsv',
help="Data file format (not extension) to save data. Default: 'ascii.ecsv'\nFor more info, check: https://docs.astropy.org/en/stable/io/unified.html#built-in-table-readers-writers")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--set-mag-filter', type=str, default="g_rp",
help='Select the Gaia filter you want to use to divide data.\nOptions: {"g_rp", "g_bp", "g"}. Default="g_rp"')
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--set-limits', action="store_true",
help= "Apply custom upper and lower limits in magnitude for data.\nIf not provided (default), maximum and minimum mags will be used to create bins.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--mag-upper-limit', type=float, default=19.5,
help="Maximum magnitude to start creating bins")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--mag-lower-limit', type=float, default=10.5,
help='Minimum magnitude to start creating bins')
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-print-bins', action='store_true',
help='Do not print details for bins created')
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--pmra', type=float, default=0.0,
help="If provided, set explicitly Median PMRA value. Usually where the data is centered in VPD.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--pmdec', type=float, default= 0.0,
help="If provided, set explicitly Median PMDEC value. Usually where the data is centered in VPD.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-as-gof-al', action="store_true",
help="Avoid filtering data by 'as_gof_al' parameter")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-mu-R', action="store_true",
help="Avoid filtering data by 'μ_R' parameter")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-parallax', action="store_true",
help="Avoid filtering data by 'parallax' parameter")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--show-all-plots', action="store_true",
help = "Show all the plots generated in all the iterations")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-plot-as-gof-al', action="store_true", help="Do not plot 'astrometric_gof_al' process")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-plot-mu-R', action="store_true", help="Do not plot 'μ_R' process")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-plot-parallax', action="store_true", help="Do not plot 'parallax' process")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--plot-dark-mode', action="store_true", help="Plot in dark mode (adapt colors in plots to this mode)")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--re-compute-ellipse-center', action="store_true", help="Forces to re-compute the ellipse center even if the was found or not in Archives.")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--no-save-output', action="store_true", help="Do not save data output")
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--force-create-directory', action="store_false", help='Forces (do not ask) creating a folder where all data output will be stored')
extract_subcommand_filter_subsubcommand_cordoni.add_argument('--force-overwrite-outfile', action="store_true", help='Forces overwriting/replace old file without asking to the user')
### 'plot' command
str_plot_command: str = 'plot'
plot_command = commands.add_parser(str_plot_command, help=f"{colors['GREEN']}Plot data{colors['NC']}")
# Sub-command plot -> raw -- Plot data without any filter
parser_subcommand_plot = plot_command.add_subparsers(dest='subcommand', help="Different modes to plot Gaia data")
str_plot_subcommand_raw: str = 'raw'
plot_subcommand_raw = parser_subcommand_plot.add_parser(str_plot_subcommand_raw,
help='Plot data directly extracted from Gaia Archive',
description=f'{colors["L_RED"]}Plot data directly extracted from Gaia Archive{colors["NC"]}')
plot_subcommand_raw.add_argument('-n', '--name', help="Set a object name for the sample. Example: 'NGC104', 'my_sample'")
plot_subcommand_raw.add_argument("--right-ascension", help="Right Ascension (J2000) for the center of data")
plot_subcommand_raw.add_argument("--declination", help="Declination (J2000) for the center of data")
plot_subcommand_raw.add_argument('-r', "--radii", help="Radius for the data centered in (RA, DEC) flags in arcmin")
plot_subcommand_raw.add_argument('--ra-units', default="degree", type=str,
help="Specify the units to use based on 'astropy' (default: degree). Options: {deg, }")
# Sub-command plot -> filter -- Plot data filtered
str_plot_subcommand_filter : str = "from-file"
plot_subcommand_filter = parser_subcommand_plot.add_parser(str_plot_subcommand_filter,
help=f"Plot data from a file containing Gaia data")
plot_subcommand_filter.add_argument("-n", "--name", help="Set a object name for the sample. Example: 'NGC104', 'my_sample'")
### 'show-gaia-content' command
str_show_content_command: str = 'show-gaia-content'
show_content_command = commands.add_parser(str_show_content_command,
help=f"{colors['BROWN']}Show the type of content that different Gaia Releases can provide{colors['NC']}")
show_content_command.add_argument('-r', '--gaia-release', default='gdr3',
help="Select the Gaia Data Release you want to display what type of data contains. \
Valid options: {gdr3, gaiadr3, g3dr3, gaia3dr3, gdr2, gaiadr2}")
show_content_command.add_argument('-t', '--table-format', default='grid',
help="Table display format (default='grid'). To check all formats available visit: https://pypi.org/project/tabulate/")
# parse the command-line arguments
args = parser.parse_args()
return parser, args
def checkUserHasProvidedArguments(parser_provided, args_provided, n_args_provided) -> None:
"""
Display help messages if the user has not provided arguments to a command/subcommand
"""
# If user has not provided a command
if args_provided.command is None:
parser_provided.parse_args(['-h'])
# If user has not provided a subcommand
if args_provided.command == "extract" and args_provided.subcommand is None:
parser_provided.parse_args(['extract', '-h'])
# If user has not provided any argument for the subcommand
if args_provided.command == "extract" and args_provided.subcommand == "raw" and n_args_provided == 3:
parser_provided.parse_args(['extract', 'raw', '-h'])
if args_provided.command == "extract" and args_provided.subcommand == "filter" and n_args_provided == 3:
parser_provided.parse_args(['extract', 'filter', '-h'])
if args_provided.command == "extract" and args_provided.subcommand == "raw" and args_provided.subsubcommand=="rectangle" and n_args_provided == 4:
parser_provided.parse_args(['extract', 'raw', 'rectangle', '-h'])
if args_provided.command == "plot" and args_provided.subcommand is None:
parser_provided.parse_args(['plot', '-h'])
if args_provided.command == "plot" and args_provided.subcommand == "raw" and n_args_provided == 3:
parser_provided.parse_args(['plot', 'raw', '-h'])
if args_provided.command == "plot" and args_provided.subcommand == "from-file" and n_args_provided == 3:
parser_provided.parse_args(['plot', 'from-file', '-h'])
def checkNameObjectProvidedByUser(name_object) -> None:
"""
Checks if a user has provided a valid object name. For example, object name 'NGC104' is valid, '<NGC104>' is not.
Also, 'NGC 104' is converted to 'NGC_104' for future functions/usage
"""
pattern = r'^[\w ]+$'
pass_test = bool(re.match(pattern, name_object))
if pass_test:
return
if not pass_test:
print(f"{warning} You have provided an invalid name (which may contain invalid characters): {colors['RED']}'{name_object}'{colors['NC']}")
print(f" Valid object names examples: NGC104 -- alessa1 -- myRandomObject -- i_love_my_dog")
sys.exit(1)
def printBanner() -> None:
# Color 1
rand_number = random.randint(31,36)
c = f'\033[1;{rand_number}m' # color
sh = f'\033[{rand_number}m' # shadow
nc = colors['NC'] # no color / reset color
# Color 2
rand_number2 = random.randint(31,36)
c2 = f'\033[1;{rand_number2}m' # color
sh2 = f'\033[{rand_number2}m' # shadow
rand_number3 = random.randint(31,36)
c3 = f'\033[1;{rand_number3}m' # color
banner = rf''' {c}_____ __{nc}
{c} / {sh}_ {c}\ _______/ |________ ____{nc}
{c} / {sh}/_\ {c}\ / ___/\ __\_ __ \/ {sh}_ {c}\{nc}
{c}/ | \\___ \ | | | | \( {sh}<_> {c}){nc}
{c}\____|__ /____ > |__| |__| \____/{nc}
{c} \/ \/{nc}
{c2} ________ __{nc}
{c2} / _____/_____ |__|____{nc}
{c2} / \ ___\__ \ | \__ \{nc}
{c2} \ \_\ \/ {sh2}__ {c2}\| |/ {sh2}__ {c2}\_{nc}
{c2} \______ (____ /__(____ /{nc}
{c2} \/ \/ \/{nc} {colors['GRAY']} {script_version}{nc}
'''
print(banner)
print(f"\n{' ' * 11}by {c3}Francisco Carrasco Varela{nc}")
print(f"{' ' * 6}{c3} P. Universidad Católica de Chile{nc}")
print(f"{' ' * 21}{c3}(ffcarrasco@uc.cl){nc}\n")
return
def randomColor() -> str:
"""
Select a random color for text
"""
return f'\033[{random.randint(31,36)}m'
def displaySections(text, color_chosen=colors['NC'], character='#', c=randomColor()):
"""
Displays a section based on the user option/command
"""
nc = colors['NC']
# Get the user's terminal width and compute its half size
terminal_width = shutil.get_terminal_size().columns
total_width = terminal_width // 2
text_width = len(text) + 2
padding_width = (total_width - text_width) // 2
left_padding_width = padding_width
right_padding_width = padding_width
# If the number of characters is odd, add 1 extra character to readjust the size
if (total_width - text_width) % 2 == 1:
right_padding_width += 1
left_padding = character * left_padding_width
right_padding = character * right_padding_width
# Create the text to display
centered_text = f"{c}{left_padding} {color_chosen}{text} {c}{right_padding}{nc}"
border = character * total_width
# Print the result
print(f"\n{c}{border}{nc}\n{centered_text}\n{c}{border}{nc}\n")
def randomChar() -> str:
"""
Select a random character to be printed
"""
char_list = ['#', '=', '+', '$', '@']
# 80% to pick '#', 20% remaining distributed for other characters
weight_list = [0.8, 0.05, 0.05, 0.05, 0.05]
return random.choices(char_list, weights=weight_list,k=1)[0]
#######################
## show-gaia-content ##
#######################
def read_columns_in_gaia_table(output_list):
"""
We saved each column separated by '|'. Now use that character to split every row into its respective columns
"""
rows = []
for line in output_list:
col = []
row = line.strip().split("|")
for column in row:
col.append(column.strip())
rows.append(col)
return rows
def create_table_elements(width_terminal, printable_data_rows_table):
"""
Add colors to the table and sets their parts ready to be printed
"""
# Headers for the table
headers_table = ["Row", "Name" ,"Var Type", "Unit", "Description"]
# Get the max length (the sum of them) for columns that are not the "Description column"
max_length = 0
extra_gap = 19
table_to_show = [row for row in printable_data_rows_table]
for col in printable_data_rows_table:
new_length = len(col[0]) + len(col[1]) + len(col[2]) + len(col[3]) + extra_gap
if new_length > max_length:
max_length = new_length
# Max allowed length before 'wrapping' text
max_allowed_length = width_terminal - max_length - extra_gap
colors_headers_table = [f"{colors['L_CYAN']}Row{colors['NC']}",
f"{colors['PINK']}Name{colors['NC']}",
f"{colors['YELLOW']}Var Type{colors['NC']}",
f"{colors['L_RED']}Units{colors['NC']}",
f"{colors['L_GREEN']}Description{colors['NC']}"]
# Create a table body containing ANSI escape codes so it will print in colors
colors_row_table = []
for column_value in printable_data_rows_table:
color_column = []
# 'Row' column
color_column.append(f"{colors['CYAN']}{column_value[0]}{colors['NC']}")
# 'Name' column
color_column.append(f"{colors['PURPLE']}{column_value[1]}{colors['NC']}")
# 'Var Type' column
color_column.append(f"{colors['BROWN']}{column_value[2]}{colors['NC']}")
# 'Unit' column
color_column.append(f"{colors['RED']}{column_value[3]}{colors['NC']}")
# 'Description' column
color_column.append(f"{colors['GREEN']}{column_value[4]}{colors['NC']}")
colors_row_table.append(color_column)
return colors_headers_table, colors_row_table, max_allowed_length
def print_table(body_table, headers_table, max_allowed_length, table_format):
"""
Print the final table/result
"""
print()
print(tabulate(body_table,
headers=headers_table, tablefmt=table_format,
maxcolwidths=[None, None, None, None, max_allowed_length]))
def select_gaia_astroquery_service(service_requested: str) -> str:
"""
Check the service the user wants to use
"""
service_requested = service_requested.lower()
if 'gaiadr3' in service_requested or 'gdr3' in service_requested:
service = 'gaiadr3.gaia_source'
elif 'gaiaedr3' in service_requested or 'gedr3' in service_requested:
service = 'gaiaedr3.gaia_source'
elif 'gaiadr2' in service_requested or 'gdr2' in service_requested:
service = 'gaiadr2.gaia_source'
else:
print(f"The service you provided is not valid ('{service_requested}'). Using 'GaiaDR3' (default)...")
service = 'gaiadr3.gaia_source'
return service
def get_data_via_astroquery(args, object_info, mode, purpose='normal'):
#(args, input_ra, input_dec, mode)
"""
Get data applying a query to Astroquery
"""
# Get the service to request data
service = select_gaia_astroquery_service(args.gaia_release)
### Get the input parameters
# Mode for "show-gaia-content" command
if purpose == 'content' and mode == 'cone':
input_ra = 280
input_dec = -60
radius_units = u.deg
input_radius = 1.0
input_rows = 1
# Mode for "normal" cone search
if purpose == 'normal' and mode == 'cone':
# Get the coordinates of the object in degrees
input_ra = object_info.RA
input_dec = object_info.DEC
# Get the units for the radius, and check if the radius is valid (positive number)
radius_units = decide_units_parameter(args.radii, args.radius_units)
# Check if the user has provided a valid number of rows to extract
check_number_of_rows_provided(args.row_limit)
input_radius = args.radii
input_rows = args.row_limit
# Mode for "normal" rectangle search
if purpose == 'normal' and mode == 'rectangle':
input_ra = object_info.RA
input_dec = object_info.DEC
width_units = decide_units_parameter(args.width, args.width_units)
height_units = decide_units_parameter(args.height, args.height_units)
check_number_of_rows_provided(args.row_limit)
input_width = args.width
input_height = args.height
input_rows = args.row_limit
# Mode for "normal" ring search
if purpose == 'normal' and mode == 'ring':
# For an annulus/ring, first get the parameters for the external radius
# Get RA, DEC for the object
input_ra = object_info.RA
input_dec = object_info.DEC
# Get the value and units for the external radius
external_radius_units = decide_units_parameter(args.external_radius, args.external_rad_units)
external_radius = args.external_radius
# Check the number of row limit is valid
check_number_of_rows_provided(args.row_limit)
input_rows = args.row_limit
# Get the values for inner radius and its units
inner_radius_units = decide_units_parameter(args.inner_radius, args.inner_rad_units)
inner_radius = args.inner_radius
check_if_inner_and_ext_radius_are_valid(external_radius*external_radius_units, inner_radius*inner_radius_units)
if mode == 'cone':
### Get data via Astroquery
Gaia.MAIN_GAIA_TABLE = service
Gaia.ROW_LIMIT = input_rows
p = log.progress(f'{colors["L_GREEN"]}Requesting data{colors["NC"]}')
logging.getLogger('astroquery').setLevel(logging.WARNING)
# Make request to the service
try:
p.status(f"{colors['PURPLE']}Querying table for '{service.replace('.gaia_source', '')}' service...{colors['NC']}")
coord = SkyCoord(ra=input_ra, dec=input_dec, unit=(u.degree, u.degree), frame='icrs')
radius = u.Quantity(input_radius, radius_units)
j = Gaia.cone_search_async(coord, radius)
logging.getLogger('astroquery').setLevel(logging.INFO)
except:
p.failure(f"{colors['RED']}Error while trying to request data{colors['NC']}")
sys.exit(1)
p.success(f"{colors['L_GREEN']}Data obtained!{colors['NC']}")
# Get the final data to display its columns as a table
r = j.get_results()
return r
if mode == 'rectangle':
### Get data via Astroquery
Gaia.MAIN_GAIA_TABLE = service
Gaia.ROW_LIMIT = input_rows
p = log.progress(f'{colors["L_GREEN"]}Requesting data{colors["NC"]}')
logging.getLogger('astroquery').setLevel(logging.WARNING)
# Make request to the service
try:
p.status(f"{colors['PURPLE']}Querying table for '{service.replace('.gaia_source', '')}' service...{colors['NC']}")
coord = SkyCoord(ra=input_ra, dec=input_dec, unit=(u.degree, u.degree), frame='icrs')
width = u.Quantity(input_width, width_units)
height = u.Quantity(input_height, height_units)
r = Gaia.query_object_async(coordinate=coord, width=width, height=height)
logging.getLogger('astroquery').setLevel(logging.INFO)
except:
p.failure(f"{colors['RED']}Error while trying to request data{colors['NC']}")
sys.exit(1)
p.success(f"{colors['L_GREEN']}Data obtained!{colors['NC']}")
return r
if mode == 'ring':
### Get data via Astroquery
Gaia.MAIN_GAIA_TABLE = service
Gaia.ROW_LIMIT = input_rows
p = log.progress(f"{colors['L_GREEN']}Requesting data{colors['NC']}")
logging.getLogger('astroquery').setLevel(logging.WARNING)
# Make request to the service
try:
# First, make the request for the external radius, which is a normal cone
p.status(f"{colors['PURPLE']}Querying table for '{service.replace('.gaia_source', '')}' service...{colors['NC']}")
coord = SkyCoord(ra=input_ra, dec=input_dec, unit=(u.degree, u.degree), frame='icrs')
radius = u.Quantity(external_radius, external_radius_units)
j = Gaia.cone_search_async(coord, radius)
logging.getLogger('astroquery').setLevel(logging.INFO)
except:
p.failure(f"{colors['RED']}Error while trying to request data for cone (external radius for ring){colors['NC']}")
sys.exit(1)
# Get the final data to display its columns as a table
r = j.get_results()
# Create a mask that filters data which is inside inner radius. So it excludes it
inner_radius_mask = create_mask_for_inner_radius(r, input_ra, input_dec, inner_radius, inner_radius_units, p)
final_data = r[inner_radius_mask]
p.success(f"{colors['L_GREEN']}Data obtained!{colors['NC']}")
return final_data
def check_if_inner_and_ext_radius_are_valid(external_value, inner_value) -> None:
"""
Check if the user provides a inner radius bigger than external radius for a ring, which cannot be possible
"""
if external_value > inner_value:
return
else:
print(f"{warning} {colors['RED']}The inner radius you provided ('{inner_value}') cannot be bigger than external radius ('{external_value}'{colors['NC']})")
sys.exit(1)
def projected_distance_in_sky(point1_ra, point1_dec, point2_ra, point2_dec):
"""
Projected distance in Sky
"""
c1 = SkyCoord(point1_ra, point1_dec, unit=(u.degree, u.degree), frame='icrs')
c2 = SkyCoord(point2_ra, point2_dec, unit=(u.degree, u.degree), frame='icrs')
return c1.separation(c2) # separation in 'deg'
def print_percentage(total, current_value) ->str:
"""
Simple function to print percentage process
"""
return f"{current_value/total * 100.:.2f}%"
def create_mask_for_inner_radius(original_data, coord_ra, coord_dec, inner_radius, inner_radius_units, p, nsteps=400):
message = f"{colors['GREEN']}Creating ring/annulus from Cone Search...{colors['NC']}"
p.status(message)
# Give 2 seconds to read the message
time.sleep(2)
filter_mask = []
original_length = len(original_data)
for index, element in enumerate(original_data):
projected_distance = projected_distance_in_sky(element['ra'], element['dec'], coord_ra, coord_dec)
# Check if the distance of the object is minor than the inner radius
# If it is, exclude that data; otherwise include it
if projected_distance < inner_radius*inner_radius_units:
filter_mask.append(False)
else:
filter_mask.append(True)
# Print process every 400 steps
if index%nsteps == 0:
p.status(f"{message} ({colors['PURPLE']}{print_percentage(original_length, index)}{colors['NC']})")
if original_length != len(filter_mask):
print(f"{warning} {colors['RED']}The Mask used to filter Inner Radius data has a different size ({len(filter_mask)}) compared to original data ({len(original_data)}){colors['NC']}.")
sys.exit(1)
return filter_mask
def get_content_table_to_display(data):
"""
Get the content obtained via Astroquery and set it into a table-readable format, replacing some invalid/null values
"""
output = ""
output_list = []
# Clean the data
for j in range(0, len(data.colnames)):
prop = data.colnames[j]
# Set a value for 'unknown'/not set units
if data[prop].info.unit == None:
data[prop].info.unit = "-"
# Clean '{\rm}', '$' and '}' characters from output
if isinstance(data[prop].info.description, str):
data[prop].info.description = data[prop].info.description.replace('$', '').replace('{\\rm','').replace("}",'')
# If no description is provided, say it
if isinstance(data[prop].info.description, type(None)):
data[prop].info.description = "No description provided"
output_list.append(f'{j+1} | {data[prop].info.name} | {data[prop].info.dtype} | {data[prop].info.unit} | {data[prop].info.description}')
return output_list
def check_if_filename_flag_was_provided(args)->bool | None:
"""
Check if the user has provided a filename. If not, check if other needed flags has been provided and can be
used in its place
"""
# If the user has provided a filename, we can just continue
if args.file is not None:
return True
# If the user has not provided it, then we will have to if some flags has been provided
if args.file is None:
print(f"{sb} {colors['PURPLE']}Filename not provided ('-f'). Attempting to use other parameters provided...{colors['NC']}")
if args.name is None:
print(f"{warning} {colors['RED']}No name provided to object ('-n' or '--name'). Provide a valid value to this parameter and retry.{colors['NC']}")
sys.exit(1)
if args.radii is None:
print(f"{warning} {colors['RED']}No radius provided ('-r' or '--radii'). Provide a valid value to this parameter and retry.{colors['NC']}")
sys.exit(1)
# If the user has provided all of these parameters, then just return a boolean
# to indicate we will have to use them in future steps
return False
def showGaiaContent(args) -> None:
"""
Get columns to display for GaiaDR3, GaiaEDR3 or GaiaDR2
"""
displaySections('show-gaia-content', randomColor(), randomChar())
# Get table format to display the content
table_format = args.table_format
# Create a random 'objectInfo' object just to fill
object_example = objectInfo(name='', RA=280, DEC=-60, pmra=0.0, pmdec=0.0, identifiedAs="Other")
# Get an example data
data = get_data_via_astroquery(args, object_example, 'cone', 'content')
# Get the data into a table format
output_list = get_content_table_to_display(data)
# To display the table first we need to get terminal width
width = shutil.get_terminal_size()[0]
# Get the data for the table (an array where every element is a row of the table)
printable_data_table = read_columns_in_gaia_table(output_list)
# Create table body that will be printed
headers_table, body_table, max_allowed_length = create_table_elements(width, printable_data_table)
# Print the obtained table
print_table(body_table, headers_table, max_allowed_length, table_format)
####################
##### extract ######
####################
def get_object_coordinates(object_name):
"""
Get the coordinates using service from Strasbourg astronomical Data Center (http://cdsweb.u-strasbg.fr)
"""
try:
# Use the SkyCoord.from_name() function to get the coordinates
object_coord = SkyCoord.from_name(object_name)
found_object = True
except NameResolveError:
found_object = False
return None, found_object
return object_coord, found_object
def try_to_extract_angles(coord_parameter):
try:
coord_parameter_angle = Angle(coord_parameter)
return coord_parameter_angle.dec, True
except UnitsError:
coord_parameter_angle = Angle(coord_parameter, unit='deg')
return coord_parameter_angle, True
except:
return None, False
def decide_coords(args, print_process=True):
"""
Based if the object provided by the user was found or not, decide what coordinates the program will use
"""
if print_process:
p = log.progress(f'{colors["L_GREEN"]}Obtaining coordinates for object{colors["NC"]}')
object_coordinates, found_object = get_object_coordinates(args.name)
if found_object:
if print_process:
p.success(f'{colors["GREEN"]}Coords found in Archive{colors["NC"]}')
return object_coordinates.ra, object_coordinates.dec
if not found_object:
# Check if the user has provided parameters so we can extract the coordinates manually
if args.right_ascension is None:
print(f"{warning}{colors['RED']} Invalid object name ('{args.name}') and Right Ascension not provided ('--right-ascension')")
sys.exit(1)
if args.declination is None:
print(f"{warning}{colors['RED']} Invalid object name ('{args.name}') and Declination not provided ('--declination')")
sys.exit(1)
# If the user has provided coordinates, use them
if print_process:
p.failure(f"{colors['RED']} Object could not be found in Archives (astropy). Using coordinates provided by the user instead{colors['NC']}")
# Try to create SkyCoord with provided units
RA, DEC = args.right_ascension, args.declination
try:
coord_manual = SkyCoord(RA, DEC)
except UnitsError:
# Assume default units (degrees) if no units are specified
coord_manual = SkyCoord(ra=RA, dec=DEC, unit=(u.deg, u.deg))
except:
print(f"{warning} {colors['RED']}Unable to convert coordinates provided (RA '{args.right_ascension}' and DEC '{args.declination}') to degree units. Review your input and retry...{colors['NC']}")
sys.exit(1)
return coord_manual.ra.degree, coord_manual.dec.degree
@dataclass(kw_only=True)
class astroStudy:
"""
Studies where the data is extracted from
"""
authors: List[str]
year: int
magazine: str
vol: str
page: str
study_url: str
data_url: str
def show_study(self) -> str:
"""
Prints the classic "Author & Author 2 (2024)" or "Author et al. (2024)"
"""
if len(self.authors) <= 2:
author1 = self.authors[0].split(',')[0]
author2 = self.authors[1].split(',')[0]
return f"{author1} & {author2} ({self.year}, {self.magazine}, {self.vol}, {self.page})"
else:
first_author = self.authors[0].split(',')[0]
return f"{first_author} et al. ({self.year}, {self.magazine}, {self.vol}, {self.page})"
@dataclass(kw_only=True)
class onlineVasilievObject:
"""
Create a data structure for data obtained from Vasiliev & Baumgardt (2021, MNRAS, 505, 5978V)
"""
name: str = '' # object name
opt_name: str = ''# optional name if available
ra: float # deg J2000
dec:float # deg J2000
pmra:float # mas/yr
e_pmra:float # mas/yr
pmdec:float # mas/yr
e_pmdec:float # mas/yr
parallax:float # mas
e_parallax:float # mas
rscale:float # arcmin
nstar:int # number of Gaia-detected cluster stars
def get_extra_object_info_globular_cluster(args, p):
"""
Request Globular Cluster data from Vasiliev & Baumgardt (2021, MNRAS, 505, 5978V) if available
"""
# Check data from Vasiliev & Baumgardt (2021, MNRAS, 505, 5978V)
vasiliev_baumgardt_study = astroStudy(authors=["Vasiliev, E.", "Baumgardt, H."],
year=2021, magazine="MNRAS",
vol="505", page="597V",
study_url='https://ui.adsabs.harvard.edu/abs/2021MNRAS.505.5978V/abstract',
data_url='https://cdsarc.cds.unistra.fr/ftp/J/MNRAS/505/5978/tablea1.dat')
p.status(f"{colors['GREEN']}Requesting data from {vasiliev_baumgardt_study.show_study()}{colors['NC']}")
response = requests.get(vasiliev_baumgardt_study.data_url)
# Check the HTTP status code
if response.status_code == 200:
# Read the content of the response
source_code = response.text
# Split the source code into lines
lines = source_code.splitlines()
# Objects with a single word name
exceptions_object_names = ['Eridanus', 'Pyxis', 'Crater']
# Iterate over each line
for line in lines:
# Split the line into columns
columns = line.split()
single_name_condition = columns[0].lower() == args.name.lower() and args.name.lower() in [exception_object.lower() for exception_object in exceptions_object_names]
single_name_condition = single_name_condition and len(columns) == 12
if single_name_condition:
vasiliev_name = columns[0]
vasiliev_ra = float(columns[1])
vasiliev_dec = float(columns[2])