serverWsgi.py 59.1 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

import astropy

import sys
import socket
import getopt
import os
import traceback
import collections
import numpy as np
73
import cv2
Caillat Michel's avatar
Caillat Michel committed
74
75
76
77

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

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

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

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

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

def enable_cors(fn):
Caillat Michel's avatar
Caillat Michel committed
99
100
101
    """
    enable cross domain ajax requests
    """
102
    def _enable_cors(*args, **kwargs):
Caillat Michel's avatar
Caillat Michel committed
103
    # set CORS headers
104
105
106
        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
107

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

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

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":
121
        return int(float(value))
Caillat Michel's avatar
Caillat Michel committed
122
123
124
125
126
127
128
129
    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
130
131
132
    """
    	Replaces NaN values by None in array
    """
Caillat Michel's avatar
Caillat Michel committed
133
134
135
136
137
    result = arr
    result[np.isnan(result)] = None
    return result

def rebuildFilename(filename):
Caillat Michel's avatar
Caillat Michel committed
138
139
140
141
    """
        Given filename ( string )
        returns a string where each space character present in filename is replaced by '+' character.
    """
Caillat Michel's avatar
Caillat Michel committed
142
143
144
145
146
147
    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
148
149
150
151
    """
        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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    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
175
@api {GET} artemix/ping ping - Check server readiness
Caillat Michel's avatar
Caillat Michel committed
176
177
178
179
180
181
@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
182
"""
Caillat Michel's avatar
Caillat Michel committed
183

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

Caillat Michel's avatar
Caillat Michel committed
187
def upAnWaiting():
Caillat Michel's avatar
Caillat Michel committed
188
189
190
191
192
193
194
195
196
197
198
    """/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
199
200

"""
Caillat Michel's avatar
Caillat Michel committed
201
@api {GET} artemix/degToHMSDMS degToHMSDMS - Converts a (RA,DEC) pair from decimal degrees to HMS DMS
Caillat Michel's avatar
Caillat Michel committed
202
203
204
205
206
207
208
209
210
211
212
@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
213
214
215
216
"""
@route( baseUrl+'/degToHMSDMS', name='degToHMSDMS', method='GET')
@enable_cors
def degToHMSDMS():
Caillat Michel's avatar
Caillat Michel committed
217
218
219
220
    """
        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
    """
221
222
223
224
225
    logger.debug("degToHMSDMS - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
        RAinDD = getFloatValue(request.GET['RAinDD'])
        DECinDD = getFloatValue(request.GET['DECinDD'])
226
        result = json.dumps(dm.degToHMSDMS(relFITSFilePath, RAinDD, DECinDD))
227
    except Exception as e:
228
229
230
231
        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}
232

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

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


Caillat Michel's avatar
Caillat Michel committed
239

Caillat Michel's avatar
Caillat Michel committed
240
"""
Caillat Michel's avatar
Caillat Michel committed
241
@api {GET} artemix/rangeToHMS rangeToHMS - Generates a sequence of HMS from a range of RA indexes
Caillat Michel's avatar
Caillat Michel committed
242
243
244
245
246
247
248
249
250
251
252
@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
253
254
255
256
"""
@route( baseUrl+'/rangeToHMS', name='rangeToHMS', method='GET')
@enable_cors
def rangeToHMS():
257
258
259
260
261
262
    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'])
263
        result = json.dumps(dm.rangeToHMS(relFITSFilePath, iRA0, iRA1, iRAstep))
264
    except Exception as e:
265
266
267
268
        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
269
270

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

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

"""
Caillat Michel's avatar
Caillat Michel committed
276
277
278
279
280
281
282
283
284
285
286
287
@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
288
289
290
291
"""
@route( baseUrl+'/rangeToDMS', name='rangeToDMS', method='GET')
@enable_cors
def rangeToDMS():
292
293
294
295
296
297
    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'])
298
        result =json.dumps(dm.rangeToDMS(relFITSFilePath, iDEC0, iDEC1, iDECstep))
299
    except Exception as e:
300
301
302
303
304
        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
305
    response.content_type = "application/json; charset=utf-8"
306
    logger.debug("rangeToDMS - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
307

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

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

Caillat Michel's avatar
Caillat Michel committed
316
317
318
319
@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
320
321
322
@route( baseUrl+'/setData', name='setData', method='GET')
@enable_cors
def setData():
323
324
325
    logger.debug("setData - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
326
        result = json.dumps(dm.setData(relFITSFilePath))
327
    except Exception as e:
328
329
330
331
332
        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
333
    response.content_type = "application/json; charset=utf-8"
334
    logger.debug("setData - wrapper : exiting")
335
    return result
Caillat Michel's avatar
Caillat Michel committed
336
337

"""
Caillat Michel's avatar
Caillat Michel committed
338
339
340
341
342
343
344
345
346
347
@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
348
349
350
351
"""
@route( baseUrl+'/getDimensions', name='getDimensions', method='GET')
@enable_cors
def getDimensions():
352
353
354
    logger.debug("getDimensions - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
355
        result = json.dumps(dm.getDimensions(relFITSFilePath))
356
    except Exception as e:
357
358
359
360
361
        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
362
    response.content_type = "application/json; charset=utf-8"
363
    logger.debug("getDimensions - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
364

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

"""
    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
