DTObs.py 13.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
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
        self.myDT = telescope(setmode='J2000', consoleHost='consoledemo.dmz.camras.nl')
marc's avatar
marc committed
92
        
marc's avatar
marc committed
93
94
        self.myMeas = Measurements()
        self.myMetaData = MetaData()
marc's avatar
marc committed
95
        self.myBackend = Backend(setmode='Pulsar')
marc's avatar
marc committed
96
        
97
        self.radec = None
98
99
        self.measProgramma = None
        self.integrationTime = None
marc's avatar
marc committed
100
        self.inputFile = None
101
102
        self.outputDir = None
        self.outputFile = None
marc's avatar
marc committed
103
        self.GUIbackendMode = None
104

105
106
        Ui_mainWindow.__init__(self)
        self.setupUi(mainWindow)
marc's avatar
marc committed
107
108
109
110
111
112
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
        
        '''
        Define slot and connections for GUI
        '''
113
114
115
116
117
        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)
118
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
119
        self.tableWidgetPointings.itemDoubleClicked.connect(self.viewResultMeasurement)
marc's avatar
marc committed
120

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

        self.threadpool = QThreadPool()

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

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

    def quitDTObs(self):
171
        logging.shutdown()
172
173
174
175
        sys.exit(app.exec_())

    def allCompleted(self):
        """ Callback function when all pointings completed """
marc's avatar
marc committed
176
        logging.info("All pointings completed")
177
178
179
180
181
182
183

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

marc's avatar
marc committed
191
        for column in range(4):
192
193
194
195
196
197
            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):
198
199
200
201
202
203
204
205
206
207
208
            self.myDT.setRaDec(setpoint)
            oneSetpointCompleteSignal.emit(setpoint_nr, 'Slewing')
            time.sleep(3)
            dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
            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)
209
                dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
marc's avatar
marc committed
210
                    
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
            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
226
227
228
229
230
231
232
233
234
    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()
235
236
237
238
        self.myMetaData.tool = self.measProgramme 
        self.myMetaData.integrationTime = self.integrationTime        
        self.myMetaData.outputDirectory = self.outputDir
        self.myMetaData.outputFile = self.outputFile
marc's avatar
marc committed
239
        self.myMetaData.RaDEC = self.radec
marc's avatar
marc committed
240
241
242
243
244
        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
245
246

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

275
276
277
278
        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)
279
            time.sleep(1)
marc's avatar
marc committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
        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'
                    
295
    def startMeasurement(self):
marc's avatar
marc committed
296
297
298
299
300
301
302
        '''
        Put backend in proper mode
        '''
        logging.info('Start Measurement')
        self.readGUIBackendMode()
        self.myBackend.setMode(self.GUIbackendMode)       
        
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
        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
320
        worker.signals.allfinished.connect(self.allCompleted)
marc's avatar
marc committed
321
        logging.info("Measurement stopped")
322

marc's avatar
marc committed
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
    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
        '''
               
341
342
343
344
345
346
347
348
if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()

    prog = DTObservationProgram(mainWindow)

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