DTObs.py 16.4 KB
Newer Older
1
2
3
4
5
#!/usr/bin/env python3

import sys
import csv
import logging
6
from configparser import ConfigParser
7
8
9
import subprocess
import traceback
import time
10
import os.path
11
12
13
14
import numpy as np

from PyQt5 import QtGui
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, pyqtSlot, QThreadPool
marc's avatar
marc committed
15
from PyQt5.QtWidgets import (QFileDialog, QApplication, QMainWindow, QTableWidgetItem, QWidget)
16
17
18
19
from ui.dtobswindow import Ui_mainWindow

from astropy import units as u
from astropy.coordinates import SkyCoord
20

marc's avatar
marc committed
21
from telescope.telescope import telescope
marc's avatar
marc committed
22
23
from measurements import Measurements
from metadata import MetaData
marc's avatar
marc committed
24
from backend import Backend
marc's avatar
marc committed
25
from createpointings import CreatePointings
marc's avatar
marc committed
26
27
#from createpointings import CreatePointings

28
'''
marc's avatar
marc committed
29
Setup config ini
30
31
32
'''
config = ConfigParser()
config.readfp(open(os.path.join(os.path.dirname(__file__), 'dtobsgui.ini')))
marc's avatar
marc committed
33
34
35
36
37
38
39
40

'''
Setup logging
'''
logger = logging.getLogger('root')
FORMAT = "[%(asctime)s%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s,filename='dtobsgui.log',filemode='w'"
logging.basicConfig(format=FORMAT)
logger.setLevel(logging.DEBUG)
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

class WorkerSignals(QObject):
    """
    Defines the signals available from a running worker thread.

    Supported signals are:

    finished
        `int` of the setpoint number

    error
        `tuple` (exctype, value, traceback.format_exc() )

    result
        `object` data returned from processing, anything

    progress
        `int` inicating % progress
    """

    finished = pyqtSignal(int, str)
    allfinished = pyqtSignal()
    # error = pyqtSignal(tuple)
    # result = pyqtSignal(object)
    progress = pyqtSignal(int, int, str) # meas_num, percent_complete, remaining

