DTObs.py 13.6 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
15
16
17
18
19
import numpy as np

from PyQt5 import QtGui
from PyQt5.QtCore import QRunnable, QObject, pyqtSignal, pyqtSlot, QThreadPool
from PyQt5.QtWidgets import (QFileDialog, QApplication, QMainWindow, QTableWidgetItem)
from ui.dtobswindow import Ui_mainWindow

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

marc's avatar
marc committed
21
22
23
from telescope import telescope
from measurements import Measurements
from metadata import MetaData
marc's avatar
marc committed
24
from backend import Backend
marc's avatar
marc committed
25

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

'''
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)
39

40
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

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
87
88
class DTObservationProgram(Ui_mainWindow):    
  
89
    def __init__(self, mainWindow):
marc's avatar
marc committed
90
        logger.info('Initialization of DTObsGUI')
91
92
93
94
        self.demo = config.getboolean('TestConfig','demo')
        logging.debug('Demo status is: {}'.format(self.demo))
        if self.demo == False:
            self.myDT = telescope(setmode='J2000', consoleHost='consoledemo.dmz.camras.nl')
marc's avatar
marc committed
95
        
marc's avatar
marc committed
96
97
        self.myMeas = Measurements()
        self.myMetaData = MetaData()
marc's avatar
marc committed
98
        self.myBackend = Backend(setmode='Pulsar')
marc's avatar
marc committed
99
        
100
        self.radec = None
101
102
        self.measProgramma = None
        self.integrationTime = None
marc's avatar
marc committed
103
        self.inputFile = None
104
105
        self.outputDir = None
        self.outputFile = None
marc's avatar
marc committed
106
        self.GUIbackendMode = None
107

108
109
        Ui_mainWindow.__init__(self)
        self.setupUi(mainWindow)
marc's avatar
marc committed
110
111
112
113
114
115
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
        
        '''
        Define slot and connections for GUI
        '''
116
117
118
119
120
        self.actionOpen.triggered.connect(self.openFileNameDialog)
        self.actionClose.triggered.connect(self.closeMeasurement)
        self.actionQuit.triggered.connect(self.quitDTObs)
        self.pushButtonStartMeasurement.clicked.connect(self.startMeasurement)
        self.pushButtonStopMeasurement.clicked.connect(self.stopMeasurement)
121
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
122
        self.tableWidgetPointings.itemDoubleClicked.connect(self.viewResultMeasurement)
marc's avatar
marc committed
123

124
        '''
marc's avatar
marc committed
125
        Read the available tools from measurement Class and store them in the 
