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

106
107
108
109
110
111
112
        Ui_mainWindow.__init__(self)
        self.setupUi(mainWindow)
        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)
113
        self.pushButtonOutputDirectory.clicked.connect(self.selectOutputDirectory)
marc's avatar
marc committed
114
115
        self.homedir = os.environ['HOME']
        self.lineEditOutputDir.setText(self.homedir)
marc's avatar
marc committed
116

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

        self.threadpool = QThreadPool()

    def openFileNameDialog(self):
        '''
        Clear tableWidget and read pointings from file
        '''
132
        fileName,_ = QFileDialog.getOpenFileName(None, 'Open File', "","Meas Files (*.dat);;All Files (*)")
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
        if fileName:
            reader = csv.reader(open(fileName),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(""))

                rowPosition += 1

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

    def quitDTObs(self):
164
        logging.shutdown()
165
166
167
168
        sys.exit(app.exec_())

    def allCompleted(self):
        """ Callback function when all pointings completed """
marc's avatar
marc committed
169
        logging.info("All pointings completed")
170
171
172
173
174
175
176
177

    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
178
179
180
181
182
        '''
        TODO
        '''
        #self.tableWidgetPointings.item(meas_num, 3).setText(measFile.name))

marc's avatar
marc committed
183
        for column in range(3):
184
185
186
187
188
189
            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):
marc's avatar
marc committed
190
191
192
193
            if self.TelescopeEnabled == True:
                self.myDT.setRaDec(setpoint)
                oneSetpointCompleteSignal.emit(setpoint_nr, 'Slewing')
                time.sleep(3)
194
                dist = np.sqrt(self.myDT.dist_el**2+self.myDT.dist_az**2)
marc's avatar
marc committed
195
196
197
198
199
200
201
202
203
                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)
                    
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            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
219
220
221
222
223
224
225
226
227
    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()
228
229
230
231
        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
232
        self.myMetaData.RaDEC = self.radec
marc's avatar
marc committed
233
234
235
236
237
        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
238
239

    def writeMetaData(self,file):
240
        for key, value in self.myMetaData.getMetaData().items():
marc's avatar
marc committed
241
242
            #file.write('#' + str(key) + ':' + '\t' + str(value) + '\n')
            file.write('#key: {}\t value: {}\n'.format(key,value))
243
            logging.info('#key: {}\t value: {}'.format(key,value))
244
245
246
247

    def doMeasurement(self, measnum, progressSignal=None):
        """ 
        Dumping data into file including meta-data
248
        measProgramme: cli command read from comboBoxProgramma
249
        integrationTime: string read from spinBoxIntTime
250
        measurement: result of measurement of which 
251
        data: is the stdout result which need to be stored line by line
marc's avatar
marc committed
252
        measFile: result file storing the stdout results including meta data
253
        """
254
255
256
257
258
        self.integrationTime = self.spinBoxIntTime.value()
        self.measProgramme = self.comboBoxProgramma.currentText()
        self.outputDir = self.lineEditOutputDir.text()
        self.outputFile = '/DT-' + '{:03d}'.format(measnum) + '-' + time.strftime("%Y%m%d") + '.dat'
        measFile = open(self.outputDir + self.outputFile,'w')
marc's avatar
marc committed
259
        
260
261
        self.setMetaData()
        self.writeMetaData(measFile)
marc's avatar
marc committed
262
                
263
        logging.info("Output to file: {}".format(measFile))
marc's avatar
marc committed
264
        
265
        self.myMeas.startMeasurement(self.measProgramme, self.integrationTime, measFile)
266

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

if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = QMainWindow()

    prog = DTObservationProgram(mainWindow)

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