380
381
382
383
384
385
386
387
388
389
390
391
392
@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
393
394
395
396
397
"""
@route( baseUrl+'/getPixelValueAtiFreqiRAiDEC', name="getPixelValueAtiFreqiRAiDEC", method='GET')
@enable_cors
def getPixelValueAtiFreqiRAiDEC():
    logger.debug("getPixelValueAtiFreqiRAiDEC - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
398
399
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
400
        iFREQ = getIntValue(request.GET['iFREQ'])
Caillat Michel's avatar
Caillat Michel committed
401
402
        iRA = getIntValue(request.GET['iRA'])
        iDEC = getIntValue(request.GET['iDEC'])
403
        result = json.dumps(dm.getPixelValueAtiFreqiRAiDEC(relFITSFilePath, iFREQ, iRA, iDEC))
Caillat Michel's avatar
Caillat Michel committed
404
    except Exception as e:
405
406
407
408
        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
409
410
411
412
413
414

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

"""
Caillat Michel's avatar
Caillat Michel committed
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
@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
430
431
432
433
"""
@route( baseUrl+'/getSlice', name='getSlice', method='GET')
@enable_cors
def getSlice():
434
    logger.debug("getSlice - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
435

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

Caillat Michel's avatar
Caillat Michel committed
441
    except Exception as e:
442
443
444
445
        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
446

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

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

"""
    Given one pair, x and y, of integer coordinates in the RAxDEC plane and
Caillat Michel's avatar
Caillat Michel committed
454
455
    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
456
"""
Caillat Michel's avatar
Caillat Michel committed
457
@route( baseUrl+'/getSpectrum', name='getSpectrum', method='GET')
Caillat Michel's avatar
Caillat Michel committed
458
@enable_cors
Caillat Michel's avatar
Caillat Michel committed
459
def getSpectrum():
Caillat Michel's avatar
Caillat Michel committed
460
    logger.debug("getSpectrum - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
461
462
463
464
465
466
    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
467
        result = json.dumps(dm.getSpectrum(relFITSFilePath, iRA, iDEC, iFREQ0, iFREQ1))
Caillat Michel's avatar
Caillat Michel committed
468
    except Exception as e:
469
470
471
472
473
        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
474
    response.content_type = "application/json; charset=utf-8"
Caillat Michel's avatar
Caillat Michel committed
475
    logger.debug("getSpectrum - wrapper : exiting")
Caillat Michel's avatar
Caillat Michel committed
476

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

"""
Caillat Michel's avatar
Caillat Michel committed
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
508
@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
509
510
511
512
"""
@route( baseUrl+'/getAverageSpectrum', name='getAverageSpectrum', method='GET')
@enable_cors
def getAverageSpectrum():
513
    logger.debug("getAverageSpectrum - wrapper : entering" )
Caillat Michel's avatar
Caillat Michel committed
514
515
516

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

Caillat Michel's avatar
Caillat Michel committed
517
518
519
520
521
522
    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'])
523
        result = json.dumps(dm.getAverageSpectrum(relFITSFilePath, iDEC0, iDEC1, iRA0, iRA1, retFITS))
Caillat Michel's avatar
Caillat Michel committed
524
    except Exception as e:
525
526
527
528
        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
529

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

    return result

"""
Caillat Michel's avatar
Caillat Michel committed
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
@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
556
557
558
559
"""
@route( baseUrl+'/getSumOverSliceRectArea', name='getSumOverSliceRectArea', method='GET')
@enable_cors
def getSumOverSliceRectArea():
Caillat Michel's avatar
Caillat Michel committed
560
561
562
    logger.debug("getSumOverSliceRectArea - wrapper : entering" )
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
563
564
565
566
        iDEC0 = getIntValue(request.GET['iDEC0'])
        iDEC1 = getIntValue(request.GET['iDEC1'])
        iRA0 = getIntValue(request.GET['iRA0'])
        iRA1 = getIntValue(request.GET['iRA1'])
Caillat Michel's avatar
Caillat Michel committed
567
        iFREQ = getIntValue(request.GET['iFREQ'])
568
        result = json.dumps(dm.getSumOverSliceRectArea(relFITSFilePath,  iFREQ, iRA0, iRA1, iDEC0, iDEC1))
Caillat Michel's avatar
Caillat Michel committed
569
    except Exception as e:
570
571
572
573
        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
574
575

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

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

"""
Caillat Michel's avatar
Caillat Michel committed
581
    Given two pairs -- iRA0,iRA1 and iDEC0,iDEC1 -- defining a rectangular area the RAxDEC plane
