├── .gitignore
├── README.md
├── catalog.csv
├── config.cfg
├── data
├── vignetting_sigma8mm_f35.cfg
└── vignetting_sigma8mm_f40.cfg
├── gpl-3.0.txt
├── pyasb
├── __init__.py
├── __main__.py
├── astrometry.py
├── bouguer_fit.py
├── cloud_coverage.py
├── cr2fits.py
├── fits_operator.py
├── flat_field_generator.py
├── fromftp.py
├── help.py
├── image_info.py
├── input_options.py
├── jpeg2fits.py
├── load_fitsimage.py
├── read_config.py
├── sky_brightness.py
├── skymap_plot.py
├── star_calibration.py
└── write_summary.py
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | # C extensions
5 | *.so
6 | # Distribution / packaging
7 | .Python
8 | env/
9 | build/
10 | develop-eggs/
11 | dist/
12 | downloads/
13 | eggs/
14 | .eggs/
15 | lib/
16 | lib64/
17 | parts/
18 | sdist/
19 | var/
20 | *.egg-info/
21 | .installed.cfg
22 | *.egg
23 |
24 |
25 | # PyInstaller
26 | # Usually these files are written by a python script from a template
27 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
28 | *.manifest
29 | *.spec
30 | # Installer logs
31 | pip-log.txt
32 | pip-delete-this-directory.txt
33 | # Unit test / coverage reports
34 | htmlcov/
35 | .tox/
36 | .coverage
37 | .coverage.*
38 | .cache
39 | nosetests.xml
40 | coverage.xml
41 | # Translations
42 | *.mo
43 | *.pot
44 | # Django stuff:
45 | *.log
46 | # Sphinx documentation
47 | docs/_build/
48 | # PyBuilder
49 | target/
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | PyASB (Python - All Sky Brightness pipeline)
2 | ===========
3 |
4 | Absolute photometry and Sky Brightness with all-sky images.
5 |
6 | PyASB is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU General Public License as published by
8 | the Free Software Foundation, either version 3 of the License, or
9 | (at your option) any later version..
10 |
11 | PyASB is distributed in the hope that it will be useful,
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | GNU General Public License for more details.
15 |
16 | You should have received a copy of the GNU General Public License
17 | along with PyASB. If not, see .
18 |
19 | Zenodo:
20 | https://zenodo.org/account/settings/github/repository/mireianievas/PyASB
21 |
--------------------------------------------------------------------------------
/config.cfg:
--------------------------------------------------------------------------------
1 | ####################################
2 | ##### PyASB configuration file #####
3 | ####################################
4 |
5 | ### Observatory
6 | latitude = 40.450941
7 | longitude = -3.726065
8 | obs_name = "UCM"
9 |
10 | # Lateral displacement between optical axis and sensor center.
11 | delta_x = -18.63912476
12 | delta_y = -31.06643504
13 | # Correction for non-perfect zenith pointing.
14 | latitude_offset = -0.64102456
15 | longitude_offset = 0.67447422
16 | # Scale and rotation
17 | radial_factor = 14.19766968
18 | azimuth_zeropoint = 88.64589921
19 | #flip_image = False
20 | #calibrate_astrometry = True
21 |
22 |
23 | ### Zeropoints (ZP-2.5*log10(F/t/A))
24 | #zero_point_B = 9.962,0.018
25 | #zero_point_V = 10.366,0.048
26 | #zero_point_R = 10.298,0.065
27 |
28 | ### Color terms
29 | #color_term_B = 0.0215419,0.00729124
30 | #color_term_B = -0.66622668,0.04550068
31 | #color_term_R = -0.34425197,0.005027
32 |
33 | ### Background levels (Sky Brightness Map limits)
34 | bkgnd_minmax_B = 16.0,19.5
35 | bkgnd_minmax_V = 15.5,18.5
36 | bkgnd_minmax_R = 15.0,17.5
37 |
38 | ### FlatFields, DarkFrame, BiasFrame
39 | #flatfield_B = "/astmon/cal/AstMon_flat_V_mod.fits"
40 | #flatfield_V = "/astmon/cal/AstMon_flat_V_mod.fits"
41 | #flatfield_R = "/astmon/cal/AstMon_flat_V_mod.fits"
42 | #darkframe = "/astmon/cal/MasterDark_40s.fits"
43 |
44 | ### CCD pixel scale and noise sources
45 | #pixel_scale = 61265.160324
46 | ccd_bits = 16
47 | ccd_gain = 0.5
48 | read_noise = 8.7
49 | thermal_noise = 0.02
50 |
51 | # Skymap stretch limits
52 | perc_low = 1
53 | perc_high = 99
54 |
55 | ### Image analysis
56 | min_altitude = 15
57 | base_radius = 0.8
58 | baseflux_detectable = 3
59 | lim_Kendall_tau = 3
60 | max_magnitude = 5
61 | max_star_number = 300
62 |
63 | ### Other options
64 | backgroundmap_title = "NSB at UCM Observatory [AstMon-UCM]"
65 | cloudmap_title = "Cloud Map at UCM Observatory [AstMon-UCM]"
66 |
67 | #skymap_path = "/astmon/"
68 | #photometry_table_path = "/astmon/"
69 | #bouguerfit_path = "/astmon/"
70 | #skybrightness_map_path = "/astmon/"
71 | #skybrightness_table_path = "/astmon/"
72 | #summary_path = "/astmon/"
73 |
74 | ### PyAnalysis Options
75 | #pyanalysis_limits_sb = [16,20.0]
76 | #pyanalysis_limits_extinction = [0,1]
77 | #pyanalysis_limits_colorcolor = [0,3]
78 | #pyanalysis_limits_extext = [-4,4]
79 | #pyanalysis_limits_dates = [None,None]
80 |
--------------------------------------------------------------------------------
/data/vignetting_sigma8mm_f35.cfg:
--------------------------------------------------------------------------------
1 | # Angle, illumination
2 | 000, 1.000
3 | 027, 0.902
4 | 045, 0.783
5 | 063, 0.645
6 | 081, 0.494
7 | 090, 0.398
8 | 100, 0.3
9 | 120, 0.2
10 | 180, 0.2
11 |
--------------------------------------------------------------------------------
/data/vignetting_sigma8mm_f40.cfg:
--------------------------------------------------------------------------------
1 | 0, 1.000
2 | 5.466, 1.000
3 | 10.186, 0.997
4 | 16.025, 0.994
5 | 22.236, 0.987
6 | 28.571, 0.968
7 | 32.174, 0.949
8 | 36.025, 0.929
9 | 39.503, 0.912
10 | 43.727, 0.890
11 | 46.832, 0.872
12 | 50.683, 0.853
13 | 54.037, 0.835
14 | 57.888, 0.816
15 | 61.863, 0.791
16 | 64.348, 0.773
17 | 67.578, 0.749
18 | 70.559, 0.726
19 | 73.292, 0.703
20 | 76.273, 0.677
21 | 79.627, 0.650
22 | 82.484, 0.622
23 | 84.720, 0.601
24 | 87.453, 0.570
25 | 89.441, 0.543
26 | 92.298, 0.502
27 |
--------------------------------------------------------------------------------
/pyasb/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mireianievas/PyASB/478a5e9ad1e16ccaf56cffe40749e7e5ed2f2f5f/pyasb/__init__.py
--------------------------------------------------------------------------------
/pyasb/__main__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python2
2 |
3 | '''
4 | PyASB launcher module
5 |
6 | Concatenate processes
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 |
26 | try:
27 | #import gc
28 | import sys,os,inspect
29 | import signal
30 | import time
31 |
32 | from input_options import *
33 | from image_info import *
34 | from help import *
35 | from astrometry import *
36 | from star_calibration import *
37 | from load_fitsimage import *
38 | from bouguer_fit import *
39 | from sky_brightness import *
40 | from skymap_plot import *
41 | from cloud_coverage import *
42 | from write_summary import *
43 | except:
44 | #raise
45 | print(str(inspect.stack()[0][2:4][::-1])+\
46 | ': One or more modules missing')
47 | raise# SystemExit
48 |
49 | config_file_default = 'config.cfg'
50 |
51 |
52 | '''
53 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54 | ~~~~~~~~~~ Halt handler ~~~~~~~~~~~
55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | '''
57 |
58 |
59 | def handler(signum, frame):
60 | print 'Signal handler called with signal', signum
61 | print "CTRL-C pressed"
62 | raise SystemExit
63 | #sys.exit(0)
64 |
65 | signal.signal(signal.SIGTERM, handler)
66 | signal.signal(signal.SIGINT, handler)
67 |
68 |
69 | #@profile
70 | class LoadImage(object):
71 | def __init__(self, InputOptions, ImageInfo, ConfigOptions, input_file=None):
72 | # Load Image file list
73 | if input_file == None:
74 | input_file = InputOptions.fits_filename_list[0]
75 |
76 | ''' Load fits image '''
77 | self.FitsImage = FitsImage(input_file)
78 | # Local copy of ImageInfo. We will process it further.
79 | self.ImageInfo = ImageInfo
80 | self.ImageInfo.read_header(self.FitsImage.fits_Header)
81 | self.ImageInfo.config_processing_specificfilter(ConfigOptions)
82 |
83 | try:
84 | self.FitsImage.subtract_corners_background = True
85 | self.FitsImage.reduce_science_frame(\
86 | self.ImageInfo.darkframe,\
87 | self.ImageInfo.sel_flatfield,\
88 | MasterBias=None,\
89 | Mask=ImageInfo.maskframe,\
90 | ImageInfo=self.ImageInfo)
91 | except:
92 | raise
93 | print(inspect.stack()[0][2:4][::-1])
94 | print('Cannot reduce science frame')
95 |
96 | # Flip image if needed
97 | self.FitsImage.flip_image_if_needed(self.ImageInfo)
98 |
99 | self.FitsImage.__clear__()
100 | self.output_paths(InputOptions)
101 |
102 |
103 | def output_paths(self,InputOptions):
104 | # Output file paths (NOTE: should be moved to another file or at least separated function)
105 | # Photometric table
106 |
107 | path_list = [\
108 | "photometry_table_path", "skymap_path", "bouguerfit_path", \
109 | "skybrightness_map_path", "skybrightness_table_path", \
110 | "cloudmap_path", "clouddata_path", "summary_path"]
111 |
112 | for path in path_list:
113 | try: setattr(self.ImageInfo,path,getattr(InputOptions,path))
114 | except:
115 | try: getattr(InputOptions,path)
116 | except:
117 | setattr(self.ImageInfo,path,False)
118 |
119 | #@profile
120 | class ImageAnalysis():
121 | def __init__(self,Image):
122 | ''' Analize image and perform star astrometry & photometry.
123 | Returns ImageInfo and StarCatalog'''
124 | self.StarCatalog = StarCatalog(Image.ImageInfo)
125 |
126 | if (Image.ImageInfo.calibrate_astrometry==True):
127 | Image.ImageInfo.skymap_path="screen"
128 | TheSkyMap = SkyMap(Image.ImageInfo,Image.FitsImage)
129 | TheSkyMap.setup_skymap()
130 | TheSkyMap.set_starcatalog(self.StarCatalog)
131 | TheSkyMap.astrometry_solver()
132 |
133 | self.StarCatalog.process_catalog_specific(Image.FitsImage,Image.ImageInfo)
134 | self.StarCatalog.save_to_file(Image.ImageInfo)
135 | TheSkyMap = SkyMap(Image.ImageInfo,Image.FitsImage)
136 | TheSkyMap.setup_skymap()
137 | TheSkyMap.set_starcatalog(self.StarCatalog)
138 | TheSkyMap.complete_skymap()
139 |
140 | '''#@profile
141 | class MultipleImageAnalysis():
142 | def __init__(self,InputOptions):
143 | class StarCatalog_():
144 | StarList = []
145 | StarList_woPhot = []
146 |
147 | InputFileList = InputOptions.fits_filename_list
148 |
149 | for EachFile in InputFileList:
150 | EachImage = LoadImage(EachFile)
151 | EachAnalysis = ImageAnalysis(EachImage)
152 | self.StarCatalog.StarList.append(EachAnalysis.StarCatalog.StarList)
153 | self.StarCatalog.StarList_woPhot.append(EachAnalysis.StarCatalog.StarList_woPhot)
154 | '''
155 |
156 | #@profile
157 | class InstrumentCalibration():
158 | def __init__(self,ImageInfo,StarCatalog):
159 | try:
160 | self.BouguerFit = BouguerFit(ImageInfo,StarCatalog)
161 | except Exception as e:
162 | print(inspect.stack()[0][2:4][::-1])
163 | print('Cannot perform the Bouguer Fit. Error is: ')
164 | print type(e)
165 | print e
166 | exit(0)
167 | #raise
168 |
169 |
170 | #@profile
171 | class MeasureSkyBrightness():
172 | def __init__(self,FitsImage,ImageInfo,BouguerFit):
173 | ImageCoordinates_ = ImageCoordinates(ImageInfo)
174 | TheSkyBrightness = SkyBrightness(\
175 | FitsImage,ImageInfo,ImageCoordinates_,BouguerFit)
176 | TheSkyBrightnessGraph = SkyBrightnessGraph(\
177 | TheSkyBrightness,ImageInfo,BouguerFit)
178 |
179 | '''
180 | TheSkyBrightness = SkyBrightness(ImageInfo)
181 | TheSkyBrightness.load_mask(altitude_cut=10)
182 | TheSkyBrightness.load_sky_image(FitsImage)
183 | #TheSkyBrightness.calibrate_image(FitsImage,ImageInfo,BouguerFit)
184 | TheSkyBrightness.zernike_decomposition(BouguerFit,npoints=5000,order=10)
185 | '''
186 |
187 | self.SBzenith = TheSkyBrightness.SBzenith
188 | self.SBzenith_err = TheSkyBrightness.SBzenith_err
189 |
190 | #@profile
191 | def perform_complete_analysis(InputOptions,ImageInfoCommon,ConfigOptions,input_file):
192 | # Load Image into memory & reduce it.
193 | # Clean (no leaks)
194 | Image_ = LoadImage(InputOptions,ImageInfoCommon,ConfigOptions,input_file)
195 |
196 | # Look for stars that appears in the catalog, measure their fluxes. Generate starmap.
197 | # Clean (no leaks)
198 | ImageAnalysis_ = ImageAnalysis(Image_)
199 |
200 | print('Image date: '+str(Image_.ImageInfo.date_string)+\
201 | ', Image filter: '+str(Image_.ImageInfo.used_filter))
202 |
203 | 'Create the needed classes for the summary write'
204 | class InstrumentCalibration_:
205 | class BouguerFit:
206 | class Regression:
207 | mean_zeropoint = -1
208 | error_zeropoint = -1
209 | extinction = -1
210 | error_extinction = -1
211 | Nstars_rel = -1
212 | Nstars_initial = -1
213 |
214 | try:
215 | # Calibrate instrument with image. Generate fit plot.
216 | # Clean (no leaks)
217 | InstrumentCalibration_ = InstrumentCalibration(\
218 | Image_.ImageInfo,
219 | ImageAnalysis_.StarCatalog)
220 | except:
221 | class ImageSkyBrightness:
222 | SBzenith = '-1'
223 | SBzenith_err = '-1'
224 |
225 | else:
226 | # Measure sky brightness / background. Generate map.
227 | ImageSkyBrightness = MeasureSkyBrightness(\
228 | Image_.FitsImage,
229 | Image_.ImageInfo,
230 | InstrumentCalibration_.BouguerFit)
231 |
232 | '''
233 | Even if calibration fails,
234 | we will try to determine cloud coverage
235 | and write the summary
236 | '''
237 |
238 | # Detect clouds on image
239 | ImageCloudCoverage = CloudCoverage(\
240 | Image_,
241 | ImageAnalysis_,
242 | InstrumentCalibration_.BouguerFit)
243 |
244 | Summary_ = Summary(Image_, InputOptions, ImageAnalysis_, \
245 | InstrumentCalibration_, ImageSkyBrightness, ImageCloudCoverage)
246 |
247 | #gc.collect()
248 | #print(gc.garbage)
249 |
250 |
251 | def get_config_filename(InputOptions):
252 | config_file = config_file_default
253 | try:
254 | assert(InputOptions.configfile!=False)
255 | except:
256 | print(str(inspect.stack()[0][2:4][::-1])+\
257 | ': config file not specified, using the default one:')
258 | else:
259 | config_file = InputOptions.configfile
260 |
261 | return(config_file)
262 |
263 |
264 | if __name__ == '__main__':
265 | #gc.set_debug(gc.DEBUG_STATS)
266 | PlatformHelp_ = PlatformHelp()
267 | InputOptions = ReadOptions(sys.argv)
268 |
269 | config_file = get_config_filename(InputOptions)
270 | ConfigOptions_ = ConfigOptions(config_file)
271 | ImageInfoCommon = ImageInfo()
272 | ImageInfoCommon.config_processing_common(ConfigOptions_,InputOptions)
273 | try:
274 | assert(InputOptions.show_help == False)
275 | except:
276 | print(inspect.stack()[0][2:4][::-1])
277 | # Show help and halt
278 | PlatformHelp_.show_help()
279 | raise SystemExit
280 |
281 | for input_file in InputOptions.fits_filename_list:
282 | perform_complete_analysis(InputOptions,ImageInfoCommon,ConfigOptions_,input_file)
283 |
284 | '''gc.collect()
285 |
286 | d = dict()
287 | for o in gc.get_objects():
288 | name = type(o).__name__
289 | if name not in d:
290 | d[name] = 1
291 | else:
292 | d[name] += 1
293 |
294 | items = d.items()
295 | items.sort(key=lambda x:x[1])
296 | debug_file = open("debug_objects.txt",'w')
297 | debug_file.close()
298 | debug_file = open("debug_objects.txt",'a+')
299 | for key, value in items:
300 | print key, value
301 | debug_file.write(str(key)+",\t"+str(value)+"\n")
302 |
303 | debug_file.close()
304 | '''
305 |
306 |
--------------------------------------------------------------------------------
/pyasb/astrometry.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | PyASB astrometry functions.
5 |
6 | Convert from one coordinate system to another.
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | try:
15 | import sys,os,inspect
16 | import numpy as np
17 | import math
18 | #from numpy.math import pi,sin,cos,sqrt,atan2,asin
19 | import ephem
20 | except:
21 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
22 | raise SystemExit
23 |
24 | __author__ = "Mireia Nievas"
25 | __copyright__ = "Copyright 2012, PyASB project"
26 | __credits__ = ["Mireia Nievas"]
27 | __license__ = "GNU GPL v3"
28 | __shortname__ = "PyASB"
29 | __longname__ = "Python All-Sky Brightness pipeline"
30 | __version__ = "1.99.0"
31 | __maintainer__ = "Mireia Nievas"
32 | __email__ = "mirph4k[at]gmail[dot]com"
33 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
34 |
35 |
36 | ''' Astrometry with Pyephem '''
37 |
38 | # Setup Pyephem Observatory
39 |
40 | def pyephem_setup_common(ImageInfo):
41 | ObsPyephem = ephem.Observer()
42 | ObsPyephem.pressure = 0 # Dont consider atmospheric effects
43 | ObsPyephem.date = ImageInfo.date_string
44 | return ObsPyephem
45 |
46 | def pyephem_setup_image(ImageInfo):
47 | ObsPyephem = pyephem_setup_common(ImageInfo)
48 | ObsPyephem.lat = (ImageInfo.latitude-ImageInfo.latitude_offset)*np.pi/180
49 | ObsPyephem.lon = (ImageInfo.longitude-ImageInfo.longitude_offset)*np.pi/180
50 | return ObsPyephem
51 |
52 | def pyephem_setup_real(ImageInfo):
53 | ObsPyephem = pyephem_setup_common(ImageInfo)
54 | ObsPyephem.lat = (ImageInfo.latitude)*np.pi/180
55 | ObsPyephem.lon = (ImageInfo.longitude)*np.pi/180
56 | return ObsPyephem
57 |
58 | '''
59 | Standalone functions.
60 | To be used on single points
61 | '''
62 |
63 | def horiz2xy_old(azimuth,altitude,ImageInfo):
64 | '''
65 | Return X,Y position in the image from azimuth/altitude horizontal coord.
66 | azimuth and altitude must be in degrees.
67 | '''
68 |
69 | Rfactor = ImageInfo.radial_factor*(180.0/np.pi)*np.sqrt(2*(1-np.sin(altitude*np.pi/180.0)))
70 | X = ImageInfo.resolution[0]/2 + ImageInfo.delta_x -\
71 | Rfactor*np.cos(azimuth*np.pi/180.0-ImageInfo.azimuth_zeropoint*np.pi/180.0)
72 | Y = ImageInfo.resolution[1]/2 + ImageInfo.delta_y +\
73 | Rfactor*np.sin(azimuth*np.pi/180.0-ImageInfo.azimuth_zeropoint*np.pi/180.0)
74 | return(X,Y)
75 |
76 | def horiz2xy(azimuth,altitude,ImageInfo,derotate=True):
77 | '''
78 | Return X,Y position in the image from azimuth/altitude horizontal coord.
79 | azimuth and altitude must be in degrees.
80 | '''
81 |
82 | if derotate==True and (ImageInfo.latitude_offset!=0 or ImageInfo.longitude_offset!=0):
83 | # We have a real azimuth and altitude coordinates. If the camera is not
84 | # pointing to the zenith, we need to derotate the image.
85 | ra_appa,dec_appa = horiz2eq(\
86 | azimuth,altitude,\
87 | ImageInfo,\
88 | lat = ImageInfo.latitude,\
89 | lon = ImageInfo.longitude)
90 |
91 | azimuth,altitude = eq2horiz(\
92 | ra_appa,dec_appa,\
93 | ImageInfo,\
94 | lat = ImageInfo.latitude-ImageInfo.latitude_offset,\
95 | lon = ImageInfo.longitude-ImageInfo.longitude_offset)
96 |
97 | ### allow for different projections
98 | if ImageInfo.projection == 'ZEA':
99 | Rfactor = ImageInfo.radial_factor*(180.0/np.pi)*np.sqrt(2*(1-np.sin(altitude*np.pi/180.0)))
100 | elif ImageInfo.projection == 'ARC':
101 | Rfactor = ImageInfo.radial_factor*(180.0/np.pi)*(1-altitude/90.0)
102 |
103 | #X = ImageInfo.resolution[0]/2 + ImageInfo.delta_x -\
104 | # Rfactor*np.cos(azimuth*np.pi/180.0-ImageInfo.azimuth_zeropoint*np.pi/180.0)
105 |
106 | X = ImageInfo.resolution[0]/2 - ImageInfo.delta_x +\
107 | Rfactor*np.cos(azimuth*np.pi/180.0-ImageInfo.azimuth_zeropoint*np.pi/180.0)
108 | Y = ImageInfo.resolution[1]/2 + ImageInfo.delta_y +\
109 | Rfactor*np.sin(azimuth*np.pi/180.0-ImageInfo.azimuth_zeropoint*np.pi/180.0)
110 | return(X,Y)
111 |
112 | def xy2horiz(X,Y,ImageInfo,derotate=True):
113 | '''
114 | Return horizontal coordinates from X,Y position in the image.
115 | azimuth and altitude are in degrees.
116 | '''
117 |
118 | X = X - ImageInfo.resolution[0]/2.-ImageInfo.delta_x
119 | Y = Y - ImageInfo.resolution[1]/2.-ImageInfo.delta_y
120 | X = ImageInfo.resolution[0] - X # flip the image horizontally
121 | Rfactor = np.sqrt(X**2 + Y**2)/ImageInfo.radial_factor
122 |
123 | if np.size(Rfactor)>1:
124 | Rfactor[Rfactor>360./np.pi]=360./np.pi
125 |
126 | ### allow for different projections
127 | if ImageInfo.projection == 'ZEA':
128 | alt_factor = np.array(1-0.5*(np.pi*Rfactor/180.0)**2)
129 | altitude = (180.0/np.pi)*np.arcsin(alt_factor)
130 |
131 | elif ImageInfo.projection == 'ARC':
132 | altitude = 90*(1-(Rfactor/ImageInfo.radial_factor)*(np.pi/180.0))
133 |
134 | azimuth = (360+ImageInfo.azimuth_zeropoint + 180.0*np.arctan2(Y,-X)/np.pi)%360
135 |
136 | if derotate==True and (ImageInfo.latitude_offset!=0 or ImageInfo.longitude_offset!=0):
137 | # We have a real azimuth and altitude coordinates. If the camera is not
138 | # pointing to the zenith, we need to rotate the image.
139 |
140 | ra_real,dec_real = horiz2eq(\
141 | azimuth,altitude,\
142 | ImageInfo,\
143 | lat = ImageInfo.latitude-ImageInfo.latitude_offset,\
144 | lon = ImageInfo.longitude-ImageInfo.longitude_offset)
145 |
146 | azimuth,altitude = eq2horiz(\
147 | ra_real,dec_real,\
148 | ImageInfo,\
149 | lat = ImageInfo.latitude,\
150 | lon = ImageInfo.longitude)
151 |
152 | return(azimuth,altitude)
153 |
154 | def eq2horiz(ra,dec,ImageInfo=None,sidtime=None,lat=None,lon=None):
155 | '''
156 | Calculate horizontal coordinates for the given observation site
157 | and the given point in the sky.
158 | The coordinates must be given in degrees or hours
159 | '''
160 |
161 | if lat==None: lat=ImageInfo.latitude
162 | if lon==None: lon=ImageInfo.longitude
163 | if sidtime==None: sidtime = ImageInfo.sidereal_time
164 |
165 | # Sidereal Time to Local Sidereal Time
166 | sidtime = sidtime + lon/15.
167 |
168 | lat = lat*np.pi/180.
169 | lon = lon*np.pi/180.
170 | sidtime = sidtime*np.pi/12.
171 | ra = ra*np.pi/12.
172 | dec = dec*np.pi/180.
173 |
174 | H = sidtime - ra
175 |
176 | _sina = np.sin(dec)*np.sin(lat)+np.cos(dec)*np.cos(lat)*np.cos(H)
177 | alt = np.arcsin(_sina)
178 | _cosa = np.cos(alt)
179 | _sinA = -np.sin(H)*np.cos(dec)/_cosa
180 | _cosA = (np.sin(dec)-np.sin(lat)*_sina)/(_cosa*np.cos(lat))
181 | az = np.arctan2(_sinA,_cosA)
182 |
183 | az = (az*180./np.pi)%360
184 | alt = alt*180./np.pi
185 |
186 | return(az,alt)
187 |
188 | def horiz2eq(az,alt,ImageInfo=None,sidtime=None,lat=None,lon=None):
189 | '''
190 | Calculate equatorial coordinates for the given observation site
191 | and the given point in the sky
192 | The coordinates must be given in degrees or hours
193 | '''
194 |
195 | if lat==None: lat=ImageInfo.latitude
196 | if lon==None: lon=ImageInfo.longitude
197 | if sidtime==None: sidtime = ImageInfo.sidereal_time
198 |
199 | # Sidereal Time to Local Sidereal Time
200 | sidtime = sidtime + lon/15.
201 |
202 | lat = lat*np.pi/180.
203 | lon = lon*np.pi/180.
204 | sidtime = sidtime*np.pi/12.
205 | az = az*np.pi/180.
206 | alt = alt*np.pi/180.
207 |
208 | _sindec = np.sin(alt)*np.sin(lat)+np.cos(alt)*np.cos(lat)*np.cos(az)
209 | dec = np.arcsin(_sindec)
210 | _cosdec = np.cos(dec)
211 | _sinH = -np.sin(az)*np.cos(alt)/_cosdec
212 | _cosH = (np.sin(alt)-_sindec*np.sin(lat))/(_cosdec*np.cos(lat))
213 |
214 | H = np.arctan2(_sinH,_cosH)
215 | ra = sidtime - H
216 |
217 | ra = (ra*12./np.pi)%24
218 | dec = dec*180./np.pi
219 |
220 | return(ra,dec)
221 |
222 | def zenith_position(ImageInfo):
223 | # Return X,Y position of zenith in the image.
224 | return horiz2xy(0,90,ImageInfo)
225 |
226 | def optical_axis(ImageInfo):
227 | # Return horizontal coordinates of the optical axis
228 | return xy2horiz(ImageInfo.resolution[0]/2,ImageInfo.resolution[1]/2,ImageInfo)
229 |
230 | def atmospheric_refraction(altitude,mode):
231 | # Return apparent (non-corrected from refraction) or
232 | # real (corrected from refraction) altitude.
233 | # Garfinkel (1967), http://en.wikipedia.org/wiki/Atmospheric_refraction
234 | def cot(x):
235 | # Return cotangent of the given value
236 | return np.cos(x)/np.sin(x)
237 |
238 | if mode=='dir':
239 | # Return apparent altitude from the real one.
240 | return altitude + (1.02/60)*cot(altitude + 10.3/(altitude+5.11))
241 | elif mode=='inv':
242 | # Return real altitude from the apparent one
243 | return altitude - (1.00/60)*cot(altitude + 7.31/(altitude+4.4))
244 | else:
245 | print 'Unknow mode '+mode+'. Cannot correct from atmospheric refraction.'
246 | return altitude
247 |
248 | def calculate_airmass(altitude):
249 | # Estimate airmass from apparent altitude using Pickering (2002) model.
250 | # zdist in degrees
251 | return 1/np.sin((altitude+244./(165+47*altitude**1.1))*np.pi/180.)
252 |
253 | '''
254 | Vectorial functions.
255 | Generate a class that contains a map of coordinates
256 | that match the Image pixels
257 | '''
258 |
259 | class ImageCoordinates():
260 | def __init__(self,ImageInfo):
261 | self.calculate_altaz(ImageInfo)
262 |
263 | def calculate_altaz(self,ImageInfo):
264 | ''' Reimplementation with numpy arrays (fast on large arrays).
265 | We need it as we will use a very large array'''
266 |
267 | # Image coordinates
268 | x = np.arange(ImageInfo.resolution[0])
269 | y = np.arange(ImageInfo.resolution[1])
270 | X,Y = np.meshgrid(x,y)
271 |
272 | # Unreal/projected altitude and azimuth
273 | az,alt = xy2horiz(X,Y,ImageInfo,derotate=False)
274 | self.azimuth_map = np.array(az,dtype='float16')
275 | self.altitude_map = np.array(alt,dtype='float16')
276 |
--------------------------------------------------------------------------------
/pyasb/bouguer_fit.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Bouguer fitting module
5 |
6 | Fit fluxes and star data to an extinction law to obtain
7 | extinction and instrument zeropoint.
8 | ____________________________
9 |
10 | This module is part of the PyASB project,
11 | created and maintained by Mireia Nievas [UCM].
12 | ____________________________
13 | '''
14 |
15 | DEBUG=False
16 |
17 | __author__ = "Mireia Nievas"
18 | __copyright__ = "Copyright 2012, PyASB project"
19 | __credits__ = ["Mireia Nievas"]
20 | __license__ = "GNU GPL v3"
21 | __shortname__ = "PyASB"
22 | __longname__ = "Python All-Sky Brightness pipeline"
23 | __version__ = "1.99.0"
24 | __maintainer__ = "Mireia Nievas"
25 | __email__ = "mirph4k[at]gmail[dot]com"
26 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
27 |
28 |
29 | try:
30 | import sys,os,inspect
31 | import matplotlib.pyplot as plt
32 | import matplotlib.colors as mpc
33 | import matplotlib.patches as mpp
34 | import scipy.stats as stats
35 | import math
36 | import numpy as np
37 | import astrometry
38 | except:
39 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
40 | raise SystemExit
41 |
42 | class BouguerFit():
43 | def __init__(self,ImageInfo,PhotometricCatalog):
44 | print('Calculating Instrument zeropoint and extinction ...')
45 | self.can_continue = False
46 | # Get Zero Point from ImageInfo and Stars from the Catalog
47 | self.bouguer_fixedy(ImageInfo)
48 | self.bouguer_data(PhotometricCatalog)
49 | # Set the default values
50 | self.bouguer_setdefaults(ImageInfo)
51 | # Try to fit the data
52 | try:
53 | self.bouguer_fit(ImageInfo)
54 | self.bouguer_plot(ImageInfo)
55 | except:
56 | print(str(inspect.stack()[0][2:4][::-1])+\
57 | ' cannot fit the data. Npoints='+str(len(self.ydata)))
58 | assert(self.can_continue == True)
59 | raise
60 | else:
61 | self.can_continue = True
62 |
63 | assert(self.can_continue == True)
64 | print("Bouguer extinction fit results: \n"+\
65 | " -> C=%.3f+/-%.3f, K=%.3f+/-%.3f, r=%.3f" \
66 | %(self.Regression.mean_zeropoint,self.Regression.error_zeropoint,\
67 | self.Regression.extinction,self.Regression.error_extinction,\
68 | self.Regression.kendall_tau))
69 |
70 | def bouguer_data(self,StarCatalog):
71 | ''' Get Star data from the catalog '''
72 | self.xdata = np.array([Star.airmass \
73 | for Star in StarCatalog.StarList_Phot])
74 | self.ydata = np.array([Star.m25logF \
75 | for Star in StarCatalog.StarList_Phot])
76 | self.yerr = np.array([Star.m25logF_unc \
77 | for Star in StarCatalog.StarList_Phot])
78 |
79 | def bouguer_fixedy(self,ImageInfo):
80 | ''' Try to get the fixed Y (zero point)'''
81 | try:
82 | self.fixed_y = ImageInfo.used_zero_point[0]
83 | self.fixed_y_unc = ImageInfo.used_zero_point[1]
84 | self.yisfixed=True
85 | except:
86 | if DEBUG==True: print(str(inspect.stack()[0][2:4][::-1])+' dont fix the Zero Point')
87 | self.yisfixed=False
88 |
89 | def bouguer_setdefaults(self,ImageInfo):
90 | ''' Set default values (just in case that the bouguer fit fails '''
91 | if self.yisfixed == True:
92 | class Regression:
93 | mean_zeropoint = self.fixed_y
94 | error_zeropoint = self.fixed_y_unc
95 | mean_slope = 10.0
96 | error_slope = 10.0
97 | extinction = 10.0
98 | error_extinction = 10.0
99 | kendall_tau = 0.0
100 | Nstars_initial = 0
101 | Nstars_final = 0
102 | Nstars_rel = 0
103 | self.Regression = Regression()
104 | self.can_continue = True
105 |
106 | def bouguer_fit(self,ImageInfo):
107 | '''
108 | Fit measured fluxes to an extinction model
109 | Return regression parameters (ZeroPoint, Extinction)
110 | '''
111 |
112 | if self.yisfixed:
113 | self.Regression = TheilSenRegression(\
114 | Xpoints = self.xdata,\
115 | Ypoints = self.ydata,\
116 | ImageInfo = ImageInfo,\
117 | y0 = self.fixed_y,\
118 | y0err = self.fixed_y_unc)
119 | else:
120 | try:
121 | self.Regression = TheilSenRegression(\
122 | Xpoints = self.xdata,\
123 | Ypoints = self.ydata,\
124 | ImageInfo = ImageInfo)
125 | except:
126 | print(inspect.stack()[0][2:4][::-1]);
127 | raise
128 |
129 | # Apply bad point filter to data
130 | self.xdata = self.xdata[self.Regression.badfilter]
131 | self.ydata = self.ydata[self.Regression.badfilter]
132 | self.yerr = self.yerr[self.Regression.badfilter]
133 |
134 | def bouguer_plot(self,ImageInfo):
135 | if ImageInfo.bouguerfit_path==False:
136 | # Don't draw anything
137 | print('Skipping BouguerFit Graph')
138 | return(None)
139 |
140 | ''' Plot photometric data from the bouguer fit '''
141 |
142 | xfit = np.linspace(1,astrometry.calculate_airmass(ImageInfo.min_altitude),10)
143 | yfit = np.polyval([self.Regression.mean_slope,self.Regression.mean_zeropoint],xfit)
144 |
145 | bouguerfigure = plt.figure(figsize=(8,6))
146 | bouguerplot = bouguerfigure.add_subplot(111)
147 | bouguerplot.set_title('Bouguer extinction law fit\n',size="xx-large")
148 | bouguerplot.set_xlabel('Airmass')
149 | bouguerplot.set_ylabel(r'$m_0+2.5\log_{10}(F)$',size="large")
150 | bouguerplot.errorbar(self.xdata, self.ydata, yerr=self.yerr, fmt='*', ecolor='g')
151 | bouguerplot.plot(xfit,yfit,'r-')
152 |
153 | try:
154 | plot_infotext = \
155 | ImageInfo.date_string+"\n"+str(ImageInfo.latitude)+5*" "+str(ImageInfo.longitude)+"\n"+\
156 | ImageInfo.used_filter+4*" "+"Rcorr="+str("%.3f"%float(self.Regression.kendall_tau))+"\n"+\
157 | "C="+str("%.3f"%float(self.Regression.mean_zeropoint))+\
158 | "+/-"+str("%.3f"%float(self.Regression.error_zeropoint))+"\n"+\
159 | "K="+str("%.3f"%float(self.Regression.extinction))+"+/-"\
160 | +str("%.3f"%float(self.Regression.error_slope))+"\n"+\
161 | str("%.0f"%(self.Regression.Nstars_rel))+"% of "+\
162 | str(self.Regression.Nstars_initial)+" photometric measures shown"
163 | bouguerplot.text(0.05,0.05,plot_infotext,fontsize='x-small',transform = bouguerplot.transAxes)
164 | except:
165 | print(inspect.stack()[0][2:4][::-1])
166 | raise
167 |
168 | # Show or save the bouguer plot
169 | if ImageInfo.bouguerfit_path=="screen":
170 | plt.show()
171 | else:
172 | bouguer_filename = str("%s/BouguerFit_%s_%s_%s.png" %(\
173 | ImageInfo.bouguerfit_path, ImageInfo.obs_name,\
174 | ImageInfo.fits_date, ImageInfo.used_filter))
175 | plt.tight_layout(pad=0)
176 | plt.savefig(bouguer_filename,bbox_inches='tight')
177 |
178 | #plt.clf()
179 | #plt.close('all')
180 |
181 | class TheilSenRegression():
182 | # Robust Theil Sen estimator, instead of the classic least-squares.
183 | def __init__(self,Xpoints,Ypoints,ImageInfo,y0=None,y0err=None,x0=None,x0err=None):
184 | assert(len(Xpoints)==len(Ypoints) and len(Ypoints)>2)
185 | self.Xpoints = np.array(Xpoints)
186 | self.Ypoints = np.array(Ypoints)
187 | if y0!=None:
188 | self.fixed_zp = True
189 | self.y0 = y0
190 | if y0err!=None:
191 | self.y0err=y0err
192 | else:
193 | self.y0err=0.0
194 |
195 | if x0!=None:
196 | self.x0 = x0
197 | if x0err!=None:
198 | self.x0err = 0.0
199 | else:
200 | self.x0 = 0.0
201 | self.x0err = 0.0
202 | else: self.fixed_zp = False
203 | self.Nstars_initial = len(self.Ypoints)
204 | self.Nstars_final = self.Nstars_initial
205 | self.pair_blacklist = []
206 | # Perform the regression
207 | self.perform_regression()
208 | # Delete bad points
209 | self.delete_bad_points(ImageInfo)
210 | # Try to improve the regression with filtered data
211 | self.perform_regression()
212 |
213 | self.Nstars_final = sum(self.badfilter)
214 | self.Nstars_rel = 100.*self.Nstars_final/self.Nstars_initial
215 |
216 | def perform_regression(self):
217 | # Prepare data for regression
218 | self.build_matrix_values()
219 | self.build_complementary_matrix()
220 | self.build_slopes_matrix()
221 | self.upper_diagonal_slope_matrix_values()
222 | # Slope
223 | self.calculate_mean_slope()
224 | # Zero point
225 | self.build_zeropoint_array()
226 | self.calculate_mean_zeropoint()
227 | # Errors and fit quality
228 | self.calculate_residuals()
229 | self.calculate_kendall_tau()
230 | self.calculate_errors()
231 | if self.fixed_zp == True:
232 | self.mean_zeropoint = self.y0
233 | self.error_zeropoint = self.y0err
234 |
235 | self.Nstars_final = len(self.Ypoints)
236 |
237 | def build_matrix_values(self):
238 | self.X_matrix_values = \
239 | np.array([[column for column in self.Xpoints] for line in self.Xpoints])
240 | self.Y_matrix_values = \
241 | np.array([[line for line in self.Ypoints] for line in self.Ypoints])
242 |
243 | def build_complementary_matrix(self):
244 | if self.fixed_zp == False:
245 | self.X_complementary_values = self.X_matrix_values.transpose()
246 | self.Y_complementary_values = self.Y_matrix_values.transpose()
247 | if self.fixed_zp == True:
248 | self.X_complementary_values = np.array([[self.x0\
249 | for column in self.Xpoints] for line in self.Xpoints])
250 | self.Y_complementary_values = np.array([[self.y0\
251 | for column in self.Ypoints] for line in self.Ypoints])
252 |
253 | def build_slopes_matrix(self):
254 | self.slopes_matrix = \
255 | ((self.Y_matrix_values-self.Y_complementary_values +1e-20)/ \
256 | (self.X_matrix_values-self.X_complementary_values +1e-20))
257 | # +1e-20 lets us hide Numpy warning with 0/0
258 |
259 | def upper_diagonal_slope_matrix_values(self):
260 | self.upper_diag_slopes = \
261 | np.array([self.slopes_matrix[l][c] \
262 | for l in xrange(len(self.slopes_matrix)) \
263 | for c in xrange(len(self.slopes_matrix[0])) if c>l])
264 |
265 | def calculate_mean_slope(self):
266 | self.mean_slope = np.median(self.upper_diag_slopes)
267 | self.extinction = -self.mean_slope
268 |
269 | def build_zeropoint_array(self):
270 | self.zeropoint_array = self.Ypoints - self.Xpoints*self.mean_slope
271 |
272 | def calculate_mean_zeropoint(self):
273 | self.mean_zeropoint = np.median(self.zeropoint_array)
274 |
275 | def calculate_residuals(self):
276 | self.residuals = self.zeropoint_array-self.mean_zeropoint
277 |
278 | def delete_bad_points(self,ImageInfo):
279 | # 3*std_residuals threshold
280 | std_residual = np.std(self.residuals)
281 | self.badfilter = np.abs(self.residuals)0)-1.*np.sum(self.upper_diag_slopes<0))\
300 | /(1.*np.size(self.upper_diag_slopes))
301 |
--------------------------------------------------------------------------------
/pyasb/cloud_coverage.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | '''
4 | Cloud covering module
5 |
6 | Determine if clouds are present in the image.
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | DEBUG = False
15 |
16 | __author__ = "Mireia Nievas"
17 | __copyright__ = "Copyright 2012, PyASB project"
18 | __credits__ = ["Mireia Nievas"]
19 | __license__ = "GNU GPL v3"
20 | __shortname__ = "PyASB"
21 | __longname__ = "Python All-Sky Brightness pipeline"
22 | __version__ = "1.99.0"
23 | __maintainer__ = "Mireia Nievas"
24 | __email__ = "mirph4k[at]gmail[dot]com"
25 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
26 |
27 | try:
28 | import sys,os,inspect
29 | import copy
30 | import numpy as np
31 | import warnings
32 | import scipy.interpolate as sint
33 | import matplotlib as mpl
34 | import matplotlib.pyplot as plt
35 | import matplotlib.colors as mpc
36 | import matplotlib.cm as mpcm
37 | import matplotlib.patches as mpp
38 | from star_calibration import StarCatalog
39 | except:
40 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
41 | raise SystemExit
42 |
43 | warnings.simplefilter("ignore", category=RuntimeWarning)
44 |
45 | class CloudCoverage():
46 | ''' This is a completely optional module.
47 | If user doesn't want neither the map nor the table,
48 | just don't do anything'''
49 | def __init__(self,Image,ImageAnalysis,BouguerFit):
50 | ''' Calculate the mean cloud covering (at the whole image) '''
51 | #Old method, directly from the % stars. Gives higher values
52 | #TotalStars = len(ImageAnalysis.StarCatalog.StarList_woPhot)
53 | #TotalStarsWithPhot = len(ImageAnalysis.StarCatalog.StarList)
54 |
55 | #TotalStars = 0;
56 | #TotalStarsWithPhot = 0;
57 | ImageAnalysis.StarCatalog.look_for_nearby_stars(Image.FitsImage,Image.ImageInfo)
58 |
59 | '''
60 | for Star in ImageAnalysis.StarCatalog.StarList_TotVisible:
61 | #if Star.masked==True: continue
62 | #elif Star.saturated == True: continue
63 | #elif Star.cold_pixels==True: continue
64 | #if Star.altit_real<30: continue
65 | # Star is not saturated and present in the sky, add it [weighted]
66 | TotalStars += 1
67 | if Star not in ImageAnalysis.StarCatalog.StarList_WithNearbyStar: continue
68 | # Star has photometric measures, add it [weighted]
69 | TotalStarsWithPhot += 1
70 | '''
71 |
72 | #TotalPercStars = (TotalStarsWithPhot+1e-6)*1./(TotalStars+1e-6)
73 | TotalStars = len(ImageAnalysis.StarCatalog.StarList_TotVisible)
74 | TotalDetected = len(ImageAnalysis.StarCatalog.StarList_WithNearbyStar)
75 | TotalPercStars = TotalDetected*1./TotalStars
76 |
77 | # Calculate the mean Cloud Coverage value (AllSky)
78 | self.mean_cloudcover = self.cloud_coverage_value(\
79 | np.array([TotalPercStars]))[0]
80 |
81 | # Binomial error for the All Sky Cloud Coverage value.
82 | self.error_cloudcover = self.cloud_coverage_error(\
83 | self.mean_cloudcover,
84 | np.array([TotalPercStars]),
85 | np.array([TotalStars]))[0]
86 |
87 | print("Mean cloud coverage: %.3f+/-%.3f" \
88 | %(self.mean_cloudcover,self.error_cloudcover))
89 |
90 | ''' If asked, calculate the cloud covering by sectors in the image '''
91 | try:
92 | assert(\
93 | Image.ImageInfo.clouddata_path!=False or\
94 | Image.ImageInfo.cloudmap_path!=False)
95 | except Exception as e:
96 | #print(inspect.stack()[0][2:4][::-1])
97 | print('Skipping cloud coverage detection')
98 | #print(type(e))
99 | #print(e)
100 | return(None)
101 | else:
102 | print('Measuring Cloud Covering ...')
103 | self.create_bins()
104 | #self.star_detection(Image)
105 | self.StarCatalog = ImageAnalysis.StarCatalog
106 | self.cloud_coverage(
107 | self.StarCatalog.StarList_WithNearbyStar,
108 | self.StarCatalog.StarList_TotVisible,BouguerFit)
109 | self.cloud_map(BouguerFit,ImageInfo=Image.ImageInfo)
110 | self.clouddata_table(ImageInfo=Image.ImageInfo)
111 |
112 | def star_detection(self,Image):
113 | # relax requisites to get more stars
114 | #normal_stdout = sys.stdout
115 | #sys.stdout = open(os.devnull,'w')
116 | II = copy.deepcopy(Image.ImageInfo)
117 | II.baseflux_detectable = II.baseflux_detectable/5.
118 | II.base_radius *= 5
119 | #II.max_magnitude += 0.5
120 | II.min_altitude = 0
121 | II.skymap_path = False
122 |
123 | self.StarCatalog = StarCatalog(ImageInfo=II)
124 | self.StarCatalog.process_catalog_specific(Image.FitsImage,II)
125 | #sys.stdout = normal_stdout
126 |
127 | def create_bins(self):
128 | ''' Returns binned sky '''
129 | self.azseparation = 45
130 | self.zdseparation = 20
131 |
132 | self.AZdirs = np.arange(0,360+1,self.azseparation)
133 | self.ZDdirs = np.arange(0,90+1,self.zdseparation)
134 | self.ALTdirs = 90-self.ZDdirs
135 |
136 | self.AZgrid,self.ZDgrid = np.meshgrid(self.AZdirs,self.ZDdirs)
137 | self.ALTgrid = 90.-self.ZDgrid
138 | self.AZgrid = self.AZgrid*np.pi/180.0
139 |
140 | @staticmethod
141 | def cloud_coverage_value(PercentageStars):
142 | # Stretch the data and estimate the coverage
143 | TheCloudCoverage = 1.0-PercentageStars
144 | #self.CloudCoverage = self.CloudCoverage/0.2
145 | TheCloudCoverage[TheCloudCoverage<0.0]=0.0 # Cloudless
146 | TheCloudCoverage[TheCloudCoverage>1.0]=1.0 # Overcast
147 | return(TheCloudCoverage)
148 |
149 | @staticmethod
150 | def cloud_coverage_error(TheCloudCoverage,PercentageStars,TotalStars):
151 | '''
152 | Estimate the cloud coverage uncertainty as binomial distribution
153 | '''
154 | TheCloudCoverageErr = \
155 | TheCloudCoverage*(1-TheCloudCoverage)/np.sqrt(TotalStars)
156 | return(TheCloudCoverageErr)
157 |
158 | def cloud_coverage(self,StarList_Photom,StarList_woPhot,BouguerFit):
159 | Stars_in_field = [Star for Star in StarList_woPhot]
160 |
161 | # The number of predicted/observable stars on a region
162 | PredictedStars = np.zeros((len(self.ALTdirs),len(self.AZdirs)))
163 | # The number of detected stars on that region
164 | ObservedStars = np.zeros((len(self.ALTdirs),len(self.AZdirs)))
165 |
166 | # Sum of flux percentage (~ mean extinction in absolute units)
167 | PercentageFlux = np.zeros((len(self.ALTdirs),len(self.AZdirs)))
168 | PercentageFluxErr = np.zeros((len(self.ALTdirs),len(self.AZdirs)))
169 |
170 | minimum_stars = 3;
171 |
172 | for column in xrange(len(self.AZdirs)):
173 | for line in xrange(len(self.ALTdirs)):
174 | for Star in Stars_in_field: # Here we should divide /2, /1 will smooth data
175 | # See if the Star is in the given azimuth and altitude
176 | if (\
177 | (abs(Star.altit_real-self.ALTdirs[line])>self.zdseparation) or\
178 | (abs(Star.azimuth-self.AZdirs[column])>self.azseparation and\
179 | abs(360-abs(Star.azimuth-self.AZdirs[column]))>self.azseparation and\
180 | 90-Star.altit_real>self.zdseparation)
181 | ):
182 | continue
183 |
184 | if Star.saturated == True: continue
185 | elif Star.cold_pixels == True: continue
186 | elif Star.masked == True: continue
187 |
188 | # Star is not saturated and present in the sky, add it [weighted]
189 | PredictedStars[line][column] += 1
190 |
191 | if Star not in StarList_Photom:
192 | continue
193 |
194 | # Star has photometric measures, add it [weighted]
195 | ObservedStars[line][column] += 1
196 |
197 | # Alternative estimation based on flux extinction. To be completed.
198 | Predicted_Flux = \
199 | 10**(0.4*(BouguerFit.Regression.mean_zeropoint-Star.FilterMag))
200 | Predicted_FluxError =\
201 | Predicted_Flux*np.log(10)*0.4*BouguerFit.Regression.error_zeropoint
202 | Measured_Flux = Star.starflux
203 | Measured_FluxError = Star.starflux_err
204 |
205 | PercentageFlux[line][column] += np.clip(Measured_Flux/Predicted_Flux,0,1)
206 |
207 | # If there are not enough stars in the field, truncate the measure
208 | if PredictedStars[line][column] < minimum_stars:
209 | ObservedStars[line][column] = 0;
210 | PredictedStars[line][column] = 0+1e-6;
211 |
212 |
213 | # Normalization of flux percentages
214 | PercentageFlux = PercentageFlux*1./(1e-5 + ObservedStars)
215 |
216 | PercentageStars = \
217 | np.array((0+ObservedStars*1.0)/(1e-5+PredictedStars*1.0))
218 |
219 | self.CloudCoverage = self.cloud_coverage_value(PercentageStars)
220 | self.CloudCoverageErr = self.cloud_coverage_error(self.CloudCoverage,PercentageStars,PredictedStars)
221 | self.CloudCoverage[PredictedStars<2] = None # not enough stars
222 |
223 | def clouddata_table(self,ImageInfo):
224 | try:
225 | assert(ImageInfo.clouddata_path!=False)
226 | except:
227 | print(inspect.stack()[0][2:4][::-1])
228 | print('Skipping write clouddata table to file')
229 | else:
230 | print('Write clouddata table to file')
231 | header = '#Altitude\Azimuth'
232 | for az_ in self.AZdirs:
233 | header += ', '+str(az_)
234 | header+='\n'
235 |
236 | content = [header]
237 |
238 | for k,alt_ in enumerate(self.ALTdirs):
239 | line = str(alt_)
240 | for j in xrange(len(self.AZdirs)):
241 | line += ', '+str("%.3f +/- %.3f" %\
242 | (float(self.CloudCoverage[k][j]), \
243 | float(self.CloudCoverageErr[k][j])))
244 | line+='\n'
245 | content.append(line)
246 |
247 | if ImageInfo.clouddata_path == "screen":
248 | print(content)
249 | else:
250 | cloudtable_filename = str("%s/CloudTable_%s_%s_%s.txt" %(\
251 | ImageInfo.clouddata_path, ImageInfo.obs_name,\
252 | ImageInfo.fits_date,ImageInfo.used_filter))
253 | cloudfile = open(cloudtable_filename,'w+')
254 | cloudfile.writelines(content)
255 | cloudfile.close()
256 |
257 |
258 | def cloud_map(self,BouguerFit,ImageInfo):
259 | try:
260 | assert(ImageInfo.cloudmap_path!=False)
261 | except:
262 | print(inspect.stack()[0][2:4][::-1])
263 | print('Skipping write cloudmap to file')
264 | return(None)
265 | else:
266 | print('Output cloudmap')
267 |
268 | ''' Create the cloud map '''
269 | self.Cloudfigure = plt.figure(figsize=(8,7.5))
270 | self.Cloudgraph = self.Cloudfigure.add_subplot(111,projection='polar')
271 |
272 |
273 | # Grid and interpolate data
274 | self.AZgridi,self.ZDgridi = np.mgrid[0:2*np.pi:1000j, 0:90:1000j]
275 | self.ALTgridi = 90. - self.ZDgridi
276 | coord_reshape = np.array([[self.AZgrid[j][k],self.ZDgrid[j][k]] \
277 | for k in xrange(len(self.AZgrid[0])) for j in xrange(len(self.AZgrid))])
278 | data_reshape = np.array([self.CloudCoverage[j][k] \
279 | for k in xrange(len(self.AZgrid[0])) for j in xrange(len(self.AZgrid))])
280 | self.CloudCoveragei = sint.griddata(coord_reshape,data_reshape, \
281 | (self.AZgridi,self.ZDgridi), method='nearest')
282 |
283 | # Colormap
284 | #cloud_cmap = mpcm.get_cmap('gray', 5)
285 |
286 | cdict = {
287 | 'red': [(0.0,0.2,0.2),
288 | (1.0,0.8,0.8)],
289 | 'green': [(0.0,0.2,0.2),
290 | (1.0,0.8,0.8)],
291 | 'blue': [(0.0,0.2,0.2),
292 | (1.0,0.8,0.8)]}
293 |
294 | cloud_cmap = mpc.LinearSegmentedColormap('gray_colormap',cdict,N=5)
295 |
296 | # Create the graph
297 | self.ColorMesh = self.Cloudgraph.pcolormesh(\
298 | self.AZgridi,
299 | self.ZDgridi,
300 | self.CloudCoveragei,
301 | vmin=0,
302 | vmax=1,
303 | cmap=cloud_cmap)
304 |
305 | # Ticks
306 | def ticks_and_locators():
307 | ''' Add ticks to the graph '''
308 | radial_locator = np.arange(10,90+1,10)
309 | radial_label = ["$80$","$70$","$60$","$50$","$40$","$30$","$20$","$10$","$0$"]
310 | theta_locator = np.arange(0,360,45)
311 | theta_label = ["$N$","$NE$","$E$","$SE$","$S$","$SW$","$W$","$NW$"]
312 |
313 | self.Cloudgraph.set_rgrids(radial_locator,radial_label,\
314 | size="large",color='k',alpha=0.75)
315 | self.Cloudgraph.set_thetagrids(theta_locator,theta_label,size="large")
316 | # rotate the graph (North up)
317 | self.Cloudgraph.set_theta_direction(-1)
318 | self.Cloudgraph.set_theta_offset(np.pi/2)
319 |
320 | self.Cloudgraph.grid(True)
321 |
322 | # Colorbar
323 | def color_bar():
324 | ''' Add the colorbar '''
325 | # Separation between colour bar and graph
326 | self.Cloudfigure.subplots_adjust(right=1)
327 | # Color bar
328 | self.Cloudcolorbar = plt.colorbar(\
329 | self.ColorMesh,orientation='vertical',pad=0.07,shrink=0.75)
330 | self.Cloudfigure.subplots_adjust(right=0.80) # Restore separation
331 | #self.ColorMesh.set_clim(0.0,1.0)
332 | self.Cloudcolorbar.set_ticks(np.arange(0,1+1e-6,0.1))
333 | self.Cloudcolorbar.set_label("Cloud Coverage",rotation="vertical",size="large")
334 |
335 |
336 | # Ticks and colorbar
337 | ticks_and_locators()
338 | color_bar()
339 |
340 | # Information text on image
341 | self.Cloudgraph.text(0,np.max(self.ZDgridi)+15, unicode(ImageInfo.cloudmap_title, 'utf-8'),\
342 | horizontalalignment='center',size='xx-large')
343 | # Image information
344 | image_information = str(ImageInfo.date_string)+" UTC\n"+str(ImageInfo.latitude)+5*" "+\
345 | str(ImageInfo.longitude)+"\n"+ImageInfo.used_filter+4*" "+\
346 | "K="+str("%.3f" % float(BouguerFit.Regression.extinction))+"+-"+\
347 | str("%.3f" % float(BouguerFit.Regression.error_extinction))+"\n"+\
348 | str("Cloud coverage: %.3f +/- %.3f" %\
349 | (float(self.mean_cloudcover),float(self.error_cloudcover)))
350 |
351 | self.Cloudgraph.text(5*np.pi/4,145,unicode(image_information,'utf-8'),fontsize='x-small')
352 |
353 | if ImageInfo.cloudmap_path=="screen":
354 | plt.show()
355 | else:
356 | cloudmap_filename = str("%s/CloudMap_%s_%s_%s.png" %(\
357 | ImageInfo.cloudmap_path, ImageInfo.obs_name,\
358 | ImageInfo.fits_date, ImageInfo.used_filter))
359 | plt.tight_layout(pad=-1.5,rect=[0.1,0.05,1.,0.95])
360 | plt.savefig(cloudmap_filename)
361 |
362 | #plt.clf()
363 | #plt.close('all')
364 |
365 |
366 |
367 |
368 |
369 |
--------------------------------------------------------------------------------
/pyasb/cr2fits.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python2
2 | # -*- coding: utf-8 -*-
3 |
4 | # 3rd attempt
5 | # 15th Feb 2012, 09:38AM
6 | # http://eayd.in
7 | # http://github.com/eaydin/cr2fits
8 |
9 | ### This script is redistributable in anyway.
10 | ### But it includes netpbmfile.py which is NOT written by M. Emre Aydin.
11 | ### It has its own copyright and it has been stated in the source code.
12 | ### BUT, there's nothing to worry about usage, laws etc.
13 | ### Enjoy.
14 |
15 | # Changes by Mireia Nievas:
16 | ## Metadata in Spanish
17 | ## Convert from local to UTC time
18 | ## Header fit the AstMon and PyASB keywords
19 | ## Clobber in fits save (overwrite)
20 | ## Saturation limit
21 | ## Extract 3 channels
22 |
23 |
24 |
25 | TimeZone = 'Europe/Madrid' # UTC
26 |
27 | sourceweb = "http://github.com/eaydin/cr2fits"
28 | version = "1.0.3"
29 |
30 | try :
31 | from copy import deepcopy
32 | import numpy, subprocess, sys, re, datetime, math
33 | import pytz
34 | import astropy.io.fits as pyfits
35 | except :
36 | print("ERROR : Missing some libraries!")
37 | print("Check if you have the following :\n\tnumpy\n\tpyfits\n\tdcraw")
38 | print("For details : %s" % sourceweb)
39 | raise SystemExit
40 |
41 | ### --- NETPBMFILE SOURCE CODE --- ###
42 |
43 | # Copyright (c) 2011, Christoph Gohlke
44 | # Copyright (c) 2011, The Regents of the University of California
45 | # All rights reserved.
46 | #
47 | # Redistribution and use in source and binary forms, with or without
48 | # modification, are permitted provided that the following conditions are met:
49 | #
50 | # * Redistributions of source code must retain the above copyright
51 | # notice, this list of conditions and the following disclaimer.
52 | # * Redistributions in binary form must reproduce the above copyright
53 | # notice, this list of conditions and the following disclaimer in the
54 | # documentation and/or other materials provided with the distribution.
55 | # * Neither the name of the copyright holders nor the names of any
56 | # contributors may be used to endorse or promote products derived
57 | # from this software without specific prior written permission.
58 | #
59 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
60 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
61 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
62 | # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
63 | # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
64 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
65 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
66 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
67 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
68 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
69 | # POSSIBILITY OF SUCH DAMAGE.
70 |
71 | __all__ = ['NetpbmFile']
72 |
73 | class NetpbmFile(object):
74 | """Read and write Netpbm PAM, PBM, PGM, PPM, files."""
75 |
76 | _types = {b'P1': b'BLACKANDWHITE', b'P2': b'GRAYSCALE', b'P3': b'RGB',
77 | b'P4': b'BLACKANDWHITE', b'P5': b'GRAYSCALE', b'P6': b'RGB',
78 | b'P7 332': b'RGB', b'P7': b'RGB_ALPHA'}
79 |
80 | def __init__(self, arg=None, **kwargs):
81 | """Initialize instance from filename, open file, or numpy array."""
82 | for attr in ('header', 'magicnum', 'width', 'height', 'maxval',
83 | 'depth', 'tupltypes', '_filename', '_fileid', '_data'):
84 | setattr(self, attr, None)
85 | if arg is None:
86 | self._fromdata([], **kwargs)
87 | elif isinstance(arg, basestring):
88 | self._fileid = open(arg, 'rb')
89 | self._filename = arg
90 | self._fromfile(self._fileid, **kwargs)
91 | elif hasattr(arg, 'seek'):
92 | self._fromfile(arg, **kwargs)
93 | self._fileid = arg
94 | else:
95 | self._fromdata(arg, **kwargs)
96 |
97 | def asarray(self, copy=True, cache=False, **kwargs):
98 | """Return image data from file as numpy array."""
99 | data = self._data
100 | if data is None:
101 | data = self._read_data(self._fileid, **kwargs)
102 | if cache:
103 | self._data = data
104 | else:
105 | return data
106 | return deepcopy(data) if copy else data
107 |
108 | def write(self, arg, **kwargs):
109 | """Write instance to file."""
110 | if hasattr(arg, 'seek'):
111 | self._tofile(arg, **kwargs)
112 | else:
113 | with open(arg, 'wb') as fid:
114 | self._tofile(fid, **kwargs)
115 |
116 | def close(self):
117 | """Close open file. Future asarray calls might fail."""
118 | if self._filename and self._fileid:
119 | self._fileid.close()
120 | self._fileid = None
121 |
122 | def __del__(self):
123 | self.close()
124 |
125 | def _fromfile(self, fileid):
126 | """Initialize instance from open file."""
127 | fileid.seek(0)
128 | data = fileid.read(4096)
129 | if (len(data) < 7) or not (b'0' < data[1:2] < b'8'):
130 | raise ValueError("Not a Netpbm file:\n%s" % data[:32])
131 | try:
132 | self._read_pam_header(data)
133 | except Exception:
134 | try:
135 | self._read_pnm_header(data)
136 | except Exception:
137 | raise ValueError("Not a Netpbm file:\n%s" % data[:32])
138 |
139 | def _read_pam_header(self, data):
140 | """Read PAM header and initialize instance."""
141 | regroups = re.search(
142 | b"(^P7[\n\r]+(?:(?:[\n\r]+)|(?:#.*)|"
143 | b"(HEIGHT\s+\d+)|(WIDTH\s+\d+)|(DEPTH\s+\d+)|(MAXVAL\s+\d+)|"
144 | b"(?:TUPLTYPE\s+\w+))*ENDHDR\n)", data).groups()
145 | self.header = regroups[0]
146 | self.magicnum = b'P7'
147 | for group in regroups[1:]:
148 | key, value = group.split()
149 | setattr(self, unicode(key).lower(), int(value))
150 | matches = re.findall(b"(TUPLTYPE\s+\w+)", self.header)
151 | self.tupltypes = [s.split(None, 1)[1] for s in matches]
152 |
153 | def _read_pnm_header(self, data):
154 | """Read PNM header and initialize instance."""
155 | bpm = data[1:2] in b"14"
156 | regroups = re.search(b"".join((
157 | b"(^(P[123456]|P7 332)\s+(?:#.*[\r\n])*",
158 | b"\s*(\d+)\s+(?:#.*[\r\n])*",
159 | b"\s*(\d+)\s+(?:#.*[\r\n])*" * (not bpm),
160 | b"\s*(\d+)\s(?:\s*#.*[\r\n]\s)*)")), data).groups() + (1, ) * bpm
161 | self.header = regroups[0]
162 | self.magicnum = regroups[1]
163 | self.width = int(regroups[2])
164 | self.height = int(regroups[3])
165 | self.maxval = int(regroups[4])
166 | self.depth = 3 if self.magicnum in b"P3P6P7 332" else 1
167 | self.tupltypes = [self._types[self.magicnum]]
168 |
169 | def _read_data(self, fileid, byteorder='>'):
170 | """Return image data from open file as numpy array."""
171 | fileid.seek(len(self.header))
172 | data = fileid.read()
173 | dtype = 'u1' if self.maxval < 256 else byteorder + 'u2'
174 | depth = 1 if self.magicnum == b"P7 332" else self.depth
175 | shape = [-1, self.height, self.width, depth]
176 | size = numpy.prod(shape[1:])
177 | if self.magicnum in b"P1P2P3":
178 | data = numpy.array(data.split(None, size)[:size], dtype)
179 | data = data.reshape(shape)
180 | elif self.maxval == 1:
181 | shape[2] = int(math.ceil(self.width / 8))
182 | data = numpy.frombuffer(data, dtype).reshape(shape)
183 | data = numpy.unpackbits(data, axis=-2)[:, :, :self.width, :]
184 | else:
185 | data = numpy.frombuffer(data, dtype)
186 | data = data[:size * (data.size // size)].reshape(shape)
187 | if data.shape[0] < 2:
188 | data = data.reshape(data.shape[1:])
189 | if data.shape[-1] < 2:
190 | data = data.reshape(data.shape[:-1])
191 | if self.magicnum == b"P7 332":
192 | rgb332 = numpy.array(list(numpy.ndindex(8, 8, 4)), numpy.uint8)
193 | rgb332 *= [36, 36, 85]
194 | data = numpy.take(rgb332, data, axis=0)
195 | return data
196 |
197 | def _fromdata(self, data, maxval=None):
198 | """Initialize instance from numpy array."""
199 | data = numpy.array(data, ndmin=2, copy=True)
200 | if data.dtype.kind not in "uib":
201 | raise ValueError("not an integer type: %s" % data.dtype)
202 | if data.dtype.kind == 'i' and numpy.min(data) < 0:
203 | raise ValueError("data out of range: %i" % numpy.min(data))
204 | if maxval is None:
205 | maxval = numpy.max(data)
206 | maxval = 255 if maxval < 256 else 65535
207 | if maxval < 0 or maxval > 65535:
208 | raise ValueError("data out of range: %i" % maxval)
209 | data = data.astype('u1' if maxval < 256 else '>u2')
210 | self._data = data
211 | if data.ndim > 2 and data.shape[-1] in (3, 4):
212 | self.depth = data.shape[-1]
213 | self.width = data.shape[-2]
214 | self.height = data.shape[-3]
215 | self.magicnum = b'P7' if self.depth == 4 else b'P6'
216 | else:
217 | self.depth = 1
218 | self.width = data.shape[-1]
219 | self.height = data.shape[-2]
220 | self.magicnum = b'P5' if maxval > 1 else b'P4'
221 | self.maxval = maxval
222 | self.tupltypes = [self._types[self.magicnum]]
223 | self.header = self._header()
224 |
225 | def _tofile(self, fileid, pam=False):
226 | """Write Netbm file."""
227 | fileid.seek(0)
228 | fileid.write(self._header(pam))
229 | data = self.asarray(copy=False)
230 | if self.maxval == 1:
231 | data = numpy.packbits(data, axis=-1)
232 | data.tofile(fileid)
233 |
234 | def _header(self, pam=False):
235 | """Return file header as byte string."""
236 | if pam or self.magicnum == b'P7':
237 | header = "\n".join(("P7",
238 | "HEIGHT %i" % self.height,
239 | "WIDTH %i" % self.width,
240 | "DEPTH %i" % self.depth,
241 | "MAXVAL %i" % self.maxval,
242 | "\n".join("TUPLTYPE %s" % unicode(i) for i in self.tupltypes),
243 | "ENDHDR\n"))
244 | elif self.maxval == 1:
245 | header = "P4 %i %i\n" % (self.width, self.height)
246 | elif self.depth == 1:
247 | header = "P5 %i %i %i\n" % (self.width, self.height, self.maxval)
248 | else:
249 | header = "P6 %i %i %i\n" % (self.width, self.height, self.maxval)
250 | if sys.version_info[0] > 2:
251 | header = bytes(header, 'ascii')
252 | return header
253 |
254 | def __str__(self):
255 | """Return information about instance."""
256 | return unicode(self.header)
257 |
258 |
259 | if sys.version_info[0] > 2:
260 | basestring = str
261 | unicode = lambda x: str(x, 'ascii')
262 |
263 | ### --- END OF NETPBMFILE SOURCE CODE --- ###
264 |
265 | ### CR2FITS SOURCE CODE ###
266 |
267 |
268 | try :
269 | cr2FileName = sys.argv[1]
270 | except :
271 | print("ERROR : You probably don't know how to use it?")
272 | print("./cr2fits.py ")
273 | print("The can take 3 values:0,1,2 for R,G,B respectively.")
274 | print("Example :\n\t$ ./cr2fits.py myimage.cr2 1")
275 | print("The above example will create 2 outputs.")
276 | print("\tmyimage.ppm : The PPM, which you can delete.")
277 | print("\tmyimage-G.fits : The FITS image in the Green channel, which is the purpose!")
278 | print("For details : http://github.com/eaydin/cr2fits")
279 | print("Version : %s" % version)
280 | raise SystemExit
281 |
282 | colors = {0:"Red",1:"Green",2:"Blue"}
283 | pseudofilters = {0:"Johnson_R",1:"Johnson_V",2:"Johnson_B"}
284 |
285 |
286 | def extract_channel(colorInput):
287 | print("Reading file %s...") % cr2FileName
288 | try:
289 | print('Converting to PPM')
290 | #Converting the CR2 to PPM
291 | '''
292 | p = subprocess.Popen(["dcraw","-6","-W",\
293 | "-g","1","1","-t","0","-m","3",\
294 | "-H","3","-S","3700","-q","3",\
295 | cr2FileName]).communicate()[0]
296 | '''
297 | p = subprocess.Popen(["dcraw","-W","-w","-4","-h",\
298 | cr2FileName]).communicate()[0]
299 | #p = subprocess.Popen(["dcraw","-H","5","-n","300",\
300 | # "-S","3700","-g","1","1","-6","-W","-t","0",\
301 | # "-m","5","-q","3","-f",cr2FileName]).communicate()[0]
302 |
303 | #Getting the EXIF of CR2 with dcraw
304 | print('Getting the EXIG')
305 | p = subprocess.Popen(["dcraw","-i","-v",cr2FileName],stdout=subprocess.PIPE)
306 | cr2header = p.communicate()[0]
307 | except:
308 | raise
309 |
310 | try:
311 | #Catching the Timestamp
312 | print('Catching the Timestamp')
313 | try:
314 | m = re.search('(?<=Timestamp:).*',cr2header)
315 | except:
316 | m = re.search('(?<=Marca de fecha:).*',cr2header)
317 | date1=m.group(0).split()
318 | months = { 'Jan' : 1, 'Feb' : 2, 'Mar' : 3, 'Apr' : 4, 'May' : 5, 'Jun' : 6, 'Jul' : 7, 'Aug' : 8, 'Sep' : 9, 'Oct' : 10, 'Nov' : 11, 'Dec' : 12 }
319 | date = datetime.datetime(\
320 | int(date1[4]),months[date1[1]],int(date1[2]),\
321 | int(date1[3].split(':')[0]),int(date1[3].split(':')[1]), \
322 | int(date1[3].split(':')[2]))
323 | local = pytz.timezone(TimeZone)
324 | local_dt = local.localize(date, is_dst=None)
325 | utcdate = local_dt.astimezone(pytz.utc)
326 | date ='{0:%Y-%m-%d %H:%M:%S}'.format(date)
327 | utcdate ='{0:%Y%m%d_%H%M%S}'.format(utcdate)
328 |
329 | #Catching the Shutter Speed
330 | print('Catching the Shutter Speed')
331 | try:
332 | m = re.search('(?<=Shutter:).*(?=sec)',cr2header)
333 | except:
334 | m = re.search('(?<=Disparador:).*(?=seg)',cr2header)
335 | shutter = m.group(0).strip()
336 |
337 | #Catching the Aperture
338 | print('Catching the Aperture')
339 | try:
340 | m = re.search('(?<=Aperture: f/).*',cr2header)
341 | except:
342 | m = re.search('(?<=Obertura: f/).*',cr2header)
343 | aperture = m.group(0).strip()
344 |
345 | #Catching the ISO Speed
346 | print('Catching the ISO Speed')
347 | try:
348 | m = re.search('(?<=ISO speed:).*',cr2header)
349 | except:
350 | m = re.search('(?<=Velocidad ISO:).*',cr2header)
351 | iso = m.group(0).strip()
352 |
353 | #Catching the Focal length
354 | print('Catching the Focal length')
355 | try:
356 | m = re.search('(?<=Focal length: ).*(?=mm)',cr2header)
357 | except:
358 | m = re.search('(?<=Distancia focal: ).*(?=mm)',cr2header)
359 | focal = m.group(0).strip()
360 |
361 | #Catching the Original Filename of the cr2
362 | print('Catching the Original Filename')
363 | try:
364 | m = re.search('(?<=Filename:).*',cr2header)
365 | except:
366 | m = re.search('(?<=Nombre de archivo:).*',cr2header)
367 | original_file = m.group(0).strip()
368 | original_file = original_file.split("/")[-1]
369 |
370 | #Catching the Camera Type
371 | print('Catching the Camera type')
372 | try:
373 | m = re.search('(?<=Camera:).*',cr2header)
374 | except:
375 | m = re.search('(?<=Cámara:).*',cr2header)
376 | camera = m.group(0).strip()
377 |
378 | except :
379 | print("ERROR : Something went wrong with dcraw. Do you even have dcraw?")
380 | raise SystemExit
381 |
382 | print("Reading the PPM output...")
383 | try :
384 | #Reading the PPM
385 | ppm_name = cr2FileName.replace("CR2","cr2")
386 | ppm_name = ppm_name.replace("NEF","cr2")
387 | ppm_name = ppm_name.split('.cr2')[0] + '.ppm'
388 | im_ppm = NetpbmFile(ppm_name).asarray()
389 | except :
390 | print("ERROR : Something went wrong while reading the PPM file.")
391 | raise SystemExit
392 |
393 | print("Extracting %s color channels... (may take a while)" % colors[colorInput])
394 | try :
395 | #Extracting the Green Channel Only
396 | im_green = numpy.zeros((im_ppm.shape[0],im_ppm.shape[1]),dtype=numpy.uint16)
397 | for row in xrange(0,im_ppm.shape[0]) :
398 | for col in xrange(0,im_ppm.shape[1]) :
399 | im_green[row,col] = im_ppm[row,col][colorInput]
400 | # PyASB works with left-right reversed images
401 | #im_green = numpy.fliplr(im_green)
402 | except :
403 | print("ERROR : Something went wrong while extracting color channels.")
404 | raise SystemExit
405 |
406 | print("Creating the FITS file...")
407 | try :
408 | #Creating the FITS File
409 | hdu = pyfits.PrimaryHDU(im_green)
410 | hdu.header['OBSTIME'] = date.encode('latin-1')
411 | hdu.header['DATE'] = utcdate.encode('latin-1')
412 | hdu.header['EXPTIME'] = shutter.encode('latin-1')
413 | hdu.header['EXPOSURE'] = shutter.encode('latin-1')
414 | hdu.header['APERTUR'] = aperture.encode('latin-1')
415 | hdu.header['ISO'] = iso.encode('latin-1')
416 | hdu.header['FOCAL'] = focal.encode('latin-1')
417 | hdu.header['ORIGIN'] = original_file.encode('latin-1')
418 | #hdu.header['ORIGIN'] = Canon CR2 file'
419 | #hdu.header['FILTER'] = colors[colorInput]
420 | hdu.header['FILTER'] = pseudofilters[colorInput].encode('latin-1')
421 | hdu.header['CAMERA'] = camera.encode('latin-1')
422 | hdu.header.add_comment('FITS File Created with cr2fits.py available at %s'% (sourceweb.encode('latin-1')))
423 | hdu.header.add_comment('cr2fits.py version %s'%(version.encode('latin-1')))
424 | hdu.header.add_comment('EXPTIME is in seconds.')
425 | hdu.header.add_comment('APERTUR is the ratio as in f/APERTUR')
426 | hdu.header.add_comment('FOCAL is in mm')
427 | except :
428 | print("ERROR : Something went wrong while creating the FITS file.")
429 | raise SystemExit
430 |
431 | print("Writing the FITS file...")
432 | try :
433 | hdu.writeto(ppm_name.replace(" ","_").split('.ppm')[0]+"-"+colors[colorInput][0]+'.fits',clobber=True)
434 | except :
435 | raise
436 | print("ERROR : Something went wrong while writing the FITS file. Maybe it already exists?")
437 | raise SystemExit
438 |
439 | print("Conversion successful!")
440 |
441 |
442 | for colorInput in colors.keys():
443 | extract_channel(colorInput)
444 |
--------------------------------------------------------------------------------
/pyasb/fits_operator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | FITS operations
5 |
6 | Calculate the sum, difference, multiply or divide fits images.
7 | The header is taken from the first image, with added comment.
8 | ____________________________
9 |
10 | This module is part of the PyASB project,
11 | created and maintained by Mireia Nievas [UCM].
12 | ____________________________
13 | '''
14 |
15 | DEBUG = False
16 |
17 | __author__ = "Mireia Nievas"
18 | __copyright__ = "Copyright 2012, PyASB project"
19 | __credits__ = ["Mireia Nievas"]
20 | __license__ = "GNU GPL v3"
21 | __shortname__ = "PyASB"
22 | __longname__ = "Python All-Sky Brightness pipeline"
23 | __version__ = "1.99.0"
24 | __maintainer__ = "Mireia Nievas"
25 | __email__ = "mirph4k[at]gmail[dot]com"
26 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
27 |
28 | try:
29 | import sys,os,inspect
30 | import signal
31 | import numpy as np
32 | import astropy.io.fits as pyfits
33 | import datetime
34 | except:
35 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
36 | raise SystemExit
37 |
38 |
39 | '''
40 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 | ~~~~~~~~~~ Halt handler ~~~~~~~~~~~
42 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43 | '''
44 |
45 | def handler(signum, frame):
46 | print 'Signal handler called with signal', signum
47 | print "CTRL-C pressed"
48 | sys.exit(0)
49 |
50 | signal.signal(signal.SIGTERM, handler)
51 | signal.signal(signal.SIGINT, handler)
52 |
53 |
54 | '''
55 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56 | ~~ Exec Function in verbose mode ~~
57 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
58 | '''
59 |
60 | def verbose(function, *args):
61 | '''
62 | Run a function in verbose mode
63 | '''
64 | try:
65 | out = function(*args)
66 | except:
67 | # Something happened while runing function
68 | raise
69 | if DEBUG==True:
70 | print(str(inspect.stack()[0][2:4][::-1])+' Error')
71 | raise
72 | else:
73 | return(out)
74 |
75 |
76 | def load_image(image):
77 | print('Loading Data and Header for the given Image ...'),
78 | try:
79 | Image_HDU = pyfits.open(image)
80 | Image_Data = Image_HDU[0].data
81 | Image_Header = Image_HDU[0].header
82 | # Convert to string
83 | #Image_Header_text = Image_Header.tostring()
84 | #Image_Header_text = encode_utf8_to_iso88591(Image_Header_text)
85 | #Image_Header.fromstring(Image_Header_text)
86 | #print(Image_Header)
87 | except:
88 | print(inspect.stack()[0][2:4][::-1])
89 | raise
90 | else:
91 | print('OK')
92 | return(Image_Data,Image_Header)
93 |
94 | class FitsOperation():
95 | def __init__(self,fits1,fits2):
96 | self.loadfits(fits1,fits2)
97 |
98 | @staticmethod
99 | def get_datetime_filename(self):
100 | return(str(datetime.datetime.now()).replace(" ","_").replace(":","-").split(".")[0])
101 |
102 | def loadfits(self,fits1,fits2):
103 | self.Data1,self.Header1 = \
104 | verbose(load_image,fits1)
105 | self.Data2,self.Header2 = \
106 | verbose(load_image,fits2)
107 |
108 | def sumfits(self):
109 | self.DataResult = self.Data1+self.Data2
110 | self.HeaderResult = self.Header1
111 |
112 | def subtractfits(self):
113 | self.DataResult = self.Data1-self.Data2
114 | self.HeaderResult = self.Header1
115 |
116 | def multiplyfits(self):
117 | self.DataResult = self.Data1*self.Data2
118 | self.HeaderResult = self.Header1
119 |
120 | def dividefits(self):
121 | self.DataResult = self.Data1*1./self.Data2
122 | self.HeaderResult = self.Header1
123 |
124 | def normalizefits(self):
125 | self.DataResult = self.DataResult*1./np.median(self.DataResult)
126 |
127 | def addnote(self,note="Fits edited with PyASB.fits_operator.py"):
128 | self.HeaderResult.add_comment(note)
129 |
130 | def savefits(self,filename=None):
131 | if filename==None: filename=self.get_datetime_filename
132 | pyfits.writeto(filename, self.DataResult, self.HeaderResult, clobber=True)
133 |
134 |
--------------------------------------------------------------------------------
/pyasb/flat_field_generator.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Synthetic FlatField generator
5 |
6 | Create AllSky FlatFields from:
7 | - Low freq flatfields + High freq flatfields
8 | - Low freq flatfields only
9 | - Radial profiles + High freq flatfields
10 | - Radial profiles only
11 | ____________________________
12 |
13 | This module is part of the PyASB project,
14 | created and maintained by Mireia Nievas [UCM].
15 | ____________________________
16 | '''
17 |
18 | DEBUG = False
19 |
20 | __author__ = "Mireia Nievas"
21 | __copyright__ = "Copyright 2012, PyASB project"
22 | __credits__ = ["Mireia Nievas"]
23 | __license__ = "GNU GPL v3"
24 | __shortname__ = "PyASB"
25 | __longname__ = "Python All-Sky Brightness pipeline"
26 | __version__ = "1.99.0"
27 | __maintainer__ = "Mireia Nievas"
28 | __email__ = "mirph4k[at]gmail[dot]com"
29 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
30 |
31 | try:
32 | import sys,os,inspect
33 | import signal
34 | import numpy as np
35 | import scipy
36 | import scipy.interpolate
37 | import scipy.ndimage
38 | import astropy.io.fits as pyfits
39 | import re
40 | # Aux functions from PyASB
41 | from fits_operator import *
42 | from read_config import *
43 | from image_info import *
44 | from astrometry import *
45 | except:
46 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
47 | raise SystemExit
48 |
49 | '''
50 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51 | ~~~~~~~~~~ Halt handler ~~~~~~~~~~~
52 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
53 | '''
54 |
55 | def handler(signum, frame):
56 | print 'Signal handler called with signal', signum
57 | print "CTRL-C pressed"
58 | sys.exit(0)
59 |
60 | signal.signal(signal.SIGTERM, handler)
61 | signal.signal(signal.SIGINT, handler)
62 |
63 |
64 | '''
65 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66 | ~~ Exec Function in verbose mode ~~
67 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68 | '''
69 |
70 | def verbose(function, *args):
71 | '''
72 | Run a function in verbose mode
73 | '''
74 | try:
75 | out = function(*args)
76 | except:
77 | # Something happened while runing function
78 | raise
79 | if DEBUG==True:
80 | print(str(inspect.stack()[0][2:4][::-1])+' Error')
81 | raise
82 | else:
83 | return(out)
84 |
85 |
86 | '''
87 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88 | ~~~~~~~~~~ Help message ~~~~~~~~~~~
89 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
90 | '''
91 |
92 | class PlatformHelp():
93 | def __init__(self):
94 | self.make_title()
95 | self.make_welcome()
96 | self.make_requisites()
97 | self.make_options()
98 |
99 | def make_title(self):
100 | nameversion = 10*'#'+3*' '+__shortname__+' v'+__version__+3*' '+10*'#'
101 | self.separator = len(nameversion)*'#'
102 | self.title = nameversion
103 |
104 | def make_welcome(self):
105 | self.welcome = 'Welcome to '+__shortname__+' ('+__longname__+')\n'
106 |
107 | def make_requisites(self):
108 | self.requisites = \
109 | 'Requisites: Python 2.7; Scipy; Numpy;\n'+\
110 | ' Matplotlib; Pyfits; Pyephem\n\n'
111 |
112 | def make_options(self):
113 | self.options = \
114 | '-h: print this help message\n\n'+\
115 | '-b base_dir: \n'+\
116 | ' Use alternative base dir for the input/output\n'+\
117 | '-c config_file: \n'+\
118 | ' Use alternative config file\n'+\
119 | '-lowfreq file: Use an existing FlatField for the low freqs\n'+\
120 | '-highfreq file: Use an existing FlatField for the high freqs\n'+\
121 | '-radialprofile file: Use a CSV table with angular response as\n'+\
122 | ' the low freq component\n'+\
123 | '--reference file: Reference light image to determine the shape\n'+\
124 | ' of the synthetic FlatField if only radialprofile is given\n'+\
125 | '\n'
126 |
127 | def show_help(self):
128 | print(\
129 | self.separator+'\n'+self.title+'\n'+self.separator+'\n'+self.welcome+\
130 | self.requisites+self.options+self.separator)
131 | sys.exit(0)
132 |
133 | def incorrect_parameter(self,parameter):
134 | print('ERROR. Incorrect parameter: '+str(parameter))
135 | self.show_help()
136 |
137 | def date_or_file_input_error(self):
138 | print('ERROR. Date or file input')
139 | self.show_help()
140 |
141 | def no_parameters_error(self):
142 | print('ERROR. No input parameters especified')
143 | self.show_help()
144 |
145 |
146 | '''
147 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
148 | ~~~~~~~ Config file loading ~~~~~~~
149 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
150 | '''
151 |
152 | def get_config_filename(InputOptions):
153 | config_file = None
154 | try:
155 | assert(InputOptions.configfile!=False)
156 | except:
157 | print(str(inspect.stack()[0][2:4][::-1])+\
158 | ': config file not specified, use the default one:')
159 | else:
160 | config_file = InputOptions.configfile
161 |
162 | return(config_file)
163 |
164 |
165 |
166 | class ReadOptions():
167 | def __init__(self,input_options):
168 | '''
169 | Read user input.
170 | Returns an Object with all user input parameters
171 | '''
172 | self.show_help = False
173 | self.process_input(input_options)
174 | self.test_flatfield_possible_builds()
175 |
176 | if self.show_help == True:
177 | Help = PlatformHelp()
178 | Help.show_help()
179 |
180 |
181 | def process_input(self,input_options):
182 | '''
183 | Process all input options
184 | '''
185 |
186 | # Lambda: http://docs.python.org/release/2.5.2/tut/node6.html [4.7.5]
187 | self.options = {\
188 | '-h': lambda: 'show_help',\
189 | '-c': lambda: 'use_configfile',\
190 | '-b': lambda: 'use_basedir',\
191 | '-lowfreq': lambda: 'lowfreq',\
192 | '-highfreq': lambda: 'highfreq',\
193 | '-radialprofile': lambda: 'radialprofile',\
194 | '--reference': lambda: 'file_reference'\
195 | }
196 |
197 | # By default, we wont show on screen nor save on disk.
198 | self.configfile = False;
199 |
200 | print('Input Options: '+str(input_options))
201 |
202 | self.input_options = input_options
203 | try: self.input_options[1]
204 | except Exception as e:
205 | print(str(inspect.stack()[0][2:4][::-1])+'ERR. No imput options')
206 | self.no_parameters()
207 | else:
208 | while len(self.input_options)>1:
209 | input_option = self.options.get(self.input_options[1], lambda : None)()
210 | if input_option == 'show_help':
211 | self.show_help = True
212 | # Stop reading options. Program will halt
213 | self.input_options = []
214 | elif input_option == 'use_configfile':
215 | self.configfile = self.reference_file()
216 | elif input_option == 'use_basedir':
217 | self.base_path = self.reference_file()
218 | elif input_option == 'lowfreq':
219 | self.lowfreq_path = self.reference_file()
220 | elif input_option == 'highfreq':
221 | self.highfreq_path = self.reference_file()
222 | elif input_option == 'file_reference':
223 | self.file_reference = self.reference_file()
224 | elif input_option == 'radialprofile':
225 | self.radialprofile = self.reference_file()
226 | else:
227 | self.input_options.pop(1)
228 |
229 | def no_parameters(self):
230 | print('\nERR: Need more than one parameter')
231 | self.input_options = []
232 | self.show_help = True
233 |
234 | def reference_file(self):
235 | print('Path specified with '+self.input_options[1]+'. Extracting path')
236 | file_reference = None
237 | try: self.input_options[2]
238 | except:
239 | self.input_options.remove(self.input_options[1])
240 | else:
241 | if self.options.get(self.input_options[2], lambda : None)():
242 | self.input_options.remove(self.input_options[1])
243 | else:
244 | file_reference=self.input_options[2]
245 | self.input_options.remove(self.input_options[2])
246 | self.input_options.remove(self.input_options[1])
247 | return(file_reference)
248 |
249 | def not_enough_data(self):
250 | print(\
251 | '\n'+\
252 | 'ERR: Not enough data given to build a synthetic FlatField\n'+\
253 | ' Need at least the low freq component or a file with\n'+\
254 | ' the angular response of the fisheye'\
255 | )
256 |
257 | self.input_options = []
258 | self.show_help = True
259 |
260 | def test_flatfield_possible_builds(self):
261 | '''
262 | Test what type of synthetic flatfield can be build from
263 | the user input data. We will try preferably to build
264 | a full Low+High Freq high quality synthetic FlatField.
265 |
266 | Returns the best synthetic FlatField that can be build (if any)
267 | as .build_type property.
268 | '''
269 |
270 | self.build_type=None
271 |
272 | def test(param):
273 | try: vars(self)[param]
274 | except: return(False)
275 | else: return(True)
276 |
277 | if test("lowfreq_path")== True and test("highfreq_path")==True:
278 | self.file_reference = self.lowfreq_path
279 | self.build_type = 'LowHighFreq'
280 | elif test("radialprofile")==True and test("highfreq_path")==True:
281 | self.file_reference = self.highfreq_path
282 | self.build_type = 'RadialHighFreq'
283 | elif test("lowfreq_path")== True:
284 | self.file_reference = self.lowfreq_path
285 | self.build_type = 'OnlyLowFreq'
286 | elif test("radialprofile")==True and test("file_reference")==True:
287 | self.build_type = 'OnlyRadial'
288 |
289 | if self.build_type == None:
290 | self.not_enough_data()
291 |
292 |
293 | def load_config_file(config_file):
294 | '''
295 | Open the config file
296 | This will set-up the observatory properties
297 | such as Latitude and Longitude of the Observatory
298 | '''
299 | ConfigOptions_ = ConfigOptions(config_file)
300 | ImageInfoCommon = ImageInfo()
301 |
302 | class FakeInputOptions:
303 | void = None
304 |
305 | ImageInfoCommon.config_processing_common(ConfigOptions_,FakeInputOptions)
306 | return(ImageInfoCommon)
307 |
308 |
309 | '''
310 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
311 | ~~~~~~ UTF-8 to latin1 conv. ~~~~~~
312 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313 | '''
314 |
315 | def encode_utf8_to_iso88591(utf8_text):
316 | '''
317 | Encode and return the given UTF-8 text as ISO-8859-1 (latin1) with
318 | unsupported characters replaced by '?', except for common special
319 | characters like smart quotes and symbols that we handle as well as we can.
320 | For example, the copyright symbol => '(c)' etc.
321 |
322 | If the given value is not a string it is returned unchanged.
323 |
324 | References:
325 | en.wikipedia.org/wiki/Quotation_mark_glyphs#Quotation_marks_in_Unicode
326 | en.wikipedia.org/wiki/Copyright_symbol
327 | en.wikipedia.org/wiki/Registered_trademark_symbol
328 | en.wikipedia.org/wiki/Sound_recording_copyright_symbol
329 | en.wikipedia.org/wiki/Service_mark_symbol
330 | en.wikipedia.org/wiki/Trademark_symbol
331 | '''
332 | if not isinstance(utf8_text, basestring):
333 | return utf8_text
334 | # Replace "smart" and other single-quote like things
335 | utf8_text = re.sub(
336 | u'[\u02bc\u2018\u2019\u201a\u201b\u2039\u203a\u300c\u300d]',
337 | "'", utf8_text)
338 | # Replace "smart" and other double-quote like things
339 | utf8_text = re.sub(
340 | u'[\u00ab\u00bb\u201c\u201d\u201e\u201f\u300e\u300f]',
341 | '"', utf8_text)
342 | # Replace copyright symbol
343 | utf8_text = re.sub(u'[\u00a9\u24b8\u24d2]', '(c)', utf8_text)
344 | # Replace registered trademark symbol
345 | utf8_text = re.sub(u'[\u00ae\u24c7]', '(r)', utf8_text)
346 | # Replace sound recording copyright symbol
347 | utf8_text = re.sub(u'[\u2117\u24c5\u24df]', '(p)', utf8_text)
348 | # Replace service mark symbol
349 | utf8_text = re.sub(u'[\u2120]', '(sm)', utf8_text)
350 | # Replace trademark symbol
351 | utf8_text = re.sub(u'[\u2122]', '(tm)', utf8_text)
352 | # Replace/clobber any remaining UTF-8 characters that aren't in ISO-8859-1
353 | return utf8_text.encode('ISO-8859-1', 'replace')
354 |
355 |
356 | '''
357 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
358 | ~~~~~~~ FlatField generation ~~~~~~
359 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
360 | '''
361 |
362 | '''def load_image(image):
363 | print('Loading Data and Header for the given Image ...'),
364 | try:
365 | Image_HDU = pyfits.open(image)
366 | Image_Data = Image_HDU[0].data
367 | Image_Header = Image_HDU[0].header
368 | # Convert to string
369 | #Image_Header_text = Image_Header.tostring()
370 | #Image_Header_text = encode_utf8_to_iso88591(Image_Header_text)
371 | #Image_Header.fromstring(Image_Header_text)
372 | #print(Image_Header)
373 | except:
374 | print(inspect.stack()[0][2:4][::-1])
375 | raise
376 | else:
377 | print('OK')
378 | return(Image_Data,Image_Header)
379 | '''
380 |
381 | def low_pass_filter(image,sigma=5):
382 | ''' Returns filtered image '''
383 | return(scipy.ndimage.filters.median_filter(image,sigma))
384 |
385 | def high_pass_filter(image,sigma=5):
386 | ''' Returns filtered image '''
387 | return(image - low_pass_filter(image,sigma))
388 |
389 | def angular_response(radialprofile,interporder=3,smoothing=None):
390 | '''
391 | Process a table (comma separated values) with angular
392 | response of the lens.
393 | Returns a tck tuple (knots,Bspline coef and degree).
394 | See scipy.interpolate.splrep for further details.
395 | '''
396 |
397 | separation = ','
398 | endline = ''
399 |
400 | the_file = open(radialprofile)
401 | raw_data = the_file.readlines()
402 |
403 | data = raw_data
404 |
405 | # Remove trailing characters
406 | data = [item.replace(endline+'\n','') for item in data]
407 | # Remove spaces
408 | data = [item.replace(' ','') for item in data]
409 | # Remove empty lines
410 | data = [item for item in data if item!='']
411 | # Remove comments
412 | data = [item for item in data if item[0]!='#']
413 |
414 | # Separate angle and value
415 | data = np.array([item.split(separation) \
416 | for item in data],dtype='float')
417 |
418 | # Interpolation (spline)
419 | tck = scipy.interpolate.splrep(data[:,0],data[:,1],k=interporder,s=smoothing)
420 | return(tck)
421 |
422 | def create_synthetic_image(radialprofile,ImageInfo):
423 | tck = angular_response(radialprofile,interporder=3,smoothing=None)
424 | IC = ImageCoordinates(ImageInfo)
425 | sx,sy = np.shape(IC.altitude_map)
426 | map_response = scipy.interpolate.splev(90.0-IC.altitude_map.flatten(),tck,ext=3)
427 | map_response = np.array(map_response).reshape(sx,sy)
428 | map_response[np.isnan(map_response)] = 1
429 | return(map_response)
430 |
431 | class FlatField():
432 | '''
433 | Synthetic FlatField generator
434 | '''
435 |
436 | def flat_from_low_and_highfreq(self,lowfreq_path,highfreq_path):
437 | # Load Low and High freq flats
438 | FlatLowFreq_Data,FlatLowFreq_Header = \
439 | verbose(load_image,lowfreq_path)
440 | FlatHighFreq_Data,FlatHighFreq_Header = \
441 | verbose(load_image,highfreq_path)
442 |
443 | # Check if the size match
444 | assert np.shape(FlatLowFreq_Data)==np.shape(FlatHighFreq_Data)
445 |
446 | # Normalization of Flats
447 | FlatHighFreq_Data = FlatHighFreq_Data/np.mean(FlatHighFreq_Data)
448 | FlatLowFreq_Data = FlatLowFreq_Data/np.mean(FlatLowFreq_Data)
449 |
450 | # Fourier filters
451 | print('Applying low and high pass filters ...')
452 | # Low pass
453 | FlatLow_filt = low_pass_filter(FlatLowFreq_Data)
454 | # High pass
455 | FlatHigh_filt = high_pass_filter(FlatLowFreq_Data)
456 |
457 | # Build the synthetic flat field
458 | print('Creating synthetic Flatfield ...')
459 | self.FlatField = pyfits.HDUList(hdus=\
460 | [pyfits.PrimaryHDU(\
461 | data = FlatLow_filt+FlatHigh_filt,
462 | header = FlatLowFreq_Header)])
463 | self.FlatField[0].header.add_comment('Synthetic FF [PyASB]. Low + High')
464 |
465 | self.prefixname = 'Synthetic_FlatField_'+str(FlatLowFreq_Header['FILTER'])
466 |
467 | def flat_from_lowfreq(self,lowfreq_path):
468 | # Load Low and High freq flats
469 | FlatLowFreq_Data,FlatLowFreq_Header = \
470 | verbose(load_image,lowfreq_path)
471 |
472 | # Normalization of Flats
473 | FlatLowFreq_Data = FlatLowFreq_Data/np.mean(FlatLowFreq_Data)
474 |
475 | # Fourier filters
476 | print('Applying low and high pass filters ...')
477 | # Low pass
478 | FlatLow_filt = low_pass_filter(FlatLowFreq_Data)
479 |
480 | # Build the synthetic flat field
481 | print('Creating synthetic Flatfield ...')
482 | self.FlatField = pyfits.HDUList(hdus=\
483 | [pyfits.PrimaryHDU(\
484 | data = FlatLow_filt, header = FlatHighFreq_Header)])
485 | self.FlatField[0].header.add_comment('Synthetic FF [PyASB]. Low freqs')
486 |
487 | self.prefixname = 'Synthetic_FlatField_any'
488 |
489 | def flat_from_radial_and_highfreq(self,radialprofile,hightfreq_flat,ImageInfo):
490 | FlatHighFreq_Data,FlatHighFreq_Header = \
491 | verbose(load_image,highfreq_path)
492 |
493 | print('Loading LowFreq from radial profile ...')
494 | ImageInfo.read_header(FlatHighFreq_Header)
495 | # Do not offset the center
496 | ImageInfo.delta_x = 0
497 | ImageInfo.delta_y = 0
498 | map_response = create_synthetic_image(radialprofile,ImageInfo)
499 |
500 | # Check if the size match
501 | assert np.shape(map_response)==np.shape(FlatHighFreq_Data)
502 |
503 | # Normalization of Flats
504 | FlatHighFreq_Data = FlatHighFreq_Data/np.mean(FlatHighFreq_Data)
505 |
506 | # Fourier filters
507 | print('Applying low and high pass filters ...')
508 | # High pass
509 | FlatHigh_filt = high_pass_filter(FlatLowFreq_Data)
510 |
511 | # Build the synthetic flat field
512 | print('Creating synthetic Flatfield ...')
513 | self.FlatField = pyfits.HDUList(hdus=\
514 | [pyfits.PrimaryHDU(\
515 | data = map_response+FlatHigh_filt,
516 | header = FlatHighFreq_Header)])
517 | self.FlatField[0].header.add_comment('Synthetic FF [PyASB]. Radial profile + High')
518 |
519 | self.prefixname = 'Synthetic_FlatField_'+str(FlatHighFreq_Header['FILTER'])
520 |
521 | def flat_from_radial(self,radialprofile,file_reference,ImageInfo):
522 | Header = verbose(load_image,file_reference)[1]
523 |
524 | # Tune a bit the Reference Header
525 | Header['FILTER'] = 'Johnson_common'
526 |
527 | print('Loading LowFreq from radial profile ...')
528 | ImageInfo.read_header(Header)
529 | # Do not offset the center
530 | ImageInfo.delta_x = 0
531 | ImageInfo.delta_y = 0
532 | map_response = create_synthetic_image(radialprofile,ImageInfo)
533 |
534 | # Build the synthetic flat field
535 | print('Creating synthetic Flatfield ...')
536 | self.FlatField = pyfits.HDUList(hdus=\
537 | [pyfits.PrimaryHDU(\
538 | data = map_response,
539 | header = Header)])
540 | self.FlatField[0].header.add_comment('Synthetic FF [PyASB]. Radial profile')
541 |
542 | self.prefixname = 'Synthetic_FlatField_any'
543 |
544 |
545 | def save_generated_flatfield(self,path_to_save):
546 | file_to_save = path_to_save+'/'+self.prefixname +'.fits'
547 | print('Save file to '+str(file_to_save))
548 | self.FlatField.writeto(file_to_save,clobber=True)
549 |
550 |
551 | if __name__ == '__main__':
552 | # Read config and Observatory properties
553 | InputOptions = ReadOptions(sys.argv)
554 | config_file = get_config_filename(InputOptions)
555 | ImageInfo = load_config_file(config_file)
556 |
557 | print('Synthetic Flat Field generator ...')
558 | print('Generating ['+str(InputOptions.build_type)+'] flatfield')
559 | FlatField_ = FlatField()
560 |
561 | if InputOptions.build_type == 'LowHighFreq':
562 | FlatField_.flat_from_low_and_highfreq(\
563 | InputOptions.lowfreq_path,InputOptions.highfreq_path)
564 | elif InputOptions.build_type == 'RadialHighFreq':
565 | FlatField_.flat_from_radial_and_highfreq(\
566 | InputOptions.radialprofile,InputOptions.highfreq_path,ImageInfo)
567 | elif InputOptions.build_type == 'OnlyLowFreq':
568 | FlatField_.flat_from_lowfreq(InputOptions.lowfreq_path)
569 | elif InputOptions.build_type == 'OnlyRadial':
570 | FlatField_.flat_from_radial(\
571 | InputOptions.radialprofile,\
572 | InputOptions.file_reference,\
573 | ImageInfo)
574 |
575 | try: path_to_save = InputOptions.base_dir
576 | except:
577 | path_to_save = os.path.split(\
578 | os.path.abspath(InputOptions.file_reference))[0]
579 |
580 | FlatField_.save_generated_flatfield(path_to_save)
581 |
582 |
583 |
584 |
585 |
--------------------------------------------------------------------------------
/pyasb/fromftp.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | PyASB FTP module.
5 |
6 | Get images iteratively from FTP server.
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 |
15 | __author__ = "Mireia Nievas"
16 | __copyright__ = "Copyright 2012, PyASB project"
17 | __credits__ = ["Mireia Nievas"]
18 | __license__ = "GNU GPL v3"
19 | __shortname__ = "PyASB"
20 | __longname__ = "Python All-Sky Brightness pipeline"
21 | __version__ = "1.99.0"
22 | __maintainer__ = "Mireia Nievas"
23 | __email__ = "mirph4k[at]gmail[dot]com"
24 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
25 |
26 |
27 | try:
28 | import sys,os,inspect
29 | import datetime
30 | import time
31 | import signal
32 | import urllib
33 | import gzip
34 | import ftputil
35 | import pyfits
36 | except:
37 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
38 | raise SystemExit
39 |
40 |
41 | ''' User defined options '''
42 |
43 | base_dir = "/usr/users/mnievas/PyASB"
44 | temporary_path = "/tmp/"
45 | skymap_path = base_dir+"/skymaps/"
46 | photometry_table_path = base_dir+"/starphotometry/"
47 | bouguerfit_path = base_dir+"/bouguerfits/"
48 | skybrightness_map_path = base_dir+"/skybrightnessmaps/"
49 | skybrightness_table_path = base_dir+"/skybrightnesstable/"
50 | cloudmap_path = base_dir+"/cloudmaps/"
51 | clouddata_table_path = base_dir+"/clouddata/"
52 | summary_path = base_dir+"/summary/"
53 | register_analyzed_files = base_dir+"/register.txt"
54 |
55 | ftp_images_since_reconnect = 0
56 | ftp_max_images_until_reconnect = 20
57 |
58 |
59 | '''
60 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
61 | ~~~~~~~~~~ Halt handler ~~~~~~~~~~~
62 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
63 | '''
64 |
65 | def handler(signum, frame):
66 | print 'Signal handler called with signal', signum
67 | print "CTRL-C pressed"
68 | sys.exit(0)
69 |
70 | signal.signal(signal.SIGTERM, handler)
71 | signal.signal(signal.SIGINT, handler)
72 |
73 |
74 | class FtpSession():
75 | def __init__(self,ftp_server,ftp_user,ftp_pass,ftp_basedir,analysis_basedir):
76 | self.ftp_server = ftp_server
77 | self.ftp_user = ftp_user
78 | self.ftp_pass = ftp_pass
79 | self.ftp_basedir = ftp_basedir
80 |
81 | self.ftp_connect()
82 | self.fitslist = []
83 | self.fits_search_remotepath(basedir=ftp_basedir)
84 | self.ftp_disconnect()
85 |
86 | self.update_dirs_path(analysis_basedir)
87 | self.create_analysis_paths()
88 | #self.perform_analysis()
89 |
90 | def ftp_connect(self):
91 | ''' Establish FTP connection. Returns ftp session '''
92 | self.ftp = ftputil.FTPHost(ftp_server,ftp_user,ftp_pass)
93 |
94 | def ftp_disconnect(self):
95 | ''' End FTP connection '''
96 | self.ftp.close()
97 |
98 | def test_is_directory(self,path):
99 | try:
100 | self.ftp.chdir(path)
101 | self.ftp.chdir('..')
102 | return(True)
103 | except:
104 | return(False)
105 |
106 | def list_files(self):
107 | ''' List files in FTP remote dir '''
108 | try: listfiles = self.ftp.listdir()
109 | except: return []
110 | else: return listfiles
111 |
112 | def update_dirs_path(self,analysis_basedir):
113 | try:
114 | global base_dir,temporary_path,skymap_path,photometry_table_path,\
115 | bouguerfit_path,skybrightness_map_path,skybrightness_table_path,\
116 | cloudmap_path,clouddata_table_path,summary_path,register_analyzed_files
117 |
118 | base_dir = analysis_basedir
119 | temporary_path = "/tmp/"
120 | skymap_path = base_dir+"/skymaps/"
121 | photometry_table_path = base_dir+"/starphotometry/"
122 | bouguerfit_path = base_dir+"/bouguerfits/"
123 | skybrightness_map_path = base_dir+"/skybrightnessmaps/"
124 | skybrightness_table_path = base_dir+"/skybrightnesstable/"
125 | cloudmap_path = base_dir+"/cloudmaps/"
126 | clouddata_table_path = base_dir+"/clouddata/"
127 | summary_path = base_dir+"/summary/"
128 | register_analyzed_files = base_dir+"/register.txt"
129 | except:
130 | print(str(inspect.stack()[0][2:4][::-1])+' Dont update base_dir')
131 |
132 | def download_file(self,remote_filename,local_filename):
133 | ''' Download remote file to local temporary copy '''
134 | if not os.path.exists(temporary_path): os.makedirs(temporary_path)
135 | #local_filename = temporary_path + tempfilename
136 | #temporary_file = open(tempfilename,'wb')
137 | if os.path.isfile(local_filename):
138 | os.remove(local_filename)
139 |
140 | # Binary files are easily corrupted if downloaded from Win->Linux through ftp.
141 | # Download instead using urllib.
142 | urllib.urlretrieve(\
143 | 'ftp://'+str(self.ftp_user)+':'+str(self.ftp_pass)+\
144 | '@'+str(self.ftp_server)+str(remote_filename),str(local_filename))
145 |
146 | #self.ftp.download(remote_filename, local_filename,'w')
147 | #self.ftp.retrbinary('RETR '+filename,temporary_file.write)
148 | #temporary_file.close()
149 |
150 | def create_analysis_paths(self):
151 | ''' Create neccesary file path. Must be defined at the beginning of the script'''
152 | for directory in [\
153 | base_dir, skymap_path, photometry_table_path, \
154 | bouguerfit_path, skybrightness_map_path, skybrightness_table_path, \
155 | summary_path, temporary_path, cloudmap_path, clouddata_table_path]:
156 | if not os.path.exists(directory): os.makedirs(directory)
157 |
158 | def fits_search_remotepath(self,basedir=''):
159 | ''' Recursively search fits files in the remote server. Return a self.list of fits files'''
160 | recursive = self.ftp.walk(basedir,topdown=True,onerror=None)
161 | for root,dirs,files in recursive:
162 | for name in files:
163 | thefile = os.path.join(root, name)
164 | #FileName, FileExt = os.path.splitext(thefile)
165 | valid_ext = ['.fts','.fit','.fits','.FTS','.FIT','.FITS']
166 | invalid_prefixes = ['SBJo']
167 |
168 | use_file = False
169 | for known_ext in valid_ext:
170 | if known_ext in thefile:
171 | use_file = True
172 |
173 | if use_file == True:
174 | for bad_prefix in invalid_prefixes:
175 | #if FileExt == known_ext:
176 | if bad_prefix in thefile:
177 | use_file = False
178 |
179 | if use_file == True:
180 | self.fitslist.append("/"+thefile)
181 |
182 |
183 | def perform_analysis(self,pyasb_fullanalysis,pyasb_overwrite):
184 | '''
185 | For each file in the remote list:
186 | 1.- Download the image to a tmp file.
187 | 2.- Perform the analysis.
188 | 3.- write results and append the file to the list of analyzed files.
189 | '''
190 |
191 | ftp_images_since_reconnect = 0;
192 |
193 | for each_fitsfile in self.fitslist:
194 | ftp_images_since_reconnect+=1;
195 | if "Johnson_U" in each_fitsfile:
196 | print('Johnson U file detected, SNR will be too low, discarding')
197 | continue
198 |
199 | if not os.path.isfile(register_analyzed_files):
200 | register_analyzed = open(register_analyzed_files,"w+")
201 | register_analyzed.close()
202 |
203 | register_analyzed = open(register_analyzed_files,"r+").readlines()
204 |
205 | if os.path.split(each_fitsfile)[1] in str(register_analyzed):
206 | if pyasb_overwrite==True:
207 | print 'Previous analysis results detected, Overwrite mode is ON'
208 | elif pyasb_overwrite==False:
209 | print 'Previous analysis results detected, Overwrite mode is OFF'
210 | continue
211 |
212 | self.ftp_connect()
213 | print('---> Downloading file '+str(each_fitsfile))
214 | actual_localdir = os.getcwd()
215 | # Download the image
216 | temp_filename = temporary_path+'/pyasb_'+\
217 | str(datetime.datetime.now())\
218 | .replace("-","").replace(":","").\
219 | replace(" ","_").replace(".","_")+\
220 | '.fits'
221 |
222 | try:
223 | self.download_file(each_fitsfile,temp_filename)
224 | except:
225 | print(inspect.stack()[0][2:4][::-1])
226 | raise
227 | print 'File cannot be downloaded, continue with next one';
228 | self.ftp_disconnect()
229 |
230 | print('---> Launching PyASB to analyze '+str(each_fitsfile))
231 | # Analize the image
232 | complete_analysis_exec = " "
233 | if pyasb_fullanalysis==True:
234 | complete_analysis_exec = \
235 | " -os " + skybrightness_map_path +\
236 | " -om " + skymap_path +\
237 | " -ost " + skybrightness_table_path +\
238 | " -ocm " + cloudmap_path +\
239 | " -oct " + clouddata_table_path
240 | try:
241 | os.system("python pyasb_launcher.py -i "+\
242 | temp_filename+\
243 | " -ob " + bouguerfit_path + \
244 | " -ot " + photometry_table_path +\
245 | " -or " + summary_path + " -c "+ pyasb_config + \
246 | complete_analysis_exec)
247 | os.remove(temp_filename)
248 | except:
249 | print('Error performing pyasb analysis, please check the file.')
250 | #pass
251 |
252 | register_analyzed = open(register_analyzed_files,"a+")
253 | register_analyzed.write(each_fitsfile+"\r\n");
254 | self.ftp_disconnect()
255 | time.sleep(2)
256 |
257 |
258 | def show_help():
259 | ''' Print Help message on std output '''
260 | print(\
261 | 'PyASB ftp module. This program tries to recursively analize '+\
262 | 'located on a remote FTP repository'+\
263 | 'Parameters:'+\
264 | ' -h : print this help message and exit\n'+\
265 | ' -s server_ip: the FTP server IP\n'+\
266 | ' -u ftp_username: the FTP username\n'+\
267 | ' -p ftp_userpass: the FTP user passwd\n'+\
268 | ' -b base_dir: base dir to start the iterative search\n'+\
269 | ' -c config_file: pyasb config file to use\n'+\
270 | ' -d analysis_basedir: pyasb analysis base directory\n'+\
271 | ' --overwrite: overwrite any previous analysis data in dir.\n'+\
272 | ' Default is to keep the old data\n'+\
273 | ' --full: perform full analysis (generates sky brightness map, takes more time).\n'+\
274 | ' Default is to do a simple analysis (no SB map generation)\n')
275 | exit(0)
276 |
277 |
278 | if __name__ == '__main__':
279 | ''' Read input options and start the analysis
280 | Results will be stored in the same directory'''
281 | try:
282 | input_options = sys.argv[1:]
283 | print('Input Options: '+str(input_options))
284 | except:
285 | print('ERR. No INPUT parameters detected\n')
286 | show_help()
287 | else:
288 | pyasb_fullanalysis = False
289 | pyasb_overwrite = False
290 | ftp_user = 'anonymous'
291 | ftp_pass = ''
292 | ftp_basedir = ''
293 | pyasb_config = 'pyasb_config.cfg'
294 |
295 | while len(input_options)>0:
296 | input_option = input_options[0]
297 | if input_option == '-h':
298 | show_help()
299 | elif input_option == '-s':
300 | ftp_server = input_options[1]
301 | input_options.pop(1)
302 | elif input_option == '-u':
303 | ftp_user = input_options[1]
304 | input_options.pop(1)
305 | elif input_option == '-p':
306 | ftp_pass = input_options[1]
307 | input_options.pop(1)
308 | elif input_option == '-b':
309 | ftp_basedir = input_options[1]
310 | input_options.pop(1)
311 | elif input_option == '-c':
312 | pyasb_config = input_options[1]
313 | input_options.pop(1)
314 | elif input_option == '-d':
315 | analysis_basedir = input_options[1]
316 | input_options.pop(1)
317 | elif input_option == '--full':
318 | pyasb_fullanalysis = True
319 | elif input_option == '--overwrite':
320 | pyasb_overwrite = True
321 | input_options.pop(0)
322 |
323 | try: ftp_server
324 | except:
325 | print(inspect.stack()[0][2:4][::-1])
326 | print('ERR. No FTP server specified')
327 | show_help()
328 |
329 | try:
330 | FtpRemoteSession = FtpSession(\
331 | ftp_server,\
332 | ftp_user,\
333 | ftp_pass,\
334 | ftp_basedir,\
335 | analysis_basedir)
336 |
337 | print('Number of files found: '+str(len(FtpRemoteSession.fitslist)))
338 | FtpRemoteSession.perform_analysis(pyasb_fullanalysis,pyasb_overwrite)
339 | except:
340 | print(inspect.stack()[0][2:4][::-1])
341 | raise
342 |
--------------------------------------------------------------------------------
/pyasb/help.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | PyASB help module
5 |
6 | Build and show program help
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 | try:
26 | import sys,os,inspect
27 | except:
28 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
29 | raise SystemExit
30 |
31 | class PlatformHelp():
32 | def __init__(self):
33 | self.make_title()
34 | self.make_welcome()
35 | self.make_requisites()
36 | self.make_options()
37 |
38 | def make_title(self):
39 | nameversion = 10*'#'+3*' '+__shortname__+' v'+__version__+3*' '+10*'#'
40 | self.separator = len(nameversion)*'#'
41 | self.title = nameversion
42 |
43 | def make_welcome(self):
44 | self.welcome = 'Welcome to '+__shortname__+' ('+__longname__+')\n'
45 |
46 | def make_requisites(self):
47 | self.requisites = \
48 | 'Requisites: Python 2.7; Scipy; Numpy;\n'+\
49 | ' Matplotlib; Pyfits; Pyephem\n\n'
50 |
51 | def make_options(self):
52 | self.options = \
53 | '-h: print this help message\n\n'+\
54 | '-i input_allsky_image: \n'+\
55 | ' All Sky image you want to be analyzed\n\n'+\
56 | '-c config_file: \n'+\
57 | ' Use alternative config file\n\n'+\
58 | '-d [year,month,day]:\n'+\
59 | ' Date to be analyzed (AstMon-UCM only)\n'+\
60 | ' month and day are optional\n\n'+\
61 | '-om output_map_image path:\n'+\
62 | ' Output star map image, full or relative path\n'+\
63 | ' if no output file, show the map on screen\n\n'+\
64 | '-ot output_photometric_table path:\n'+\
65 | ' Output photometric table full or relative path\n'+\
66 | ' if no output file, show the table on screen\n\n'+\
67 | '-or output_results_summary path:\n'+\
68 | ' Summary of analysis, fit parameters and zenith SB\n'+\
69 | ' full or relative path. If no output file, \n'+\
70 | ' show the table on screen\n\n'+\
71 | '-ob output_bouguerfit_graph path:\n'+\
72 | ' Output bouguer-fit graph, full or relative path.\n'+\
73 | ' If no output file, show the graph on screen\n\n'+\
74 | '-ocm output_cloudmap_image path:\n'+\
75 | ' Output cloud map image, full or relative path\n'+\
76 | ' if no output file, show the map on screen\n\n'+\
77 | '-oct output_clouddata_table path:\n'+\
78 | ' Output cloud data table, full or relative path\n'+\
79 | ' if no output file, show the map on screen\n\n'+\
80 | '-os output_skybrightness_graph path:\n'+\
81 | ' Output Sky Brightness graph, full or relative path\n'+\
82 | ' if no output file, show the graph on screen\n\n'+\
83 | '-ost output_skybrightness_table path:\n'+\
84 | ' Output Sky Brightness table, full or relative path\n'+\
85 | ' if no output file, show the graph on screen\n\n'
86 |
87 | def show_help(self):
88 | print(\
89 | self.separator+'\n'+self.title+'\n'+self.separator+'\n'+self.welcome+\
90 | self.requisites+self.options+self.separator)
91 |
92 | def incorrect_parameter(self,parameter):
93 | print('ERROR. Incorrect parameter: '+str(parameter))
94 | self.show_help()
95 |
96 | def date_or_file_input_error(self):
97 | print('ERROR. Date or file input')
98 | self.show_help()
99 |
100 | def no_parameters_error(self):
101 | print('ERROR. No input parameters especified')
102 | self.show_help()
103 |
104 |
105 |
--------------------------------------------------------------------------------
/pyasb/image_info.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Load FITS Info data
5 |
6 | This module processes the Image metadata and the program main options
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 | try:
26 | import sys,os,inspect
27 | import numpy as np
28 | import astropy.io.fits as pyfits
29 | from read_config import *
30 | from load_fitsimage import ImageTest
31 | from astrometry import pyephem_setup_real, pyephem_setup_common
32 | except:
33 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
34 | raise SystemExit
35 |
36 |
37 | class ImageInfo(ImageTest):
38 | '''
39 | Extract some data from the image header
40 | and put it in a class ImageInfo
41 | '''
42 | def __init__(self):
43 | pass
44 |
45 | '''
46 | def __init__(self,fits_header,ConfigOptions):
47 | self.read_header(fits_header)
48 | self.config_processing(ConfigOptions)
49 | '''
50 |
51 | def read_header(self,fits_header):
52 | # Date and time in different formats
53 | self.fits_date = ImageTest.correct_date(fits_header)
54 | self.date_array = [self.fits_date[0:4],self.fits_date[4:6],self.fits_date[6:8],
55 | self.fits_date[9:11],self.fits_date[11:13],self.fits_date[13:15]]
56 | self.date_string = self.date_array[0]+"/"+self.date_array[1]+"/"+self.date_array[2]+" "+\
57 | self.date_array[3]+":"+self.date_array[4]+":"+self.date_array[5]
58 |
59 | ObsPyephem = pyephem_setup_real(self)
60 | self.local_sidereal_time = ObsPyephem.sidereal_time()*12./np.pi
61 | ObsPyephem.lon=0
62 | self.sidereal_time = ObsPyephem.sidereal_time()*12./np.pi
63 |
64 | # Exposure (float), resolution (2d int array), filter (str)
65 | self.exposure = ImageTest.correct_exposure(fits_header)
66 | self.resolution = ImageTest.correct_resolution(fits_header)
67 | self.used_filter = ImageTest.correct_filter(fits_header)
68 |
69 | # Update radial factor guess
70 | try: self.radial_factor
71 | except: self.radial_factor = \
72 | (np.min(self.resolution)/2 - 10*self.base_radius)\
73 | /(180.0*np.sqrt(2)/np.pi)
74 |
75 | def config_processing_common(self,ConfigOptions,InputOptions):
76 | # Default values
77 | self.darkframe = False
78 | self.biasframe = False
79 | self.latitude_offset = 0.0
80 | self.longitude_offset = 0.0
81 | self.delta_x = 0.0
82 | self.delta_y = 0.0
83 | self.azimuth_zeropoint = 0.0
84 | self.catalog_filename="catalog.csv"
85 | self.flip_image = False
86 | self.calibrate_astrometry = False
87 | self.projection = 'ZEA'
88 | self.sel_flatfield=None
89 | # A better aprox would be np.min(self.resolution)/(180.0*np.sqrt(2)/np.pi)
90 | # but it depends on the image, which has not yet been read.
91 |
92 | for atribute in list(InputOptions.__dict__):
93 | ConfigOptions.FileOptions.append([atribute,vars(InputOptions)[atribute]])
94 |
95 | # Config processing
96 |
97 | list_float_options = [\
98 | "latitude", "longitude", \
99 | "latitude_offset", "longitude_offset", "delta_x", "delta_y",\
100 | "radial_factor", "azimuth_zeropoint", "min_altitude", \
101 | "base_radius", "baseflux_detectable", "lim_Kendall_tau",\
102 | "ccd_bits", "ccd_gain", "perc_low", "perc_high", "read_noise", \
103 | "thermal_noise", "max_magnitude"]
104 |
105 | list_int_options = [ "max_star_number" ]
106 |
107 | list_bool_options = [ "calibrate_astrometry", "flip_image" ]
108 |
109 | list_str_options = [\
110 | "obs_name", "backgroundmap_title", "cloudmap_title", "skymap_path",\
111 | "photometry_table_path", "bouguerfit_path", "skybrightness_map_path", \
112 | "skybrightness_table_path", "cloudmap_path", "clouddata_path", \
113 | "summary_path", "catalog_filename", "darkframe", "biasframe", \
114 | "maskframe","projection" ]
115 |
116 | for option in ConfigOptions.FileOptions:
117 | setattr(self,option[0],option[1])
118 | if option[0] in list_float_options:
119 | setattr(self,option[0],float(option[1]))
120 | elif option[0] in list_int_options:
121 | setattr(self,option[0],int(option[1]))
122 | elif option[0] in list_str_options:
123 | while (str(option[1])[0]==" "): option[1]=option[1][1:]
124 | while (str(option[1])[-1]==" "): option[1]=option[1][:-1]
125 | if (option[1]=="False"): setattr(self,option[0],False)
126 | else: setattr(self,option[0],str(option[1]))
127 | elif option[0] in list_bool_options:
128 | setattr(self,option[0],bool(option[1] in ["1","True","T","true", True]))
129 | elif option[0]=="obs_latitude" : self.latitude = float(option[1])
130 | elif option[0]=="obs_longitude": self.longitude = float(option[1])
131 | # obs_latitude and obs_longitude were the old name.
132 |
133 | def config_processing_specificfilter(self,ConfigOptions):
134 | filters=["U","B","V","R","I"]
135 |
136 | self.zero_points = {}
137 | self.color_terms = {}
138 | self.background_levels = {}
139 | self.flatfield = {}
140 |
141 | for the_filter in filters:
142 | self.zero_points["Johnson_"+the_filter] = [False,False]
143 | self.color_terms["Johnson_"+the_filter] = [0,0]
144 | self.background_levels["Johnson_"+the_filter] = [False,False]
145 | self.flatfield["Johnson_"+the_filter] = False
146 |
147 | # Options that depends on the filter
148 | for option in ConfigOptions.FileOptions:
149 | for the_filter in xrange(len(filters)):
150 | filter_name = "Johnson_"+filters[the_filter]
151 | if option[0]=="zero_point_"+filters[the_filter]:
152 | self.zero_points[filter_name] = \
153 | [float(option[1].split(",")[0]), float(option[1].split(",")[1])]
154 | if "Johnson_"+filters[the_filter] == self.used_filter:
155 | self.used_zero_point = self.zero_points[filter_name]
156 | elif option[0]=="color_term_"+filters[the_filter]:
157 | self.color_terms[filter_name] = \
158 | [float(option[1].split(",")[0]), float(option[1].split(",")[1])]
159 | if "Johnson_"+filters[the_filter] == self.used_filter:
160 | self.sel_color_terms = self.color_terms[filter_name]
161 | elif option[0]=="bkgnd_minmax_"+filters[the_filter]:
162 | self.background_levels[filter_name] = [float(option[1].split(",")[0]), \
163 | float(option[1].split(",")[1])]
164 | if "Johnson_"+filters[the_filter] == self.used_filter:
165 | self.sel_background_levels = self.background_levels[filter_name]
166 | elif option[0]=="flatfield_"+filters[the_filter]:
167 | self.flatfield[filter_name] = str(option[1]).replace(" ","")
168 | if "Johnson_"+filters[the_filter] == self.used_filter:
169 | self.sel_flatfield = self.flatfield[filter_name]
170 |
171 | # Some hacks to improve detectability in specific filters
172 |
173 | if self.used_filter == 'Johnson_B':
174 | self.base_radius = self.base_radius*1.2
175 | self.baseflux_detectable = self.baseflux_detectable*1.2
176 |
177 | if self.used_filter == 'Johnson_U':
178 | self.base_radius = self.base_radius*2
179 | self.baseflux_detectable = self.baseflux_detectable*2
180 | self.max_magnitude = self.max_magnitude-0.5
181 |
--------------------------------------------------------------------------------
/pyasb/input_options.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | PyASB input options module
5 |
6 | Read input options
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 | try:
26 | import sys,os,inspect
27 | except:
28 | print(str(inspect.stack()[0][2:4][::-1]+': One or more modules missing'))
29 | raise SystemExit
30 |
31 | class ReadOptions():
32 | def __init__(self,input_options):
33 | # Lambda: http://docs.python.org/release/2.5.2/tut/node6.html [4.7.5]
34 | self.options = { '-h': lambda: 'show_help', '-d': lambda: 'use_date', '-i': lambda: 'use_file',
35 | '-c': lambda: 'use_configfile', '-om': lambda: 'use_skymap', '-ocm': lambda: 'use_cloudmap',
36 | '-oct': lambda: 'use_clouddata', '-or': lambda: 'use_results', '-ot': lambda: 'use_phottable',
37 | '-ob': lambda: 'use_bouguerfit', '-os': lambda: 'use_sb' , '-ost': lambda: 'use_sbtable'}
38 |
39 | # By default, we wont show on screen nor save on disk.
40 | self.configfile = False;
41 | self.show_help = False;
42 | self.photometry_table_path=False;
43 | self.skymap_table_path=False;
44 | self.bouguerfit_path=False;
45 | self.skybrightness_map_path=False;
46 | self.skybrightness_table_path=False;
47 | self.cloudmap_path=False;
48 | self.clouddata_path=False;
49 | self.summary_path=False;
50 |
51 | print('Input Options: '+str(input_options))
52 |
53 | self.input_options = input_options
54 | try: self.input_options[1]
55 | except Exception as e:
56 | #print(str(inspect.stack()[0][2:4][::-1]+'ERR. No imput options'))
57 | self.no_parameters()
58 | else:
59 | while len(self.input_options)>1:
60 | input_option = self.options.get(self.input_options[1], lambda : None)()
61 | if input_option == 'show_help':
62 | self.show_help = True
63 | # Stop reading options. Program will halt
64 | self.input_options = []
65 | elif input_option == 'use_date':
66 | self.dates=self.input_date()
67 | elif input_option == 'use_file':
68 | self.fits_filename_list = self.input_file()
69 | elif input_option == 'use_configfile':
70 | self.configfile = self.reference_file()
71 | elif input_option == 'use_phottable':
72 | self.photometry_table_path=self.output_file()
73 | elif input_option == 'use_skymap':
74 | self.skymap_path=self.output_file()
75 | elif input_option == 'use_bouguerfit':
76 | self.bouguerfit_path=self.output_file()
77 | elif input_option == 'use_cloudmap':
78 | self.cloudmap_path=self.output_file()
79 | elif input_option == 'use_clouddata':
80 | self.clouddata_path=self.output_file()
81 | elif input_option == 'use_sb':
82 | self.skybrightness_map_path=self.output_file()
83 | elif input_option == 'use_sbtable':
84 | self.skybrightness_table_path=self.output_file()
85 | elif input_option == 'use_results':
86 | self.summary_path=self.output_file()
87 | else:
88 | self.input_options.remove(self.input_options[1])
89 | continue
90 | #self.incorrect_parameter()
91 |
92 | if self.show_help==False: # NOTE: Check this statement
93 | self.date_set=True
94 | self.inputfile_set=True
95 |
96 | try:
97 | assert(len(dates)>=1)
98 | except:
99 | self.date_set=False
100 |
101 | try:
102 | assert(len(self.fits_filename_list)>=1)
103 | except:
104 | self.inputfile_set=False
105 |
106 | if not (self.date_set or self.inputfile_set):
107 | self.need_date_or_file()
108 |
109 | def incorrect_parameter(self):
110 | print '\nERR: Incorrect parameter '+str(self.input_options[1])
111 | self.input_options = []
112 | self.show_help = True
113 |
114 | def need_date_or_file(self):
115 | print '\nERR: Need date or input file to proceed'
116 | self.input_options = []
117 | self.show_help = True
118 |
119 | def no_parameters(self):
120 | print '\nERR: Need more than one parameter'
121 | self.input_options = []
122 | self.show_help = True
123 |
124 | def reference_file(self):
125 | print('Alternative config file specified')
126 | file_reference = None
127 | try: self.input_options[2]
128 | except:
129 | self.input_options.remove(self.input_options[1])
130 | else:
131 | if self.options.get(self.input_options[2], lambda : None)():
132 | self.input_options.remove(self.input_options[1])
133 | else:
134 | file_reference=self.input_options[2]
135 | self.input_options.remove(self.input_options[2])
136 | self.input_options.remove(self.input_options[1])
137 | return(file_reference)
138 |
139 | def input_file(self):
140 | # Input could be a list of files, comma separated
141 | try: self.input_options[2]
142 | except: self.need_date_or_file()
143 | else:
144 | if self.options.get(self.input_options[2], lambda : None)():
145 | self.need_date_or_file()
146 | else:
147 | iterate = True
148 | list_files = []
149 | while(iterate == True):
150 | list_files.append(self.input_options[2])
151 | self.input_options.remove(self.input_options[2])
152 | try:
153 | assert(self.options.get(self.input_options[2], lambda : None)()==True)
154 | except:
155 | iterate=False
156 |
157 | self.input_options.remove(self.input_options[1])
158 | return list_files
159 |
160 | def output_file(self):
161 | # If output is not disabled, then show on screen or save to file
162 | try: self.input_options[2]
163 | except:
164 | self.input_options.remove(self.input_options[1])
165 | return('screen')
166 | else:
167 | if self.options.get(self.input_options[2], lambda : None)():
168 | self.input_options.remove(self.input_options[1])
169 | file_output='screen'
170 | else:
171 | file_output=self.input_options[2]
172 | self.input_options.remove(self.input_options[2])
173 | self.input_options.remove(self.input_options[1])
174 | if not os.path.exists(file_output):
175 | os.makedirs(file_output)
176 | return(file_output)
177 |
178 | def input_date(self):
179 | # Format input date (takes into account short format)
180 | # Date should be something like
181 | # YYYY-MM-DD (long format, complete), YY-M-D (short format, complete),
182 | # YYYY-MM (lf, entire month), YY (sf, entire year) ...
183 | try: self.input_options[2]
184 | except: self.need_date_or_file()
185 | else:
186 | try: date=self.input_options[2].split("-")
187 | except:
188 | self.need_date_or_file()
189 | else:
190 | if self.input_options[2]=='todo' or self.input_options[2]=="todo":
191 | years=range(2010,2012+1,1)
192 | months=range(1,12+1,1)
193 | else:
194 | if len(date)==1:
195 | years=[int(date[0])]
196 | months=range(1,12+1,1)
197 | elif len(date)==2:
198 | years=[int(date[0])]
199 | months=[int(date[1])]
200 | elif len(date)==3:
201 | years=[int(date[0])]
202 | months=[int(date[1])]
203 | days=[int(date[2])]
204 | else:
205 | self.need_date_or_file()
206 | days_month={1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31}
207 | dates=[]
208 | today_yearmonthday=str(datetime.now()).split(' ')[0].split('-')
209 | for year in years:
210 | # Short format processing
211 | year_ = year%2000 + 2000
212 | for month in months:
213 | try: days
214 | except:
215 | # Leap year?
216 | if month==2 and (year_%4==0 and ((year_ %100!=0) or (year_%400==0))):
217 | days=range(1,29+1,1)
218 | else:
219 | days=range(1,days_month[month]+1,1)
220 |
221 | for day in days:
222 | use_date=True
223 | if year_>int(today_yearmonthday[0]):
224 | use_date=False
225 | elif year_==int(today_yearmonthday[0]):
226 | if month>int(today_yearmonthday[1]):
227 | use_date=False
228 | elif month==int(today_yearmonthday[1]):
229 | if day>=int(today_yearmonthday[2]):
230 | use_date=False
231 | if use_date==True:
232 | dates.append([year_,month,day])
233 | self.input_options.remove(self.input_options[2]); self.input_options.remove(self.input_options[1])
234 | return dates
235 |
236 |
237 |
238 |
--------------------------------------------------------------------------------
/pyasb/jpeg2fits.py:
--------------------------------------------------------------------------------
1 | #!/bin/env python2
2 |
3 | import os,sys
4 | import os.path
5 | import astropy.io.fits as pyfits
6 | import numpy as np
7 | from PIL import Image
8 | import datetime
9 |
10 | def modification_date(filename):
11 | t = os.path.getmtime(filename)
12 | dt = datetime.datetime.fromtimestamp(t)
13 | return(dt.strftime("%Y%m%d_%H%M%S")
14 |
15 | fullpath=sys.argv[1]
16 | filepath=os.path.dirname(os.path.abspath(filename))
17 | filename=fullpath.split("/")[-1]
18 |
19 | img = Image.open(fullpath)
20 | array = np.array(img)
21 |
22 | header = pyfits.Header()
23 | header['SIMPLE'] = 'T'
24 | header['BITPIX'] = '8'
25 | header['NAXIS'] = '2'
26 | header['NAXIS1'] = array.shape[1]
27 | header['NAXIS2'] = array.shape[0]
28 | header['BZERO'] = 0
29 | header['BSCALE'] = 1
30 | header['DATAMIN'] = 0
31 | header['DATAMAX'] = 255
32 | header['EXPOSURE'] = 1
33 | header['FILTER'] = 'Johnson_V'
34 | header['DATE'] = modification_date(fullpath)
35 | header.add_historhy('This file was created with Python/PIL/numpy/astropy')
36 | header.add_comment('Created by M. Nievas-Rosillo ')
37 |
38 | pyfits.writeto(fullpath+'.fits',data=array,header=header,overwrite=True)
39 |
40 |
--------------------------------------------------------------------------------
/pyasb/load_fitsimage.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Load FITS image and header
5 |
6 | This module loads the AllSky FITS image and returns both
7 | the Image binary data and the plain-text header.
8 | ____________________________
9 |
10 | This module is part of the PyASB project,
11 | created and maintained by Mireia Nievas [UCM].
12 | ____________________________
13 | '''
14 |
15 | __author__ = "Mireia Nievas"
16 | __copyright__ = "Copyright 2012, PyASB project"
17 | __credits__ = ["Mireia Nievas"]
18 | __license__ = "GNU GPL v3"
19 | __shortname__ = "PyASB"
20 | __longname__ = "Python All-Sky Brightness pipeline"
21 | __version__ = "1.99.0"
22 | __maintainer__ = "Mireia Nievas"
23 | __email__ = "mirph4k[at]gmail[dot]com"
24 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
25 |
26 | try:
27 | import sys,os,inspect
28 | import numpy as np
29 | import astropy.io.fits as pyfits
30 | from astrometry import ImageCoordinates
31 | from read_config import *
32 | except:
33 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
34 | raise SystemExit
35 |
36 | class ImageTest():
37 | '''Perform some test on the image header and extract information'''
38 |
39 | @staticmethod
40 | def correct_exposure(file_header):
41 | # Exposure
42 | try: texp = float(file_header['EXPOSURE'])
43 | except:
44 | print(inspect.stack()[0][2:4][::-1])
45 | raise
46 | else:
47 | #assert texp>0., '0s exposure time detected.'
48 | return texp
49 |
50 | @staticmethod
51 | def correct_date(file_header):
52 | # Date and time
53 | try: date = file_header['DATE']
54 | except:
55 | print(inspect.stack()[0][2:4][::-1])
56 | raise
57 | else:
58 | assert len(date)==15 and date[8]=="_", 'Date format not YYYYMMDD_HHMMSS'
59 | return date
60 |
61 | @staticmethod
62 | def correct_resolution(file_header):
63 | # Resolution
64 | try: resolution = [int(file_header['NAXIS1']),int(file_header['NAXIS2'])]
65 | except:
66 | print(inspect.stack()[0][2:4][::-1])
67 | raise
68 | else:
69 | assert resolution[0]>0 and resolution[1]>0, 'Matrix not 2 dimensional'
70 | return resolution
71 |
72 | @staticmethod
73 | def correct_filter(file_header):
74 | # Test if there's a known filter
75 | try: used_filter = file_header['FILTER']
76 | except:
77 | print(inspect.stack()[0][2:4][::-1])
78 | raise
79 | else:
80 | # due to an inconsistent format in AstMon,
81 | # we found 4 possible formats 'Jonhson_V','JohnsonV','Johnson_V','JonhsonV'
82 | used_filter = used_filter.replace('_','')
83 | assert used_filter[0:7] in ['Johnson','Jonhson'], 'Filter type not Johnson'
84 | assert used_filter[7:] in ['U','B','V','R','I','common'], 'Filter not U,B,V,R or I'
85 | return 'Johnson_'+used_filter[7:]
86 |
87 |
88 | class FitsImage(ImageTest):
89 | def __init__(self,input_file):
90 | self.load_science(input_file)
91 | # Backup original data
92 | print('Backup original (non-calibrated) data')
93 | self.fits_data_notcalibrated = np.array(self.fits_data)
94 |
95 | def load_science(self,input_file):
96 | print('Loading ScienceFrame ['+str(input_file)+'] ...'),
97 | try:
98 | file_opened = pyfits.open(input_file)
99 | self.fits_data = file_opened[0].data
100 | self.fits_Header = file_opened[0].header
101 | self.fits_Texp = float(ImageTest.correct_exposure(self.fits_Header))
102 | except:
103 | print(inspect.stack()[0][2:4][::-1])
104 | raise
105 | else:
106 | print('OK')
107 |
108 | def load_mask(self,Mask):
109 | print('Loading Mask ...'),
110 | try:
111 | Mask_HDU = pyfits.open(Mask)
112 | self.mask = Mask_HDU[0].data
113 | except:
114 | print(inspect.stack()[0][2:4][::-1])
115 | #raise
116 | else: print('OK')
117 |
118 | def load_dark(self,MasterDark):
119 | print('Loading MasterDark ...'),
120 | try:
121 | MasterDark_HDU = pyfits.open(MasterDark)
122 | self.MasterDark_Data = MasterDark_HDU[0].data
123 | self.MasterDark_Header = MasterDark_HDU[0].header
124 | self.MasterDark_Texp = float(ImageTest.correct_exposure(self.MasterDark_Header))
125 | except:
126 | print(inspect.stack()[0][2:4][::-1])
127 | raise
128 | else: print('OK')
129 |
130 | def load_flat(self,MasterFlat):
131 | print('Loading MasterFlat ...'),
132 | try:
133 | MasterFlat_HDU = pyfits.open(MasterFlat)
134 | self.MasterFlat_Data = MasterFlat_HDU[0].data
135 | # Normalize MasterFlat
136 | self.MasterFlat_Data = self.MasterFlat_Data / np.mean(self.MasterFlat_Data)
137 | self.MasterFlat_Header = MasterFlat_HDU[0].header
138 | self.MasterFlat_Texp = float(ImageTest.correct_exposure(self.MasterFlat_Header))
139 | except:
140 | print(inspect.stack()[0][2:4][::-1])
141 | raise
142 | else: print('OK')
143 |
144 | def load_bias(self,MasterBias):
145 | print('Loading MasterBias ...'),
146 | try:
147 | MasterBias_HDU = pyfits.open(MasterBias)
148 | self.MasterBias_Data = MasterBias_HDU[0].data
149 | self.MasterBias_Header = MasterBias_HDU[0].header
150 | self.MasterBias_Texp = float(ImageTest.correct_exposure(self.MasterBias_Header))
151 | except:
152 | print(inspect.stack()[0][2:4][::-1])
153 | raise
154 | else: print('OK')
155 |
156 |
157 |
158 | def reduce_science_frame(self,MasterDark=None,MasterFlat=None,MasterBias=None,Mask=None,ImageInfo=None):
159 | '''
160 | Load MasterDark and MasterFlat. MasterBias is neccesary only if working
161 | with different exposures between Dark and Science frames
162 | '''
163 |
164 | skip_dark = False
165 | skip_flat = False
166 |
167 | ### Load FLAT Field
168 | try:
169 | self.load_flat(MasterFlat)
170 | except:
171 | print(str(inspect.stack()[0][2:4][::-1])+\
172 | ' WARNING: MasterFlat cannot be loaded, SKIP the flat calibration')
173 | skip_flat = True
174 | else:
175 | skip_flat = False
176 |
177 | try:
178 | self.load_mask(Mask)
179 | except:
180 | print(str(inspect.stack()[0][2:4][::-1])+\
181 | ' WARNING: Mask cannot be loaded. Using entire image.')
182 | skip_mask = True
183 | else:
184 | skip_mask = False
185 |
186 | ### Load DARK Frame
187 | try:
188 | self.load_dark(MasterDark)
189 | except:
190 | ''' Try to use MasterDark as a fixed offset value '''
191 | try:
192 | self.SyntDark_Data = float(MasterDark)
193 | except:
194 | #raise
195 | print(str(inspect.stack()[0][2:4][::-1])+\
196 | ' WARNING: MasterDark cannot be loaded, SKIP the dark calibration')
197 | skip_dark = True
198 | else:
199 | print(str(inspect.stack()[0][2:4][::-1])+\
200 | ' WARNING: MasterDark used as a fixed offset value.\n'+\
201 | ' Its *STRONGLY* recommended to use a proper MasterDark')
202 | skip_dark = False
203 | else:
204 | if self.MasterDark_Texp == self.fits_Texp:
205 | self.SyntDark_Data = self.MasterDark_Data
206 | self.SyntDark_Texp = self.MasterDark_Texp
207 | self.SyntDark_Header = self.MasterDark_Header
208 | elif self.MasterDark_Texp != self.fits_Texp and MasterBias==None:
209 | if MasterBias==None:
210 | print("WARNING: Science and Dark don't have the same exposure ! ")
211 | print('Science_Texp='+str(self.fits_Texp)+'; Dark_Texp='+str(self.MasterDark_Texp))
212 | self.SyntDark_Data = self.MasterDark_Data
213 | self.SyntDark_Texp = self.MasterDark_Texp
214 | self.SyntDark_Header = self.MasterDark_Header
215 | elif self.MasterDark_Texp != self.fits_Texp and MasterBias!=None:
216 | self.load_bias(MasterBias)
217 | print('Creating synthetic Dark ...'),
218 | try:
219 | self.SyntDark_Data = (self.MasterDark_Data-self.MasterBias_Data)/ \
220 | (self.MasterDark_Texp-self.MasterBias_Data) *\
221 | (self.ScienceFrame_Texp-self.MasterBias_Texp)+\
222 | self.MasterBias_Data
223 | self.SyntDark_Texp = self.fits_Texp
224 | self.SyntDark_Header = self.MasterDark_Header
225 | self.SyntDark_Header['EXPOSURE'] = self.SyntDark_Texp
226 | except:
227 | print(inspect.stack()[0][2:4][::-1])
228 | raise
229 | else: print('OK')
230 |
231 | skip_dark = False
232 |
233 | print('Calibrating image with MasterFlat and MasterDark ...'),
234 |
235 | # Subtract dark frame
236 | if skip_dark == False:
237 | self.fits_data = self.fits_data - self.SyntDark_Data
238 |
239 | # Subtract background / bias (measure it in the non-illuminated corners of the image).
240 | try: assert(self.subtract_corners_background == True and ImageInfo!=None)
241 | except: pass
242 | else:
243 | ImageCoordinates_ = ImageCoordinates(ImageInfo)
244 | data_corners = self.fits_data[ImageCoordinates_.altitude_map<-20]
245 | self.bias_image_median = np.median(data_corners)
246 | self.bias_image_std = np.std(data_corners)
247 | self.bias_image_err = self.bias_image_std/np.sqrt(np.size(data_corners))
248 | if np.isfinite(self.bias_image_median):
249 | self.fits_data = self.fits_data-self.bias_image_median
250 | print("Removed: %.2f +/- %.2f counts from measured background" \
251 | %(self.bias_image_median,self.bias_image_err))
252 |
253 | if ImageInfo.summary_path not in [ False, "False", "false", "F", "screen" ]:
254 | if not os.path.exists(ImageInfo.summary_path):
255 | os.makedirs(ImageInfo.summary_path)
256 | measured_bias_log = open(ImageInfo.summary_path+'/measured_image_bias.txt','a+')
257 | text_to_log = str(ImageInfo.date_string)+','+str(ImageInfo.used_filter)+','+\
258 | str(self.bias_image_median)+','+str(self.bias_image_err)+'\r\n'
259 | measured_bias_log.write(text_to_log)
260 | measured_bias_log.close()
261 |
262 | # Flat field correction
263 | if skip_flat == False:
264 | # Skip flat correction for points with <10% illumination?
265 | #self.MasterFlat_Data[self.MasterFlat_Data=limits['alt_min'])*\
105 | np.array(ImageCoordinates.altitude_map=limits['az_min'])*\
108 | np.array(ImageCoordinates.azimuth_map=limits['az_min']+360)+\
111 | np.array(ImageCoordinates.azimuth_map=90-zenith_acceptance]
178 | self.SBzenith,self.SBzenith_err = \
179 | self.sky_brightness_region(BouguerFit,ImageInfo,fits_zenith_region_values,limits)
180 |
181 |
182 | class SkyBrightnessGraph():
183 | def __init__(self,SkyBrightness,ImageInfo,BouguerFit):
184 | if ImageInfo.skybrightness_map_path==False:
185 | # Don't draw anything
186 | print('Skipping SkyBrightness Graph ...')
187 | return(None)
188 | else:
189 | print('Generating Sky Brightness Map ...')
190 |
191 | self.create_plot()
192 | self.plot_labels(SkyBrightness,ImageInfo,BouguerFit)
193 | self.define_contours(ImageInfo)
194 | self.ticks_and_locators()
195 | self.grid_data(SkyBrightness)
196 | self.plot_data()
197 | self.color_bar()
198 | self.show_map(ImageInfo)
199 |
200 | def create_plot(self):
201 | ''' Create the figure (empty) with matplotlib '''
202 | self.SBfigure = plt.figure(figsize=(8,7.5))
203 | self.SBgraph = self.SBfigure.add_subplot(111,projection='polar')
204 |
205 | def grid_data(self,SkyBrightness):
206 | # Griddata.
207 | self.AZgridi,self.ZDgridi = np.mgrid[0:2*np.pi:1000j, 0:75:1000j]
208 | self.ALTgridi = 90. - self.ZDgridi
209 |
210 | coord_reshape = [[SkyBrightness.AZgrid[j][k],SkyBrightness.ZDgrid[j][k]] \
211 | for k in xrange(len(SkyBrightness.AZgrid[0])) \
212 | for j in xrange(len(SkyBrightness.AZgrid))]
213 |
214 | data_reshape = [ SkyBrightness.SBgrid[j][k] \
215 | for k in xrange(len(SkyBrightness.AZgrid[0])) \
216 | for j in xrange(len(SkyBrightness.AZgrid))]
217 |
218 | self.SBgridi = sint.griddata(coord_reshape,data_reshape, \
219 | (self.AZgridi,self.ZDgridi), method='linear')
220 |
221 | def plot_data(self):
222 | ''' Returns the graph with data plotted.'''
223 | self.SBcontoursf = self.SBgraph.contourf(\
224 | self.AZgridi, self.ZDgridi, self.SBgridi, cmap=plt.cm.YlGnBu,levels=self.level_list)
225 | self.SBcontours = self.SBgraph.contour(\
226 | self.AZgridi, self.ZDgridi, self.SBgridi,
227 | colors='k',alpha=0.3,levels=self.coarse_level_list)
228 | self.SBcontlabel = self.SBgraph.clabel(self.SBcontours,inline=True,fmt='%.1f',fontsize=10)
229 | # Limit radius
230 | self.SBgraph.set_ylim(0,75)
231 |
232 | def plot_labels(self,SkyBrightness,ImageInfo,BouguerFit):
233 | ''' Set the figure title and add extra information (annotation) '''
234 | # Image title
235 | self.SBgraph.text(0,90, unicode(ImageInfo.backgroundmap_title,'utf-8'),\
236 | horizontalalignment='center',size='xx-large')
237 |
238 | # Image information
239 | image_information = str(ImageInfo.date_string)+" UTC\n"+str(ImageInfo.latitude)+5*" "+\
240 | str(ImageInfo.longitude)+"\n"+ImageInfo.used_filter+4*" "+\
241 | "K="+str("%.3f" % float(BouguerFit.Regression.extinction))+"+-"+\
242 | str("%.3f" % float(BouguerFit.Regression.error_extinction))+"\n"+\
243 | "SB="+str("%.2f" % float(SkyBrightness.SBzenith))+"+-"+\
244 | str("%.2f" % float(SkyBrightness.SBzenith_err))+" mag/arcsec2 (zenith)"
245 |
246 | self.SBgraph.text(5*np.pi/4,125,unicode(image_information,'utf-8'),fontsize='x-small')
247 |
248 | def define_contours(self,ImageInfo):
249 | ''' Calculate optimal contours for pyplot.contour and pyplot.contourf '''
250 |
251 | _min_ = float(ImageInfo.background_levels[ImageInfo.used_filter][0])
252 | _max_ = float(ImageInfo.background_levels[ImageInfo.used_filter][1])
253 |
254 | sval = 0.1
255 | def create_ticks(_min_,_max_,sval):
256 | return np.arange(_min_,_max_+sval/10.,sval)
257 |
258 | self.level_list = create_ticks(_min_,_max_,0.1)
259 | self.label_list = create_ticks(_min_,_max_,sval)
260 | self.coarse_level_list = create_ticks(_min_,_max_,0.2)
261 |
262 | while len(self.label_list)>15:
263 | sval = sval*2.
264 | self.label_list = create_ticks(_min_,_max_,sval)
265 |
266 | if len(self.level_list)<3: self.update_ticks=False
267 | else: self.update_ticks=True
268 |
269 | def ticks_and_locators(self):
270 | ''' Add ticks to the graph '''
271 | radial_locator = np.arange(10,90+1,10)
272 | radial_label = ["$80$","$70$","$60$","$50$","$40$","$30$","$20$","$10$","$0$"]
273 | theta_locator = np.arange(0,360,45)
274 | theta_label = ["$N$","$NE$","$E$","$SE$","$S$","$SW$","$W$","$NW$"]
275 | self.SBgraph.set_rgrids(radial_locator,radial_label,size="large",color='k',alpha=0.75)
276 | self.SBgraph.set_thetagrids(theta_locator,theta_label,size="large")
277 | # rotate the graph (North up)
278 | self.SBgraph.set_theta_direction(-1)
279 | self.SBgraph.set_theta_offset(np.pi/2)
280 |
281 | def color_bar(self):
282 | ''' Add the colorbar '''
283 | # Separation between colour bar and graph
284 | self.SBfigure.subplots_adjust(right=1)
285 | # Color bar
286 | self.SBcolorbar = plt.colorbar(self.SBcontoursf,orientation='vertical',pad=0.07,shrink=0.75)
287 | self.SBfigure.subplots_adjust(right=0.80) # Restore separation
288 | self.SBcolorbar.set_ticks(self.label_list,update_ticks=self.update_ticks)
289 | self.SBcolorbar.set_label("mag/arcsec2",rotation="vertical",size="large")
290 |
291 | def show_map(self,ImageInfo):
292 | if ImageInfo.skybrightness_map_path=="screen":
293 | plt.show()
294 | else:
295 | skybrightness_filename = str("%s/SkyBrightnessMap_%s_%s_%s.png" %(\
296 | ImageInfo.skybrightness_map_path, ImageInfo.obs_name,\
297 | ImageInfo.fits_date, ImageInfo.used_filter))
298 | plt.tight_layout(pad=-1.5,rect=[0.1,0.05,1.,0.95])
299 | plt.savefig(skybrightness_filename)
300 |
301 | #plt.clf()
302 | #plt.close('all')
303 |
304 |
305 |
--------------------------------------------------------------------------------
/pyasb/skymap_plot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | SkyMap module
5 |
6 | Auxiliary functions to plot the SkyMap
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 |
26 | try:
27 | import sys,os,inspect
28 | from astrometry import *
29 | from scipy.ndimage import uniform_filter
30 | from scipy.ndimage import median_filter
31 | import numpy as np
32 | import math
33 | import matplotlib.pyplot as plt
34 | import matplotlib.colors as mpc
35 | import matplotlib.patches as mpp
36 | import matplotlib as mpl
37 | except:
38 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
39 | raise SystemExit
40 |
41 | class SkyMap():
42 | ''' SkyMap class '''
43 |
44 | def __init__(self,ImageInfo,FitsImage):
45 | # Set ImageInfo as local sub-object, we will use
46 | # it a lot.
47 | self.ImageInfo = ImageInfo
48 |
49 | if self.ImageInfo.skymap_path==False:
50 | # Don't draw anything
51 | print('Skipping Skymap Graph ...')
52 | else:
53 | print('Star Map plot ...')
54 | bitpix = ImageInfo.ccd_bits
55 | self.stretch_data(\
56 | FitsImage.fits_data_notcalibrated*1./2**bitpix,\
57 | ImageInfo.perc_low,\
58 | ImageInfo.perc_high)
59 | #self.setup_skymap()
60 | #self.draw_catalog_stars()
61 | #self.draw_detected_stars()
62 | #self.astrometry_solver()
63 | #self.draw_polar_axes()
64 | #self.show_figure()
65 |
66 | def setup_skymap(self):
67 | '''
68 | To be executed at the beginning (no stars)
69 | '''
70 | if (self.ImageInfo.skymap_path!=False):
71 | self.define_skymap()
72 | self.draw_skymap_data()
73 | self.skyfigure.canvas.draw()
74 | self.skyfigure.canvas.flush_events()
75 |
76 | if (self.ImageInfo.skymap_path=="screen"):
77 | plt.show(block=False)
78 |
79 | def complete_skymap(self):
80 | '''
81 | To be executed when an astrometric solution is found
82 | '''
83 | if (self.ImageInfo.skymap_path!=False):
84 | self.draw_catalog_stars()
85 | self.draw_detected_stars()
86 | self.draw_polar_axes()
87 | self.skyfigure.canvas.draw()
88 | self.skyfigure.canvas.flush_events()
89 | self.show_figure()
90 |
91 | def set_starcatalog(self,StarCatalog):
92 | self.StarCatalog = StarCatalog
93 |
94 | def draw_catalog_stars(self):
95 | for Star in self.StarCatalog.StarList_Tot:
96 | self.draw_annotate_star(Star, type=0)
97 |
98 | def draw_detected_stars(self):
99 | for Star in self.StarCatalog.StarList_Det:
100 | self.draw_annotate_star(Star, type=1)
101 | for Star in self.StarCatalog.StarList_Phot:
102 | self.draw_annotate_star(Star, type=2)
103 |
104 | def stretch_data(self,fits_data,pmin,pmax):
105 | #log_fits_data = np.log(fits_data-np.min(fits_data)+1,dtype="float32")
106 | # some statistics: min, max, mean
107 | print(np.min(fits_data),np.max(fits_data),np.mean(fits_data))
108 | #fits_data = median_filter(fits_data, 3)
109 | fits_data = uniform_filter(fits_data, 5)
110 | log_fits_data = np.arcsinh(fits_data-np.min(fits_data)+1.,dtype="float32")
111 | valuemin = np.percentile(log_fits_data,pmin)
112 | valuemax = np.percentile(log_fits_data,pmax)
113 | self.stretched_fits_data = log_fits_data.clip(valuemin,valuemax)
114 |
115 | def define_skymap(self):
116 | ''' Create figure and self.skyimage subplot. '''
117 | self.skyfigure = plt.figure(figsize=(8,8))
118 | self.skyimage = self.skyfigure.add_subplot(111)
119 | self.skyfigure.canvas.draw()#(block=False)
120 |
121 | def mouse_press_callback(self,event):
122 | ''' Coordinate input '''
123 | if event.button == 3:
124 | ix, iy = event.xdata, event.ydata
125 | print('x = %d, y = %d'%(ix, iy))
126 | self.identified_stars.append([self.name,self.azim,self.alti,ix,iy])
127 | self.star_index +=1
128 | self.astrometry_optimizer(full=(self.star_index>3))
129 | self.scatter_stars.append(\
130 | self.skyimage.scatter(ix,iy,marker='o',c='red',alpha=0.2))
131 | self.label_stars.append(\
132 | self.skyimage.annotate(\
133 | self.name,xy=(ix,iy), \
134 | xycoords='data',xytext=(0, 3),\
135 | textcoords='offset points',fontsize=8,alpha=0.8))
136 |
137 | self.name=self.StarCatalog.StarList_Tot[self.star_index].name
138 | self.azim=self.StarCatalog.StarList_Tot[self.star_index].azimuth
139 | self.alti=self.StarCatalog.StarList_Tot[self.star_index].altit_real
140 | px,py = horiz2xy(self.azim,self.alti,self.ImageInfo,derotate=True)
141 |
142 | try: self.preliminary_star.remove()
143 | except: pass
144 |
145 | self.preliminary_star = \
146 | self.skyimage.scatter(px,py,marker='o',c='yellow',alpha=0.5)
147 | print('Name: %s, Az: %s, Alt: %s' %(self.name,self.azim,self.alti))
148 | self.skyfigure.canvas.draw()
149 | self.skyfigure.canvas.flush_events()
150 |
151 | return(None)
152 |
153 | def key_press_callback(self, event):
154 | 'whenever a key is pressed'
155 | if not event.inaxes: return(None)
156 |
157 | if event.key=='n':
158 | print('Next star')
159 | self.star_index += 1
160 | self.name=self.StarCatalog.StarList_Tot[self.star_index].name
161 | self.azim=self.StarCatalog.StarList_Tot[self.star_index].azimuth
162 | self.alti=self.StarCatalog.StarList_Tot[self.star_index].altit_real
163 | px,py = horiz2xy(self.azim,self.alti,self.ImageInfo,derotate=True)
164 | self.preliminary_star.remove()
165 | self.preliminary_star = \
166 | self.skyimage.scatter(px,py,marker='o',c='yellow',alpha=0.5)
167 | print('Name: %s, Az: %s, Alt: %s' %(self.name,self.azim,self.alti))
168 | elif event.key=='p':
169 | print('Previous star')
170 | self.preliminary_star.remove()
171 | self.scatter_stars[-1].remove()
172 | self.label_stars[-1].remove()
173 | self.scatter_stars.pop()
174 | self.label_stars.pop()
175 | self.identified_stars.pop()
176 | self.star_index -= 1
177 | self.name=self.StarCatalog.StarList_Tot[self.star_index].name
178 | self.azim=self.StarCatalog.StarList_Tot[self.star_index].azimuth
179 | self.alti=self.StarCatalog.StarList_Tot[self.star_index].altit_real
180 | px,py = horiz2xy(self.azim,self.alti,self.ImageInfo,derotate=True)
181 | self.preliminary_star = \
182 | self.skyimage.scatter(px,py,marker='o',c='yellow',alpha=0.5)
183 | self.skyfigure.canvas.draw()
184 | self.skyfigure.canvas.flush_events()
185 | print('Name: %s, Az: %s, Alt: %s' %(self.name,self.azim,self.alti))
186 |
187 | if event.key=='q':
188 | print('End')
189 | self.skyfigure.canvas.mpl_disconnect(self.cid_mouse)
190 | self.skyfigure.canvas.mpl_disconnect(self.cid_keyboard)
191 | print(self.identified_stars)
192 | plt.close()
193 | self.astrometry_optimizer(full=(self.star_index>5))
194 |
195 | return(None)
196 |
197 | def astrometry_optimizer(self,full=True):
198 | from scipy.optimize import minimize
199 | from astrometry import horiz2xy
200 |
201 | def horiz2xy_chi2(sol,az,alt,x,y):
202 | self.ImageInfo.radial_factor = sol[0]
203 | self.ImageInfo.azimuth_zeropoint = sol[1]
204 | if (full==True):
205 | self.ImageInfo.delta_x = sol[2]
206 | self.ImageInfo.delta_y = sol[3]
207 | self.ImageInfo.latitude_offset = sol[4]
208 | self.ImageInfo.longitude_offset = sol[5]
209 | else:
210 | self.ImageInfo.delta_x = 0
211 | self.ImageInfo.delta_y = 0
212 | self.ImageInfo.latitude_offset = 0
213 | self.ImageInfo.longitude_offset = 0
214 |
215 | xf,yf = horiz2xy(az,alt,self.ImageInfo,derotate=True)
216 | return(np.sum((xf-x)**2 + (yf-y)**2))
217 |
218 | coords = np.array(self.identified_stars)[:,1:] # Remove star name
219 | coords = np.array(coords,dtype=float) # Convert to float
220 | [_az,_alt,_x,_y] = np.transpose(coords) # Transpose and split
221 | print('Solving equation system')
222 |
223 | if (full==True):
224 | initial=[10,0,0,0,0,0]
225 | else:
226 | initial=[0,0]
227 |
228 | res = minimize(horiz2xy_chi2,initial,args = (_az,_alt,_x,_y),tol=1e-3)
229 |
230 | # Fix negative radial factor
231 | if (res.x[0]<0):
232 | res.x[0] = -res.x[0]
233 | res.x[1] = 180-res.x[1]
234 |
235 | print("Parameters (radial_factor, azimuth_zeropoint, delta_x, delta_y, lat_offset, lon_offset): ")
236 | print(res.x)
237 | print("Score [sum(dev^2)] = %.3f" %horiz2xy_chi2(res.x,_az,_alt,_x,_y))
238 | print("Success: %s" %res.success)
239 |
240 | def astrometry_solver(self):
241 | print(\
242 | '*** Star select tool. Press right-click to begin. *** \n'+\
243 | 'Right-click: assign star coords. \n'+\
244 | 'n: next star (skip current). \n'+\
245 | 'p: previous star (remove last entry). \n'+\
246 | 'q: quit star select tool. \n')
247 |
248 | self.identified_stars = []
249 | self.scatter_stars = []
250 | self.label_stars = []
251 | self.star_index = 0
252 | self.completed=0
253 |
254 | # For the northern hemisphere, put Polaris as the first star
255 | try:
256 | assert(self.ImageInfo.latitude>0)
257 | polaris_index=[Star.HDcode for Star in self.StarCatalog.StarList_Tot].index("HD8890")
258 | AuxStar = self.StarCatalog.StarList_Tot[polaris_index]
259 | self.StarCatalog.StarList_Tot[polaris_index] = self.StarCatalog.StarList_Tot[0]
260 | self.StarCatalog.StarList_Tot[0] = AuxStar
261 | except:
262 | pass
263 |
264 | self.name=self.StarCatalog.StarList_Tot[0].name
265 | self.azim=self.StarCatalog.StarList_Tot[0].azimuth
266 | self.alti=self.StarCatalog.StarList_Tot[0].altit_real
267 | print('Name: %s, Az: %s, Alt: %s' %(self.name,self.azim,self.alti))
268 |
269 | self.cid_mouse = self.skyfigure.canvas.mpl_connect('button_press_event', self.mouse_press_callback)
270 | self.cid_keyboard = self.skyfigure.canvas.mpl_connect('key_press_event', self.key_press_callback)
271 | plt.show(block=True)
272 |
273 | def draw_skymap_data(self):
274 | ''' Draw image '''
275 | self.skyimage.imshow(self.stretched_fits_data,cmap=mpl.cm.gray)
276 |
277 | self.skyimage.axis([0,self.ImageInfo.resolution[0],0,self.ImageInfo.resolution[1]])
278 | information=str(self.ImageInfo.date_string)+" UTC\n"+str(self.ImageInfo.latitude)+5*" "+\
279 | str(self.ImageInfo.longitude)+"\n"+self.ImageInfo.used_filter
280 |
281 | self.skyimage.text(0.010,0.010,information,fontsize='small',color='white',\
282 | transform = self.skyimage.transAxes,backgroundcolor=(0,0,0,0.75))
283 |
284 | plt.draw()
285 |
286 | def draw_polar_axes(self):
287 | ''' Draws meridian and altitude isolines. '''
288 |
289 | zenith_xy = zenith_position(self.ImageInfo)
290 |
291 | for each_altitude in np.arange(0,90,15):
292 | coord_altitude_0 = horiz2xy(0,each_altitude,self.ImageInfo)
293 | radius = math.sqrt(\
294 | (coord_altitude_0[0]-zenith_xy[0])**2 +\
295 | (coord_altitude_0[1]-zenith_xy[1])**2)
296 | self.skyimage.add_patch(\
297 | mpp.Circle((zenith_xy[0],zenith_xy[1]),radius,
298 | facecolor='k',fill=False, alpha=0.2,label='_nolegend_'))
299 | self.skyimage.annotate(\
300 | str(each_altitude),
301 | xy=(radius+zenith_xy[0],zenith_xy[1]),
302 | alpha=0.2,
303 | fontsize=10)
304 |
305 | key_azimuths = {0: "N",90: "E", 180: "S", 270: "W"}
306 |
307 | for each_azimuth in np.arange(0,360,30):
308 | coord_azimuth_0 = horiz2xy(each_azimuth,0,self.ImageInfo)
309 | self.skyimage.plot(\
310 | [zenith_xy[0],coord_azimuth_0[0]],
311 | [zenith_xy[1],coord_azimuth_0[1]],
312 | color='k',
313 | alpha=0.2,)
314 |
315 | if each_azimuth in key_azimuths:
316 | azimuth_label = str(key_azimuths[each_azimuth])
317 | else:
318 | azimuth_label = str(each_azimuth)
319 | self.skyimage.annotate(\
320 | azimuth_label,
321 | xy=horiz2xy(each_azimuth,self.ImageInfo.min_altitude,self.ImageInfo),
322 | color='k',
323 | alpha=0.2,
324 | fontsize=10)
325 |
326 | def draw_annotate_star(self,Star,type=0):
327 | # Draw identified stars and measuring circles.
328 | # Annotate HD catalog code and Magnitude for each star.
329 |
330 | if(type==0):
331 | self.skyimage.scatter(Star.Xcoord,Star.Ycoord,\
332 | marker='+',c='red',alpha=0.2,label='Catalog')
333 | self.skyimage.annotate(\
334 | Star.name,xy=(Star.Xcoord,Star.Ycoord), \
335 | xycoords='data',xytext=(0, 3),\
336 | textcoords='offset points',fontsize=8,alpha=0.8)
337 | elif(type==1):
338 | self.skyimage.add_patch(mpp.Circle(\
339 | (Star.Xcoord,Star.Ycoord),Star.R1,\
340 | facecolor='none',edgecolor=(0,0,0.8),\
341 | linewidth=1, fill=False, alpha=0.5,\
342 | label='Detected'))
343 | elif(type==2):
344 | self.skyimage.add_patch(mpp.Circle(\
345 | (Star.Xcoord,Star.Ycoord),Star.R2,\
346 | facecolor='none',edgecolor=(0,0.8,0),\
347 | linewidth=1, fill=False, alpha=0.5,\
348 | label='Photometric'))
349 | self.skyimage.add_patch(mpp.Circle(\
350 | (Star.Xcoord,Star.Ycoord),Star.R3,\
351 | facecolor='none',edgecolor=(0.8,0,0),\
352 | linewidth=1, fill=False, alpha=0.5,\
353 | label='_nolegend_'))
354 | self.skyimage.annotate(Star.FilterMag,xy=(Star.Xcoord,Star.Ycoord), \
355 | xycoords='data',xytext=(0,-10),\
356 | textcoords='offset points',fontsize=8)
357 |
358 | def show_figure(self):
359 | #self.skyimage.legend(('Catalog','Detected','Photometric'),loc='upper right')
360 | if self.ImageInfo.skymap_path=="screen":
361 | plt.show()
362 | #self.skyfigure.canvas.draw()
363 | #self.skyfigure.canvas.flush_events()
364 | else:
365 | skymap_filename = str("%s/SkyMap_%s_%s_%s.png" %(\
366 | self.ImageInfo.skymap_path, self.ImageInfo.obs_name,\
367 | self.ImageInfo.fits_date, self.ImageInfo.used_filter))
368 |
369 | plt.tight_layout(pad=0)
370 | plt.savefig(skymap_filename)
371 |
372 | #plt.clf()
373 | #plt.close('all')
374 |
--------------------------------------------------------------------------------
/pyasb/star_calibration.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | Load Catalog file and make PyASB StarCatalog
5 |
6 | This module loads the catalog file and returns
7 | an array of Star objects (StarCatalog) with
8 | their fluxes.
9 |
10 | ____________________________
11 |
12 | This module is part of the PyASB project,
13 | created and maintained by Mireia Nievas [UCM].
14 | ____________________________
15 | '''
16 |
17 | DEBUG = False
18 |
19 | __author__ = "Mireia Nievas"
20 | __copyright__ = "Copyright 2012, PyASB project"
21 | __credits__ = ["Mireia Nievas"]
22 | __license__ = "GNU GPL v3"
23 | __shortname__ = "PyASB"
24 | __longname__ = "Python All-Sky Brightness pipeline"
25 | __version__ = "1.99.0"
26 | __maintainer__ = "Mireia Nievas"
27 | __email__ = "mirph4k[at]gmail[dot]com"
28 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
29 |
30 | try:
31 | import sys,os,inspect
32 | import ephem
33 | import scipy.stats
34 | import scipy.ndimage as ndimage
35 | import scipy.ndimage.filters as filters
36 | from astrometry import *
37 | from skymap_plot import *
38 | except:
39 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
40 | raise SystemExit
41 |
42 |
43 | def verbose(function, *args):
44 | '''
45 | Run a function in verbose mode
46 | '''
47 | try:
48 | out = function(*args)
49 | except:
50 | # Something happened while runing function
51 | #raise
52 | if DEBUG==True:
53 | print(str(inspect.stack()[0][2:4][::-1])+' Error')
54 | #raise
55 | else:
56 | return(out)
57 |
58 | class Star():
59 | def __init__(self,StarCatalogLine,ImageInfo):
60 | ''' Takes StarCatalogLine (line from catalog file) and
61 | FitsImage, ImageInfo and ObsPyephem objects
62 | Returns a Star object with photometric and astrometic properties
63 | or a destroy flag if errors ocurred during process'''
64 | self.destroy=False
65 | self.saturated=False
66 | self.cold_pixels=False
67 | self.masked=False
68 | self.to_be_masked=False
69 | self.camera_independent_astrometry(StarCatalogLine,ImageInfo)
70 |
71 | def camera_independent_astrometry(self,StarCatalogLine,ImageInfo):
72 | # Extract stars from Catalog
73 | self.verbose_detection(self.from_catalog,StarCatalogLine,\
74 | ImageInfo.used_filter,\
75 | errormsg=' Error extracting from catalog')
76 | # Estimate magnitude on the given image
77 | self.verbose_detection(self.magnitude_on_image,ImageInfo,\
78 | errormsg=' Error reading used filter or magnitude>max')
79 | # Astrometry for the current star (sky)
80 | self.verbose_detection(self.star_astrometry_sky,ImageInfo,\
81 | errormsg=' Error performing star astrometry (sky), Star not visible?')
82 |
83 | def camera_dependent_starpositions(self,FitsImage,ImageInfo):
84 | # Astrometry for the current star (image)
85 | self.verbose_detection(self.star_astrometry_image,ImageInfo,\
86 | errormsg=' Error performing star astrometry (image)')
87 | self.verbose_detection(self.star_astrometry_ismasked,FitsImage,\
88 | errormsg=' Star is in a masked region')
89 | # Estimate radius to do the aperture photometry
90 | self.verbose_detection(self.photometric_radius,ImageInfo,\
91 | errormsg=' Error generating photometric radius')
92 |
93 | def camera_dependent_regions(self,FitsImage,ImageInfo):
94 | # Create regions of stars and star+background
95 | self.verbose_detection(self.estimate_fits_region_star,FitsImage,\
96 | errormsg=' Cannot create the Star region')
97 | self.verbose_detection(self.estimate_fits_region_complete,FitsImage,\
98 | errormsg=' Cannot create the Star+Background region')
99 |
100 | def camera_dependent_detectpeaks(self,FitsImage,ImageInfo):
101 | self.verbose_detection(self.detect_peaks,FitsImage,\
102 | errormsg=' Cannot detect peaks')
103 |
104 | def camera_dependent_astrometry(self,FitsImage,ImageInfo):
105 | # Measure fluxes
106 | self.verbose_detection(self.measure_star_fluxes,FitsImage.fits_data,\
107 | errormsg=' Error measuring fluxes')
108 | # Estimate centroid
109 | self.verbose_detection(self.estimate_fits_region_centroid,\
110 | FitsImage,True,\
111 | errormsg=' Cannot create the Star+SecurityRing region')
112 | self.verbose_detection(self.estimate_centroid,\
113 | errormsg=' Star centroid calculated')
114 |
115 | def camera_dependent_photometry(self,FitsImage,ImageInfo):
116 | # Measure fluxes
117 | self.verbose_detection(self.measure_star_fluxes,FitsImage.fits_data,\
118 | errormsg=' Error measuring fluxes')
119 | # Check if star is detectable
120 | self.verbose_detection(self.star_is_detectable,ImageInfo,\
121 | errormsg=' Star is not detectable')
122 | # Check star saturation
123 | self.verbose_detection(self.star_is_saturated,ImageInfo,\
124 | errormsg=' Star is satured or has hot pixels')
125 | # Check cold pixels
126 | self.verbose_detection(self.star_has_cold_pixels,ImageInfo,\
127 | errormsg=' Star has cold pixels')
128 | # Update regions with new improved centroid.
129 | self.verbose_detection(self.estimate_fits_region_star,FitsImage,\
130 | errormsg=' Cannot create the Star region')
131 | self.verbose_detection(self.estimate_fits_region_complete,FitsImage,\
132 | errormsg=' Cannot create the Star+Background region')
133 | # Optimal aperture photometry
134 | self.verbose_detection(\
135 | self.measure_star_fluxes,FitsImage.fits_data,\
136 | errormsg=' Error doing optimal photometry')
137 | # Optimal aperture photometry
138 | #self.verbose_detection(\
139 | # self.optimal_aperture_photometry,ImageInfo,FitsImage.fits_data,\
140 | # errormsg=' Error doing optimal photometry')
141 |
142 | def check_star_issues(self,FitsImage,ImageInfo):
143 | # Check if star region is masked
144 | self.verbose_detection(self.star_region_is_masked,FitsImage,\
145 | errormsg=' Star is masked')
146 | # Check if star is detectable (with optimal astrometry)
147 | self.verbose_detection(self.star_is_detectable,ImageInfo,\
148 | errormsg=' Star is not detectable')
149 | # Calculate Bouguer variables
150 | self.verbose_detection(self.photometry_bouguervar,ImageInfo,\
151 | errormsg=' Error calculating bouguer variables')
152 | # Append star to star mask
153 | self.verbose_detection(self.append_to_star_mask,FitsImage,\
154 | errormsg=' Cannot add star to mask')
155 | if not self.destroy and self.to_be_masked==True:
156 | try: self.append_to_star_mask(FitsImage)
157 | except:
158 | raise
159 | print(str(inspect.stack()[0][2:4][::-1])+' Cannot add star to mask')
160 |
161 | def clear_objects(self):
162 | if DEBUG==True:
163 | print('Clearing Object')
164 | self.__clear__()
165 |
166 | if self.destroy==False:
167 | if DEBUG==True:
168 | print(self.name + ' DONE. Star detected.')
169 |
170 |
171 | def verbose_detection(self,function, *args, **kwargs):
172 | '''
173 | Exec a detection step in verbose mode
174 | '''
175 | if self.destroy==False:
176 | function(*args)
177 | if self.destroy==True:
178 | if DEBUG==True:
179 | print(str(inspect.stack()[0][2:4][::-1])+str(function)+kwargs['errormsg'])
180 |
181 | def from_catalog(self,CatalogLine,filter):
182 | ''' Populate class with properties extracted from catalog:
183 | recno, HDcode, RA1950, DEC1950, Vmag, U_V, B_V, R_V, I_V '''
184 |
185 | def coord_pyephem_format(coord_str):
186 | # Return coordinate in pyepheem str
187 | while coord_str[0]==' ':
188 | coord_str = coord_str[1:]
189 |
190 | coord_separated = coord_str.split(' ')
191 | coord_pyephem = str(int(coord_separated[0]))+\
192 | ':'+str(int(coord_separated[1]))+\
193 | ":"+str(float(coord_separated[2]))
194 | return coord_pyephem
195 |
196 | def get_float(value):
197 | '''
198 | Try to get the magnitude of the star,
199 | If it is missing, then flat it as Incomplete Photometry
200 | '''
201 | try:
202 | return(float(value))
203 | except:
204 | self.IncompletePhot = True
205 | return(0)
206 |
207 | def star_is_photometric(self):
208 | '''
209 | Flag the star for its photometry usefulness.
210 | It must have a complete photometric magnitudes
211 | and not to be double, variable or with
212 | [manual flag] bad photometric properties
213 | '''
214 |
215 | self.PhotometricStandard=True
216 | # Check few variables
217 | if self.isDouble: self.PhotometricStandard = False
218 | if self.isVariab: self.PhotometricStandard = False
219 | if self.isBadPhot: self.PhotometricStandard = False
220 | if self.IncompletePhot: self.PhotometricStandard = False
221 |
222 | # Also, if colors are too blue or red, discard them
223 | if self.B_V<-1.: self.PhotometricStandard = False
224 | if self.B_V>+2.: self.PhotometricStandard = False
225 |
226 | self.IncompletePhot = False
227 | try:
228 | self.recno = int(CatalogLine[0])
229 | self.HDcode = str(CatalogLine[1]).replace(' ','')
230 | self.RA2000 = coord_pyephem_format(CatalogLine[2])
231 | self.DEC2000 = coord_pyephem_format(CatalogLine[3])
232 | self.RA1950 = coord_pyephem_format(CatalogLine[4])
233 | self.DEC1950 = coord_pyephem_format(CatalogLine[5])
234 | self.Vmag = get_float(CatalogLine[6])
235 | if (filter=="Johnson_U"): self.U_V = get_float(CatalogLine[7])
236 | else: self.U_V = 0
237 | self.B_V = get_float(CatalogLine[8])
238 | if (filter=="Johnson_R"): self.R_V = get_float(CatalogLine[9])
239 | else: self.R_V = 0
240 | if (filter=="Johnson_I"): self.I_V = get_float(CatalogLine[10])
241 | else: self.I_V = 0
242 | self.isDouble = str(CatalogLine[11]).replace(' ','')=="D"
243 | self.isVariab = str(CatalogLine[12]).replace(' ','')=="V"
244 | self.r_SpTy = str(CatalogLine[13]).replace(' ','')
245 | self.SpType = str(CatalogLine[14]).replace(' ','')
246 | self.isBadPhot = str(CatalogLine[15]).replace(' ','')=="*"
247 |
248 | try:
249 | # Try to find the common name
250 | self.name = str(CatalogLine[16])
251 | except:
252 | # Use the HDcode as name
253 | self.name = self.HDcode
254 |
255 | #self.name = self.HDcode
256 |
257 | star_is_photometric(self)
258 |
259 | self.Umag = self.Vmag + self.U_V
260 | self.Bmag = self.Vmag + self.B_V
261 | self.Rmag = self.Vmag + self.R_V
262 | self.Imag = self.Vmag + self.I_V
263 | except:
264 | self.destroy=True
265 |
266 | def magnitude_on_image(self,ImageInfo):
267 | ''' Set the magnitude and color (#-V) that match image filter.'''
268 | if ImageInfo.used_filter=="Johnson_U":
269 | self.FilterMag = self.Umag
270 | self.Color = self.U_V
271 | elif ImageInfo.used_filter=="Johnson_B":
272 | self.FilterMag = self.Bmag
273 | self.Color = self.B_V
274 | elif ImageInfo.used_filter=="Johnson_V":
275 | self.FilterMag = self.Vmag
276 | self.Color = 0.0
277 | elif ImageInfo.used_filter=="Johnson_R":
278 | self.FilterMag = self.Rmag
279 | self.Color = self.R_V
280 | elif ImageInfo.used_filter=="Johnson_I":
281 | self.FilterMag = self.Imag
282 | self.Color = self.I_V
283 | else:
284 | self.destroy=True
285 |
286 | try:
287 | assert(self.FilterMagfloat(ImageInfo.min_altitude)
320 | except:
321 | self.destroy=True
322 | else:
323 | self.zdist_real = 90.0-self.altit_real
324 |
325 | if self.destroy==False:
326 | # Apparent coordinates in sky. Atmospheric refraction effect.
327 | self.altit_appa = atmospheric_refraction(self.altit_real,'dir')
328 | try:
329 | assert(self.altit_appa)>float(ImageInfo.min_altitude)
330 | except:
331 | self.destroy=True
332 | else:
333 | self.zdist_appa = 90.0-self.altit_appa
334 | self.airmass = calculate_airmass(self.altit_appa)
335 |
336 | def star_astrometry_image(self,ImageInfo):
337 | if self.destroy==False:
338 | # Get the X,Y image coordinates
339 | XYCoordinates = horiz2xy(self.azimuth,self.altit_appa,ImageInfo)
340 | self.Xcoord = XYCoordinates[0]
341 | self.Ycoord = XYCoordinates[1]
342 | try:
343 | assert(self.Xcoord>0. and self.Xcoord0. and self.Ycoord=0):
365 | MF_decl = 0.2*ImageInfo.exposure*abs(1.-self.dec/90.)
366 | else:
367 | MF_decl = 0.2*ImageInfo.exposure*abs(1.+self.dec/90.)
368 |
369 | MF_totl = 1+MF_magn+MF_reso+MF_decl+MF_airm
370 |
371 | self.R1 = int(ImageInfo.base_radius*MF_totl)
372 | self.R2 = self.R1*1.5+1
373 | self.R3 = self.R1*3.0+3
374 | except:
375 | self.destroy=True
376 |
377 | @staticmethod
378 | def valid_point(x,y,FitsImage):
379 | max_y,max_x = FitsImage.fits_data.shape
380 | return(x>=0 and y>=0 and x threshold)
444 | maxima[diff==0] = 0
445 | labeled, num_objects = ndimage.label(maxima)
446 | if num_objects == 0:
447 | self.destroy = True
448 |
449 | def measure_star_fluxes(self,fits_data,background_mode='median'):
450 | '''Needs self.Xcoord, self.Ycoord and self.R[1-3] defined
451 | Returns star fluxes'''
452 |
453 | # Pixels in each ring
454 | def less_distance(Xi,Yi,reference):
455 | # returns True if distance from pixel to the star center is less than a value.
456 | # False otherwise
457 | return (Xi)**2 + (Yi)**2 <= reference**2
458 |
459 | try:
460 | self.pixels1 = [self.fits_region_complete[y][x] \
461 | for y in xrange(len(self.fits_region_complete))\
462 | for x in xrange(len(self.fits_region_complete[0])) \
463 | if less_distance(x-len(self.fits_region_complete)/2.,\
464 | y-len(self.fits_region_complete[0])/2.,self.R1)]
465 |
466 | self.pixels2 = [self.fits_region_complete[y][x] \
467 | for y in xrange(len(self.fits_region_complete))\
468 | for x in xrange(len(self.fits_region_complete[0])) \
469 | if less_distance(x-len(self.fits_region_complete)/2.,\
470 | y-len(self.fits_region_complete[0])/2.,self.R2) and\
471 | not less_distance(x-len(self.fits_region_complete)/2.,\
472 | y-len(self.fits_region_complete[0])/2.,self.R1)]
473 |
474 | self.pixels3 = [self.fits_region_complete[y][x] \
475 | for y in xrange(len(self.fits_region_complete))\
476 | for x in xrange(len(self.fits_region_complete[0])) \
477 | if less_distance(x-len(self.fits_region_complete)/2.,\
478 | y-len(self.fits_region_complete[0])/2.,self.R3) and\
479 | not less_distance(x-len(self.fits_region_complete)/2.,\
480 | y-len(self.fits_region_complete[0])/2.,self.R2)]
481 |
482 | # Sky background flux. t_student 95%.
483 | t_skyflux = scipy.stats.t.isf(0.025,np.size(self.pixels3))
484 |
485 | # 4 possible background estimators. Mean, Median and a Mode approx.
486 | # Each one has its own drawbacks.
487 | # Mean may include stars (but this is not neccessarily bad, the pixel1
488 | # region may include stars too).
489 | # Median is less sensitive to stars, gives better background approx.
490 | # Mode is not really the mode, but an approximation based on mean and median.
491 | # but its correctness heavily depends on the assumed background dist.
492 | # Mean over sigma clipped values (preffered)
493 |
494 | if (background_mode=='mean'):
495 | self.skyflux = np.mean(self.pixels3)
496 | elif (background_mode=='median'):
497 | self.skyflux = np.median(self.pixels3)
498 | elif (background_mode=='mode'):
499 | self.skyflux = 2.5*np.median(self.pixels3)-1.5*np.mean(self.pixels3)
500 | elif (background_mode=='mean_sigma_clipped'):
501 | median = np.median(self.pixels3)
502 | filtered = (0.2*medianself.pixels3)
503 | self.pixels3 = np.array(self.pixels3)[filtered]
504 | self.skyflux = np.mean(self.pixels3)
505 |
506 | self.skyflux_err = t_skyflux*np.std(self.pixels3)/np.sqrt(np.size(self.pixels3))
507 | # Sky background + Star flux
508 | on_flux = np.sum(self.pixels1)
509 | off_flux = np.sum(self.pixels3)
510 | # Only star flux.
511 | self.starflux = on_flux - np.size(self.pixels1)*self.skyflux
512 | self.starflux_err = np.sqrt(2)*np.size(self.pixels1)*self.skyflux_err
513 | # LiMa (1983) Significance
514 | alpha = 1.*len(self.pixels1)/len(self.pixels3)
515 | self.lima_sig = np.sqrt(2*(\
516 | on_flux*np.log((1.+alpha)/(alpha)*(1.*on_flux/(on_flux + off_flux)))+\
517 | off_flux*np.log((1.+alpha)*(1.*off_flux/(on_flux+off_flux)))))
518 | if (DEBUG==True):
519 | print("alpha = %.3f, Li&Ma significance = %.2f" %(alpha,self.lima_sig))
520 | except:
521 | self.destroy=True
522 |
523 | def star_region_is_masked(self,FitsImage):
524 | ''' Check if the star is in the star mask'''
525 | self.masked = False
526 | for x in xrange(int(self.Xcoord - self.R1 + 0.5),int(self.Xcoord + self.R1 + 0.5)):
527 | for y in xrange(int(self.Ycoord - self.R1 + 0.5),int(self.Ycoord + self.R1 + 0.5)):
528 | if FitsImage.star_mask[y][x] == True:
529 | self.masked=True
530 | self.destroy = True
531 | return(0)
532 |
533 | def star_is_saturated(self,ImageInfo):
534 | ''' Return true if star has one or more saturated pixels
535 | requires a defined self.fits_region_star'''
536 | try:
537 | assert(np.max(self.fits_region_star_uncalibrated)<0.9*2**ImageInfo.ccd_bits)
538 | except:
539 | #self.destroy=True
540 | self.PhotometricStandard=False
541 | self.saturated=True
542 | else:
543 | self.saturated=False
544 |
545 | def star_has_cold_pixels(self,ImageInfo):
546 | ''' Return true if star has one or more cold (0 value) pixels
547 | requires a defined self.fits_region_star'''
548 | try:
549 | min_region = np.min(self.fits_region_star_uncalibrated)
550 | med_region = np.median(self.fits_region_star_uncalibrated)
551 | assert(min_region>0.2*med_region)
552 | except:
553 | #self.destroy=True
554 | self.PhotometricStandard=False
555 | self.cold_pixels=True
556 | else:
557 | self.cold_pixels=False
558 |
559 | def star_is_detectable(self,ImageInfo):
560 | ''' Set a detection limit to remove weak stars'''
561 | ''' Check if star is detectable '''
562 |
563 | try:
564 | assert(self.starflux>0)
565 | assert(self.lima_sig>0)
566 | assert(self.lima_sig>ImageInfo.baseflux_detectable)
567 | #assert(self.starflux>\
568 | # ImageInfo.baseflux_detectable*self.starflux_err+1e-10)
569 | except:
570 | self.destroy=True
571 |
572 | def estimate_centroid(self):
573 | ''' Returns star centroid from a region that contains the star
574 | needs self.R2'''
575 |
576 | try:
577 | data = (self.fits_region_centroid - self.skyflux)**2.
578 | h,w=data.shape
579 | x=np.arange(w)
580 | y=np.arange(h)
581 | x1=np.ones((1,h))
582 | y1=np.ones((w,1))
583 | self.Xcoord += (np.dot(np.dot(x1, data), y))/(np.dot(np.dot(x1, data), y1)) -w/2.
584 | self.Ycoord += (np.dot(np.dot(x, data), y1))/(np.dot(np.dot(x1, data), y1)) -h/2.
585 | except:
586 | self.destroy=True
587 |
588 | def optimal_aperture_photometry(self,ImageInfo,fits_data):
589 | '''
590 | Optimize the aperture to minimize uncertainties and assert
591 | all flux is contained in R1
592 | '''
593 |
594 | try:
595 | radius = (ImageInfo.base_radius+self.R1)/2.
596 | iterate = True
597 | num_iterations = 0
598 |
599 | self.starflux = 0
600 | while iterate:
601 | num_iterations+=1
602 | old_starflux = self.starflux
603 | self.R1 = radius
604 | self.measure_star_fluxes(fits_data)
605 | if self.starflux < (1+0.002*num_iterations**2)*old_starflux:
606 | iterate=False
607 | else:
608 | radius+=1
609 |
610 | assert(radius=MinLine-1 and line_is_star(CatalogContent[line])]
684 | except IOError:
685 | print('IOError. Error opening file '+catalog_filename+'.')
686 | #return 1
687 | except:
688 | print('Unknown error:')
689 | raise
690 | #return 2
691 | else:
692 | print('File '+str(catalog_filename)+' opened correctly.')
693 |
694 | def process_catalog_general(self,ImageInfo):
695 | '''
696 | Returns the processed catalog with
697 | all the starts that should be visible.
698 | '''
699 |
700 | try: ImageInfo.max_star_number
701 | except: ImageInfo.max_star_number = len(self.CatalogLines)
702 |
703 | self.StarList_Tot = []
704 | for each_star in self.CatalogLines[0:ImageInfo.max_star_number]:
705 | TheStar = Star(each_star,ImageInfo)
706 | if (TheStar.destroy==False):
707 | self.StarList_Tot.append(TheStar)
708 |
709 | print(" - Total stars: %d" %len(self.StarList_Tot))
710 |
711 | def process_catalog_specific(self,FitsImage,ImageInfo):
712 | '''
713 | Returns the processed catalog with
714 | all the starts that are detected.
715 | '''
716 |
717 | #Create the masked star matrix
718 | FitsImage.star_mask = np.zeros(np.array(FitsImage.fits_data).shape,dtype=bool)
719 |
720 | self.StarList_TotVisible = []
721 | self.StarList_Det = []
722 | self.StarList_Phot = []
723 | for TheStar in self.StarList_Tot:
724 | TheStar.camera_dependent_starpositions(FitsImage,ImageInfo)
725 | #TheStar.clear_objects()
726 | if (TheStar.destroy==False):
727 | TheStar.camera_dependent_regions(FitsImage,ImageInfo)
728 | self.StarList_TotVisible.append(TheStar)
729 |
730 | print(" - Observable stars: %d" %len(self.StarList_TotVisible))
731 |
732 | for TheStar in self.StarList_TotVisible:
733 | TheStar.camera_dependent_astrometry(FitsImage,ImageInfo)
734 | TheStar.camera_dependent_photometry(FitsImage,ImageInfo)
735 | TheStar.check_star_issues(FitsImage,ImageInfo)
736 | if (TheStar.destroy==False):
737 | self.StarList_Det.append(TheStar)
738 | if TheStar.PhotometricStandard==True:
739 | self.StarList_Phot.append(TheStar)
740 |
741 | print(" - Detected stars: %d" %len(self.StarList_Det))
742 | print(" - With photometry: %d" %len(self.StarList_Phot))
743 |
744 | def look_for_nearby_stars(self,FitsImage,ImageInfo):
745 | '''
746 | Process the catalog. For each star, look for close stars in the field
747 | (useful for example to detect clouds)
748 | '''
749 |
750 | self.StarList_WithNearbyStar = []
751 | for TheStar in self.StarList_TotVisible:
752 | TheStar.destroy = False
753 | TheStar.camera_dependent_detectpeaks(FitsImage,ImageInfo)
754 | if (TheStar.destroy==False):
755 | self.StarList_WithNearbyStar.append(TheStar)
756 |
757 | print(" - Observable stars: %d" %len(self.StarList_TotVisible))
758 | print(" - With nearby stars: %d" %len(self.StarList_WithNearbyStar))
759 |
760 | def save_to_file(self,ImageInfo):
761 | try:
762 | assert(ImageInfo.photometry_table_path not in [False, "False", "false", "F"])
763 | except:
764 | print('Skipping write photometric table to file')
765 | else:
766 | print('Write photometric table to file')
767 |
768 | content = ['#HDcode, CommonName, RA1950, DEC1950, Azimuth, '+\
769 | 'Altitude, Airmass, Magnitude, Color(#-V), StarFlux, StarFluxErr, '+\
770 | 'mag+2.5logF, [mag+2.5logF]_Err\n']
771 | for Star in self.StarList_Phot:
772 | content.append(str(Star.HDcode)+', '+str(Star.name)+', '+str(Star.RA1950)+', '+\
773 | str(Star.DEC1950)+', '+str(Star.azimuth)+', '+str(Star.altit_real)+\
774 | ', '+str(Star.airmass)+', '+str(Star.FilterMag)+', '+str(Star.Color)+\
775 | ', '+str(Star.starflux)+', '+str(Star.starflux_err)+', '+str(Star.m25logF)+\
776 | ', '+str(Star.m25logF_unc)+'\n')
777 |
778 | if (ImageInfo.photometry_table_path == "screen"):
779 | print(content)
780 | else:
781 | phottable_filename = str("%s/PhotTable_%s_%s_%s.txt" %(\
782 | ImageInfo.photometry_table_path, ImageInfo.obs_name,\
783 | ImageInfo.fits_date,ImageInfo.used_filter))
784 | photfile = open(phottable_filename,'w+')
785 | photfile.writelines(content)
786 | photfile.close()
787 |
788 |
789 |
790 |
--------------------------------------------------------------------------------
/pyasb/write_summary.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | '''
4 | SkyMap module
5 |
6 | Auxiliary functions to plot the SkyMap
7 | ____________________________
8 |
9 | This module is part of the PyASB project,
10 | created and maintained by Mireia Nievas [UCM].
11 | ____________________________
12 | '''
13 |
14 | __author__ = "Mireia Nievas"
15 | __copyright__ = "Copyright 2012, PyASB project"
16 | __credits__ = ["Mireia Nievas"]
17 | __license__ = "GNU GPL v3"
18 | __shortname__ = "PyASB"
19 | __longname__ = "Python All-Sky Brightness pipeline"
20 | __version__ = "1.99.0"
21 | __maintainer__ = "Mireia Nievas"
22 | __email__ = "mirph4k[at]gmail[dot]com"
23 | __status__ = "Prototype" # "Prototype", "Development", or "Production"
24 |
25 | try:
26 | import sys,os,inspect
27 | except:
28 | print(str(inspect.stack()[0][2:4][::-1])+': One or more modules missing')
29 | raise
30 |
31 |
32 |
33 | # NOTE: The following 2 functions should be moved to separate file or at least to a new class
34 | # NOTE: Maybe should be rewrite as follows?:
35 | # 1.) Create the file with the header
36 | # 2.) Iterative add lines
37 |
38 | class Summary():
39 | def __init__(self,Image,InputOptions,ImageAnalysis,InstrumentCalibration,ImageSkyBrightness,CloudCoverage):
40 | self.summarize_results(InputOptions, Image, ImageAnalysis,\
41 | InstrumentCalibration, ImageSkyBrightness, CloudCoverage)
42 | self.save_summary_to_file(Image.ImageInfo)
43 |
44 | def summarize_results(self,InputOptions, Image, ImageAnalysis,\
45 | InstrumentCalibration, ImageSkyBrightness, CloudCoverage):
46 |
47 | sum_date = str(Image.ImageInfo.fits_date)
48 | sum_filter = str(Image.ImageInfo.used_filter)
49 | sum_stars = str(InstrumentCalibration.BouguerFit.Regression.Nstars_initial)
50 | sum_gstars = str("%.1f"%float(InstrumentCalibration.BouguerFit.Regression.Nstars_rel))
51 | sum_zpoint = \
52 | str("%.3f"%float(InstrumentCalibration.BouguerFit.Regression.mean_zeropoint))+' +/- '+\
53 | str("%.3f"%float(InstrumentCalibration.BouguerFit.Regression.error_zeropoint))
54 | sum_extinction = \
55 | str("%.3f"%float(InstrumentCalibration.BouguerFit.Regression.extinction))+' +/- '+\
56 | str("%.3f"%float(InstrumentCalibration.BouguerFit.Regression.error_extinction))
57 | sum_skybrightness = \
58 | str("%.3f"%float(ImageSkyBrightness.SBzenith))+' +/- '+\
59 | str("%.3f"%float(ImageSkyBrightness.SBzenith_err))
60 | sum_cloudcoverage = \
61 | str("%.3f"%float(CloudCoverage.mean_cloudcover))+' +/- '+\
62 | str("%.3f"%float(CloudCoverage.error_cloudcover))
63 |
64 | self.summary_content = \
65 | [sum_date, sum_filter,sum_stars, sum_gstars, \
66 | sum_zpoint, sum_extinction, sum_skybrightness, sum_cloudcoverage]
67 |
68 | def save_summary_to_file(self,ImageInfo):
69 | try:
70 | assert(ImageInfo.summary_path!=False)
71 | except:
72 | #print(inspect.stack()[0][2:4][::-1])
73 | print('Skipping write summary to file')
74 | else:
75 | print('Write summary to file')
76 |
77 | content = ['#Date, Filter, Stars, % Good Stars, ZeroPoint, Extinction, SkyBrightness, CloudCoverage\n']
78 | for line in self.summary_content:
79 | content_line = ""
80 | for element in line:
81 | content_line += element
82 | content.append(content_line+", ")
83 |
84 | if ImageInfo.summary_path == "screen":
85 | print(content)
86 | else:
87 | summary_filename = str("%s/Summary_%s_%s_%s.txt" %(\
88 | ImageInfo.summary_path, ImageInfo.obs_name,\
89 | ImageInfo.fits_date, ImageInfo.used_filter))
90 |
91 | summaryfile = open(summary_filename,'w+')
92 | summaryfile.writelines(content)
93 | summaryfile.close()
94 |
95 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from setuptools import setup
4 |
5 | setup(name='pyasb',
6 | version='1.0.dev0',
7 | description='Python - All Sky Brightness pipeline',
8 | author='Mireia Nievas',
9 | author_email='mnievas[at]ucm[dot]es',
10 | license='GPLv3',
11 | packages=['pyasb'],
12 | install_requires=['numpy', 'scipy', 'astropy', 'ephem', 'matplotlib'],
13 | )
14 |
--------------------------------------------------------------------------------