#!/usr/bin/env python ''' PySQM plotting program ____________________________ Copyright (c) Mireia Nievas <mnievas[at]ucm[dot]es> This file is part of PySQM. PySQM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PySQM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with PySQM. If not, see <http://www.gnu.org/licenses/>. ____________________________ ''' import os,sys import ephem import numpy as np import matplotlib matplotlib.use('Agg') import matplotlib.ticker as ticker import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime,timedelta # Read configuration if __name__ != '__main__': import pysqm.settings as settings config = settings.GlobalConfig.config for directory in [\ config.monthly_data_directory,\ config.daily_graph_directory,\ config.current_graph_directory]: if not os.path.exists(directory): os.makedirs(directory) class Ephemerids(object): def __init__(self): from pysqm.common import define_ephem_observatory self.Observatory = define_ephem_observatory() def ephem_date_to_datetime(self,ephem_date): # Convert ephem dates to datetime date_,time_ = str(ephem_date).split(' ') date_ = date_.split('/') time_ = time_.split(':') return(datetime(\ int(date_[0]),int(date_[1]),int(date_[2]),\ int(time_[0]),int(time_[1]),int(time_[2]))) def end_of_the_day(self,thedate): newdate = thedate+timedelta(days=1) newdatetime = datetime(\ newdate.year,\ newdate.month,\ newdate.day,0,0,0) newdatetime = newdatetime-timedelta(hours=config._local_timezone) return(newdatetime) def calculate_moon_ephems(self,thedate): # Moon ephemerids self.Observatory.horizon = '0' self.Observatory.date = str(self.end_of_the_day(thedate)) # Moon phase Moon = ephem.Moon() Moon.compute(self.Observatory) self.moon_phase = Moon.phase self.moon_maxelev = Moon.transit_alt try: float(self.moon_maxelev) except: # The moon has no culmination time for 1 day # per month, so there is no max altitude. # As a workaround, we use the previous day culmination. # The error should be small. # Set the previous day date thedate2 = thedate - timedelta(days=1) self.Observatory.date = str(self.end_of_the_day(thedate2)) Moon2 = ephem.Moon() Moon2.compute(self.Observatory) self.moon_maxelev = Moon2.transit_alt # Recover the real date self.Observatory.date = str(self.end_of_the_day(thedate)) # Moon rise and set self.moon_prev_rise = \ self.ephem_date_to_datetime(self.Observatory.previous_rising(ephem.Moon())) self.moon_prev_set = \ self.ephem_date_to_datetime(self.Observatory.previous_setting(ephem.Moon())) self.moon_next_rise = \ self.ephem_date_to_datetime(self.Observatory.next_rising(ephem.Moon())) self.moon_next_set = \ self.ephem_date_to_datetime(self.Observatory.next_setting(ephem.Moon())) def calculate_twilight(self,thedate,twilight=-18): ''' Changing the horizon forces ephem to calculate different types of twilights: -6: civil, -12: nautical, -18: astronomical, ''' self.Observatory.horizon = str(twilight) self.Observatory.date = str(self.end_of_the_day(thedate)) self.twilight_prev_rise = self.ephem_date_to_datetime(\ self.Observatory.previous_rising(ephem.Sun(),use_center=True)) self.twilight_prev_set = self.ephem_date_to_datetime(\ self.Observatory.previous_setting(ephem.Sun(),use_center=True)) self.twilight_next_rise = self.ephem_date_to_datetime(\ self.Observatory.next_rising(ephem.Sun(),use_center=True)) self.twilight_next_set = self.ephem_date_to_datetime(\ self.Observatory.next_setting(ephem.Sun(),use_center=True)) class SQMData(object): # Split pre and after-midnight data class premidnight(object): pass class aftermidnight(object): pass class Statistics(object): pass def __init__(self,filename,Ephem): self.all_night_sb = [] self.all_night_dt = [] self.all_night_temp = [] for variable in [\ 'utcdates','localdates','sun_altitudes',\ 'temperatures','tick_counts','frequencies',\ 'night_sbs','label_dates','sun_altitude']: setattr(self.premidnight,variable,[]) setattr(self.aftermidnight,variable,[]) self.load_rawdata(filename) self.process_rawdata(Ephem) self.check_number_of_nights() def extract_metadata(self,raw_data_and_metadata): from pysqm.common import format_value metadata_lines = [\ line for line in raw_data_and_metadata \ if format_value(line)[0]=='#'] # Extract the serial number serial_number_line = [\ line for line in metadata_lines \ if 'SQM serial number:' in line][0] self.serial_number = format_value(serial_number_line.split(':')[-1]) def check_validdata(self,data_line): from pysqm.common import format_value try: assert(format_value(data_line)[0]!='#') assert(format_value(data_line)[0]!='') except: return(False) else: return(True) def load_rawdata(self,filename): ''' Open the file, read the data and close the file ''' sqm_file = open(filename, 'r') raw_data_and_metadata = sqm_file.readlines() self.metadata = self.extract_metadata(raw_data_and_metadata) self.raw_data = [\ line for line in raw_data_and_metadata \ if self.check_validdata(line)==True] sqm_file.close() def process_datetimes(self,str_datetime): ''' Get date and time in a str format Return as datetime object ''' str_date,str_time = str_datetime.split('T') year = int(str_date.split('-')[0]) month = int(str_date.split('-')[1]) day = int(str_date.split('-')[2]) # Time may be not complete. Workaround hour = int(str_time.split(':')[0]) try: minute = int(str_time.split(':')[1]) except: minute = 0 second = 0 else: try: second = int(str_time.split(':')[2]) except: second = 0 return(datetime(year,month,day,hour,minute,second)) def process_rawdata(self,Ephem): from pysqm.common import format_value_list ''' Get the important information from the raw_data and put it in a more useful format ''' self.raw_data = format_value_list(self.raw_data) for k,line in enumerate(self.raw_data): # DateTime extraction utcdatetime = self.process_datetimes(line[0]) localdatetime = self.process_datetimes(line[1]) # Check that datetimes are corrent calc_localdatetime = utcdatetime+timedelta(hours=config._local_timezone) if (calc_localdatetime != localdatetime): return 1 # Set the datetime for astronomical calculations. Ephem.Observatory.date = ephem.date(utcdatetime) # Date in str format: 20130115 label_date = str(localdatetime.date()).replace('-','') # Temperature temperature = float(line[2]) # Counts tick_counts = float(line[3]) # Frequency frequency = float(line[4]) # Night sky background night_sb = float(line[5]) try: config._plot_corrected_nsb except AttributeError: config._plot_corrected_data=False if (config._plot_corrected_data): night_sb += config._plot_corrected_data*config._offset_calibration # Define sun in pyephem Sun = ephem.Sun(Ephem.Observatory) self.premidnight.label_date=[] self.aftermidnight.label_dates=[] if localdatetime.hour > 12: self.premidnight.utcdates.append(utcdatetime) self.premidnight.localdates.append(localdatetime) self.premidnight.temperatures.append(temperature) self.premidnight.tick_counts.append(tick_counts) self.premidnight.frequencies.append(frequency) self.premidnight.night_sbs.append(night_sb) self.premidnight.sun_altitude.append(Sun.alt) if label_date not in self.premidnight.label_dates: self.premidnight.label_dates.append(label_date) else: self.aftermidnight.utcdates.append(utcdatetime) self.aftermidnight.localdates.append(localdatetime) self.aftermidnight.temperatures.append(temperature) self.aftermidnight.tick_counts.append(tick_counts) self.aftermidnight.frequencies.append(frequency) self.aftermidnight.night_sbs.append(night_sb) self.aftermidnight.sun_altitude.append(Sun.alt) if label_date not in self.aftermidnight.label_dates: self.aftermidnight.label_dates.append(label_date) # Data for the complete night self.all_night_dt.append(utcdatetime) # Must be in UTC! self.all_night_sb.append(night_sb) self.all_night_temp.append(temperature) def check_number_of_nights(self): ''' Check that the number of nights is exactly 1 and extract it to a new variable self.Night. Needed for the statistics part of the analysis and to make the plot. ''' if np.size(self.premidnight.localdates)>0: self.Night = np.unique([DT.date() \ for DT in self.premidnight.localdates])[0] elif np.size(self.aftermidnight.localdates)>0: self.Night = np.unique([(DT-timedelta(hours=12)).date() \ for DT in self.aftermidnight.localdates])[0] else: print('Warning, No Night detected.') self.Night = None def data_statistics(self,Ephem): ''' Make statistics on the data. Useful to summarize night conditions. ''' def select_bests(values,number): return(np.sort(values)[::-1][0:number]) def fourier_filter(array,nterms): ''' Make a fourier filter for the first nterms terms. ''' array_fft = np.fft.fft(array) # Filter data array_fft[nterms:]=0 filtered_array = np.fft.ifft(array_fft) return(filtered_array) def window_smooth(x,window_len=10,window='hanning'): # http://scipy-cookbook.readthedocs.io/items/SignalSmooth.html if x.ndim != 1: raise ValueError("smooth requires 1-d arrays") if x.size < window_len: raise ValueError("size(input) < window_size") if window_len < 3: return x if not window in ['flat','hanning','hamming','bartlett','blackman']: raise ValueError("Window is on of 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'") s=np.r_[x[window_len-1:0:-1],x,x[-2:-window_len-1:-1]] if window == 'flat': #moving average w=np.ones(window_len,'d') else: w=eval('np.'+window+'(window_len)') y=np.convolve(w/w.sum(),s,mode='valid') return(y) astronomical_night_filter = (\ (np.array(self.all_night_dt)>Ephem.twilight_prev_set)*\ (np.array(self.all_night_dt)<Ephem.twilight_next_rise)) if np.sum(astronomical_night_filter)>10: self.astronomical_night_sb = \ np.array(self.all_night_sb)[astronomical_night_filter] self.astronomical_night_temp = \ np.array(self.all_night_temp)[astronomical_night_filter] else: print((\ 'Warning, < 10 points in astronomical night, '+\ ' using the whole night data instead')) self.astronomical_night_sb = np.array(self.all_night_sb) self.astronomical_night_temp = np.array(self.all_night_temp) Stat = self.Statistics #with self.Statistics as Stat: # Complete list Stat.mean = np.mean(self.astronomical_night_sb) Stat.median = np.median(self.astronomical_night_sb) Stat.std = np.median(self.astronomical_night_sb) Stat.number = np.size(self.astronomical_night_sb) # Only the best 1/100th. Stat.bests_number = round(1+Stat.number/25) Stat.bests_mean = np.mean(select_bests(self.astronomical_night_sb,Stat.bests_number)) Stat.bests_median = np.median(select_bests(self.astronomical_night_sb,Stat.bests_number)) Stat.bests_std = np.std(select_bests(self.astronomical_night_sb,Stat.bests_number)) Stat.bests_err = Stat.bests_std*1./np.sqrt(Stat.bests_number) Stat.model_nterm = round(1+Stat.number/25) #data_smooth = fourier_filter(self.astronomical_night_sb,nterms=Stat.model_nterm) data_smooth = window_smooth(self.astronomical_night_sb, window_len=Stat.model_nterm) min_length = min(len(data_smooth),len(self.astronomical_night_sb)) data_residuals = self.astronomical_night_sb[:min_length]-data_smooth[:min_length] Stat.data_model_abs_meandiff = np.mean(np.abs(data_residuals)) Stat.data_model_sum_squareresiduals = np.sum(data_residuals**2) # Other interesting data Stat.min_temperature = np.min(self.astronomical_night_temp) Stat.max_temperature = np.max(self.astronomical_night_temp) class Plot(object): def __init__(self,Data,Ephem): plt.clf() # plt.hold(True/False) is deprecated Data = self.prepare_plot(Data,Ephem) try: config.full_plot except: config.full_plot = False if (config.full_plot): self.make_figure(thegraph_altsun=True,thegraph_time=True) self.plot_data_sunalt(Data,Ephem) else: self.make_figure(thegraph_altsun=False,thegraph_time=True) self.plot_data_time(Data,Ephem) self.plot_moonphase(Ephem) self.plot_twilight(Ephem) def plot_moonphase(self,Ephem): ''' shade the period of time for which the moon is above the horizon ''' if Ephem.moon_next_rise > Ephem.moon_next_set: # We need to divide the plotting in two phases #(pre-midnight and after-midnight) self.thegraph_time.axvspan(\ Ephem.moon_prev_rise+timedelta(hours=config._local_timezone),\ Ephem.moon_next_set+timedelta(hours=config._local_timezone),\ edgecolor='#d62728',facecolor='#d62728', alpha=0.1,clip_on=True) else: self.thegraph_time.axvspan(\ Ephem.moon_prev_rise+timedelta(hours=config._local_timezone),\ Ephem.moon_prev_set+timedelta(hours=config._local_timezone),\ edgecolor='#d62728',facecolor='#d62728', alpha=0.1,clip_on=True) self.thegraph_time.axvspan(\ Ephem.moon_next_rise+timedelta(hours=config._local_timezone),\ Ephem.moon_next_set+timedelta(hours=config._local_timezone),\ edgecolor='#d62728',facecolor='#d62728', alpha=0.1,clip_on=True) def plot_twilight(self,Ephem): ''' Plot vertical lines on the astronomical twilights ''' self.thegraph_time.axvline(\ Ephem.twilight_prev_set+timedelta(hours=config._local_timezone),\ color='black', ls='dashdot', lw=1, alpha=0.75, clip_on=True) self.thegraph_time.axvline(\ Ephem.twilight_next_rise+timedelta(hours=config._local_timezone),\ color='black', ls='dashdot', lw=1, alpha=0.75, clip_on=True) def make_subplot_sunalt(self,twinplot=0): ''' Make a subplot. If twinplot = 0, then this will be the only plot in the figure if twinplot = 1, this will be the first subplot if twinplot = 2, this will be the second subplot ''' if twinplot == 0: self.thegraph_sunalt = self.thefigure.add_subplot(1,1,1) else: self.thegraph_sunalt = self.thefigure.add_subplot(2,1,twinplot) self.thegraph_sunalt.set_title(\ 'Sky Brightness ('+config._device_shorttype+'-'+\ config._observatory_name+')\n',fontsize='x-large') self.thegraph_sunalt.set_xlabel('Solar altitude (deg)',fontsize='large') self.thegraph_sunalt.set_ylabel('Sky Brightness (mag/arcsec2)',fontsize='medium') # Auxiliary plot (Temperature) ''' self.thegraph_sunalt_temp = self.thegraph_sunalt.twinx() self.thegraph_sunalt_temp.set_ylim(-10, 50) self.thegraph_sunalt_temp.set_ylabel('Temperature (C)',fontsize='medium') ''' # format the ticks (frente a alt sol) tick_values = list(range(config.limits_sunalt[0],config.limits_sunalt[1]+5,5)) tick_marks = np.multiply([deg for deg in tick_values],np.pi/180.0) tick_labels = [str(deg) for deg in tick_values] self.thegraph_sunalt.set_xticks(tick_marks) self.thegraph_sunalt.set_xticklabels(tick_labels) self.thegraph_sunalt.yaxis.set_minor_locator(ticker.MultipleLocator(0.5)) self.thegraph_sunalt.grid(True,which='major', alpha=0.2,color='k',ls='',lw=0.5) self.thegraph_sunalt.grid(True,which='minor', alpha=0.2,color='k',ls='solid',lw=0.5) def make_subplot_time(self,twinplot=0): ''' Make a subplot. If twinplot = 0, then this will be the only plot in the figure if twinplot = 1, this will be the first subplot if twinplot = 2, this will be the second subplot ''' if twinplot == 0: self.thegraph_time = self.thefigure.add_subplot(1,1,1) else: self.thegraph_time = self.thefigure.add_subplot(2,1,twinplot) if config._local_timezone<0: UTC_offset_label = '-'+str(abs(config._local_timezone)) elif config._local_timezone>0: UTC_offset_label = '+'+str(abs(config._local_timezone)) else: UTC_offset_label = '' #self.thegraph_time.set_title('Sky Brightness (SQM-'+config._observatory_name+')',\ # fontsize='x-large') self.thegraph_time.set_xlabel('Time (UTC'+UTC_offset_label+')',fontsize='large') self.thegraph_time.set_ylabel('Sky Brightness (mag/arcsec2)',fontsize='medium') # Auxiliary plot (Temperature) ''' self.thegraph_time_temp = self.thegraph_time.twinx() self.thegraph_time_temp.set_ylim(-10, 50) self.thegraph_time_temp.set_ylabel('Temperature (C)',fontsize='medium') ''' # format the ticks (vs time) daylocator = mdates.HourLocator(byhour=[4,20]) hourlocator = mdates.HourLocator() dayFmt = mdates.DateFormatter('%d %b %Y') hourFmt = mdates.DateFormatter('%H') self.thegraph_time.xaxis.set_major_locator(daylocator) self.thegraph_time.xaxis.set_major_formatter(dayFmt) self.thegraph_time.xaxis.set_minor_locator(hourlocator) self.thegraph_time.xaxis.set_minor_formatter(hourFmt) self.thegraph_time.yaxis.set_minor_locator(ticker.MultipleLocator(0.5)) self.thegraph_time.xaxis.set_tick_params(which='major', pad=15) self.thegraph_time.format_xdata = mdates.DateFormatter('%Y-%m-%d_%H:%M:%S') self.thegraph_time.grid(True,which='major', alpha=0.2,color='k',ls='',lw=0.5) self.thegraph_time.grid(True,which='minor', alpha=0.2,color='k',ls='solid',lw=0.5) def make_figure(self,thegraph_altsun=True,thegraph_time=True): # Make the figure and the graph if thegraph_time==False: self.thefigure = plt.figure(figsize=(7,3.)) self.make_subplot_sunalt(twinplot=0) elif thegraph_altsun==False: self.thefigure = plt.figure(figsize=(7,3.)) self.make_subplot_time(twinplot=0) else: self.thefigure = plt.figure(figsize=(7,6.)) self.make_subplot_sunalt(twinplot=1) self.make_subplot_time(twinplot=2) # Adjust the space between plots plt.subplots_adjust(hspace=0.35) def prepare_plot(self,Data,Ephem): ''' Warning! Multiple night plot implementation is pending. Until the support is implemented, check that no more than 1 night is used ''' # Mean datetime dts = Data.all_night_dt mean_dt = dts[0]+np.sum(np.array(dts)-dts[0])/np.size(dts) sel_night = (mean_dt - timedelta(hours=12)).date() Data.premidnight.filter = np.array(\ [Date.date()==sel_night for Date in Data.premidnight.localdates]) Data.aftermidnight.filter = np.array(\ [(Date-timedelta(days=1)).date()==sel_night\ for Date in Data.aftermidnight.localdates]) return(Data) def plot_data_sunalt(self,Data,Ephem): ''' Plot NSB data vs Sun altitude ''' # Plot the data TheData = Data.premidnight if np.size(TheData.filter)>0: self.thegraph_sunalt.plot(\ np.array(TheData.sun_altitude)[TheData.filter],\ np.array(TheData.night_sbs)[TheData.filter],color='#2ca02c') ''' self.thegraph_sunalt.plot(\ np.array(TheData.sun_altitude)[TheData.filter],\ np.array(TheData.temperatures)[TheData.filter],color='#9467bd',alpha=0.5)) ''' TheData = Data.aftermidnight if np.size(TheData.filter)>0: self.thegraph_sunalt.plot(\ np.array(TheData.sun_altitude)[TheData.filter],\ np.array(TheData.night_sbs)[TheData.filter],color='#1f77b4') ''' self.thegraph_sunalt.plot(\ np.array(TheData.sun_altitude)[TheData.filter],\ np.array(TheData.temperatures)[TheData.filter],color='#9467bd',alpha=0.5)) ''' # Make limits on data range. self.thegraph_sunalt.set_xlim([\ config.limits_sunalt[0]*np.pi/180.,\ config.limits_sunalt[1]*np.pi/180.]) if np.size(config.limits_nsb) == 2: self.thegraph_sunalt.set_ylim(config.limits_nsb) premidnight_label = str(Data.premidnight.label_dates).replace('[','').replace(']','') aftermidnight_label = str(Data.aftermidnight.label_dates).replace('[','').replace(']','') self.thegraph_sunalt.text(0.00,1.015,\ config._device_shorttype+'-'+config._observatory_name+' '*5+'Serial #'+str(Data.serial_number),\ color='0.25',fontsize='small',fontname='monospace',\ transform = self.thegraph_sunalt.transAxes) self.thegraph_sunalt.text(0.75,0.92,'PM: '+premidnight_label,\ color='#2ca02c',fontsize='small',transform = self.thegraph_sunalt.transAxes) self.thegraph_sunalt.text(0.75,0.84,'AM: '+aftermidnight_label,\ color='#1f77b4',fontsize='small',transform = self.thegraph_sunalt.transAxes) ''' if np.size(Data.Night)==1: self.thegraph_sunalt.text(0.75,1.015,'Moon: %d%s (%d%s)' \ %(Ephem.moon_phase, "%", Ephem.moon_maxelev*180./np.pi,"$^\mathbf{o}$"),\ color='#d62728',fontsize='small',fontname='monospace',\ transform = self.thegraph_sunalt.transAxes) ''' def plot_data_time(self,Data,Ephem): ''' Plot NSB data vs Sun altitude ''' # Plot the data (NSB and temperature) TheData = Data.premidnight if np.size(TheData.filter)>0: self.thegraph_time.plot(\ np.array(TheData.localdates)[TheData.filter],\ np.array(TheData.night_sbs)[TheData.filter],color='#2ca02c') ''' self.thegraph_time_temp.plot(\ np.array(TheData.localdates)[TheData.filter],\ np.array(TheData.temperatures)[TheData.filter],color='#9467bd',alpha=0.5) ''' TheData = Data.aftermidnight if np.size(TheData.filter)>0: self.thegraph_time.plot(\ np.array(TheData.localdates)[TheData.filter],\ np.array(TheData.night_sbs)[TheData.filter],color='#1f77b4') ''' self.thegraph_time_temp.plot(\ np.array(TheData.localdates)[TheData.filter],\ np.array(TheData.temperatures)[TheData.filter],color='#9467bd',alpha=0.5) ''' # Vertical line to mark 0h self.thegraph_time.axvline(\ Data.Night+timedelta(days=1), color='black', alpha=0.75,lw=1,ls='solid',clip_on=True) # Set the xlimit for the time plot. if np.size(Data.premidnight.filter)>0: begin_plot_dt = Data.premidnight.localdates[-1] begin_plot_dt = datetime(\ begin_plot_dt.year,\ begin_plot_dt.month,\ begin_plot_dt.day,\ config.limits_time[0],0,0) end_plot_dt = begin_plot_dt+timedelta(\ hours=24+config.limits_time[1]-config.limits_time[0]) elif np.size(Data.aftermidnight.filter)>0: end_plot_dt = Data.aftermidnight.localdates[-1] end_plot_dt = datetime(\ end_plot_dt.year,\ end_plot_dt.month,\ end_plot_dt.day,\ config.limits_time[1],0,0) begin_plot_dt = end_plot_dt-timedelta(\ hours=24+config.limits_time[1]-config.limits_time[0]) else: print('Warning: Cannot calculate plot limits') return(None) self.thegraph_time.set_xlim(begin_plot_dt,end_plot_dt) if np.size(config.limits_nsb) == 2: self.thegraph_time.set_ylim(config.limits_nsb) premidnight_label = str(Data.premidnight.label_dates).replace('[','').replace(']','') aftermidnight_label = str(Data.aftermidnight.label_dates).replace('[','').replace(']','') self.thegraph_time.text(0.00,1.015,\ config._device_shorttype+'-'+config._observatory_name+' '*5+'Serial #'+str(Data.serial_number),\ color='0.25',fontsize='small',fontname='monospace',\ transform = self.thegraph_time.transAxes) if np.size(Data.Night)==1: self.thegraph_time.text(0.75,1.015,'Moon: %d%s (%d%s)' \ %(Ephem.moon_phase, "%", Ephem.moon_maxelev*180./np.pi,"$^\mathbf{o}$"),\ color='black',fontsize='small',fontname='monospace',\ transform = self.thegraph_time.transAxes) def save_figure(self,output_filename): self.thefigure.savefig(output_filename, bbox_inches='tight',dpi=150) def show_figure(self): plt.show(self.thefigure) def close_figure(self): plt.close('all') def save_stats_to_file(Night,NSBData,Ephem): from pysqm.common import set_decimals ''' Save statistics to file ''' Stat = NSBData.Statistics Header = \ '# Summary statistics for '+str(config._device_shorttype+'_'+config._observatory_name)+'\n'+\ '# Description of columns (CSV file):\n'+\ '# Col 1: Date\n'+\ '# Col 2: Total measures\n'+\ '# Col 3: Number of Best NSB measures\n'+\ '# Col 4: Median of best N NSBs (mag/arcsec2)\n'+\ '# Col 5: Err in the median of best N NSBs (mag/arcsec2)\n'+\ '# Col 6: Window size for the smoothing function\n'+\ '# Col 7: Mean of Abs diff of NSBs data - fourier model (mag/arcsec2)\n'+\ '# Col 8: Min Temp (C) between astronomical twilights\n'+\ '# Col 9: Max Temp (C) between astronomical twilights\n\n' #'# Col 6: Number of terms of the low-freq fourier model\n'+\ formatted_data = \ str(Night)+';'+\ str(Stat.number)+';'+\ str(Stat.bests_number)+';'+\ set_decimals(Stat.bests_median,4)+';'+\ set_decimals(Stat.bests_err,4)+';'+\ str(Stat.model_nterm)+';'+\ set_decimals(Stat.data_model_abs_meandiff,4)+';'+\ set_decimals(Stat.min_temperature,1)+';'+\ set_decimals(Stat.max_temperature,1)+\ '\n' statistics_filename = \ config.summary_data_directory+'/Statistics_'+\ str(config._device_shorttype+'_'+config._observatory_name)+'.dat' print('Writing statistics file') def safe_create_file(filename): if not os.path.exists(filename): open(filename, 'w').close() def read_file(filename): thefile = open(filename,'r') content = thefile.read() thefile.close() return(content) def write_file(filename,content): thefile = open(filename,'w') thefile.write(content) thefile.close() def append_file(filename,content): thefile = open(filename,'a') thefile.write(content) thefile.close() # Create file if not exists safe_create_file(statistics_filename) # Read the content stat_file_content = read_file(statistics_filename) # If the file doesnt have a proper header, add it to the beginning def valid_line(line): if '#' in line: return False elif line.replace(' ','')=='': return False else: return True if Header not in stat_file_content: stat_file_content = [line for line in stat_file_content.split('\n') \ if valid_line(line)] stat_file_content = '\n'.join(stat_file_content) stat_file_content = Header+stat_file_content write_file(statistics_filename,stat_file_content) # Remove any previous statistic for the given Night in the file if str(Night) in stat_file_content: stat_file_content = [line for line in stat_file_content.split('\n') \ if str(Night) not in line] stat_file_content = '\n'.join(stat_file_content) write_file(statistics_filename,stat_file_content) # Append to the end of the file append_file(statistics_filename,formatted_data) def make_plot(input_filename=None,send_emails=False,write_stats=False): ''' Main function (allows to execute the program from within python. - Extracts the NSB data from a given data file - Performs statistics - Save statistics to file - Create the plot ''' print('Plotting photometer data ...') if (input_filename is None): input_filename = config.current_data_directory+\ '/'+config._device_shorttype+'_'+config._observatory_name+'.dat' # Define the observatory in ephem Ephem = Ephemerids() # Get and process the data from input_filename NSBData = SQMData(input_filename,Ephem) # Moon and twilight ephemerids. Ephem.calculate_moon_ephems(thedate=NSBData.Night) Ephem.calculate_twilight(thedate=NSBData.Night) # Calculate data statistics NSBData.data_statistics(Ephem) # Write statiscs to file? if write_stats==True: save_stats_to_file(NSBData.Night,NSBData,Ephem) # Plot the data and save the resulting figure NSBPlot = Plot(NSBData,Ephem) output_filenames = [\ str("%s/%s_%s.png" %(config.current_data_directory,\ config._device_shorttype,config._observatory_name)),\ str("%s/%s_120000_%s-%s.png" \ %(config.daily_graph_directory, str(NSBData.Night).replace('-',''),\ config._device_shorttype, config._observatory_name))\ ] for output_filename in output_filenames: NSBPlot.save_figure(output_filename) # Close figure NSBPlot.close_figure() if send_emails == True: import pysqm.email night_label = str(datetime.date.today()-timedelta(days=1)) pysqm.email.send_emails(night_label=night_label,Stat=NSBData.Statistics) ''' The following code allows to execute plot.py as a standalone program. ''' if __name__ == '__main__': # Exec the main program import settings as settings InputArguments = settings.ArgParser(inputfile=True) configfilename = InputArguments.config try: settings.GlobalConfig.read_config_file(configfilename) config = settings.GlobalConfig.config make_plot(input_filename=InputArguments.input,\ send_emails=False,write_stats=True) except: raise print("Error: The arguments you provided are invalid") InputArguments.print_help()