Caillat Michel's avatar
Caillat Michel committed
582
583
    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
584
585
586
587
588
    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
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
    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}
606

Caillat Michel's avatar
Caillat Michel committed
607
    response.content_type = "application/json; charset=utf-8"
Caillat Michel's avatar
Caillat Michel committed
608
    logger.debug("getAverage - wrapper : exiting" )
Caillat Michel's avatar
Caillat Michel committed
609
610
611
612
613
614
615
616
    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
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# @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
632
633

"""
Caillat Michel's avatar
Caillat Michel committed
634
@api {GET} artemix/getHeader getHeader - Returns the header.
Caillat Michel's avatar
Caillat Michel committed
635
636
637
638
639
640
641
642
@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
643
644
645
646
"""
@route( baseUrl+'/getHeader', name='getHeader', method='GET')
@enable_cors
def getHeader():
647
648
649
    logger.debug("getHeader - wrapper : entering")
    try:
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
650
        result = json.dumps(dm.getHeader(relFITSFilePath))
651
652
    except Exception as e:
        logger.debug(f'{e}')
653
654
655
656
657
        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
658
    response.content_type = "application/json; charset=utf-8"
659
    logger.debug("getHeader - wrapper : exiting")
660
    return result
Caillat Michel's avatar
Caillat Michel committed
661

Caillat Michel's avatar
Caillat Michel committed
662

Caillat Michel's avatar
Caillat Michel committed
663
"""
Caillat Michel's avatar
Caillat Michel committed
664
@api {GET} artemix/RADECRangeInDegrees RADECRangeInDegrees - Returns the RA,DEC range.
Caillat Michel's avatar
Caillat Michel committed
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
@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
682
683
684
685
"""
@route( baseUrl+'/RADECRangeInDegrees', name='RADECRangeInDegrees', method='GET')
@enable_cors
def RADECRangeInDegrees():
686
687
688
    logger.debug("RADECRangeInDegrees - wrapper : entering")
    try :
        relFITSFilePath = rebuildFilename(request.GET['relFITSFilePath'])
689
        result = json.dumps(dm.RADECRangeInDegrees(relFITSFilePath))
690
691
    except Exception as e:
        logger.debug(f'{e}')
692
693
694
695
696
        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
697
    response.content_type = "application/json; charset=utf-8"
698
699
    logger.debug("RADECRangeInDegrees - wrapper : exiting")

700
    return result
Caillat Michel's avatar
Caillat Michel committed
701
702

"""
Caillat Michel's avatar
Caillat Michel committed
703
704
705
@api {POST} artemix/getOneSliceAsPNG Generates a PNG representation of a RA,DEC plane.
@apiName getOneSliceAsPNG
@apiGroup imagery
Caillat Michel's avatar
Caillat Michel committed
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
@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
722

Caillat Michel's avatar
Caillat Michel committed
723
724
725
726
727
728
729
730
731
@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
732
733
734
735
"""
@route( baseUrl+'/getOneSliceAsPNG', name='getOneSliceAsPNG', method='POST')
@enable_cors
def getOneSliceAsPNG():
736
    logger.debug("getOneSliceAsPNG - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
737
738

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

743
744
745
746
        kwargs = {}
        for x in ['ittName', 'lutName', 'vmName']:
            if x in body :
                kwargs[x]=body[x]
747
        result = json.dumps(dm.getOneSliceAsPNG(iFREQ, relFITSFilePath, **kwargs))
Caillat Michel's avatar
Caillat Michel committed
748
749
750
751
    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))))
752
753
754
755
756
757
        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
758
759

