serverWsgi.py 54 KB
Newer Older
Caillat Michel's avatar
Caillat Michel committed
1
#!/usr/bin/env python
Caillat Michel's avatar
Caillat Michel committed
2
"""
Caillat Michel's avatar
Caillat Michel committed
3
4
5
@api {NOOP} noAPI
@apiGroup _Introduction
@apiDescription
Caillat Michel's avatar
Caillat Michel committed
6
The yafitss http server.
Caillat Michel's avatar
Caillat Michel committed
7
8
9

Summary
-------
Caillat Michel's avatar
Caillat Michel committed
10
The server accepts a number of GET and POST calls allowing to work remotely, as an HTTP client, on a collection of FITS files containing images.
Caillat Michel's avatar
Caillat Michel committed
11

Caillat Michel's avatar
Caillat Michel committed
12
13
14
Technical aspects of our server
-------------------------------
The server is a bottle HTTP server. It accepts requests ( GET or POST ), extracts their parameters, converts
Caillat Michel's avatar
Caillat Michel committed
15
them from the string type to the appropriate type, delegates the processing to a dataManager layer from which
Caillat Michel's avatar
Caillat Michel committed
16
it receives the result to be sent back to the HTTP client in **json** form.
Caillat Michel's avatar
Caillat Michel committed
17

Caillat Michel's avatar
Caillat Michel committed
18
19
Conventions
-----------
Caillat Michel's avatar
Caillat Michel committed
20
21
22
23
Input parameters
----------------

Each method GET or POST of an HTTP server expects zero, one or more parameters which are passed via a dictionary structure, i.e a sequence of ``key:value`` pairs. Of course this is true for `yafitss`, but in order to give the server the flavour of a domain specific language a convention for the names of the keys is adopted.\
Caillat Michel's avatar
Caillat Michel committed
24
The list below enumerates those names, the types and the semantics of their associated values. Indeed types are not real types as in the usual sense since the values are always passed as strings; they are indications on the form those values should have:
Caillat Michel's avatar
Caillat Michel committed
25

Caillat Michel's avatar
Caillat Michel committed
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 * ``relFITSFilePath`` {String} the path of the FITS file of interest relative to the root directory of the FITS files
 * ``iRA`` {uInt} an indice along the RA axis
 * ``iDEC`` {uInt} an indice along the DEC axis
 * ``RAinDD`` {Float} a right ascension expressed in decimal degrees
 * ``DECinDD`` {Float} a declination expressed in decimal degrees
 * ``iRA0`` {uInt} the lower bound of a range of indices along the RA axis
 * ``iRA1`` {uInt} the upper bound of a range of indices along the RA axis
 * ``iRAstep`` {uInt} a step in a range of indices along the RA axis
 * ``iDEC0`` {uInt} the lower bound of a range of indices along the DEC axis
 * ``iDEC1`` {uInt} the upper bound of a range of indices along the DEC axis
 * ``iDECstep`` {uInt} a step in a range of indices along the DEC axis
 * ``iFREQ`` {uInt} an indice along the FREQ axis
 * ``iFREQ0`` {uInt} the lower bound of a range of indices along the FREQ axis
 * ``iFREQ1`` {uInt} the upper bound of a range of indices along the FREQ axis

Returned values
---------------
Caillat Michel's avatar
Caillat Michel committed
43
API calls return systematically  a compound result with the following structure :
Caillat Michel's avatar
Caillat Michel committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

```
 {
 "status": True of False,

 "message": <some text mostly used when something went wrong>,

 "result" : <the expected result or None>
 }
 ```

 Consequently given a returned value stored in a variable `result`, a good practice is to proceed as follows:

 * `if result["status"] :`
    * retrieve the payload in `result["result"]`
 * `else :`
    * retrieve an error message in `result["message"]`
61
"""
Caillat Michel's avatar
Caillat Michel committed
62
from bottle import TEMPLATE_PATH, route, run, get, request, view, default_app, response, error, static_file, template
Caillat Michel's avatar
Caillat Michel committed
63
64
65
66
67
68
69
70
71
72
73
74
75
76

import astropy

import sys
import socket
import getopt
import os
import traceback
import collections
import numpy as np

import json
import errno
import logging
77
from autologging import logged, TRACE, traced
Caillat Michel's avatar
Caillat Michel committed
78
from itertools import cycle
Caillat Michel's avatar
Caillat Michel committed
79
80
81
82
83
84
85
86

ARTEMIXDataDir = None
FITSRootDir = None
SAMPDataDir = None
baseUrl = "/artemix"

logger = None
dm = None
Caillat Michel's avatar
Caillat Michel committed
87
app = None
Caillat Michel's avatar
Caillat Michel committed
88

89
import dataManager_michel as dataManager
Caillat Michel's avatar
Caillat Michel committed
90
91
92
93
94
95
96
97

#####################################################################
#                                                                   #
#                            Utilities                              #
#                                                                   #
#####################################################################

def enable_cors(fn):
Caillat Michel's avatar
Caillat Michel committed
98
99
100
    """
    enable cross domain ajax requests
    """
101
    def _enable_cors(*args, **kwargs):
Caillat Michel's avatar
Caillat Michel committed
102
    # set CORS headers
103
104
105
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
        response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
Caillat Michel's avatar
Caillat Michel committed
106

107
108
109
        if request.method != 'OPTIONS':
            # actual request; reply with the actual response
            return fn(*args, **kwargs)
Caillat Michel's avatar
Caillat Michel committed
110

111
    return _enable_cors
Caillat Michel's avatar
Caillat Michel committed
112
113
114
115
116
117
118
119

def getFloatValue(value):
    if value is not None and str(value) != "null":
        return float(value)
    return None

def getIntValue(value):
    if value is not None and str(value) != "null":
120
        return int(float(value))
Caillat Michel's avatar
Caillat Michel committed
121
122
123
124
125
126
127
128
    return None

def getBoolValue(value):
    if value is not None and str(value) != "null":
        return value == "true"
    return None

def cleanNanInList(arr):
Caillat Michel's avatar
Caillat Michel committed
129
130
131
    """
    	Replaces NaN values by None in array
    """
Caillat Michel's avatar
Caillat Michel committed
132
133
134
135
136
    result = arr
    result[np.isnan(result)] = None
    return result

def rebuildFilename(filename):
Caillat Michel's avatar
Caillat Michel committed
137
138
139
140
    """
        Given filename ( string )
        returns a string where each space character present in filename is replaced by '+' character.
    """
Caillat Michel's avatar
Caillat Michel committed
141
142
143
144
145
146
    result = byteify(filename.replace(" ", "+"))
    if ((result[0] == '"' and result[len(result) - 1] == '"') or ( result[0] == "'" and result[len(result) - 1] == "'" )):
        result = result[1:-1]
    return result

def byteify(input):
Caillat Michel's avatar
Caillat Michel committed
147
148
149
150
    """
        A function to transform the content of a dictionary a unicode value into its ascii's
        see https://stackoverflow.com/questions/956867/how-to-get-string-objects-instead-of-unicode-from-json
    """
