DTObs.py 14.1 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
96
97
98
99
            self.telescopeMode = 'J2000'
            self.telescopeOffset = None
        else:
            self.telescopeMode = None
            self.telescopeOffset = None
marc's avatar
marc committed
100
        
marc's avatar
marc committed
101
102
        self.myMeas = Measurements()
        self.myMetaData = MetaData()
marc's avatar
marc committed
103
        self.myBackend = Backend(setmode='Pulsar')
marc's avatar
marc committed
104
        
105
        self.radec = None
106
107
        self.measProgramma = None
        self.integrationTime = None
marc's avatar
marc committed
108
        self.inputFile = None
109
110
        self.outputDir = None
        self.outputFile = None
marc's avatar
marc committed
111
        self.GUIbackendMode = None
112

113
114
        Ui_mainWindow.__init__(self)
        self.setupUi(mainWindow)
marc's avatar
marc committed
115
116
117
118
119
120
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
        
        '''
        Define slot and connections for GUI
        '''
marc's avatar
marc committed
121
        self.actionNew.triggered.connect(self.newPointingsDialog)
122
123
124
125
126
        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)
127
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
128
        self.tableWidgetPointings.itemDoubleClicked.connect(self.viewResultMeasurement)
marc's avatar
marc committed
129

130
        '''
marc's avatar
marc committed
131
        Read the available tools from measurement Class and store them in the 
132
133
134
135
136
        comboBoxProgramma for selection.
        '''
        indexNR=0
        for tool in self.myMeas.getTools():
            self.comboBoxProgramma.setItemText(indexNR, tool)
marc's avatar
marc committed
137
            indexNR+=1
138
139

        self.threadpool = QThreadPool()
marc's avatar
marc committed
140
141
142
143
    
    def newPointingsDialog(self):
        pass
    
144
145
146
147
    def openFileNameDialog(self):
        '''
        Clear tableWidget and read pointings from file
        '''
marc's avatar
marc committed
148
        self.inputFile,_ = QFileDialog.getOpenFileName(None, 'Open File', "templates","Pointing Files (*.pnt);;All Files (*)")
marc's avatar
marc committed
149
150
        if self.inputFile:
            self.loadPointingsInTable(self.inputFile)
151
        logging.info('Selected data file is: {}'.format(self.inputFile))
marc's avatar
marc committed
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    
    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
167
168

        self.tableWidgetPointings.resizeColumnsToContents()
marc's avatar
marc committed
169
        logger.info('Imported {} pointings'.format(rowPosition))
170
171
172
173
174
175
        
    def selectOutputDirectory(self):
        '''
        Select the output directory to store the measurements
        '''
        dialog = QFileDialog()
marc's avatar
marc committed
176
        folder_path = dialog.getExistingDirectory(None, "Select Folder",self.homedir)
177
        self.lineEditOutputDir.setText(folder_path)
marc's avatar
marc committed
178
        
179
180
181
182
183
    def closeMeasurement(self):
        for rowPosition in range(self.tableWidgetPointings.rowCount(),-1,-1):
            self.tableWidgetPointings.removeRow(rowPosition)

    def quitDTObs(self):
184
        logging.shutdown()
185
186
187
188
        sys.exit(app.exec_())

    def allCompleted(self):
        """ Callback function when all pointings completed """
marc's avatar
marc committed
189
        logging.info("All pointings completed")
190
191
192
193
194
195
196

    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
197
198
        self.tableWidgetPointings.setItem(meas_num, 3, QTableWidgetItem(self.outputFile))
        
marc's avatar
marc committed
199
200
201
202
203
        '''
        TODO
        '''
        #self.tableWidgetPointings.item(meas_num, 3).setText(measFile.name))

marc's avatar
marc committed
204
        for column in range(4):
205
206
207
208
209
210
            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):
211
212
213
214
            if self.demo == False:
                self.myDT.setRaDec(setpoint)
                oneSetpointCompleteSignal.emit(setpoint_nr, 'Slewing')
                time.sleep(3)
215
                dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
216
217
218
219
220
221
222
223
                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
224
                    
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
            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
240
241
242
243
244
245
246
247
248
    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()
249
250
251
252
        self.myMetaData.tool = self.measProgramme 
        self.myMetaData.integrationTime = self.integrationTime        
        self.myMetaData.outputDirectory = self.outputDir
        self.myMetaData.outputFile = self.outputFile
253
254
        self.myMetaData.radecSetpoint = self.radec
        self.myMetaData.radecActual = self.radec
marc's avatar
marc committed
255
256
257
        self.myMetaData.telescopeMode = self.telescopeMode
        self.myMetaData.telescopeOffset = self.telescopeOffset
        
258
259
260
        '''
        TODO: read radec Actual from telescope class
        '''
marc's avatar
marc committed
261
262
263
264
265
        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
266
267

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

296
297
298
299
        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)
300
            time.sleep(1)
marc's avatar
marc committed
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
        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'
                    
316
    def startMeasurement(self):
marc's avatar
marc committed
317
318
319
320
321
322
323
        '''
        Put backend in proper mode
        '''
        logging.info('Start Measurement')
        self.readGUIBackendMode()
        self.myBackend.setMode(self.GUIbackendMode)       
        
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
        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
341
        worker.signals.allfinished.connect(self.allCompleted)
marc's avatar
marc committed
342
        logging.info("Measurement stopped")
343

marc's avatar
marc committed
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
    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
        '''
               
362
363
364
365
366
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()
    prog = DTObservationProgram(mainWindow)
    mainWindow.show()
marc's avatar
marc committed
367

368
    sys.exit(app.exec_())