DTObs.py 14.2 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
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
from createpointings import CreatePointings
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
        self.demo = config.getboolean('TestConfig','demo')
marc's avatar
marc committed
92
        logger.debug('Demo status is: {}'.format(self.demo))
93
94
        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
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
marc's avatar
minor    
marc committed
117
        self.formCreatePointings = QWidget()
marc's avatar
marc committed
118
119
120
121
        
        '''
        Define slot and connections for GUI
        '''
marc's avatar
marc committed
122
        self.actionNew.triggered.connect(self.newPointingsDialog)
123
124
125
126
127
        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)
128
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
129
        self.tableWidgetPointings.itemDoubleClicked.connect(self.viewResultMeasurement)
marc's avatar
marc committed
130

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

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

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

    def quitDTObs(self):
marc's avatar
marc committed
186
        logger.shutdown()
187
188
189
190
        sys.exit(app.exec_())

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

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

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

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

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

marc's avatar
marc committed
346
347
348
    def viewResultMeasurement(self):
        currentRow = self.tableWidgetPointings.currentRow()
        resultFile = self.tableWidgetPointings.item(currentRow,3).text()
marc's avatar
marc committed
349
350
        logger.debug('Selected row is: {}'.format(currentRow))
        logger.info('Show result in gnuplot for file: {}'.format(resultFile))
marc's avatar
marc committed
351
352
353
354
355
356
357
358
359
360
361
362
363
        '''
        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
        '''
               
364
365
366
367
368
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()
    prog = DTObservationProgram(mainWindow)
    mainWindow.show()
marc's avatar
marc committed
369

370
    sys.exit(app.exec_())