Caillat Michel's avatar
Caillat Michel committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
    result = None
    if isinstance(input, dict):
        result = {byteify(key): byteify(value)
                for key, value in input.items()}
    elif isinstance(input, list):
        result = [byteify(element) for element in input]
    elif isinstance(input, str):
        result =  input.encode('utf-8').decode('utf-8')
    elif isinstance(input, bytes):
        result = input.encode('utf-8').decode('utf-8')
    else:
        result = input
    #logging.debug("result = %r" % result)
    return result


#####################################################################
#                                                                   #
#                   Routes definitions                              #
#                                                                   #
#####################################################################

"""
Caillat Michel's avatar
Caillat Michel committed
174
@api {GET} artemix/ping ping - Check server readiness
Caillat Michel's avatar
Caillat Michel committed
175
176
177
178
179
180
@apiName Check server readiness
@apiGroup server

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {status: True, message : 'Up and waiting', 'result': }
Caillat Michel's avatar
Caillat Michel committed
181
"""
Caillat Michel's avatar
Caillat Michel committed
182

Caillat Michel's avatar
Caillat Michel committed
183
184
@route(baseUrl+"/ping", name='upAnWaiting', method='GET')
@enable_cors
Caillat Michel's avatar
Caillat Michel committed
185

Caillat Michel's avatar
Caillat Michel committed
186
def upAnWaiting():
Caillat Michel's avatar
Caillat Michel committed
187
188
189
190
191
192
193
194
195
196
197
    """/ping.

    Check that the server is alive and waiting

    Returns:
            A dict with "status" == True and "message" == "Up and Waiting"
    """
    logger.debug("upAndWaiting - wrapper : entering")
    logger.debug("upAndWaiting - wrapper : exiting")
    return "{status: %r, message : 'Up and waiting', 'result': %r}" % (True, request)

Caillat Michel's avatar
Caillat Michel committed
198
199

"""
Caillat Michel's avatar
Caillat Michel committed
200
@api {GET} artemix/degToHMSDMS degToHMSDMS - Converts a (RA,DEC) pair from decimal degrees to HMS DMS
Caillat Michel's avatar
Caillat Michel committed
201
202
203
204
205
206
207
208
209
210
211
@apiName degToHMSDMS
@apiGroup conversions

@apiParam {String} relFITSFilePath  the path to the FITS file of interest.
@apiParam {Float} RAinDD  RA in decimal degree
@apiParam {Float} DECinDD  DEC in decimale degree


@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
     {'status': True, 'message': '', 'result': '12h30m50.1624s +12d23m08.2758s'}
Caillat Michel's avatar
Caillat Michel committed
212
213
214
215
"""
@route( baseUrl+'/degToHMSDMS', name='degToHMSDMS', method='GET')
@enable_cors
def degToHMSDMS():
Caillat Michel's avatar
Caillat Michel committed
216
217
218
219
    """
        Given two values expressed in degrees one for a Right Ascension (ra) and one for a Declination
        returns their respectives conversion in Hour Minute Second and Degree Minute Second
    """
220
221
222
223
224
    logger.debug("degToHMSDMS - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        RAinDD = getFloatValue(request.GET['RAinDD'])
        DECinDD = getFloatValue(request.GET['DECinDD'])
225
        result = json.dumps(dm.degToHMSDMS(relFITSFilePath, RAinDD, DECinDD))
226
    except Exception as e:
227
228
229
230
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}
231

Caillat Michel's avatar
Caillat Michel committed
232
    response.content_type = "application/json; charset=utf-8"
233
234
    logger.debug("degToHMSDMS - wrapper : exiting")

235
    return result
Caillat Michel's avatar
Caillat Michel committed
236
237


Caillat Michel's avatar
Caillat Michel committed
238