126
127
128
129
130
        comboBoxProgramma for selection.
        '''
        indexNR=0
        for tool in self.myMeas.getTools():
            self.comboBoxProgramma.setItemText(indexNR, tool)
marc's avatar
marc committed
131
            indexNR+=1
132
133

        self.threadpool = QThreadPool()
134
           
135
136
137
138
    def openFileNameDialog(self):
        '''
        Clear tableWidget and read pointings from file
        '''
marc's avatar
marc committed
139
140
141
        self.inputFile,_ = QFileDialog.getOpenFileName(None, 'Open File', "templates","Meas Files (*.dat);;All Files (*)")
        if self.inputFile:
            self.loadPointingsInTable(self.inputFile)
142
        logging.info('Selected data file is: {}'.format(self.inputFile))
marc's avatar
marc committed
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
    
    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
158
159

        self.tableWidgetPointings.resizeColumnsToContents()
marc's avatar
marc committed
160
        logger.info('Imported {} pointings'.format(rowPosition))
161
162
163
164
165
166
        
    def selectOutputDirectory(self):
        '''
        Select the output directory to store the measurements
        '''
        dialog = QFileDialog()
marc's avatar
marc committed
167
        folder_path = dialog.getExistingDirectory(None, "Select Folder",self.homedir)
168
        self.lineEditOutputDir.setText(folder_path)
marc's avatar
marc committed
169
        
170
171
172
173
174
    def closeMeasurement(self):
        for rowPosition in range(self.tableWidgetPointings.rowCount(),-1,-1):
            self.tableWidgetPointings.removeRow(rowPosition)

    def quitDTObs(self):
175
        logging.shutdown()
176
177
178
179
        sys.exit(app.exec_())

    def allCompleted(self):
        """ Callback function when all pointings completed """
marc's avatar
marc committed
180
        logging.info("All pointings completed")
181
182
183
184
185
186
187

    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
188
189
        self.tableWidgetPointings.setItem(meas_num, 3, QTableWidgetItem(self.outputFile))
        
marc's avatar
marc committed
190
191
192
193
194
        '''
        TODO
        '''
        #self.tableWidgetPointings.item(meas_num, 3).setText(measFile.name))

marc's avatar
marc committed
195
        for column in range(4):
196
197
198
199
200
201
            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):
202
203
204
205
            if self.demo == False:
                self.myDT.setRaDec(setpoint)
                oneSetpointCompleteSignal.emit(setpoint_nr, 'Slewing')
                time.sleep(3)
206
                dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
207
208
209
210
211
212
213
214
                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
215
                    
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
            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
231
232
233
234
235
236
237
238
239
    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()
240
241
242
243
        self.myMetaData.tool = self.measProgramme 
        self.myMetaData.integrationTime = self.integrationTime        
        self.myMetaData.outputDirectory = self.outputDir
        self.myMetaData.outputFile = self.outputFile
244
245
246
247
248
        self.myMetaData.radecSetpoint = self.radec
        self.myMetaData.radecActual = self.radec
        '''
        TODO: read radec Actual from telescope class
        '''
marc's avatar
marc committed
249
250
251
252
253
        self.myMetaData.refractionEnabled = self.checkBoxRefraction.isChecked()
        logging.debug('checkBoxRefraction Setting is {}:'.format(self.checkBoxRefraction.isChecked()))
        self.myMetaData.DTModelEnabled = self.checkBoxDTModel.isChecked()
        self.myMetaData.mode = self.GUIbackendMode
        self.myMetaData.LSREnabled = self.radioButtonLSR.isChecked()
marc's avatar
marc committed
254
255

    def writeMetaData(self,file):
marc's avatar
marc committed
256
257
        myMetaData = self.myMetaData.getMetaData()
        for data in myMetaData:
marc's avatar
marc committed
258
            logging.info('#{:20}: {:40}'.format(data[0],str(data[1])))
marc's avatar
marc committed
259
            file.write('#{:20}: {:40}\n'.format(data[0],str(data[1])))      
marc's avatar
marc committed
260
 
261
262
263
    def doMeasurement(self, measnum, progressSignal=None):
        """ 
        Dumping data into file including meta-data
264
        measProgramme: cli command read from comboBoxProgramma
265
        integrationTime: string read from spinBoxIntTime
266
        measurement: result of measurement of which 
267
        data: is the stdout result which need to be stored line by line
marc's avatar
marc committed
268
        measFile: result file storing the stdout results including meta data
269
        """
270
        self.integrationTime = self.spinBoxIntTime.value()
marc's avatar
marc committed
271
        self.centralFrequency = self.spinBoxCentralFrequency.value()
272
        self.measProgramme = self.comboBoxProgramma.currentText()
marc's avatar
marc committed
273
274
        self.outputDir = self.lineEditOutputDir.text() + '/'
        self.outputFile = 'DT-' + '{:03d}'.format(measnum) + '-' + time.strftime("%Y%m%d") + '.dat'
275
        measFile = open(self.outputDir + self.outputFile,'w')
marc's avatar
marc committed
276
        
277
278
        self.setMetaData()
        self.writeMetaData(measFile)
marc's avatar
marc committed
279
                
280
        logging.info("Output to file: {}".format(measFile))
marc's avatar
marc committed
281
        
marc's avatar
marc committed
282
        self.myMeas.startMeasurement(self.measProgramme, self.centralFrequency, self.integrationTime, measFile)
283

284
285
286
287
        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)
288
            time.sleep(1)
marc's avatar
marc committed
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
        measFile.close()      
    
    def readGUIBackendMode(self):
        '''
        Read the selected Backend mode from the GUI backend checkbox group
        '''
        if self.checkBoxPulsar.isChecked():
            self.GUIbackendMode = 'Pulsar'
        if self.checkBoxHI.isChecked():
            self.GUIbackendMode = 'Hydrogen'
        if self.checkBoxSDR.isChecked():
            self.GUIbackendMode = 'SDR'
        if self.checkBoxRaw.isChecked():
            self.GUIbackendMode = 'Raw'
                    
304
    def startMeasurement(self):
marc's avatar
marc committed
305
306
307
308
309
310
311
        '''
        Put backend in proper mode
        '''
        logging.info('Start Measurement')
        self.readGUIBackendMode()
        self.myBackend.setMode(self.GUIbackendMode)       
        
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
        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
329
        worker.signals.allfinished.connect(self.allCompleted)
marc's avatar
marc committed
330
        logging.info("Measurement stopped")
331

marc's avatar
marc committed
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    def viewResultMeasurement(self):
        currentRow = self.tableWidgetPointings.currentRow()
        resultFile = self.tableWidgetPointings.item(currentRow,3).text()
        logging.debug('Selected row is: {}'.format(currentRow))
        logging.info('Show result in gnuplot for file: {}'.format(resultFile))
        '''
        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
        '''
               
350
351
352
353
354
355
356
357
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()

    prog = DTObservationProgram(mainWindow)

    mainWindow.show()
    sys.exit(app.exec_())