deblend-sofia-detections 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deblend_sofia_detections/__init__.py +61 -0
- deblend_sofia_detections/catalogue/__init__.py +0 -0
- deblend_sofia_detections/catalogue/download.py +98 -0
- deblend_sofia_detections/catalogue/search.py +304 -0
- deblend_sofia_detections/config/__init__.py +0 -0
- deblend_sofia_detections/config/config.py +51 -0
- deblend_sofia_detections/config/functions.py +110 -0
- deblend_sofia_detections/deblending/__init__.py +0 -0
- deblend_sofia_detections/deblending/deblending.py +803 -0
- deblend_sofia_detections/deblending/image_manipulation.py +354 -0
- deblend_sofia_detections/deblending/peak_handling.py +129 -0
- deblend_sofia_detections/deblending/sofia_functions.py +593 -0
- deblend_sofia_detections/main.py +39 -0
- deblend_sofia_detections/support/__init__.py +0 -0
- deblend_sofia_detections/support/errors.py +18 -0
- deblend_sofia_detections/support/support_functions.py +571 -0
- deblend_sofia_detections/support/system_functions.py +59 -0
- deblend_sofia_detections/support/table_functions.py +186 -0
- deblend_sofia_detections/template/__init__.py +0 -0
- deblend_sofia_detections/template/sofia_template.par +154 -0
- deblend_sofia_detections-0.0.1.dist-info/METADATA +88 -0
- deblend_sofia_detections-0.0.1.dist-info/RECORD +25 -0
- deblend_sofia_detections-0.0.1.dist-info/WHEEL +4 -0
- deblend_sofia_detections-0.0.1.dist-info/entry_points.txt +2 -0
- deblend_sofia_detections-0.0.1.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# -*- coding: future_fstrings -*-
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from importlib.metadata import version
|
|
5
|
+
except ImportError:
|
|
6
|
+
# Try backported to PY<37 `importlib_resources`.
|
|
7
|
+
# For Py<3.9 files is not available
|
|
8
|
+
from importlib_metadata import version
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
|
|
13
|
+
def report_version():
|
|
14
|
+
# Distutils standard way to do version numbering
|
|
15
|
+
try:
|
|
16
|
+
__version__ = version("deblend_sofia_detections")
|
|
17
|
+
except:
|
|
18
|
+
__version__ = "devel"
|
|
19
|
+
|
|
20
|
+
# perhaps we are in a github with tags; in that case return describe
|
|
21
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
try:
|
|
23
|
+
# work round possible unavailability of git -C
|
|
24
|
+
result = subprocess.check_output(
|
|
25
|
+
'cd %s; git describe --tags' % path, shell=True, stderr=subprocess.STDOUT).rstrip().decode()
|
|
26
|
+
except subprocess.CalledProcessError:
|
|
27
|
+
result = None
|
|
28
|
+
if result != None and 'fatal' not in result:
|
|
29
|
+
# will succeed if tags exist
|
|
30
|
+
return result
|
|
31
|
+
else:
|
|
32
|
+
# perhaps we are in a github without tags? Cook something up if so
|
|
33
|
+
try:
|
|
34
|
+
result = subprocess.check_output(
|
|
35
|
+
'cd %s; git rev-parse --short HEAD' % path, shell=True, stderr=subprocess.STDOUT).rstrip().decode()
|
|
36
|
+
except subprocess.CalledProcessError:
|
|
37
|
+
result = None
|
|
38
|
+
if result != None and 'fatal' not in result:
|
|
39
|
+
return __version__+'-'+result
|
|
40
|
+
else:
|
|
41
|
+
# we are probably in an installed version
|
|
42
|
+
return __version__
|
|
43
|
+
|
|
44
|
+
def report_branch():
|
|
45
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
|
46
|
+
result = None
|
|
47
|
+
try:
|
|
48
|
+
branches = subprocess.check_output(\
|
|
49
|
+
'cd %s; git branch' % path, shell=True, stderr=subprocess.STDOUT).rstrip().decode().split()
|
|
50
|
+
for i,line in enumerate(branches):
|
|
51
|
+
if line == '*':
|
|
52
|
+
result=branches[i+1]
|
|
53
|
+
break
|
|
54
|
+
except subprocess.CalledProcessError:
|
|
55
|
+
result= None
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
__version__ = report_version()
|
|
59
|
+
__branch__ = report_branch()
|
|
60
|
+
|
|
61
|
+
#set_logger_values()
|
|
File without changes
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: future_fstrings -*-
|
|
2
|
+
#Functions that look for optical image
|
|
3
|
+
|
|
4
|
+
from deblend_sofia_detections.deblending.image_manipulation import cut_optical
|
|
5
|
+
from deblend_sofia_detections.support.errors import DownloadError
|
|
6
|
+
from astropy import units as u
|
|
7
|
+
from astropy.io import fits
|
|
8
|
+
from astroquery.skyview import SkyView
|
|
9
|
+
from astropy.coordinates import SkyCoord
|
|
10
|
+
from astropy.wcs import WCS
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import warnings
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def download_full_FOV_optical(cfg):
|
|
19
|
+
SkyView.URL = 'https://skyview.gsfc.nasa.gov/current/cgi/basicform.pl'
|
|
20
|
+
if cfg.general.verbose:
|
|
21
|
+
print(f'Quering the Sky Survey')
|
|
22
|
+
if not os.path.isdir(f'{cfg.internal.ancillary_directory}/'):
|
|
23
|
+
os.system(f'mkdir {cfg.internal.ancillary_directory}/')
|
|
24
|
+
#First we open the moment header 0 to get the extend of the field
|
|
25
|
+
with warnings.catch_warnings():
|
|
26
|
+
warnings.simplefilter("ignore")
|
|
27
|
+
mom0_header = fits.getheader(f'{cfg.internal.sofia_directory}/{cfg.internal.sofia_basename}_mom0.fits')
|
|
28
|
+
mom0_wcs = WCS(mom0_header).celestial
|
|
29
|
+
#set the size of the image
|
|
30
|
+
size = np.nanmax([abs(mom0_header['NAXIS1']*mom0_header['CDELT1'])*60.,
|
|
31
|
+
abs(mom0_header['NAXIS2']*mom0_header['CDELT2'])*60.,])
|
|
32
|
+
size_quantity= u.Quantity(size,u.arcmin)
|
|
33
|
+
#get_image_list seems to guess at the pixel size let's fix it to 3 arcsec
|
|
34
|
+
beam = mom0_header['BMAJ'] * u.deg
|
|
35
|
+
optical_pixel_scale = beam.to(u.arcsec).value/cfg.general.optical_pixel_scale * u.arcsec
|
|
36
|
+
#If the resolution of our optical images is greater than 5 the deblending becomes hairy
|
|
37
|
+
if optical_pixel_scale > 4.*u.arcsec:
|
|
38
|
+
optical_pixel_scale = 4. * u.arcsec
|
|
39
|
+
size_pixels = (size_quantity.to(u.arcsec).value/optical_pixel_scale.value).astype(int)
|
|
40
|
+
#obtain the central coordinates
|
|
41
|
+
ra,dec = mom0_wcs.wcs_pix2world(mom0_header['NAXIS1']/2., mom0_header['NAXIS2']/2.,1.)
|
|
42
|
+
obj_coords = SkyCoord(ra= ra* u.degree, dec= dec * u.degree, frame='fk5')
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if not cfg.input.manual_optical_image[0] is None:
|
|
46
|
+
if cfg.general.verbose:
|
|
47
|
+
print(f'Checking manual input')
|
|
48
|
+
for identifier in cfg.general.manual_optical_image:
|
|
49
|
+
if os.path.isfile(identifier):
|
|
50
|
+
print(f'Found manual optical image: {identifier}')
|
|
51
|
+
manual_path,manual_file = os.path.split(identifier)
|
|
52
|
+
if manual_path == '':
|
|
53
|
+
manual_path = './'
|
|
54
|
+
cutout = cut_optical(mom0_header,mom0_wcs,\
|
|
55
|
+
manual_path,
|
|
56
|
+
manual_file)
|
|
57
|
+
|
|
58
|
+
cutout_hdr = cutout.wcs.to_header()
|
|
59
|
+
cutout_hdr['COMMENT'] = f'The original file was {identifier}'
|
|
60
|
+
fits.writeto(f'{cfg.internal.ancillary_directory}/moment0_full_DSS.fits',cutout.data,cutout_hdr,overwrite=True)
|
|
61
|
+
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if cfg.general.verbose:
|
|
66
|
+
print(f'''Obtaining the actual image list with the following parameters:
|
|
67
|
+
object coordinates: {obj_coords.to_string('hmsdms')},
|
|
68
|
+
radius: {size_quantity},
|
|
69
|
+
pixels: {size_pixels},''')
|
|
70
|
+
#SkyView.clear_cache()
|
|
71
|
+
sky_view_list = SkyView.get_image_list(position=obj_coords,
|
|
72
|
+
radius = size_quantity,
|
|
73
|
+
coordinates = "J2000",
|
|
74
|
+
pixels = size_pixels,
|
|
75
|
+
cache=True,
|
|
76
|
+
#survey=['WISE 3.4'])
|
|
77
|
+
#survey=['2MASS-K'])
|
|
78
|
+
survey=['DSS2 Red'])
|
|
79
|
+
|
|
80
|
+
if cfg.general.verbose:
|
|
81
|
+
print(f'''Obtained {len(sky_view_list)} images from SkyView
|
|
82
|
+
{sky_view_list}
|
|
83
|
+
''')
|
|
84
|
+
for path in sky_view_list:
|
|
85
|
+
if cfg.general.verbose:
|
|
86
|
+
print(f'''Starting the download for {cfg.internal.data_cube}.
|
|
87
|
+
This can take a while.''')
|
|
88
|
+
filename = path.split("/")[-1]
|
|
89
|
+
os.system('wget '+path)
|
|
90
|
+
if os.path.isfile(filename):
|
|
91
|
+
print("Successfully downloaded the image")
|
|
92
|
+
os.system(f'mv {filename} {cfg.internal.ancillary_directory}/moment0_full_DSS.fits')
|
|
93
|
+
else:
|
|
94
|
+
print("Failed to obtain the image from SkyView")
|
|
95
|
+
raise DownloadError(f'''Failed to download the image from SkyView: {path}
|
|
96
|
+
Check your internet connection and the SkyView service status.
|
|
97
|
+
Note that redownloading the exact same image may fail if it has recently been removed from the SkyView archive.
|
|
98
|
+
''')
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
from deblend_sofia_detections.support.errors import InputError
|
|
2
|
+
from deblend_sofia_detections.support.table_functions import check_table_length,\
|
|
3
|
+
read_manual_table, combine_tables, copy_table_header
|
|
4
|
+
from deblend_sofia_detections.support.support_functions import convertRADEC,\
|
|
5
|
+
isquantity, get_nan_for_dtype,calculate_projected_distance
|
|
6
|
+
|
|
7
|
+
from astropy.coordinates import SkyCoord
|
|
8
|
+
from astropy.io import fits
|
|
9
|
+
from astropy.table import QTable, Column
|
|
10
|
+
|
|
11
|
+
from astroquery.ipac.ned import Ned
|
|
12
|
+
|
|
13
|
+
import astropy.units as u
|
|
14
|
+
import copy
|
|
15
|
+
import numpy as np
|
|
16
|
+
import time
|
|
17
|
+
|
|
18
|
+
Ned.TIMEOUT = 3600
|
|
19
|
+
|
|
20
|
+
def find_NED_counterpart(cfg,source,header_info, sysrange = None,\
|
|
21
|
+
weights = [1.,1.,1.],weight_min_area = False ):
|
|
22
|
+
requested_columns=['Object Name','RA','DEC','Velocity','Type',\
|
|
23
|
+
'Magnitude and Filter','Distance']
|
|
24
|
+
|
|
25
|
+
coordinates = [source['ra'].unmasked,source['dec'].unmasked]
|
|
26
|
+
#coordinates, radius
|
|
27
|
+
#[source['ra'],source['dec'],source['v_sofia']],\
|
|
28
|
+
# beam_size[0]/2.
|
|
29
|
+
|
|
30
|
+
co = SkyCoord(ra=coordinates[0], dec=coordinates[1], frame='fk5')
|
|
31
|
+
|
|
32
|
+
# Setup a search table in ned
|
|
33
|
+
vsys, radius = set_search_radius(cfg,source,header_info,sysrange,
|
|
34
|
+
counterpart_region=cfg.general.counterpart_region)
|
|
35
|
+
if cfg.general.verbose:
|
|
36
|
+
print("Querying NED")
|
|
37
|
+
print(f'Search a radius {radius.to(u.arcmin)} around {", ".join(convertRADEC(*[x.value for x in coordinates[:2]]))}')
|
|
38
|
+
# get the NED table
|
|
39
|
+
try:
|
|
40
|
+
internet_table = Ned.query_region(co, radius=radius, equinox='J2000.0')
|
|
41
|
+
except:
|
|
42
|
+
time.sleep(10)
|
|
43
|
+
try:
|
|
44
|
+
internet_table = Ned.query_region(co, radius=radius, equinox='J2000.0')
|
|
45
|
+
except Exception as e:
|
|
46
|
+
raise InputError(f'Could not query NED, error {e}')
|
|
47
|
+
# as astropy is the dumbest project ever they can not be consistant so
|
|
48
|
+
# we have to correct the units for RA and DEg
|
|
49
|
+
internet_table['RA'].unit=u.deg
|
|
50
|
+
internet_table['DEC'].unit=u.deg
|
|
51
|
+
# Astropy is so stupid that it does not provide a QTable from the query
|
|
52
|
+
# so we have to do this as well.
|
|
53
|
+
result_table = QTable()
|
|
54
|
+
for x in requested_columns:
|
|
55
|
+
if x in internet_table.colnames:
|
|
56
|
+
tmp_column= internet_table[x]
|
|
57
|
+
tmp_column[tmp_column.mask] = float('NaN')
|
|
58
|
+
|
|
59
|
+
result_table[x] = Column(tmp_column,\
|
|
60
|
+
unit=internet_table[x].unit)
|
|
61
|
+
else:
|
|
62
|
+
result_table[x] = Column([None for x in range(check_table_length(internet_table))])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
#select out the galaxies
|
|
66
|
+
objects_to_select = ['G','GPAIR','GTRPL']
|
|
67
|
+
rows = [True if x.upper() in objects_to_select else False for x in result_table['Type']]
|
|
68
|
+
search_table = result_table[rows]
|
|
69
|
+
|
|
70
|
+
# if we have set a range of velocities we mask all that that are outside the range
|
|
71
|
+
if not sysrange is None:
|
|
72
|
+
rows = [True if vsys-sysrange < x < vsys+sysrange else False for x in search_table['Velocity']]
|
|
73
|
+
search_table = search_table[rows]
|
|
74
|
+
else:
|
|
75
|
+
# if we do not have a velocity range we have to make sure that we take the None as float
|
|
76
|
+
search_table['Velocity'] = [float('NaN') if x is None else x for x in search_table['Velocity']]
|
|
77
|
+
|
|
78
|
+
if check_table_length(search_table) > 0:
|
|
79
|
+
if not isquantity(search_table['Velocity']):
|
|
80
|
+
search_table['Velocity'].unit = u.km/u.s
|
|
81
|
+
else:
|
|
82
|
+
if search_table['Velocity'].unit != u.km/u.s:
|
|
83
|
+
try:
|
|
84
|
+
search_table['Velocity'] = [x.to(u.km/u.s).value for x in search_table['Velocity']]
|
|
85
|
+
search_table['Velocity'].unit = u.km/u.s
|
|
86
|
+
except:
|
|
87
|
+
raise InputError(f'the NED counterpart for {source["Name"]} has a weird unit {search_table["Velocity"].unit}')
|
|
88
|
+
|
|
89
|
+
# first sort on distance, we always do this cause else we do not get the Spatial and combined columns
|
|
90
|
+
search_table = sort_on_distance(search_table, coordinates,vsys)
|
|
91
|
+
if len(search_table) > 1:
|
|
92
|
+
# then pick NGC/UGC/ESO/M matches
|
|
93
|
+
search_table = sort_by_name(search_table)
|
|
94
|
+
|
|
95
|
+
if check_table_length(search_table) > 0:
|
|
96
|
+
name = search_table['Object Name'][0]
|
|
97
|
+
if ':' in name:
|
|
98
|
+
search_table['Object Name'][0] = name.split(':')[0]
|
|
99
|
+
if search_table['Velocity Diff'].unit != u.km/u.s:
|
|
100
|
+
print(search_table)
|
|
101
|
+
print(f'Velocity Diff unit is {search_table["Velocity Diff"].unit}')
|
|
102
|
+
|
|
103
|
+
return search_table
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def find_manual_counterpart(cfg,source,header_info, sysrange=None):
|
|
107
|
+
if cfg.input.manual_input_tables[0] is None:
|
|
108
|
+
return QTable()
|
|
109
|
+
manual_table = read_manual_table(cfg)
|
|
110
|
+
coordinates = [source['sofia_ra'][0],source['sofia_dec'][0]]
|
|
111
|
+
vsys, radius = set_search_radius(cfg,source,header_info,sysrange,
|
|
112
|
+
counterpart_region=cfg.general.counterpart_region)
|
|
113
|
+
search_table = sort_on_distance(manual_table, coordinates,vsys)
|
|
114
|
+
if cfg.general.verbose:
|
|
115
|
+
print(f'Searching for manual counterpart for {source["sofia_id"][0]}')
|
|
116
|
+
print(f'Search a radius {radius.to(u.arcsec)} around {", ".join(convertRADEC(coordinates[0].value,coordinates[1].value))}')
|
|
117
|
+
print(f' The nearest target is {search_table["Name"][0]} at a distance of {search_table["Spatial Diff"][0].to(u.arcsec)}')
|
|
118
|
+
print(f'And the velocity difference is {search_table["Velocity Diff"][0].to(u.km/u.s)} to vsys {vsys.to(u.km/u.s)}')
|
|
119
|
+
|
|
120
|
+
# if we have set a range of velocities we mask all that that are outside the range
|
|
121
|
+
if search_table['Spatial Diff'][0] > 2.*radius.to(u.arcsec) or\
|
|
122
|
+
search_table['Velocity Diff'][0] > sysrange:
|
|
123
|
+
search_table = QTable()
|
|
124
|
+
return search_table
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def search_counter_part(cfg,source,sofia_directory= './',
|
|
130
|
+
basename=None,query ='NED',insource=None):
|
|
131
|
+
'''Look for the optical counterpart of the source'''
|
|
132
|
+
try:
|
|
133
|
+
inid = source['id']
|
|
134
|
+
except:
|
|
135
|
+
inid = source['sofia_id'][0]
|
|
136
|
+
if cfg.general.verbose:
|
|
137
|
+
print(f'Searching in {query} to find a counterpart for {basename} with id {inid}')
|
|
138
|
+
|
|
139
|
+
input_dir = f'{sofia_directory}/{basename}_cubelets'
|
|
140
|
+
cube = fits.open(f'{input_dir}/{basename}_{inid}_cube.fits',\
|
|
141
|
+
output_verify='warn')
|
|
142
|
+
header_info= {'BMAJ':float(cube[0].header['BMAJ'])*u.deg,
|
|
143
|
+
'pixelsize': float(np.mean([abs(cube[0].header['CDELT1']),\
|
|
144
|
+
abs(cube[0].header['CDELT1'])]))\
|
|
145
|
+
*u.deg\
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# first try a spectroscopic match
|
|
149
|
+
if query.upper() == 'NED':
|
|
150
|
+
spectroscopic_table = find_NED_counterpart(cfg,source, header_info,\
|
|
151
|
+
sysrange=150.*u.km/u.s)
|
|
152
|
+
search_id = 'NED'
|
|
153
|
+
pref =''
|
|
154
|
+
elif query.upper() == 'MANUAL':
|
|
155
|
+
spectroscopic_table = find_manual_counterpart(cfg,source, header_info,
|
|
156
|
+
sysrange=150.*u.km/u.s)
|
|
157
|
+
search_id = 'Manual'
|
|
158
|
+
pref = 'sofia_'
|
|
159
|
+
confirmed = True
|
|
160
|
+
|
|
161
|
+
if check_table_length(spectroscopic_table) > 0:
|
|
162
|
+
new_table = spectroscopic_table
|
|
163
|
+
else:
|
|
164
|
+
if cfg.general.verbose:
|
|
165
|
+
print(f'We found no match within the velocity range, picking the closest object without velocity')
|
|
166
|
+
if query.upper() == 'NED':
|
|
167
|
+
possible_table = find_NED_counterpart(cfg,source, header_info)
|
|
168
|
+
if query.upper() == 'MANUAL':
|
|
169
|
+
possible_table = QTable()
|
|
170
|
+
new_table = possible_table
|
|
171
|
+
confirmed = False
|
|
172
|
+
#We don't need the searching rows
|
|
173
|
+
#false_table = True
|
|
174
|
+
#if query.upper() == 'NED':
|
|
175
|
+
# false_table = False
|
|
176
|
+
if check_table_length(new_table) > 0: # and false_table:
|
|
177
|
+
new_table=new_table[0:1]
|
|
178
|
+
final_row = combine_tables(new_table,source,column_indicators=[search_id,insource])
|
|
179
|
+
final_row[f'{search_id}_spectroscopic'] = confirmed
|
|
180
|
+
else:
|
|
181
|
+
if cfg.general.verbose:
|
|
182
|
+
print(f'We found no {search_id} counterpart for {source[pref+"id"]}')
|
|
183
|
+
if query.upper() == 'NED':
|
|
184
|
+
requested_columns = ['Object Name','RA','DEC','Velocity','Type',\
|
|
185
|
+
'Magnitude and Filter','Distance','Spatial Diff','Velocity Diff',\
|
|
186
|
+
'Combined Diff']
|
|
187
|
+
requested_dtypes = ['U30',float,float,float,object,object,object,float,float,float]
|
|
188
|
+
requested_units = [None,u.deg,u.deg,u.km/u.s,None,None,None,u.deg,u.km/u.s,None]
|
|
189
|
+
elif query.upper() == 'MANUAL':
|
|
190
|
+
manual_table = read_manual_table(cfg,need_velocity=False)
|
|
191
|
+
# Add the difference columns
|
|
192
|
+
add_units = [u.deg, u.km/u.s, u.dimensionless_unscaled]
|
|
193
|
+
for i,col in enumerate(['Spatial Diff','Velocity Diff','Combined Diff']):
|
|
194
|
+
if col not in manual_table.colnames:
|
|
195
|
+
manual_table.add_column(np.nan,name=col)
|
|
196
|
+
manual_table[col].unit = add_units[i]
|
|
197
|
+
requested_columns = []
|
|
198
|
+
requested_dtypes = []
|
|
199
|
+
requested_units = []
|
|
200
|
+
for col in manual_table.colnames:
|
|
201
|
+
requested_columns.append(col)
|
|
202
|
+
requested_dtypes.append(manual_table[col].dtype)
|
|
203
|
+
requested_units.append(manual_table[col].unit)
|
|
204
|
+
requested_values = []
|
|
205
|
+
for dt in requested_dtypes:
|
|
206
|
+
requested_values.append(get_nan_for_dtype(dt))
|
|
207
|
+
dummy_table = QTable(names=requested_columns \
|
|
208
|
+
,dtype=requested_dtypes,units = requested_units)
|
|
209
|
+
dummy_table.add_row(requested_values)
|
|
210
|
+
final_row = combine_tables(dummy_table,source,column_indicators=[search_id,insource])
|
|
211
|
+
final_row[f'{search_id}_spectroscopic'] = False
|
|
212
|
+
|
|
213
|
+
return final_row
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def set_search_radius(cfg,source,header_info,sysrange=None,
|
|
217
|
+
counterpart_region = 'Beam'):
|
|
218
|
+
vsys = None
|
|
219
|
+
pref = ''
|
|
220
|
+
for col in source.colnames:
|
|
221
|
+
if 'sofia_' in col.lower():
|
|
222
|
+
pref = 'sofia_'
|
|
223
|
+
if not sysrange is None:
|
|
224
|
+
vsys = source[pref+'v_sofia'].to(u.km/u.s) #systemic in km/s
|
|
225
|
+
|
|
226
|
+
if counterpart_region.lower() in ['beam']:
|
|
227
|
+
radius = header_info['BMAJ']/2.
|
|
228
|
+
elif counterpart_region.lower() in ['3beam']:
|
|
229
|
+
radius = header_info['BMAJ']*3./2.
|
|
230
|
+
elif counterpart_region.lower() in ['box']:
|
|
231
|
+
radius = np.max([source[pref+'x'].value-source[pref+'x_min'].value,\
|
|
232
|
+
source[pref+'x_max'].value-source[pref+'x'].value,
|
|
233
|
+
source[pref+'y'].value-source[pref+'y_min'].value,
|
|
234
|
+
source[pref+'y_max'].value-source[pref+'y'].value
|
|
235
|
+
])*header_info['pixelsize']
|
|
236
|
+
else:
|
|
237
|
+
raise InputError(f'We dont know what to do with {counterpart_region} for counterpart_region')
|
|
238
|
+
return vsys, radius
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
''' Sort our table based on catalogue name'''
|
|
243
|
+
def sort_by_name(table):
|
|
244
|
+
#print(table)
|
|
245
|
+
# The order we prefer things in
|
|
246
|
+
preferred_order = [['NGC'], ['UGC','ESO'], ['M']]
|
|
247
|
+
new_table = copy_table_header( table,)
|
|
248
|
+
|
|
249
|
+
# add the rows to our table based on the preferred order
|
|
250
|
+
for names in preferred_order:
|
|
251
|
+
for row in table:
|
|
252
|
+
row_name = row['Object Name']
|
|
253
|
+
for identifier in names:
|
|
254
|
+
if row_name[:len(identifier)].upper() == identifier:
|
|
255
|
+
if len(identifier) == 1:
|
|
256
|
+
if not row_name[len(identifier):].isdigit():
|
|
257
|
+
continue
|
|
258
|
+
new_table.add_row(row)
|
|
259
|
+
#Attach all final objects that did not have a preffered order
|
|
260
|
+
selected_objects = [x['Object Name'] for x in new_table]
|
|
261
|
+
for row in table:
|
|
262
|
+
if row['Object Name'] not in selected_objects:
|
|
263
|
+
new_table.add_row(row)
|
|
264
|
+
|
|
265
|
+
return new_table
|
|
266
|
+
|
|
267
|
+
'''Sort the table by distance'''
|
|
268
|
+
def sort_on_distance(table_in,coordinates,vsys,weights = [1.,1.],print_in=False):
|
|
269
|
+
# this stupid table is not ordered so get names, types ra and dec and sort on distance
|
|
270
|
+
for x in table_in.colnames:
|
|
271
|
+
if x.lower() in ['spatial_diff','velocity_diff', 'combined_diff']:
|
|
272
|
+
raise InputError(f'Column {x} is not allowed in the table, please rename it')
|
|
273
|
+
if print_in:
|
|
274
|
+
print(f'Before sorting: {table_in}')
|
|
275
|
+
print(f'This the table type {type(table_in)}')
|
|
276
|
+
table = copy.deepcopy(table_in)
|
|
277
|
+
table['Spatial Diff'] = [calculate_projected_distance([x,\
|
|
278
|
+
y],coordinates,no_PA = True).value for x,y in zip(\
|
|
279
|
+
table['RA'],table['DEC'])]* u.deg
|
|
280
|
+
if not vsys is None:
|
|
281
|
+
velocities = table['Velocity'].to(u.km/u.s)
|
|
282
|
+
if isquantity(velocities):
|
|
283
|
+
velocities = velocities.value
|
|
284
|
+
if isquantity(vsys):
|
|
285
|
+
vsys = vsys.to(u.km/u.s).value
|
|
286
|
+
table['Velocity Diff'] = [float(abs(vsys-z)/weights[1])\
|
|
287
|
+
for z in velocities] * u.km/u.s
|
|
288
|
+
table['Combined Diff']= [float(np.sqrt(x.value**2+y.to(u.arcsec).value**2)) for x,y in\
|
|
289
|
+
zip(table['Velocity Diff'],table['Spatial Diff'])] * u.dimensionless_unscaled
|
|
290
|
+
|
|
291
|
+
table.sort('Combined Diff')
|
|
292
|
+
|
|
293
|
+
else:
|
|
294
|
+
|
|
295
|
+
table['Velocity Diff'] = [float('NaN') for x in table['Spatial Diff']]\
|
|
296
|
+
* u.km/u.s
|
|
297
|
+
table['Combined Diff'] = [float('NaN') for x in table['Spatial Diff']]\
|
|
298
|
+
* u.dimensionless_unscaled
|
|
299
|
+
table.sort('Spatial Diff')
|
|
300
|
+
if print_in:
|
|
301
|
+
print(f'After sorting: {table}')
|
|
302
|
+
|
|
303
|
+
return table
|
|
304
|
+
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# OmegaConf setups
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from omegaconf import OmegaConf,MISSING
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import psutil
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Input:
|
|
13
|
+
sofia_parameters: str = 'sofia_input.par' #Give full pathe else expected in the run directory
|
|
14
|
+
manual_input_tables: List = field(default_factory=lambda: [None])
|
|
15
|
+
# If True we will update the manual input table with the information found online
|
|
16
|
+
manual_optical_image: List = field(default_factory=lambda: [None])
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class General:
|
|
20
|
+
verbose: bool = True
|
|
21
|
+
try:
|
|
22
|
+
ncpu: int = len(psutil.Process().cpu_affinity())
|
|
23
|
+
except AttributeError:
|
|
24
|
+
ncpu: int = psutil.cpu_count()
|
|
25
|
+
multiprocessing: bool = True
|
|
26
|
+
optical_pixel_scale: float = 5. # Amount of optical pixels that should cover a beam
|
|
27
|
+
counterpart_region: str = 'Beam'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Internal:
|
|
32
|
+
ancillary_directory: str = MISSING
|
|
33
|
+
data_cube: str = MISSING
|
|
34
|
+
data_directory: str = MISSING
|
|
35
|
+
run_directory: str = MISSING
|
|
36
|
+
sofia_catalogue: str = MISSING
|
|
37
|
+
sofia_directory: str = MISSING
|
|
38
|
+
sofia_parameter_file: str = MISSING
|
|
39
|
+
sofia_basename: str = MISSING
|
|
40
|
+
|
|
41
|
+
#font_file: str = "/usr/share/fonts/truetype/msttcorefonts/Times_New_Roman.ttf"
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class defaults:
|
|
45
|
+
print_examples: bool = False
|
|
46
|
+
configuration_file: Optional[str] = None
|
|
47
|
+
input: Input = field(default_factory = Input)
|
|
48
|
+
general: General = field(default_factory = General)
|
|
49
|
+
internal: Internal = field(default_factory = Internal)
|
|
50
|
+
|
|
51
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from deblend_sofia_detections.config.config import defaults
|
|
2
|
+
from deblend_sofia_detections.support.errors import InputError
|
|
3
|
+
from deblend_sofia_detections.support.system_functions import create_directory
|
|
4
|
+
from deblend_sofia_detections.deblending.sofia_functions import load_sofia_input_file
|
|
5
|
+
from omegaconf import OmegaConf
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import psutil
|
|
9
|
+
import sys
|
|
10
|
+
import deblend_sofia_detections
|
|
11
|
+
try:
|
|
12
|
+
from importlib.resources import files as import_pack_files
|
|
13
|
+
except ImportError:
|
|
14
|
+
# Try backported to PY<37 `importlib_resources`.
|
|
15
|
+
# For Py<3.9 files is not available
|
|
16
|
+
from importlib_resources import files as import_pack_files
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def setup_config(argv):
|
|
20
|
+
if '-v' in argv or '--version' in argv:
|
|
21
|
+
print(f"This is version {deblend_sofia_detections.__version__} of the program.")
|
|
22
|
+
sys.exit()
|
|
23
|
+
|
|
24
|
+
if '-h' in argv or '--help' in argv:
|
|
25
|
+
print('''
|
|
26
|
+
Use package_name in this way:
|
|
27
|
+
|
|
28
|
+
All config parameters can be set directly from the command line by setting the correct parameters, e.g:
|
|
29
|
+
create_package_name def_file=cube.fits error_generator=tirshaker
|
|
30
|
+
''')
|
|
31
|
+
sys.exit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
cfg = OmegaConf.structured(defaults)
|
|
35
|
+
if cfg.general.ncpu == psutil.cpu_count():
|
|
36
|
+
cfg.general.ncpu -= 1
|
|
37
|
+
inputconf = OmegaConf.from_cli(argv)
|
|
38
|
+
cfg_input = OmegaConf.merge(cfg,inputconf)
|
|
39
|
+
|
|
40
|
+
if cfg_input.print_examples:
|
|
41
|
+
default_name = f'{__name__.split(".")[0]}_default.yml'
|
|
42
|
+
masked_copy = OmegaConf.masked_copy(cfg,\
|
|
43
|
+
['input','general'])
|
|
44
|
+
|
|
45
|
+
with open(default_name,'w') as default_write:
|
|
46
|
+
default_write.write(OmegaConf.to_yaml(masked_copy))
|
|
47
|
+
print(f'''We have printed the file {default_name} in {os.getcwd()}.
|
|
48
|
+
Exiting {__name__.split(".")[0]}.''')
|
|
49
|
+
my_resources = import_pack_files('deblend_sofia_detections.template')
|
|
50
|
+
data = (my_resources / 'sofia_template.par').read_bytes()
|
|
51
|
+
with open('sofia_template.par','w+b') as default_write:
|
|
52
|
+
default_write.write(data)
|
|
53
|
+
print(f'''We have printed the file sofia_template.par in {os.getcwd()}.
|
|
54
|
+
''')
|
|
55
|
+
sys.exit()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if cfg_input.configuration_file:
|
|
59
|
+
succes = False
|
|
60
|
+
while not succes:
|
|
61
|
+
try:
|
|
62
|
+
yaml_config = OmegaConf.load(cfg_input.configuration_file)
|
|
63
|
+
#merge yml file with defaults
|
|
64
|
+
cfg = OmegaConf.merge(cfg,yaml_config)
|
|
65
|
+
succes = True
|
|
66
|
+
except FileNotFoundError:
|
|
67
|
+
cfg_input.configuration_file = input(f'''
|
|
68
|
+
You have provided a config file ({cfg_input.configuration_file}) but it can't be found.
|
|
69
|
+
If you want to provide a config file please give the correct name.
|
|
70
|
+
Else press CTRL-C to abort.
|
|
71
|
+
configuration_file = ''')
|
|
72
|
+
cfg = OmegaConf.merge(cfg,inputconf)
|
|
73
|
+
|
|
74
|
+
#open the input parameter file to obtain the data cube and output locations
|
|
75
|
+
cfg = read_parameter_input(cfg)
|
|
76
|
+
|
|
77
|
+
cfg = directory_check(cfg)
|
|
78
|
+
return cfg
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def directory_check(cfg):
|
|
82
|
+
|
|
83
|
+
for test_dir in [cfg.internal.data_directory, cfg.internal.sofia_directory, cfg.internal.run_directory]:
|
|
84
|
+
if not os.path.isdir(test_dir):
|
|
85
|
+
raise InputError(f'''The directory {test_dir} does not exist.''')
|
|
86
|
+
return cfg
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def read_parameter_input(cfg):
|
|
90
|
+
cfg.internal.run_directory = os.getcwd()
|
|
91
|
+
parameters = load_sofia_input_file(cfg.input.sofia_parameters)
|
|
92
|
+
input_pathname,parameter_file = os.path.split(cfg.input.sofia_parameters)
|
|
93
|
+
cfg.internal.sofia_parameter_file = parameter_file
|
|
94
|
+
if input_pathname == '' or input_pathname[0] != '/':
|
|
95
|
+
input_pathname = os.path.join(os.getcwd(),input_pathname)
|
|
96
|
+
data_path,data_file = os.path.split(parameters['input.data'])
|
|
97
|
+
if data_path == '' or data_path[0] != '/':
|
|
98
|
+
cfg.internal.data_directory = f'{input_pathname}{data_path}'
|
|
99
|
+
else:
|
|
100
|
+
cfg.internal.data_directory = data_path
|
|
101
|
+
cfg.internal.ancillary_directory = f'{cfg.internal.data_directory}/ancillary_data'
|
|
102
|
+
cfg.internal.data_cube = data_file
|
|
103
|
+
cfg.internal.sofia_basename = parameters['output.filename']
|
|
104
|
+
if cfg.internal.sofia_basename == '':
|
|
105
|
+
cfg.internal.sofia_basename = os.path.splitext(data_file)[0]
|
|
106
|
+
cfg.internal.sofia_directory = parameters['output.directory']
|
|
107
|
+
if cfg.internal.sofia_directory == '' or cfg.internal.sofia_directory[0] != '/':
|
|
108
|
+
cfg.internal.sofia_directory = os.path.join(input_pathname,cfg.internal.sofia_directory)
|
|
109
|
+
|
|
110
|
+
return cfg
|
|
File without changes
|