Caillat Michel's avatar
Caillat Michel committed
239
"""
Caillat Michel's avatar
Caillat Michel committed
240
@api {GET} artemix/rangeToHMS rangeToHMS - Generates a sequence of HMS from a range of RA indexes
Caillat Michel's avatar
Caillat Michel committed
241
242
243
244
245
246
247
248
249
250
251
@apiName rangeToHMS
@apiGroup conversions

@apiParam {String} relFITSFilePath  the path to the FITS file of interest.
@apiParam {uInt} iRAO  the lower index of the range along the RA axis.
@apiParam {uInt} iRA1 the upper index of the range along the RA axis.
@apiParam {uInt} iRAstep  the step along the RA axis.

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
      {'status': True, 'message': '', 'result': ['12h30m50.8158s +12d23m07.6437s', '12h30m50.1196s +12d23m07.6439s', '12h30m49.4234s +12d23m07.6439s', '12h30m48.7272s +12d23m07.6439s']}
Caillat Michel's avatar
Caillat Michel committed
252
253
254
255
"""
@route( baseUrl+'/rangeToHMS', name='rangeToHMS', method='GET')
@enable_cors
def rangeToHMS():
256
257
258
259
260
261
    logger.debug("rangeToHMS - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        iRA0 = getIntValue(request.GET['iRA0'])
        iRA1 = getIntValue(request.GET['iRA1'])
        iRAstep = getIntValue(request.GET['iRAstep'])
262
        result = json.dumps(dm.rangeToHMS(relFITSFilePath, iRA0, iRA1, iRAstep))
263
    except Exception as e:
264
265
266
267
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
268
269

    response.content_type = "application/json; charset=utf-8"
270
    logger.debug("rangeToHMS - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
271

272
    return result
Caillat Michel's avatar
Caillat Michel committed
273
274

"""
Caillat Michel's avatar
Caillat Michel committed
275
276
277
278
279
280
281
282
283
284
285
286
@api {GET} artemix/rangeToDMS Generates a sequence of DMS from a range of DEC indexes
@apiName rangeToDMS
@apiGroup conversions

@apiParam {String} relFITSFilePath  the path to the FITS file of interest.
@apiParam {uInt} iDECO  the lower index of the range along the DEC axis.
@apiParam {uInt} iDEC1 the upper index of the range along the DEC axis.
@apiParam {uInt}  iDECstep the step along the DEC axis.

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
{'status': True, 'message': '', 'result': ['12h30m50.8158s +12d23m07.6437s', '12h30m50.8158s +12d23m17.8437s', '12h30m50.8158s +12d23m28.0437s', '12h30m50.8158s +12d23m38.2437s']}
Caillat Michel's avatar
Caillat Michel committed
287
288
289
290
"""
@route( baseUrl+'/rangeToDMS', name='rangeToDMS', method='GET')
@enable_cors
def rangeToDMS():
291
292
293
294
295
296
    logger.debug("rangeToDMS - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        iDEC0 = getIntValue(request.GET['iDEC0'])
        iDEC1 = getIntValue(request.GET['iDEC1'])
        iDECstep = getIntValue(request.GET['iDECstep'])
297
        result =json.dumps(dm.rangeToDMS(relFITSFilePath, iDEC0, iDEC1, iDECstep))
298
    except Exception as e:
299
300
301
302
303
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
304
    response.content_type = "application/json; charset=utf-8"
305
    logger.debug("rangeToDMS - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
306

307
    return result
Caillat Michel's avatar
Caillat Michel committed
308
"""
Caillat Michel's avatar
Caillat Michel committed
309
@api {GET} artemix/setData setData - Loads a FITS file in server's memory and return its header.
Caillat Michel's avatar
Caillat Michel committed
310
311
@apiName setData
@apiGroup data selection
Caillat Michel's avatar
Caillat Michel committed
312

Caillat Michel's avatar
Caillat Michel committed
313
314
315
316
317
318
@apiParam {String} relFITSFilePath the path to the FITS file of interest. If it's already loaded, just returns the header.

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {'status': True, 'message': '', 'result': '{"SIMPLE": true, "BITPIX": -32, "NAXIS": 4, "NAXIS1": 800, "NAXIS2": 800, "NAXIS3": 24, "NAXIS4": 1, "EXTEND": true, "BSCALE": 1.0, "BZERO": 0.0, "BMAJ": 7.385119795799e-05, "BMIN": 5.762678467565e-05, "BPA": 15.67044067383, "BTYPE": "Intensity", "OBJECT": "M87", "BUNIT": "Jy/beam", "EQUINOX": 2000.0, "RADESYS": "FK5", "LONPOLE": 180.0, "LATPOLE": 12.39112331, "PC01_01": 1.0, "PC02_01": 0.0, "PC03_01": 0.0, "PC04_01": 0.0, "PC01_02": 0.0, "PC02_02": 1.0, "PC03_02": 0.0, "PC04_02": 0.0, "PC01_03": 0.0, "PC02_03": 0.0, "PC03_03": 1.0, "PC04_03": 0.0, "PC01_04": 0.0, "PC02_04": 0.0, "PC03_04": 0.0, "PC04_04": 1.0, "CTYPE1": "RA---SIN", "CRVAL1": 187.70593075, "CDELT1": -1.416666666667e-05, "CRPIX1": 401.0, "CUNIT1": "deg", "CTYPE2": "DEC--SIN", "CRVAL2": 12.39112331, "CDELT2": 1.416666666667e-05, "CRPIX2": 401.0, "CUNIT2": "deg", "CTYPE3": "FREQ", "CRVAL3": 230436102960.0, "CDELT3": -76899199.37878, "CRPIX3": 1.0, "CUNIT3": "Hz", "CTYPE4": "STOKES", "CRVAL4": 1.0, "CDELT4": 1.0, "CRPIX4": 1.0, "CUNIT4": "", "PV2_1": 0.0, "PV2_2": 0.0, "RESTFRQ": 230538000000.0, "SPECSYS": "BARYCENT", "ALTRVAL": 132507.283331, "ALTRPIX": 1.0, "VELREF": 258, "TELESCOP": "ALMA", "OBSERVER": "jtan", "DATE-OBS": "2015-08-16T19:00:41.616000", "TIMESYS": "UTC", "OBSRA": 187.70593075, "OBSDEC": 12.39112331, "OBSGEO-X": 2225142.180269, "OBSGEO-Y": -5440307.370349, "OBSGEO-Z": -2481029.851874, "DATE": "2015-12-08T18:24:15.408000", "ORIGIN": "CASA 4.5.0-REL (r35147)", "INSTRUME": "Unknown"}'}
"""
Caillat Michel's avatar
Caillat Michel committed
319
320
321
@route( baseUrl+'/setData', name='setData', method='GET')
@enable_cors
def setData():
322
323
324
    logger.debug("setData - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
325
        result = json.dumps(dm.setData(relFITSFilePath))
326
    except Exception as e:
327
328
329
330
331
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
332
    response.content_type = "application/json; charset=utf-8"
333
    logger.debug("setData - wrapper : exiting")
334
    return result
Caillat Michel's avatar
Caillat Michel committed
335
336

"""
Caillat Michel's avatar
Caillat Michel committed
337
338
339
340
341
342
343
344
345
346
@api {GET} artemix/getDimensions Returns the shape of the data.
@apiName getDimensions
@apiGroup data infos

@apiParam {String} relFITSFilePath the path to the FITS file of interest

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {'status': True, 'message': '', 'result': [24, 800, 800]}

Caillat Michel's avatar
Caillat Michel committed
347
348
349
350
"""
@route( baseUrl+'/getDimensions', name='getDimensions', method='GET')
@enable_cors
def getDimensions():
351
352
353
    logger.debug("getDimensions - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
354
        result = json.dumps(dm.getDimensions(relFITSFilePath))
355
    except Exception as e:
356
357
358
359
360
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
361
    response.content_type = "application/json; charset=utf-8"
362
    logger.debug("getDimensions - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
363

364
    return result
Caillat Michel's avatar
Caillat Michel committed
365
366
367
368
369
370
371
372
373
374
375
376
377
378

"""
    Given a multidimensional array l
    return a flattened representation of l
"""
@route( baseUrl+'/flatten', name='flatten', method='GET')
@enable_cors
def flatten():

    l = request.GET['l']

    return json.dumps(dm.flatten(l))

"""
Caillat Michel's avatar
Caillat Michel committed
379
380
381
382
383
384
385
386
387
388
389
390
391
@api {GET} artemix/getPixelValueAtiFreqiRAiDEC Returns the value of a pixel located by a triple (iRA, iDEC, iFREQ)
@apiName getPixelValueAtiFreqiRAiDEC
@apiGroup values and projections

@apiParam {String} relFITSFilePath the path to the FITS file of interest
@apiParam {uInt} iRA index along the RA axis
@apiParam {uInt} iDEC index along the DEC axis
@apiParam {uInt} iFREQ index along the FREQ axis

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {'status': True, 'message': '', 'result': -0.00207878858782351}

Caillat Michel's avatar
Caillat Michel committed
392
393
394
395
396
"""
@route( baseUrl+'/getPixelValueAtiFreqiRAiDEC', name="getPixelValueAtiFreqiRAiDEC", method='GET')
@enable_cors
def getPixelValueAtiFreqiRAiDEC():
    logger.debug("getPixelValueAtiFreqiRAiDEC - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
397
398
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
399
        iFREQ = getIntValue(request.GET['iFREQ'])
Caillat Michel's avatar
Caillat Michel committed
400
401
        iRA = getIntValue(request.GET['iRA'])
        iDEC = getIntValue(request.GET['iDEC'])
402
        result = json.dumps(dm.getPixelValueAtiFreqiRAiDEC(relFITSFilePath, iFREQ, iRA, iDEC))
Caillat Michel's avatar
Caillat Michel committed
403
    except Exception as e:
404
405
406
407
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
408
409
410
411
412
413

    response.content_type = "application/json; charset=utf-8"
    logger.debug("getPixelValueAtiFreqiRAiDEC - wrapper : exiting" )
    return result

"""
Caillat Michel's avatar
Caillat Michel committed
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
@api {GET} artemix/getSlice Returns a (RA, DEC) plane for a given frequency.
@apiName getSlice
@apiGroup  data selection

@apiDescription The result is returned in a list of list ( 2D data ) with RA varying more
rapidly than DEC. The result may contain nan values.

@apiParam {String} relFITSFilePath the path to the FITS file of interest
@apiParam {uInt} iFREQ index along the FREQ axis of the (RA, DEC) plane to return

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {'status': True, 'message': '', 'result': [[...]...[...]]}


Caillat Michel's avatar
Caillat Michel committed
429
430
431
432
"""
@route( baseUrl+'/getSlice', name='getSlice', method='GET')
@enable_cors
def getSlice():
433
    logger.debug("getSlice - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
434

Caillat Michel's avatar
Caillat Michel committed
435
436
437
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        iFREQ = int(request.GET['iFREQ'])
438
439
        result = json.dumps(dm.getSlice(relFITSFilePath, iFREQ))

Caillat Michel's avatar
Caillat Michel committed
440
    except Exception as e:
441
442
443
444
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
445

Caillat Michel's avatar
Caillat Michel committed
446
    response.content_type = "application/json; charset=utf-8"
447
448
    logger.debug("getSlice - wrapper : exiting" )

449
    return result
Caillat Michel's avatar
Caillat Michel committed
450
451
452

"""
    Given one pair, x and y, of integer coordinates in the RAxDEC plane and
Caillat Michel's avatar
Caillat Michel committed
453
454
    a frequency range defined by a pair of integers iFREQ0 and iFRE1
    returns the values contained in the data cube at [iFREQ0:iFRE1, x, y]
Caillat Michel's avatar
Caillat Michel committed
455
"""
Caillat Michel's avatar
Caillat Michel committed
456
@route( baseUrl+'/getSpectrum', name='getSpectrum', method='GET')
Caillat Michel's avatar
Caillat Michel committed
457
@enable_cors
Caillat Michel's avatar
Caillat Michel committed
458
def getSpectrum():
Caillat Michel's avatar
Caillat Michel committed
459
    logger.debug("getSpectrum - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
460
461
462
463
464
465
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        iRA = getIntValue(request.GET['iRA'])
        iDEC = getIntValue(request.GET['iDEC'])
        iFREQ0 = getIntValue(request.GET['iFREQ0'])
        iFREQ1 = getIntValue(request.GET['iFREQ1'])
Caillat Michel's avatar
Caillat Michel committed
466
        result = json.dumps(dm.getSpectrum(relFITSFilePath, iRA, iDEC, iFREQ0, iFREQ1))
Caillat Michel's avatar
Caillat Michel committed
467
    except Exception as e:
468
469
470
471
472
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
473
    response.content_type = "application/json; charset=utf-8"
Caillat Michel's avatar
Caillat Michel committed
474
    logger.debug("getSpectrum - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
475

476
    return result
Caillat Michel's avatar
Caillat Michel committed
477
478

"""
Caillat Michel's avatar
Caillat Michel committed
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
@api {GET} getAverageSpectrum Returns a spectrum whose values are obtained by sums on (RA, DEC) planes.
@apiName getAverageSpectrum
@apiGroup values and projections
@apiDescription
Given two pairs -- `iRA0,iRA1` and `iDEC0,iDEC1` -- defining a rectangular area in the RAxDEC plane
and a frequency range defined by a pair of integers `iFREQ0`,`iFREQ1`, a call to this API
returns the result of `numpy.nansum(datacube[iFREQ0:iFRE1, iDEC0:iDEC1, iRA0:iRA1], (1,2))`
multiplied by the appropriate value to take into account the telescope's beam when its relevant
(radio astronomical data)

The result is returned as a dictionary with two keys whose values are defined below :

* `"averageSpectrum"` : the average spectrum as a list of float values
* `"averageSpectrumFits"` : the average spectrum as a FITS table. Note this part is
returned **if and only if** the parameter `retFITS` has been passed with a True value. This optional
product is mainly intended for distribution via SAMP.

@apiParam {String} relFITSFilePath the path to the FITS file of interest.
@apiParam {uInt} iRAO  the lower index of the range along the RA axis.
@apiParam {uInt} iRA1  the upper index of the range along the RA axis.
@apiParam {uInt} iDECO the lower index of the range along the DEC axis.
@apiParam {uInt} iDEC1 the upper index of the range along the DEC axis.
@apiParam {uInt} iFREQO the lower index of the range along the FREQ axis.
@apiParam {uInt} iFREQ1 the upper index of the range along the FREQ axis.
@apiParam  {Boolean} retFITS True means returns the spectrum also as a FITS table.

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
{'status': True, 'message': '', 'result': {'averageSpectrum': [-0.20683714747428894, 0.06167888641357422, -0.01623634621500969, 0.024083947762846947, -0.02355683408677578, -0.05549465864896774, -0.08872086554765701, -0.08671190589666367, -0.06696601957082748, 0.05688297748565674, -0.13291282951831818, -0.21775048971176147, -0.24340537190437317, -0.10746107250452042, -0.20933619141578674, -0.19364875555038452, -0.2382306307554245, -0.28746917843818665, -0.2542598247528076, -0.2730403542518616, -0.28176575899124146, -0.17414641380310059, -0.30788716673851013, -0.39479535818099976], 'averageSpectrumFits': None}}
Caillat Michel's avatar
Caillat Michel committed
508
509
510
511
"""
@route( baseUrl+'/getAverageSpectrum', name='getAverageSpectrum', method='GET')
@enable_cors
def getAverageSpectrum():
512
    logger.debug("getAverageSpectrum - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
513
514
515

    relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])

Caillat Michel's avatar
Caillat Michel committed
516
517
518
519
520
521
    try:
        iDEC0 = getIntValue(request.GET['iDEC0'])
        iDEC1 = getIntValue(request.GET['iDEC1'])
        iRA0 = getIntValue(request.GET['iRA0'])
        iRA1 = getIntValue(request.GET['iRA1'])
        retFITS = getBoolValue(request.GET['retFITS'])
522
        result = json.dumps(dm.getAverageSpectrum(relFITSFilePath, iDEC0, iDEC1, iRA0, iRA1, retFITS))
Caillat Michel's avatar
Caillat Michel committed
523
    except Exception as e:
524
525
526
527
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
528

Caillat Michel's avatar
Caillat Michel committed
529
    response.content_type = "application/json; charset=utf-8"
530
    logger.debug("getAverageSpectrum - wrapper : exiting" )
Caillat Michel's avatar
Caillat Michel committed
531
532
533
534

    return result

"""
Caillat Michel's avatar
Caillat Michel committed
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
@api {GET} artemix/getSumOverSliceRectArea Weighted sum over a rectangular area in a RA,DEC plane
@apiName getSumOnSliceRectArea
@apiGroup values and projections
@apiDescription Given two pairs of indexes -- `iRA0,iRA1` and `iDEC0,iDEC1` -- defining a rectangular area in the RA,DEC plane
and a frequency defined by an integer iFREQ returns the result of `numpy.nansum(datacube[iFREQ, iDEC0:iDEC1, iRA0:iRA1], (1,2))`
multiplied by the appropriate value to take into account the telescope's beam when it's relevant ( radio astronomical data ).

The result is a float value.

@apiParam {String} relFITSFilePath the path to the FITS file of interest.
@apiParam {uInt} iRAO  the lower index of the range along the RA axis.
@apiParam {uInt} iRA1  the upper index of the range along the RA axis.
@apiParam {uInt} iDECO  the lower index of the range along the DEC axis.
@apiParam {uInt} iDEC1  the upper index of the range along the DEC axis.
@apiParam {uInt} iFREQ index along the FREQ axis.

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
 {'status': True, 'message': '', 'result': -0.24340537190437317}

Caillat Michel's avatar
Caillat Michel committed
555
556
557
558
"""
@route( baseUrl+'/getSumOverSliceRectArea', name='getSumOverSliceRectArea', method='GET')
@enable_cors
def getSumOverSliceRectArea():
Caillat Michel's avatar
Caillat Michel committed
559
560
561
562
563
564
565
566
    logger.debug("getSumOverSliceRectArea - wrapper : entering" )
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        DECPix0 = getIntValue(request.GET['DECPix0'])
        DECPix1 = getIntValue(request.GET['DECPix1'])
        RAPix0 = getIntValue(request.GET['RAPix0'])
        RAPix1 = getIntValue(request.GET['RAPix1'])
        iFREQ = getIntValue(request.GET['iFREQ'])
567
        result = json.dumps(dm.getSumOverSliceRectArea(relFITSFilePath,  iFREQ, RAPix0, RAPix1, DECPix0, DECPix1))
Caillat Michel's avatar
Caillat Michel committed
568
    except Exception as e:
569
570
571
572
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
573
574

    response.content_type = "application/json; charset=utf-8"
Caillat Michel's avatar
Caillat Michel committed
575
576
    logger.debug("getSumOverSliceRectArea - wrapper : exiting" )

Caillat Michel's avatar
Caillat Michel committed
577
578
579
    return result

"""
Caillat Michel's avatar
Caillat Michel committed
580
    Given two pairs -- iRA0,iRA1 and iDEC0,iDEC1 -- defining a rectangular area the RAxDEC plane
Caillat Michel's avatar
Caillat Michel committed
581
582
    and a frequency range defined by a pair of integers iFREQ0 and iFREQ1
    returns the result of numpy.nansum(datacube[iFREQ0:iFREQ1, iDEC0:iDEC1, iRA0:iRA1], 0)
Caillat Michel's avatar
Caillat Michel committed
583
584
585
586
587
    multiplied by 4 * log(2) * cdelt1 * pi / 180 / 4.86e-6 * cdelt2 * pi / 180 / 4.86e-6
"""
@route( baseUrl+'/getAverage', name='getAverage', method='GET')
@enable_cors
def getAverage():
Caillat Michel's avatar
Caillat Michel committed
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
    logger.debug("getAverage - wrapper : entering" )
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        iDEC0 = getIntValue(request.GET['iDEC0'])
        iDEC1 = getIntValue(request.GET['iDEC1'])
        iRA1 = getIntValue(request.GET['iRA1'])
        iRA0 = getIntValue(request.GET['iRA0'])
        iFREQ0 = getIntValue(request.GET['iFREQ0'])
        iFREQ1 = getIntValue(request.GET['iFREQ1'])
        retFITS = getBoolValue(request.GET['retFITS'])
        result = dm.getAverage(relFITSFilePath, iFREQ0, iFREQ1, iDEC0, iDEC1, iRA0, iRA1, retFITS)
    except Exception as e:
        logger.debug(f'{e}')
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
605

Caillat Michel's avatar
Caillat Michel committed
606
    response.content_type = "application/json; charset=utf-8"
Caillat Michel's avatar
Caillat Michel committed
607
    logger.debug("getAverage - wrapper : exiting" )
Caillat Michel's avatar
Caillat Michel committed
608
609
610
611
612
613
614
615
    return json.dumps(result)

"""
Create a FITS file whose type is TABLE and content is the spectrum at position (iRA, iDEC)
along with the frequencies/velocities.

Returns the content of the file.
"""
Caillat Michel's avatar
Caillat Michel committed
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
# @route( baseUrl+'/createFits', name='createFits', method='POST')
# @enable_cors
# def createFits():
#     logger.debug("createFits - wrapper : entering" )
#     try:
#         header = request.json['header']
#         relFITSFilePath = request.json['relFITSFilePath']
#         iRA = request.json['iRA']
#         iDEC = request.json['iDEC']
#     except Exception as e:
#         logger.debug(f'{e}')
#         raise
#     response.content_type = "application/json; charset=utf-8"
#     logger.debug("createFits - wrapper : exiting" )
#     return json.dumps(dm.createFits(relFITSFilePath, iRA, iDEC))
Caillat Michel's avatar
Caillat Michel committed
631
632

"""
Caillat Michel's avatar
Caillat Michel committed
633
@api {GET} artemix/getHeader getHeader - Returns the header.
Caillat Michel's avatar
Caillat Michel committed
634
635
636
637
638
639
640
641
@apiName getHeader
@apiGroup data infos
@apiDescription Returns the header of the FITS file of interest as a dictionary.

@apiParam {String} relFITSFilePath the path to the FITS file of interest.
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
{'status': True, 'message': '', 'result': '{"SIMPLE": true, "BITPIX": -32, "NAXIS": 4, "NAXIS1": 800, "NAXIS2": 800, "NAXIS3": 24, "NAXIS4": 1, "EXTEND": true, "BSCALE": 1.0, "BZERO": 0.0, "BMAJ": 7.385119795799e-05, "BMIN": 5.762678467565e-05, "BPA": 15.67044067383, "BTYPE": "Intensity", "OBJECT": "M87", "BUNIT": "Jy/beam", "EQUINOX": 2000.0, "RADESYS": "FK5", "LONPOLE": 180.0, "LATPOLE": 12.39112331, "PC01_01": 1.0, "PC02_01": 0.0, "PC03_01": 0.0, "PC04_01": 0.0, "PC01_02": 0.0, "PC02_02": 1.0, "PC03_02": 0.0, "PC04_02": 0.0, "PC01_03": 0.0, "PC02_03": 0.0, "PC03_03": 1.0, "PC04_03": 0.0, "PC01_04": 0.0, "PC02_04": 0.0, "PC03_04": 0.0, "PC04_04": 1.0, "CTYPE1": "RA---SIN", "CRVAL1": 187.70593075, "CDELT1": -1.416666666667e-05, "CRPIX1": 401.0, "CUNIT1": "deg", "CTYPE2": "DEC--SIN", "CRVAL2": 12.39112331, "CDELT2": 1.416666666667e-05, "CRPIX2": 401.0, "CUNIT2": "deg", "CTYPE3": "FREQ", "CRVAL3": 230436102960.0, "CDELT3": -76899199.37878, "CRPIX3": 1.0, "CUNIT3": "Hz", "CTYPE4": "STOKES", "CRVAL4": 1.0, "CDELT4": 1.0, "CRPIX4": 1.0, "CUNIT4": "", "PV2_1": 0.0, "PV2_2": 0.0, "RESTFRQ": 230538000000.0, "SPECSYS": "BARYCENT", "ALTRVAL": 132507.283331, "ALTRPIX": 1.0, "VELREF": 258, "TELESCOP": "ALMA", "OBSERVER": "jtan", "DATE-OBS": "2015-08-16T19:00:41.616000", "TIMESYS": "UTC", "OBSRA": 187.70593075, "OBSDEC": 12.39112331, "OBSGEO-X": 2225142.180269, "OBSGEO-Y": -5440307.370349, "OBSGEO-Z": -2481029.851874, "DATE": "2015-12-08T18:24:15.408000", "ORIGIN": "CASA 4.5.0-REL (r35147)", "INSTRUME": "Unknown"}'}
Caillat Michel's avatar
Caillat Michel committed
642
643
644
645
"""
@route( baseUrl+'/getHeader', name='getHeader', method='GET')
@enable_cors
def getHeader():
646
647
648
    logger.debug("getHeader - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
649
        result = json.dumps(dm.getHeader(relFITSFilePath))
650
651
    except Exception as e:
        logger.debug(f'{e}')
652
653
654
655
656
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
657
    response.content_type = "application/json; charset=utf-8"
658
    logger.debug("getHeader - wrapper : exiting")
659
    return result
Caillat Michel's avatar
Caillat Michel committed
660

Caillat Michel's avatar
Caillat Michel committed
661

Caillat Michel's avatar
Caillat Michel committed
662
"""
Caillat Michel's avatar
Caillat Michel committed
663
@api {GET} artemix/RADECRangeInDegrees RADECRangeInDegrees - Returns the RA,DEC range.
Caillat Michel's avatar
Caillat Michel committed
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
@apiName RADECRangeInDegrees
@apiGroup data infos
@apiDescription Returns the RA,DEC range of the data. The result is
returned as a pair of pairs of float values `[[ra0, dec0], [ra1, dec1], [ra2, dec2]]`
expressing angles in decimal degrees where :

* `ra0` corresponds to the index 0 along the RA axis.
* `ra1` corresponds to the index NAXIS1-1 along the RA axis.
* `dec0` corresponds to the index 0 along the DEC axis.
* `dec1` corresponds to the index NAXIS2-1 along the DEC axis.
* `ra2` corresponds to the index max(NAXIS1, NAXIS2)-1 along the RA axis.
* `dec2` corresponds to the index max(NAXIS1, NAXIS2)-1 along the DEC axis.

@apiParam {String} relFITSFilePath the path to the FITS file of interest
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
    {'status': True, 'message': '', 'result': [[187.71173244085927, 12.385456581760074], [187.700143312178, 12.396775748749931], [187.700143312178, 12.396775748749931]]}
Caillat Michel's avatar
Caillat Michel committed
681
682
683
684
"""
@route( baseUrl+'/RADECRangeInDegrees', name='RADECRangeInDegrees', method='GET')
@enable_cors
def RADECRangeInDegrees():
685
686
687
    logger.debug("RADECRangeInDegrees - wrapper : entering")
    try :
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
688
        result = json.dumps(dm.RADECRangeInDegrees(relFITSFilePath))
689
690
    except Exception as e:
        logger.debug(f'{e}')
691
692
693
694
695
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}

Caillat Michel's avatar
Caillat Michel committed
696
    response.content_type = "application/json; charset=utf-8"
697
698
    logger.debug("RADECRangeInDegrees - wrapper : exiting")

699
    return result
Caillat Michel's avatar
Caillat Michel committed
700

Caillat Michel's avatar
Caillat Michel committed
701
702
703
704
"""
@api {POST} artemix/getOneSliceAsPNG Generates a PNG representation of a RA,DEC plane.
@apiName getOneSliceAsPNG
@apiGroup imagery
Caillat Michel's avatar
Caillat Michel committed
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
@apiDescription

* Generates a PNG image of a `RA,DEC` plane defined by an index, `iFREQ`, along the FREQ axis.

   * The image is built from the pixels values used as entries in the composition of an "intensity transfert table" (`ittName`), a "look up table" (`lutName`) and a "video mode" (`vmName`).
   * The image is written to the server's filesystem in a space which can be browsed.
   * The PNG file is named after `relFITSFilePath, iFREQ, ittName, lutName, vmName`.
   * The PNG images use 256 colours palettes.

   * Valid names values for `ittName, lutName, vmName` can be retrieved with the API
`renderingCapabilities`.

* Returns a dictionary with two entries : `{'data_steps': .... , 'path_to_png' : ...}`
    * `'data_steps'` : an association (dictionary) between the 256 RGB triples used in the PNG image and the 256 levels of the sampling of the physical values present in the `RA,DEC` plane. Example of an element of that dictionary : `"255_121_0": -0.037923078107483243`
    * `'path_to_png'` : the path to the PNG file relative to some root directory defined in the server's configuration. The important point is that it can be used as an URL, e.g.
    for visualization purpose a on a browser.
Caillat Michel's avatar
Caillat Michel committed
721

Caillat Michel's avatar
Caillat Michel committed
722
723
724
725
726
727
728
729
730
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
'{"status": true, "message": "", "result": {"data_steps": {"255_0_41": -0.051212918013334274, ... ,"255_0_191": 0.06175072118639946}, "path_to_png": "2013/2013.1.00073.S/science_goal.uid___A001_X12f_X20b/group.uid___A001_X12f_X20c/member.uid___A001_X12f_X20d/product/M87_CO_v_0_2_1_image.pbcor/12.minmax.gist_rainbow.direct.png"}}'

@apiParam {String} relFITSFilePath The path to the FITS file of interest
@apiParam {uInt} iFREQ An index along the `FREQ` axis.
@apiParam {String} [ittName] The intensity transformation table name.
@apiParam {String} [lutName] The look up table name.
@apiParam {String} [vmName] The video mode name.
Caillat Michel's avatar
Caillat Michel committed
731
"""
Caillat Michel's avatar
Caillat Michel committed
732
733
734
@route( baseUrl+'/getOneSliceAsPNG', name='getOneSliceAsPNG', method='POST')
@enable_cors
def getOneSliceAsPNG():
735
    logger.debug("getOneSliceAsPNG - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
736
737

    try:
738
        body = byteify(json.loads(request.body.read()))
Caillat Michel's avatar
Caillat Michel committed
739
        relFITSFilePath = rebuildFilename(body['relFITSFilePath'])
Caillat Michel's avatar
Caillat Michel committed
740
        iFREQ = getIntValue(body['iFREQ'])
Caillat Michel's avatar
Caillat Michel committed
741

742
743
744
745
        kwargs = {}
        for x in ['ittName', 'lutName', 'vmName']:
            if x in body :
                kwargs[x]=body[x]
746
        result = json.dumps(dm.getOneSliceAsPNG(iFREQ, relFITSFilePath, **kwargs))
Caillat Michel's avatar
Caillat Michel committed
747
748
749
750
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        traceback.print_exc()
        message = "%s ! %s" %  (repr(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
751
752
753
754
755
756
        result = {"status": False, "message": message}

    response.content_type = "application/json; charset=utf-8"
    logger.debug("getOneSliceAsPNG - wrapper : exiting")

    return result
Caillat Michel's avatar
Caillat Michel committed
757
758

"""
Caillat Michel's avatar
Caillat Michel committed
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
@api {POST} artemix/getSummedSliceRangeAsPNG Generates a PNG representation of a sum of RA,DEC planes.
@apiName getSummedSliceRangeAsPNG
@apiGroup imagery
@apiDescription

* Generates a PNG image of the sum of `RA,DEC` planes with indexes contained in a range `[iFREQ0, iFREQ1]` along the `FREQ` axis.

   * The image is built from the pixels values used as entries in the composition of an "intensity transfert table" (`ittName`), a "look up table" (`lutName`) and a "video mode" (`vmName`).
   * The image is written to the server's filesystem in a space which can be browsed.
   * The PNG file is named after `relFITSFilePath, iFREQ0, iFREQ1, ittName, lutName, vmName`.
   * The PNG images use 256 colours palettes.

   * Valid names values for `ittName, lutName, vmName` can be retrieved with the API
`renderingCapabilities`.

* Returns a dictionary with two entries : `{'data_steps': .... , 'path_to_png' : ...}`
    * `'data_steps'` : an association (dictionary) between the 256 RGB triples used in the PNG image and the 256 levels of the sampling of the physical values present in the `RA,DEC` plane. Example of an element of that dictionary : `"255_121_0": -0.037923078107483243`
    * `'path_to_png'` : the path to the PNG file relative to some root directory defined in the server's configuration. The important point is that it can be used as an URL, e.g.
    for visualization purpose a on a browser.
Caillat Michel's avatar
Caillat Michel committed
778

Caillat Michel's avatar
Caillat Michel committed
779
780
781
782
783
@apiParam {String} relFITSFilePath The path to the FITS file of interest
@apiParam {uInt} iFREQ An index along the `FREQ` axis.
@apiParam {String} [ittName] The intensity transformation table name.
@apiParam {String} [lutName] The look up table name.
@apiParam {String} [vmName] The video mode name.
Caillat Michel's avatar
Caillat Michel committed
784

Caillat Michel's avatar
Caillat Michel committed
785
786
787
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
'{"status": true, "message": "", "result": {"data_steps": {"255_0_41": -56.15247344970703, ..., "255_0_191": 77.84288787841797}, "path_to_png": "2013/2013.1.00073.S/science_goal.uid___A001_X12f_X20b/group.uid___A001_X12f_X20c/member.uid___A001_X12f_X20d/product/M87_CO_v_0_2_1_image.pbcor/6-18.minmax.gist_rainbow.direct.png"}}'
Caillat Michel's avatar
Caillat Michel committed
788
789
790
791
"""
@route( baseUrl+'/getSummedSliceRangeAsPNG', name='getSummedSliceRangeAsPNG', method='POST')
@enable_cors
def getSummedSliceRangeAsPNG():
792
    logger.debug("getSummedSliceRangeAsPNG - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
793
794
795
796
797

    try:
        body = byteify(json.loads(request.body.read()))
        logger.debug("getSummedSliceRangeAsPNG(): body = %r" % json.loads(request.body.read()) )
        relFITSFilePath = rebuildFilename(body['relFITSFilePath'])
Caillat Michel's avatar
Caillat Michel committed
798
799
        iFREQ0 = getIntValue(body['iFREQ0'])
        iFREQ1 = getIntValue(body['iFREQ1'])
800
801
802
803
804

        kwargs = {}
        for x in ['ittName', 'lutName', 'vmName']:
            if x in body :
                kwargs[x]=body[x]
805
        result =  dm.getSummedSliceRangeAsPNG(relFITSFilePath, iFREQ0, iFREQ1,  **kwargs);
Caillat Michel's avatar
Caillat Michel committed
806
807
808
809
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
810
811
812
813
814
815
        result = {"status": False, "message": message}

    response.content_type = "application/json; charset=utf-8"
    logger.debug("getSummedSliceRangeAsPNG - wrapper : entering")

    return json.dumps(result)
Caillat Michel's avatar
Caillat Michel committed
816
817
818
819
820
821
822
823
824
825
826

"""
    Given the values of a spectrum 'ydata' passed with the corresponding frequency values 'xdata'
    at an (x,y) given position ( integer indexes ) in the RAxDEC plan of a cube, generate a FITS file containing that
    spectrum. Supposedly be used for interoperability based on SAMP. A typical interlocutor is CASSIS

    @header the FITS Header of the file contaning the input data cube.
    @relFITSFilePath the path to the input data cube on disk.
    @iRA a single integer value containing the index along the RA axis
    @iDEC a single integer value containing the index along the DEC axis
"""
Caillat Michel's avatar
Caillat Michel committed
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
"""
@api {POST} /artemix/createFITS Creates a FITS table containing a spectrum.
@apiName createFITS
@apiGroup misc
@apiDescription Creates a FITS ASCII table whose content is a spectrum defined by a position in the
`RA,DEC` plane. Returns the FITS table as a sequence of bytes.

@apiParam {String} relFITSFilePath The path to the FITS file of interest
@apiParam {uInt} iRA index along the RA axis
@apiParam {uInt} iDEC index along the DEC axis

@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
 {'status': True, 'message': '', 'result': "SIMPLE  =                    T / conforms to FITS standard                      BITPIX  =                    8 / array data type                                NAXIS   =                    0 / number of array dimensions                     EXTEND  =                    T                                                  DATE    = '2015-12-08T18:24:15.408000'                                          DATE-OBS= '2015-08-16T19:00:41.616000'                                          TELESCOP= 'ALMA    '                                                            OBSERVER= 'jtan    '                                                            RESTFRQ =       230538000000.0                                                  SPECSYS = 'BARYCENT'                                                            COMMENT Spectrum of a single pixel of the cube                                  END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             XTENSION= 'TABLE   '           / ASCII table extension                          BITPIX  =                    8 / array data type                                NAXIS   =                    2 / number of array dimensions                     NAXIS1  =                   30 / length of dimension 1                          NAXIS2  =                   24 / length of dimension 2                          PCOUNT  =                    0 / number of group parameters                     GCOUNT  =                    1 / number of groups                               TFIELDS =                    2 / number of table fields                         TTYPE1  = 'Frequency'                                                           TFORM1  = 'E15.7   '                                                            TUNIT1  = 'Hz      '                                                            TBCOL1  =                    1                                                  TTYPE2  = 'Flux    '                                                            TFORM2  = 'E15.7   '                                                            TUNIT2  = 'Jy/beam '                                                            TBCOL2  =                   16                                                  END                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               2.3043611E+11  3.2496294E-03  2.3035920E+11  3.1511623E-03  2.3028230E+11  2.3847336E-03  2.3020541E+11  2.2302533E-03  2.3012850E+11  2.0003885E-03  2.3005161E+11  1.2175933E-03  2.2997470E+11  1.1119752E-03  2.2989781E+11  3.6920310E-04  2.2982091E+11 -3.7513475E-04  2.2974402E+11 -3.3260061E-04  2.2966711E+11 -1.1299318E-03  2.2959020E+11 -1.6285286E-03  2.2951331E+11 -2.0787886E-03  2.2943641E+11 -1.5112817E-03  2.2935952E+11 -2.7610569E-03  2.2928261E+11 -2.2096562E-03  2.2920572E+11 -2.5501542E-03  2.2912881E+11 -2.2054571E-03  2.2905192E+11 -2.6812006E-03  2.2897502E+11 -2.6687880E-03  2.2889811E+11 -3.7882254E-03  2.2882122E+11 -3.9494671E-03  2.2874431E+11 -3.7729631E-03  2.2866742E+11 -4.9771518E-03                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                "}
"""
Caillat Michel's avatar
Caillat Michel committed
842
843
844
@route( baseUrl+'/createFits', name='createFits', method='POST')
@enable_cors
def createFits():
845
846
    logger.debug("createFits - wrapper : entering")

Caillat Michel's avatar
Caillat Michel committed
847
848
849
850
851
852
853
854
855
856
857
    try:
        body = byteify(json.loads(request.body.read()))
        relFITSFilePath = body['relFITSFilePath']
        iRA = getIntValue(body['iRA'])
        iDEC = getIntValue(body['iDEC'])
        result =  dm.createFits(relFITSFilePath, iRA, iDEC)
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
Caillat Michel's avatar
Caillat Michel committed
858

859

Caillat Michel's avatar
Caillat Michel committed
860
    response.content_type = "application/json; charset=utf-8"
861
862
    logger.debug("createFits - wrapper : exiting")

Caillat Michel's avatar
Caillat Michel committed
863
    return json.dumps(result)
Caillat Michel's avatar
Caillat Michel committed
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879


"""
    Returns all the entries directly located under a given subdirectory.
    The subdirectory must be passed as a full path implicitely
    rooted at FITSFilePrefix.
    i.e. :
    "/" will be interpreted as FITSFilePrefix+"/"
    "/2018" will be interpreted as FITSFilePrefix+"/2018"
    etc...
    The result is a JSON array of dictionaries , on the basis of one dictionary per inode:
    [{'title': filename of the entry, 'key': the FITSFilePrefix rooted path of the entry, 'folder' : true (resp. false) if the inode is (resp. is not) a subdir, 'lazy': true}]
"""
@route( baseUrl+'/getEntries', name='getEntries', method='POST')
@enable_cors
def getEntries() :
880
    logger.debug("getEntries - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
881
882
883
884
885
886
887
888
889
    try:
        body = byteify(json.loads(request.body.read()))
        relKey = body['relKey']
        result = dm.getEntries(relKey)
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
890
891
892

    response.content_type = "application/json; charset=utf-8"
    logger.debug("getEntries - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
893
    return json.dumps(result)
Caillat Michel's avatar
Caillat Michel committed
894
895

"""
Caillat Michel's avatar
Caillat Michel committed
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
@api {GET} artemix/renderingCapabilities Useful informations for the production of PNG files.
@apiName renderingCapabilities
@apiGroup imagery
@apiDescription Returns informations useful for calls to APIs dedicated to the generation of PNG files.

The returned information is structured as follows :

{

'itts': _list of itt names_, 'default_itt_index': _index to default itt name_,

'luts': _list of lut names_, 'default_lut_index': _index to default lut name_,

'vmodes': _list of video mode names_, 'default_vmode_index': _index to default video mode name_

}
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
{'status': True, 'message': '', 'result': {'itts': ['minmax', 'percent98'], 'default_itt_index': 0, 'luts': ['Greys', 'RdYlBu', 'hsv', 'gist_ncar', 'gist_rainbow', 'gist_gray', 'Spectral', 'jet', 'plasma', 'inferno', 'magma', 'afmhot', 'gist_heat'], 'default_lut_index': 2, 'vmodes': ['direct', 'inverse'], 'default_vmode_index': 0}}
Caillat Michel's avatar
Caillat Michel committed
915
916
917
918
"""
@route( baseUrl+'/renderingCapabilities', name='renderingCapabilities', method='GET')
@enable_cors
def renderingCapabilities():
919
    logger.debug("renderingCapabilities - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
920
921
922
923
924
925
926
    try:
        result = dm.renderingCapabilities()
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
927
928
929

    response.content_type = "application/json; charset=utf-8"
    logger.debug("renderingCapabilities - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
930
    return json.dumps(result)
931
932

"""
Caillat Michel's avatar
Caillat Michel committed
933
934
935
936
937
938
939
940
941
942
@api {POST} artemix/getDataBlockInfos getDataBlockInfos -
@apiName getDataBlockInfos
@apiGroup server
@apiDescription Returns informations about the server's characteristics and the DataBlock objects present in memory. A DataBlock
is an object created when a FITS file is read in memory; it encapsulates the FITS file content
plus a number of other fields.
@apiSuccessExample {json} Success-Response:
    HTTP/1.1 200 OK
{}

943
944
945
946
"""
@route( baseUrl+'/getDataBlockInfos', name='getDataBlockInfos', method='POST')
@enable_cors
def getDataBlockInfos():
947
    logger.debug("getDataBlockInfos - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
948
949
950
951
952
953
954
    try:
        result = dm.getDataBlockInfos()
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
955
956

    response.content_type = "application/json; charset=utf-8"
957
958
959
960
961
962
963
964
965
966
    logger.debug("getDataBlockInfos wrapper : exiting")
    return json.dumps(result)

"""
  Erase the DataBlocks idle for a duration greater than DataBlock.getMaxIdle
  (env var YAFITSS_MAXIDLE)
"""
@route( baseUrl+'/purgeDataBlocks', name='purgeDataBlocks', method='POST')
@enable_cors
def purgeDataBlocks():
967
    logger.debug("purgeDataBlocks - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
968
969
970
971
972
973
974
    try:
        result = dm.purgeDataBlocks()
    except Exception as e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        message = "%s ! %s" %  (type(e), "".join(traceback.format_list(traceback.extract_tb(exc_traceback))))
        traceback.print_exc()
        result = {"status": False, "message": message}
975
976
977

    response.content_type = "application/json; charset=utf-8"
    logger.debug("purgeDataBlocks- wrapper : exiting")
978
    return json.dumps(result)
Caillat Michel's avatar
Caillat Michel committed
979

980
@route('/static/png/<filepath:path>')
981
982
def server_static(filepath):
    return static_file(filepath, root=dataManager.PNGFilePrefix)
Caillat Michel's avatar
Caillat Michel committed
983

984
985
986
@route('/static/apidoc/<filepath:path>')
def server_static(filepath):
    return static_file(filepath, root=dataManager.ApiDocPrefix)
Caillat Michel's avatar
Caillat Michel committed
987

Caillat Michel's avatar
Caillat Michel committed
988
#
Caillat Michel's avatar
Caillat Michel committed
989
#    End of the server functions definitions
Caillat Michel's avatar
Caillat Michel committed
990
991
992
#
#

993

Caillat Michel's avatar
Caillat Michel committed
994
995
996
997
998
999
1000
"""
    start local bottle server
    @port port number
    @debug True if debug enabled
"""
def main(argv):

For faster browsing, not all history is shown. View entire blame