"""
Caillat Michel's avatar
Caillat Michel committed
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
@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
779

Caillat Michel's avatar
Caillat Michel committed
780
781
782
783
784
@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
785

Caillat Michel's avatar
Caillat Michel committed
786
787
788
@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
789
790
791
792
"""
@route( baseUrl+'/getSummedSliceRangeAsPNG', name='getSummedSliceRangeAsPNG', method='POST')
@enable_cors
def getSummedSliceRangeAsPNG():
793
    logger.debug("getSummedSliceRangeAsPNG - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
794
795
796
797
798

    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
799
800
        iFREQ0 = getIntValue(body['iFREQ0'])
        iFREQ1 = getIntValue(body['iFREQ1'])
801
802
803
804
805

        kwargs = {}
        for x in ['ittName', 'lutName', 'vmName']:
            if x in body :
                kwargs[x]=body[x]
806
        result =  dm.getSummedSliceRangeAsPNG(relFITSFilePath, iFREQ0, iFREQ1,  **kwargs);
Caillat Michel's avatar
Caillat Michel committed
807
808
809
810
    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()
811
812
813
814
815
816
        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
817

Caillat Michel's avatar
Caillat Michel committed
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
@route( baseUrl+'/getContours', name='getContours', method='POST')
@enable_cors
def getContours():
    logger.debug("getContours - wrapper : entering")
    try:
        body = byteify(json.loads(request.body.read()))
        relFITSFilePath = rebuildFilename(body['relFITSFilePath'])
        optParams = json.loads(body["optParams"])
        iFREQ =  getIntValue(optParams['iFREQ']) if 'iFREQ' in optParams else None
        iDEC0 =  getIntValue(optParams['iDEC0']) if 'iDEC0' in optParams else None
        iDEC1 =  getIntValue(optParams['iDEC1']) if 'iDEC1' in optParams else None
        iRA0 =  getIntValue(optParams['iRA0']) if 'iRA0' in optParams else None
        iRA1 =  getIntValue(optParams['iRA1']) if 'iRA1' in optParams else None

        kwargs = {}
        if 'levels' in optParams:
            levels = [getFloatValue(level) for level in optParams['levels']]
            kwargs['levels'] = levels

        if 'quantiles' in optParams:
            quantiles = [getFloatValue(level) for level in optParams['quantiles']]
            kwargs['quantiles'] = quantiles

841
842
843
844
        if 'numberOfBins' in optParams:
            numberOfBins = getIntValue(optParams['numberOfBins'])
            kwargs['numberOfBins'] = numberOfBins

845
846
847
        if 'histBinsMethod' in optParams:
            kwargs['histBinsMethod'] = optParams['histBinsMethod']

Caillat Michel's avatar
Caillat Michel committed
848
849
850
851
852
853
854
855
856
857
858
859
860
861
        logger.debug("kwargs = %r" % kwargs)
        result = dm.getContours(relFITSFilePath, iFREQ, iDEC0, iDEC1, iRA0, iRA1, **kwargs)

    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))))
        logger.debug(message)
        result = {"status": False, "message": message}

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

    return json.dumps(result)

862
863
864
865
866
867
868
869
870
871
@route( baseUrl+'/measureContour', name='measureContour', method='POST')
@enable_cors

def measureContour():
    logger.debug("measureContour - wrapper : entering")
    try:
        body = byteify(json.loads(request.body.read()))
        relFITSFilePath = rebuildFilename(body['relFITSFilePath'])
        iFREQ = getIntValue(body['iFREQ'])
        contour = np.asarray(json.loads(body['contour']))
872
873
        level = getFloatValue(body['level'])
        result = dm.measureContour(relFITSFilePath, iFREQ, contour, level)
874
875
876
877
878
879
880
881
882
883
    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))))
        logger.debug(message)
        result = {"status": False, "message": message}

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

884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
@route( baseUrl+'/measureBox', name='measureBox', method='POST')
@enable_cors

def measureBox():
    logger.debug("measureContour - wrapper : entering")
    try:
        body = byteify(json.loads(request.body.read()))
        relFITSFilePath = rebuildFilename(body['relFITSFilePath'])
        iFREQ = getIntValue(body['iFREQ'])
        iRA0 = getIntValue(body['iRA0']) if 'iRA0' in body else None
        iRA1 = getIntValue(body['iRA1']) if 'iRA1' in body else None
        iDEC0 = getIntValue(body['iDEC0']) if 'iDEC0' in body else None
        iDEC1 = getIntValue(body['iDEC1']) if 'iDEC1' in body else None
        result = dm.measureBox(relFITSFilePath, iFREQ, iDEC0, iDEC1, iRA0, iRA1)
    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))))
        logger.debug(message)
        result = {"status": False, "message": message}

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

Caillat Michel's avatar
Caillat Michel committed
908
909
910
911
912
913
914
915
916
917
"""
    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
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
"""
@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
933
934
935
@route( baseUrl+'/createFits', name='createFits', method='POST')
@enable_cors
def createFits():
936
937
    logger.debug("createFits - wrapper : entering")

Caillat Michel's avatar
Caillat Michel committed
938
939
940
941
942
943
944
945
946
947
948
    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
949

950

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

Caillat Michel's avatar
Caillat Michel committed
954
    return json.dumps(result)
Caillat Michel's avatar
Caillat Michel committed
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970


"""
    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() :
971
    logger.debug("getEntries - wrapper : entering")
Caillat Michel's avatar
Caillat Michel committed
972
973
974
975
976
977
978
979
980
    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}
981
982
983

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

"""
Caillat Michel's avatar
Caillat Michel committed
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
@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_