├── .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 | --------------------------------------------------------------------------------