class Worker(QRunnable):
    """ Execute a function asynchronously, connect to signals """

    def __init__(self, function_to_run, *args, **kwargs):
        super(Worker, self).__init__()
        # Store arguments
        self.function_to_run = function_to_run
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        self.kwargs['oneSetpointCompleteSignal'] = self.signals.finished
        self.kwargs['progressSignal'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        """ Initialize the runner function with passed args, kwargs """

        self.function_to_run(*self.args, **self.kwargs)
        self.signals.allfinished.emit()

marc's avatar
marc committed
89
90
class DTObservationProgram(Ui_mainWindow):    
  
91
    def __init__(self, mainWindow):
marc's avatar
marc committed
92
        logger.info('Initialization of DTObsGUI')
93
        self.demo = config.getboolean('TestConfig','demo')
94
        
marc's avatar
marc committed
95
        consoleHost = config.get('Console','HostName')
96
97
98
99
100
101
        if consoleHost == 'console':
            if socket.gethostname()=="mercurius":
                logging.warning("You are using the actual console, not a demo!")
            else:
                raise ValueError("Talking to the actual console can only be done from mercurius")
        
marc's avatar
marc committed
102
        logger.debug('Demo status is: {}'.format(self.demo))
103
     
104
        if not self.demo:
105
106
            self.myDT = telescope(setmode='J2000', consoleHost=consoleHost)
            '''
marc's avatar
marc committed
107
            telescopeMode and telescopeOffset required for meta data
108
            '''
marc's avatar
marc committed
109
            self.telescopeMode = 'J2000'
marc's avatar
marc committed
110
            self.telescopeOffset = None
111
112
113
114
115
            
            '''
            As part of the init the current setpoint will be loaded as a single shot measurement.
            This enables the operator to manually define the setpoint using the Console
            '''
marc's avatar
marc committed
116
117
118
            #self.myDT.setRaDec(self.myDT.getSetpoint_RaDec)
            #self.radec = SkyCoord(self.myDT.getSetpoint_RaDec)
            #(raStr, decStr)  = self.radec.to_string('hmsdms').split()
119
              
marc's avatar
marc committed
120
121
122
123
124
            #self.tableWidgetPointings.insertRow(rowPosition)
            #self.tableWidgetPointings.setItem(rowPosition, 0, QTableWidgetItem(raStr))
            #self.tableWidgetPointings.setItem(rowPosition, 1, QTableWidgetItem(decStr))
            #self.tableWidgetPointings.setItem(rowPosition, 2, QTableWidgetItem(""))
            #self.tableWidgetPointings.setItem(rowPosition, 3, QTableWidgetItem(""))
125
            
marc's avatar
marc committed
126
127
128
        else:
            self.telescopeMode = None
            self.telescopeOffset = None
129
130
            

marc's avatar
marc committed
131
132
        self.myMeas = Measurements()
        self.myMetaData = MetaData()
marc's avatar
marc committed
133
        self.myBackend = Backend(setmode='Pulsar')
marc's avatar
marc committed
134
        
135
        self.radec = None
136
137
        self.measProgramma = None
        self.integrationTime = None
marc's avatar
marc committed
138
        self.inputFile = None
139
140
        self.outputDir = None
        self.outputFile = None
marc's avatar
marc committed
141
        self.GUIbackendMode = None
142

143
144
        Ui_mainWindow.__init__(self)
        self.setupUi(mainWindow)
marc's avatar
marc committed
145
146
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
marc's avatar
minor    
marc committed
147
        self.formCreatePointings = QWidget()
marc's avatar
marc committed
148
149
150
151
        
        '''
        Define slot and connections for GUI
        '''
marc's avatar
marc committed
152
        self.actionNew.triggered.connect(self.newPointingsDialog)
153
        self.actionOpen.triggered.connect(self.openFileNameDialog)
marc's avatar
marc committed
154
        self.actionSave_As.triggered.connect(self.saveFileNameDialog)
155
156
157
158
        self.actionClose.triggered.connect(self.closeMeasurement)
        self.actionQuit.triggered.connect(self.quitDTObs)
        self.pushButtonStartMeasurement.clicked.connect(self.startMeasurement)
        self.pushButtonStopMeasurement.clicked.connect(self.stopMeasurement)
159
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
160
        self.tableWidgetPointings.itemDoubleClicked.connect(self.viewResultMeasurement)
marc's avatar
marc committed
161

162
        '''
marc's avatar
marc committed
163
        Read the available tools from measurement Class and store them in the 
164
165
166
167
168
        comboBoxProgramma for selection.
        '''
        indexNR=0
        for tool in self.myMeas.getTools():
            self.comboBoxProgramma.setItemText(indexNR, tool)
marc's avatar
marc committed
169
            indexNR+=1
170
171

        self.threadpool = QThreadPool()
marc's avatar
marc committed
172
173
    
    def newPointingsDialog(self):
marc's avatar
marc committed
174
175
176
        self.myForm = CreatePointings(self.formCreatePointings)
        self.formCreatePointings.show()
        
177
178
179
180
    def openFileNameDialog(self):
        '''
        Clear tableWidget and read pointings from file
        '''
marc's avatar
marc committed
181
        self.inputFile,_ = QFileDialog.getOpenFileName(None, 'Open File', "templates","Pointing Files (*.pnt);;All Files (*)")
marc's avatar
marc committed
182
183
        if self.inputFile:
            self.loadPointingsInTable(self.inputFile)
marc's avatar
marc committed
184
        logger.info('Selected data file is: {}'.format(self.inputFile))
marc's avatar
marc committed
185
    
marc's avatar
marc committed
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    def saveFileNameDialog(self):
        '''
        Clear tableWidget and read pointings from file
        '''
        saveFile,_ = QFileDialog.getSaveFileName(None, '|Save File', "templates","Pointing Files (*.pnt);;All Files (*)")
        saveFile = open(saveFile,'w')
        for meas in range(0,self.tableWidgetPointings.rowCount()):
            ra  = self.tableWidgetPointings.item(meas, 0).text()
            dec = self.tableWidgetPointings.item(meas, 1).text()
            pointing = SkyCoord(ra, dec, frame='icrs')
            saveFile.write('{}\t{}\t{}\n'.format(meas, pointing.ra.deg, pointing.dec.deg))
            logger.debug('{}\t{}\t{}\n'.format(meas, ra, dec))                 
            saveFile.flush()   
            
        logger.info('Save file as is: {}'.format(saveFile))
    
marc's avatar
marc committed
202
203
204
205
206
207
208
209
210
211
212
213
214
215
    def loadPointingsInTable(self,fileName):
        reader = csv.reader(open(self.inputFile),delimiter='\t')
        rowPosition = self.tableWidgetPointings.rowCount()    
        for meas, ra, dec in reader:
            self.radec = SkyCoord(float(ra)*u.degree, float(dec)*u.degree, frame='icrs')
            (raStr, decStr)  = self.radec.to_string('hmsdms').split()
              
            self.tableWidgetPointings.insertRow(rowPosition)
            self.tableWidgetPointings.setItem(rowPosition, 0, QTableWidgetItem(raStr))
            self.tableWidgetPointings.setItem(rowPosition, 1, QTableWidgetItem(decStr))
            self.tableWidgetPointings.setItem(rowPosition, 2, QTableWidgetItem(""))
            self.tableWidgetPointings.setItem(rowPosition, 3, QTableWidgetItem(""))

            rowPosition += 1
216
217

        self.tableWidgetPointings.resizeColumnsToContents()
marc's avatar
marc committed
218
        logger.info('Imported {} pointings'.format(rowPosition))
219
220
221
222
223
224
        
    def selectOutputDirectory(self):
        '''
        Select the output directory to store the measurements
        '''
        dialog = QFileDialog()
marc's avatar
marc committed
225
        folder_path = dialog.getExistingDirectory(None, "Select Folder",self.homedir)
226
        self.lineEditOutputDir.setText(folder_path)
marc's avatar
marc committed
227
        
228
229
230
231
232
    def closeMeasurement(self):
        for rowPosition in range(self.tableWidgetPointings.rowCount(),-1,-1):
            self.tableWidgetPointings.removeRow(rowPosition)

    def quitDTObs(self):
marc's avatar
marc committed
233
        logger.shutdown()
234
235
236
237
        sys.exit(app.exec_())

    def allCompleted(self):
        """ Callback function when all pointings completed """
marc's avatar
marc committed
238
        logger.info("All pointings completed")
239
240
241
242
243
244
245

    def measCompleted(self, meas_num, status):
        """ Make the meas_num-th row of the table green """
        colors = {'Completed': QtGui.QColor('green'),
                  'Slewing'  : QtGui.QColor('yellow'),
                  'Measuring': QtGui.QColor('cyan')}
        self.tableWidgetPointings.item(meas_num, 2).setText(status)
marc's avatar
marc committed
246
247
        self.tableWidgetPointings.setItem(meas_num, 3, QTableWidgetItem(self.outputFile))
        
marc's avatar
marc committed
248
249
250
251
252
        '''
        TODO
        '''
        #self.tableWidgetPointings.item(meas_num, 3).setText(measFile.name))

marc's avatar
marc committed
253
        for column in range(4):
254
255
256
257
258
259
            self.tableWidgetPointings.item(meas_num,column).setBackground(colors[status])
        self.tableWidgetPointings.resizeColumnsToContents()

    def goToSetpoints(self, setpoints, oneSetpointCompleteSignal=None, progressSignal=None):
        """ Send a list of setpoints to the telescope """
        for setpoint_nr, setpoint in enumerate(setpoints):
260
261
262
263
            if self.demo == False:
                self.myDT.setRaDec(setpoint)
                oneSetpointCompleteSignal.emit(setpoint_nr, 'Slewing')
                time.sleep(3)
264
                dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
265
266
267
268
269
270
271
272
                firstDist = dist
                while not dist < 0.01*u.deg:
                    percentSlew = max(100 - dist/firstDist*100, 0)
                    progressSignal.emit(setpoint_nr, percentSlew, "{:.3f}°".format(dist.value))
                    #print("{:.3f}".format(dist))
                    #print("{:2.0f}".format(percentSlew.value))
                    self.myDT.getDistance(waitForUpdate=True)
                    dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
marc's avatar
marc committed
273
                    
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
            oneSetpointCompleteSignal.emit(setpoint_nr, 'Measuring')
            self.doMeasurement(setpoint_nr, progressSignal=progressSignal)
            oneSetpointCompleteSignal.emit(setpoint_nr, 'Completed')

    def updateProgress(self, meas_num, progress_percent, remaining_str):
        """
        Update the table with some progress indicator

        meas_num: the row which needs to be updated
        progress_percent: integer giving the percentage complete
        remaining_str: string indicating how much remaining, e.g. "1:30" or "3°"
        """
        #self.tableWidgetPointings.item(meas_num, 2).setText(remaining_str)
        self.tableWidgetPointings.item(meas_num, 2).setText(str(progress_percent)+"%")

marc's avatar
marc committed
289
290
291
292
293
294
295
296
297
    def setMetaData(self):
        '''
        Define metadata
        '''   
        self.myMetaData.description = self.textEditDescription.toPlainText()
        self.myMetaData.startTime = time.strftime("%Y%m%d-%H%M%S")
        self.myMetaData.operator1 = self.lineEditOperator1.text()
        self.myMetaData.operator2 = self.lineEditOperator2.text()
        self.myMetaData.operator3 = self.lineEditOperator3.text()
298
299
300
301
        self.myMetaData.tool = self.measProgramme 
        self.myMetaData.integrationTime = self.integrationTime        
        self.myMetaData.outputDirectory = self.outputDir
        self.myMetaData.outputFile = self.outputFile
302
303
        self.myMetaData.radecSetpoint = self.radec
        self.myMetaData.radecActual = self.radec
marc's avatar
marc committed
304
305
306
        self.myMetaData.telescopeMode = self.telescopeMode
        self.myMetaData.telescopeOffset = self.telescopeOffset
        
307
308
309
        '''
        TODO: read radec Actual from telescope class
        '''
marc's avatar
marc committed
310
        self.myMetaData.refractionEnabled = self.checkBoxRefraction.isChecked()
marc's avatar
marc committed
311
        logger.debug('checkBoxRefraction Setting is {}:'.format(self.checkBoxRefraction.isChecked()))
marc's avatar
marc committed
312
313
        self.myMetaData.DTModelEnabled = self.checkBoxDTModel.isChecked()
        self.myMetaData.mode = self.GUIbackendMode
marc's avatar
marc committed
314
        self.myMetaData.LSREnabled = self.checkBoxLSR.isChecked()
marc's avatar
marc committed
315
316

    def writeMetaData(self,file):
marc's avatar
marc committed
317
318
        myMetaData = self.myMetaData.getMetaData()
        for data in myMetaData:
marc's avatar
marc committed
319
            logger.info('#{:20}: {:40}'.format(data[0],str(data[1])))
marc's avatar
marc committed
320
            file.write('#{:20}: {:40}\n'.format(data[0],str(data[1])))      
marc's avatar
marc committed
321
 
322
323
324
    def doMeasurement(self, measnum, progressSignal=None):
        """ 
        Dumping data into file including meta-data
325
        measProgramme: cli command read from comboBoxProgramma
326
        integrationTime: string read from spinBoxIntTime
327
        measurement: result of measurement of which 
328
        data: is the stdout result which need to be stored line by line
marc's avatar
marc committed
329
        measFile: result file storing the stdout results including meta data
330
        """
331
        self.integrationTime = self.spinBoxIntTime.value()
marc's avatar
marc committed
332
        self.centralFrequency = self.spinBoxCentralFrequency.value()
333
        self.measProgramme = self.comboBoxProgramma.currentText()
marc's avatar
marc committed
334
335
        self.outputDir = self.lineEditOutputDir.text() + '/'
        self.outputFile = 'DT-' + '{:03d}'.format(measnum) + '-' + time.strftime("%Y%m%d") + '.dat'
336
        measFile = open(self.outputDir + self.outputFile,'w')
marc's avatar
marc committed
337
        
338
339
        self.setMetaData()
        self.writeMetaData(measFile)
marc's avatar
marc committed
340
                
marc's avatar
marc committed
341
        logger.info("Output to file: {}".format(measFile))
marc's avatar
marc committed
342
        
marc's avatar
marc committed
343
        self.myMeas.startMeasurement(self.measProgramme, self.centralFrequency, self.integrationTime, measFile)
344

345
346
347
348
        for sec in range(self.integrationTime):
            remainstring =  str(int((self.integrationTime-sec)/60)) + ":"
            remainstring += "{:02}".format((self.integrationTime-sec)%60)
            progressSignal.emit(measnum, int(float(sec)/self.integrationTime*100), remainstring)
349
            time.sleep(1)
marc's avatar
marc committed
350
351
352
353
354
355
        measFile.close()      
    
    def readGUIBackendMode(self):
        '''
        Read the selected Backend mode from the GUI backend checkbox group
        '''
356
        if self.radioButtonPulsar.isChecked():
marc's avatar
marc committed
357
            self.GUIbackendMode = 'Pulsar'
358
        if self.radioButtonHydrogenLine.isChecked():
marc's avatar
marc committed
359
            self.GUIbackendMode = 'Hydrogen'
360
        if self.radioButtonSDR.isChecked():
marc's avatar
marc committed
361
            self.GUIbackendMode = 'SDR'
362
        if self.radioButtonRaw.isChecked():
marc's avatar
marc committed
363
364
            self.GUIbackendMode = 'Raw'
                    
365
    def startMeasurement(self):
marc's avatar
marc committed
366
367
368
        '''
        Put backend in proper mode
        '''
marc's avatar
marc committed
369
        logger.info('Start Measurement')
marc's avatar
marc committed
370
371
372
        self.readGUIBackendMode()
        self.myBackend.setMode(self.GUIbackendMode)       
        
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
        setpoints = []
        for meas in range(0,self.tableWidgetPointings.rowCount()):
            ra  = self.tableWidgetPointings.item(meas, 0).text()
            dec = self.tableWidgetPointings.item(meas, 1).text()

            self.tableWidgetPointings.item(meas, 2).setText("Scheduled")
            self.tableWidgetPointings.resizeColumnsToContents()
            setpoint = SkyCoord(ra, dec, frame='icrs')
            setpoints.append(setpoint)

        worker = Worker(self.goToSetpoints, setpoints)
        worker.signals.finished.connect(self.measCompleted)
        worker.signals.allfinished.connect(self.allCompleted)
        worker.signals.progress.connect(self.updateProgress)
        self.threadpool.start(worker)

    def stopMeasurement(self):
marc's avatar
marc committed
390
        worker.signals.allfinished.connect(self.allCompleted)
marc's avatar
marc committed
391
        logger.info("Measurement stopped")
392

marc's avatar
marc committed
393
394
395
    def viewResultMeasurement(self):
        currentRow = self.tableWidgetPointings.currentRow()
        resultFile = self.tableWidgetPointings.item(currentRow,3).text()
marc's avatar
marc committed
396
397
        logger.debug('Selected row is: {}'.format(currentRow))
        logger.info('Show result in gnuplot for file: {}'.format(resultFile))
marc's avatar
marc committed
398
399
400
401
402
403
404
405
406
407
408
409
410
        '''
        TODO:
        Create a plot file:
        marc@gamma2:~> cat temp.plg 
        set title datafile
        set grid
        plot datafile w l
        pause -1

        and call gnuplot as:
        gnuplot -e "datafile='DT-002-20171029.dat'" temp.plg
        '''
               
411
412
413
414
415
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()
    prog = DTObservationProgram(mainWindow)
    mainWindow.show()
marc's avatar
marc committed
416

417
    sys.exit(app.exec_())