DataBlock.py 51.1 KB
Newer Older
1
2
3
4
5
6
7
8
#
# A class to pack the content of a FITS file (IMAGE) and related data.
# --------------------------------------------------------------------
#
# Michel Caillat
# Observatoire de Paris - LERMA
# 27/06/2019
#
9
10
11
12
13
14
15
16
17
18
19
20
21
from astropy.io import fits
from astropy import wcs
from astropy.coordinates import SkyCoord
import numpy as np
import math
import json
from io import StringIO
from io import BytesIO
import matplotlib as mpl
import png
from PIL import Image, PngImagePlugin
import dask
import dask.array as da
22
import dask.delayed as dd
23
24

import traceback
25
from datetime import datetime, timedelta
Caillat Michel's avatar
Caillat Michel committed
26
import os
27
28
import re
from functools import reduce
Caillat Michel's avatar
Caillat Michel committed
29
30
from matplotlib import pyplot as pp

ba yaye-awa's avatar
ba yaye-awa committed
31
32
33
import yt
from spectral_cube import SpectralCube

ba yaye-awa's avatar
ba yaye-awa committed
34
import cv2
35

Caillat Michel's avatar
Caillat Michel committed
36
37
38
def cmap2palette (palette_name):
    x = pp.get_cmap(palette_name)
    return [tuple(map(lambda x: int(round(x*255)), x(i)[0:3])) for i in range(x.N)]
39

40
class DataBlock:
41
    # What is the FITS files root directory
42
    __FITSFilePrefix = ""
43
44

    # What is the PNG files root directory
45
    __PNGFilePrefix  = ""
Caillat Michel's avatar
Caillat Michel committed
46

47
48
    # What is the PNG files root directory
    __OBJFilePrefix  = ""
49

Caillat Michel's avatar
Caillat Michel committed
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
    # What are the palette names
    __palette_names = ["Greys", "RdYlBu", "hsv", "gist_ncar", "gist_rainbow", "gist_gray", "Spectral", "jet", "plasma", "inferno", "magma", "afmhot", "gist_heat"]

    __palette = {palette_name: cmap2palette(palette_name) for palette_name in __palette_names}

    __default_palette_name = "gist_rainbow"

    # What are the transformation names ?
    __transformation_names = ["minmax", "percent98"]
    __default_transformation_name = "minmax"

    # What are the video mode names ?
    __video_mode_names = ["direct", "inverse"]
    __default_video_mode_name = "direct"

    # What are the rendering capabilities ?
    __renderingCapabilities = {
Caillat Michel's avatar
Caillat Michel committed
67
68
69
70
71
72
        "itts": __transformation_names,
        "default_itt_index": 0,
        "luts": __palette_names,
        "default_lut_index": 2,
        "vmodes": __video_mode_names,
        "default_vmode_index": 0
Caillat Michel's avatar
Caillat Michel committed
73
74
    }

75
76
77
    # date time format
    __dateTimeFormat = "%m/%d/%Y - %H:%M:%S"

78
79
80
    # A regulare expression to catch the NAXISn
    __naxisn_rexp = re.compile("NAXIS[1-9]")

81
82
83
84
    # Maximum idleness duration for an instance of DataBlock
    # retrieved from the env var YAFITSS_MAXIDLE
    __max_idle = float(os.environ["YAFITSS_MAXIDLE"])

85
86
    #===========================================================================
    # Class methods - Setters and getters
Caillat Michel's avatar
Caillat Michel committed
87
88
89
90
91
92
93
94
95
96
97
    @classmethod
    def getPaletteFromName(cls, name):
        return DataBlock.__palette[name] if name in DataBlock.__palette_names else  DataBlock.__palette[DataBlock.__default_palette_name]

    @classmethod
    def getDefaultPaletteName(cls):
        return DataBlock.__default_palette_name

    @classmethod
    def getDefaults(cls):
        return (DataBlock.__default_palette_name, DataBlock.__default_transformation_name, DataBlock.__default_video_mode_name)
98

99
100
101
102
    @classmethod
    def setFITSFilePrefix(cls, FITSFilePrefix):
        DataBlock.__FITSFilePrefix = FITSFilePrefix

103
    @classmethod
104
105
    def setPNGFilePrefix(cls, PNGFilePrefix):
        DataBlock.__PNGFilePrefix = PNGFilePrefix
Caillat Michel's avatar
Caillat Michel committed
106

107
108
109
    @classmethod
    def setOBJFilePrefix(cls, OBJFilePrefix):
        DataBlock.__OBJFilePrefix = OBJFilePrefix
110

111
112
113
114
    @classmethod
    def getFITSFilePrefix(cls):
        return DataBlock.__FITSFilePrefix

115
116
117
    @classmethod
    def getPNGFilePrefix(cls):
        return DataBlock.__PNGFilePrefix
Caillat Michel's avatar
Caillat Michel committed
118

119
120
121
    @classmethod
    def getOBJFilePrefix(cls):
        return DataBlock.__OBJFilePrefix
122

Caillat Michel's avatar
Caillat Michel committed
123
124
125
126
    @classmethod
    def getRenderingCapabilities(cls):
        return DataBlock.__renderingCapabilities

Caillat Michel's avatar
Caillat Michel committed
127
128
129
130
131
132
133
134
135
    @classmethod
    def getDefaultITTName(cls):
        return DataBlock.__default_transformation_name

    @classmethod
    def getDefaultLUTName(cls):
        return DataBlock.__default_palette_name

    @classmethod
136
137
    def getDefaultVMName(cls):
        return DataBlock.__default_video_mode_name
Caillat Michel's avatar
Caillat Michel committed
138

139
    @classmethod
Caillat Michel's avatar
Caillat Michel committed
140
141
    def convert_size(cls, sizeInBytes):
        if sizeInBytes == 0:
142
143
            return "0B"
        size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
Caillat Michel's avatar
Caillat Michel committed
144
        i = int(math.floor(math.log(sizeInBytes, 1024)))
145
        p = math.pow(1024, i)
Caillat Michel's avatar
Caillat Michel committed
146
        s = round(sizeInBytes / p, 2)
147
148
        return "%s %s" % (s, size_name[i])

Caillat Michel's avatar
Caillat Michel committed
149
150
    @classmethod
    def getDataBlockInfoNames(cls):
151
152
153
154
155
156
157
158
159
        return ["path", "creationTime", "lastAccess", "naxisn", "size", "idle"]

    @classmethod
    def getMaxIdle(cls):
        return DataBlock.__max_idle

    @classmethod
    def getDateTimeFormat(cls):
        return DataBlock.__dateTimeFormat
Caillat Michel's avatar
Caillat Michel committed
160

161
    #===========================================================================
162
    # CTOR
163
164
    #

165
    def __init__(self, logger):
Caillat Michel's avatar
Caillat Michel committed
166
167
168
169
170
171
172
173
174
175
176
177
178
        self.__logger = logger
        self.__relFITSFilePath = None
        self.__data = None      # The data part of the FITS file
        self.__header = None      # The FITS file header as a dictionary
        self.__sliceMargin = None
        self.__wcs2 = None      # WCS object
        self.__wcs3 = None      # WCS object
        self.__convert = None
        self.__cdelt = None
        self.__lastAccess = None    # the last time a method was called on self
        self.__creationTime = None      # the date of birth
        self.__sizeInBytes = None
        self.__naxisn = None
Caillat Michel's avatar
Caillat Michel committed
179
180
        self.__logger.debug("A DataBlock has been just built")

Caillat Michel's avatar
Caillat Michel committed
181
182
        self.__infod = {
            "path": lambda: self.__relFITSFilePath,
183
184
            "creationTime": lambda: self.__creationTime.strftime(DataBlock.getDateTimeFormat()),
            "lastAccess": lambda: self.__lastAccess.strftime(DataBlock.getDateTimeFormat()),
Caillat Michel's avatar
Caillat Michel committed
185
            "naxisn": lambda: self.__naxisn,
186
187
            "size": lambda: DataBlock.convert_size(self.__sizeInBytes),
            "idle": None
Caillat Michel's avatar
Caillat Michel committed
188
189
        }

190
191
        self.__statistics ={}

192
193
    #===========================================================================
    #
Caillat Michel's avatar
Caillat Michel committed
194
    # Properties
195
    #
196
197
198
    @property
    def relFITSFilePath(self):
        return self.__relFITSFilePath
Caillat Michel's avatar
Caillat Michel committed
199

200
201
202
    @property
    def header(self):
        return self.__header
Caillat Michel's avatar
Caillat Michel committed
203

204
205
206
    @property
    def lastAccess(self):
        return self.__lastAccess
Caillat Michel's avatar
Caillat Michel committed
207

208
209
210
    @property
    def creationTime(self):
        return self.__creationTime
211

212
213
214
    @property
    def sizeInBytes(self):
        return self.__sizeInBytes
Caillat Michel's avatar
Caillat Michel committed
215

216
217
218
    @property
    def naxisn(self):
        return self.__naxisn
Caillat Michel's avatar
Caillat Michel committed
219
220
221
222
223
224
225
226
227
#
#===========================================================================
#
# Public setters
#

    # Set a lastAccess on self
    def setLastAccess(self):
        self.__lastAccess = datetime.now()
228
229

    #
Caillat Michel's avatar
Caillat Michel committed
230
    # Given a FITS file path:
231
232
233
    # * read the content of the FITS file in memory
    # * populates the instance variables accordingly
    #
234
    def setData(self, relFITSFilePath):
Caillat Michel's avatar
Caillat Michel committed
235
        self.__logger.debug("setData: entering")
236
237
238
239
240
241
        self.__logger.debug( "Setting data: %s" % relFITSFilePath)
        absFITSFilePath = DataBlock.getFITSFilePrefix() + relFITSFilePath
        self.__logger.debug("Full path is '%s'" % absFITSFilePath)

        try:
            hdu_list = fits.open(absFITSFilePath)
242
243
244
245
246
247
            self.__logger.debug("Opened {absFITSFilePath}")
            # MUSE detecttion.
            data_index = "PRIMARY"
            primary_header = hdu_list[data_index].header
            if "EXTEND" in primary_header and \
                primary_header["EXTEND"] and \
248
                "INSTRUME" in primary_header and \
249
250
251
252
253
254
255
256
257
258
                primary_header["INSTRUME"].strip() == "MUSE" and \
                "DATA" in hdu_list :
                    data_index = "DATA"
                    self.__header = hdu_list[data_index].header
                    self.__header["ORIGIN"] = primary_header["ORIGIN"].strip()
                    self.__header["INSTRUME"] = primary_header["INSTRUME"].strip()
                    self.__header["TELESCOP"] = primary_header["TELESCOP"].strip()
                    self.__header["OBSERVER"] = primary_header["OBSERVER"].strip()
                    self.__header["CDELT1"] = self.__header["CD1_1"];
                    self.__header["CDELT2"] = self.__header["CD2_2"];
259
260
                    if "CD3_3" in self.__header:
                        self.__header["CDELT3"] = self.__header["CD3_3"];
261
262
263
264
                    self.__header["RADESYS"] = primary_header["RADECSYS"].strip();
                    self.__header["DATE-OBS"] = primary_header["DATE"].strip();
                    self.__header["DATE"] = primary_header["DATE"].strip();
                    self.__logger.debug("We are in MUSE")
265
            # This is NOT a MUSE observation
266
267
            else:
                self.__header = hdu_list[data_index].header
268

269
            numDims = len(hdu_list[data_index].data.shape)
270
271
            if numDims > 4 :
                self.__logger.debug("Unacceptable value of numDims: %s" % numDims)
Caillat Michel's avatar
Caillat Michel committed
272
            if numDims == 4:
273
                self.__data = da.from_array(hdu_list[data_index].data[0], chunks = (hdu_list[data_index].data[0].shape[0], 128, 128))
274
            elif numDims == 3:
275
                self.__data = da.from_array(hdu_list[data_index].data, chunks = (hdu_list[data_index].data.shape[0], 128, 128))
276
277
            elif numDims == 2:
                self.__data = da.from_array(hdu_list[data_index].data, chunks = (128, 128))
Caillat Michel's avatar
Caillat Michel committed
278
279
            else:
                self.__logger.debug("Unacceptable value of numDims: %s" % numDims)
280
281

            shp = self.__data.shape
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
            if numDims > 2 :
                if shp[1] > shp[2]:
                    self.__sliceMargin = da.from_array(np.nan * np.ones(shape=(shp[1], shp[1] - shp[2])), chunks=(128, 128))
                elif shp[1] < shp[2]:
                    self.__sliceMargin = da.from_array(np.nan * np.ones(shape=(shp[2] - shp[1], shp[2])), chunks=(128, 128))
                else:
                    pass
            else :
                if shp[0] > shp[1]:
                    self.__sliceMargin = da.from_array(np.nan * np.ones(shape=(shp[0], shp[0] - shp[1])), chunks=(128, 128))
                elif shp[0] < shp[1]:
                    self.__sliceMargin = da.from_array(np.nan * np.ones(shape=(shp[1] - shp[0], shp[1])), chunks=(128, 128))
                else:
                    pass

297
298

            #
299
            # Header "normalization"
300
            #
301
302
303
            if "BUNIT" in self.__header and self.__header["BUNIT"] == "JY/BEAM":
                self.__header["BUNIT"] = "Jy/beam"

Caillat Michel's avatar
Caillat Michel committed
304
            if "TELESCOP" not in self.__header:
305
306
                self.__header["TELESCOP"] = "Unknown"

Caillat Michel's avatar
Caillat Michel committed
307
            if "INSTRUME" not in self.__header:
308
309
310
                self.__header["INSTRUME"] = "Unknown"

            if "OBSERVER" not in self.__header:
Caillat Michel's avatar
Caillat Michel committed
311
                self.__header["OBSERVER"] = "John Doe"
312

Caillat Michel's avatar
Caillat Michel committed
313
            if "ORIGIN" not in self.__header:
314
315
                self.__header["ORIGIN"] = "Unknown"

Caillat Michel's avatar
Caillat Michel committed
316
            if "SPECSYS" not in self.__header:
317
318
                self.__header["SPECSYS"] = "Unknown"

Caillat Michel's avatar
Caillat Michel committed
319
320
            if "RESTFRQ" not in self.__header:
                if "RESTFREQ" in self.__header:
321
                    self.__header["RESTFRQ"] = self.__header["RESTFREQ"]
Caillat Michel's avatar
Caillat Michel committed
322
                else:
323
324
                    self.__header["RESTFRQ"] = 0.0

Caillat Michel's avatar
Caillat Michel committed
325
            if self.__header["ORIGIN"] == "GILDAS Consortium":
326
327
328
329
330
331
332
333
334
                if "CUNIT3" not in self.__header:
                    self.__header["CUNIT3"] = "km/s"
                if "RADESYS" not in self.__header:
                    self.__header["RADESYS"] = "ICRS"
                if "DATE-OBS" not in self.__header:
                    self.__header["DATE-OBS"] = self.__header["DATE"]
                if "TELESCOP" not in self.__header:
                    self.__header["TELESCOP"] = "NOEMA"

Caillat Michel's avatar
Caillat Michel committed
335
            if "BUNIT" not in self.__header:
336
                self.__header["BUNIT"] = "Undefined"
Caillat Michel's avatar
Caillat Michel committed
337
338
            elif self.__header["INSTRUME"] == "SITELLE" \
                and self.__header["BUNIT"] == "FLUX":
339
340
341
342
                self.__header["BUNIT"] = "erg/s/cm^2/A/arcsec^2"

            self.__logger.debug("Working on '%s'" % (relFITSFilePath))

Caillat Michel's avatar
Caillat Michel committed
343
344
            # *None* prevents throwing error
            self.__header.pop("HISTORY", None)
345
346
347
348
349
350
351
352
            self.__header.pop("COMMENT", None)
            self.__header.pop("", None)
            try:
                self.__logger.debug("Got RESTFRQ %f" % self.__header["RESTFRQ"])
            except KeyError:
                self.__header["RESTFRQ"] = self.__header["RESTFREQ"]

            self.__wcs2 = wcs.WCS(self.__header, naxis=2)
353
            if self.__header["INSTRUME"] != "SITELLE" and self.__header["NAXIS"] > 2:
354
355
356
                self.__wcs3 = wcs.WCS(self.__header, naxis=3)

            pi180 = math.pi / 180 / 4.86e-6
Caillat Michel's avatar
Caillat Michel committed
357
            try:
358
359
360
361
362
363
364
365
366
367
                bmaj = self.__header["BMAJ"] * pi180
                bmin = self.__header["BMIN"] * pi180
                self.__convert = math.pi * bmaj * bmin
                self.__cdelt =  4 * math.log(2) * math.fabs(self.__header["CDELT1"] * pi180) * math.fabs(self.__header["CDELT2"] * pi180)
            except KeyError:
                self.__convert = 1.0
                self.__cdelt = 1.0

            self.__relFITSFilePath = relFITSFilePath

Caillat Michel's avatar
Caillat Michel committed
368
369
370
371
372
            # date of birth == lastAccess == now
            self.__lastAccess = self.__creationTime = datetime.now()
            naxisnValues = [self.__header[key] for key in self.__header if DataBlock.__naxisn_rexp.match(key)]
            self.__sizeInBytes = reduce(lambda x, y: x*y, naxisnValues) * abs(self.__header["BITPIX"]) / 8
            self.__naxisn = reduce(lambda x, y: "%sx%s"%(x,y), naxisnValues)
373

Caillat Michel's avatar
Caillat Michel committed
374
            result = self.getHeader()
375
        except Exception as e:
376
377
378
379
            message = f"Error while opening file {relFITSFilePath}: {e}"
            self.__logger.debug(message)
            self.__logger.debug(f"Traceback is : {traceback.format_exc()}")
            result = {"status": False, "message": message, "result": None}
380

Caillat Michel's avatar
Caillat Michel committed
381
        self.__logger.debug("setData: Exiting")
382
383
        return result

384
385

    #===========================================================================
386
387
388
    #
    # Private methods
    #
Caillat Michel's avatar
Caillat Michel committed
389
390
391
    def __getPixelValueAtiFreqiRAiDEC(self, iFreq, iRA, iDEC):

        self.__logger.debug("__getPixelValuAtiFreqiRAiDEC: entering")
392
393
        self.__logger.debug("iFreq = {:d}, iRA = {:d}, iDEC = {:d}".format(iFreq, iRA, iDEC) )
        result = self.__data[iFreq, iDEC, iRA].compute()
Caillat Michel's avatar
Caillat Michel committed
394
        if np.isnan(result):
395
            result = None
Caillat Michel's avatar
Caillat Michel committed
396
        else:
397
            result = float(result)
Caillat Michel's avatar
Caillat Michel committed
398
        self.__logger.debug("__getPixelValuAtiFreqiRAiDEC: exiting")
399
400
401
        return result

    def __getFreqsAtiRAiDEC(self, iRA, iDEC):
Caillat Michel's avatar
Caillat Michel committed
402
        self.__logger.debug("__getFreqsAtiRAiDEC: entering")
403
404
        x = [[iRA, iDEC, iFreq] for iFreq in range(self.__header["NAXIS3"])]
        result = [crdnn[2] for crdnn in self.__wcs3.all_pix2world(x, 0)]
Caillat Michel's avatar
Caillat Michel committed
405
        self.__logger.debug("__getFreqsAtiRAiDEC: exiting")
406
407
        return result

Caillat Michel's avatar
Caillat Michel committed
408
    def __getSumOverSliceRectArea_0(self, iFREQ, iRA0=None, iRA1=None, iDEC0=None, iDEC1=None):
Caillat Michel's avatar
Caillat Michel committed
409
        self.__logger.debug("__getSumOverSliceRectArea_0: entering")
410

411
412
413
414
        if self.__header["NAXIS"] > 2:
            result = (dask.array.nansum(self.__data[iFREQ, iDEC0:iDEC1, iRA0:iRA1]) / self.__convert * self.__cdelt).compute().tolist()
        else :
            result = (dask.array.nansum(self.__data[iDEC0:iDEC1, iRA0:iRA1]) / self.__convert * self.__cdelt).compute().tolist()
415

Caillat Michel's avatar
Caillat Michel committed
416
        self.__logger.debug("__getSumOverSliceRectArea__0: exiting")
417
418

        return result
419

Caillat Michel's avatar
Caillat Michel committed
420
    def __getAverage_0(self, iFREQ0=None, iFREQ1=None, iDEC0=None, iDEC1=None, iRA0=None, iRA1=None):
Caillat Michel's avatar
Caillat Michel committed
421
422
        self.__logger.debug("__getAverage_0: entering")
        self.__logger.debug("shape length: %d" % len(self.__data.shape))
423

Caillat Michel's avatar
Caillat Michel committed
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        if iRA0 == None:
            iRA0 = 0
        if iRA1 == None:
            iRA1 = self.__data.shape[2]
        if iDEC0 == None:
            iDEC0 = 0
        if iDEC1 == None:
            iDEC1 = self.__data.shape[1]
        if iFREQ0 == None:
            iFREQ0 = 0
        if iFREQ1 == None:
            iFREQ1 = self.__data.shape[0]

        self.__logger.debug(f"{iFREQ0}, {iFREQ1}, {iDEC0}, {iDEC1}, {iRA0}, {iRA1}")
438
439
440
441
442
443
444
445

        if (self.__header["ORIGIN"]=="GILDAS Consortium"):
            self.__logger.debug("Gildas data")
            cdelt = math.fabs(self.__header["CDELT3"])/1000.
            self.__logger.debug("CDELT = %f"%cdelt)
        elif self.__header["ORIGIN"].startswith("CASA"):
            self.__logger.debug("ALMA data")
            cunit3 = self.__header["CUNIT3"]
Caillat Michel's avatar
Caillat Michel committed
446
            if cunit3 == "m/s":
447
                cdelt = math.fabs(self.__header["CDELT3"]) / 1000.0
Caillat Michel's avatar
Caillat Michel committed
448
            elif cunit3 == "Hz":
449
450
451
452
453
                cdelt = 3e+5 * math.fabs(self.__header["CDELT3"] / self.__header["RESTFRQ"])
            else:
                cdelt = 0.0
        elif self.__header["INSTRUME"] == "SITELLE":
            cdelt = 1.0
454
455
        elif self.__header["INSTRUME"] == "MUSE":
            cdelt = 1.0
456
457
458
459
        else:
            cdelt = 0.0

        self.__logger.debug("slice sum begin")
460

461
462
        if self.__header["NAXIS"] > 2:
            result = (dask.array.nansum(self.__data[iFREQ0:iFREQ1, iDEC0:iDEC1, iRA0:iRA1], 0)*cdelt).compute()
463
        else:
464
465
466
467
            result = self.__data[iDEC0:iDEC1, iRA0:iRA1]*cdelt.compute()

        self.__collectStatistics("%d-%d"%(iFREQ0, iFREQ1), result)

468

469
        self.__logger.debug("slice sum end")
470
        self.__logger.debug(f"result={result}")
471
        self.__logger.debug("__getAverage_0: exiting")
472
        return result
473
474
475
476

    def __getPercentile(self, a, percent):
        return np.nanpercentile(a, percent)

Caillat Michel's avatar
Caillat Michel committed
477
    def __convertOneSlice2PNG(self, PNGPath, iFREQ, sliceData,  transformation_name, palette_name, video_mode_name):
Caillat Michel's avatar
Caillat Michel committed
478
479
        self.__logger.debug("__convertOneSlice2PNG: entering")
        self.__logger.debug("__convertOneSlice2PNG: exiting")
Caillat Michel's avatar
Caillat Michel committed
480
        return self.__convertOneSlice2PNG_0(PNGPath, iFREQ, sliceData, transformation_name, palette_name, video_mode_name)
481

Caillat Michel's avatar
Caillat Michel committed
482
    def __convertOneSlice2PNG_0 (self, PNGPath, sliceData, transformation_name, palette_name, video_mode_name):
Caillat Michel's avatar
Caillat Michel committed
483
        self.__logger.debug("__convertOneSlice2PNG_0: entering")
484
485
486
487
488

        img_f = np.zeros((sliceData.shape[1], sliceData.shape[0]), dtype=np.float32)
        img_i = np.zeros((sliceData.shape[1], sliceData.shape[0]), dtype=np.uint8)

        data_steps = {}
Caillat Michel's avatar
Caillat Michel committed
489
        palette = DataBlock.getPaletteFromName(palette_name)
490
        N = len(palette)
Caillat Michel's avatar
Caillat Michel committed
491
        if transformation_name == "percent98":
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
            self.__logger.debug("A '%s' transformation will be applied " % transformation_name)
            img_f = np.array(sliceData)
            p_high = self.__getPercentile(sliceData, 99.9)
            p_low = self.__getPercentile(sliceData, 0.1)

            data_min, data_max = self.__min_max(sliceData)
            shape = sliceData.shape

            n_low_steps = 3
            n_high_steps = 3
            n_mid_steps = N - n_low_steps - n_high_steps

            data_step = (p_low - data_min) / n_low_steps
            for i in range (0, n_low_steps):
                data_steps["%d_%d_%d"%(palette[i][0], palette[i][1], palette[i][2])] = data_min + i * data_step

            data_step = (p_high - p_low) / n_mid_steps
            for i in range(0, n_mid_steps):
                j = n_low_steps + i
                data_steps["%d_%d_%d"%(palette[j][0], palette[j][1], palette[j][2])] = p_low + i * data_step

            data_step = (data_max - p_high) / n_high_steps
            for i in range(0, n_high_steps):
                j = n_low_steps + n_mid_steps + i
                data_steps["%d_%d_%d"%(palette[j][0], palette[j][1], palette[j][2])] = p_high + i * data_step

            img_f_flat = img_f.flatten()
            img_f_flat_N = np.zeros(img_f_flat.shape)

            low_mask = img_f_flat < p_low
            img_f_flat_N[low_mask] = n_low_steps * (img_f_flat[low_mask] - data_min) / (p_low - data_min)

            mid_mask = np.logical_and((img_f_flat >= p_low), (img_f_flat < p_high))
            img_f_flat_N[mid_mask] = n_low_steps + n_mid_steps * (img_f_flat[mid_mask] - p_low) / ( p_high - p_low)

            high_mask = img_f_flat >= p_high
528
            img_f_flat_N[high_mask] = n_low_steps + n_mid_steps + n_high_steps * (img_f_flat[high_mask] - p_high) / (data_max - p_high) - 1
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552

            img_f = img_f_flat_N.reshape(shape)

        else:
            self.__logger.debug("A '%s' transformation will be applied " % "minmax")
            data_min, data_max = self.__min_max(sliceData)

            data_step = (data_max - data_min) / (N - 1)
            for i in range(0, N):
                data_steps["%d_%d_%d"%(palette[i][0], palette[i][1], palette[i][2])] = data_min + i * data_step

            data_range = data_max - data_min
            img_f = ((N - 1) * (np.array(sliceData) - data_min) / data_range)

        img_i = img_f.astype(np.uint8)

        self.__logger.debug("video_mode_name = %s" % video_mode_name)
        if (video_mode_name == "inverse"):
            self.__logger.debug("Display in inverse mode ")
            img_i = (N - 1) - img_i
        else:
            self.__logger.debug("Display in direct mode")

        for irow in range(img_i.shape[1]//2):
Caillat Michel's avatar
Caillat Michel committed
553
            img_i[irow,:], img_i[img_i.shape[1] - 1 - irow,:] = img_i[img_i.shape[1] - 1 - irow,:], img_i[irow,:].copy()
554
555
556
557
558
559

        f = open(PNGPath, 'wb')
        w = png.Writer(sliceData.shape[1], sliceData.shape[0],palette=palette)
        w.write(f, img_i.tolist())
        f.close()

Caillat Michel's avatar
Caillat Michel committed
560
        self.__logger.debug("__convertOneSlice2PNG_0: exiting")
561
562
        return data_steps

Caillat Michel's avatar
Caillat Michel committed
563
    def __convertSummedSliceRange2PNG(self, iFREQ0, iFREQ1, sliceData, transformation_name, palette_name, video_mode_name):
Caillat Michel's avatar
Caillat Michel committed
564
        self.__logger.debug("__convertSummedSliceRange2PNG: entering")
Caillat Michel's avatar
Caillat Michel committed
565
        PNGPath = "%s/%d-%d.%s.%s.%s.png" % (self.__PNGDir, iFREQ0, iFREQ1, transformation_name, palette_name, video_mode_name)
566

567
        return self.__convertOneSlice2PNG_0(PNGPath, sliceData, transformation_name, palette_name, video_mode_name)
Caillat Michel's avatar
Caillat Michel committed
568
        self.__logger.debug("__convertSummedSliceRange2PNG: exiting")
569
570
        return

Caillat Michel's avatar
Caillat Michel committed
571
    def __min_max(self, dataArray):
572
573
574
575
576
        data_max = np.nanmax(dataArray)
        data_min = np.nanmin(dataArray)
        return (data_min.astype(float), data_max.astype(float))

    def __getForcedTransformationName(self,  transformation_name):
Caillat Michel's avatar
Caillat Michel committed
577
        self.__logger.debug("__getForcedTransformationName: entering")
578
        result = transformation_name
Caillat Michel's avatar
Caillat Michel committed
579
        self.__logger.debug("__getForcedTransformationName: exiting")
580
581
582
583
        return result

    def __squareSliceData (self, sliceData):
        sliceShape = sliceData.shape
Caillat Michel's avatar
Caillat Michel committed
584
        if sliceShape[0] == sliceShape[1]:
585
586
587
            squaredSliceData = sliceData
        else:
            tmparr = [sliceData, self.__sliceMargin]
Caillat Michel's avatar
Caillat Michel committed
588
            if sliceShape[0] > sliceShape[1]:
589
                axis = 1
Caillat Michel's avatar
Caillat Michel committed
590
            else:
591
592
593
594
595
596
                axis = 0

            squaredSliceData = da.concatenate(tmparr, axis=axis)
        return squaredSliceData

    def __addWCStoPNG(self, absPNGFilePath, headerInfos):
Caillat Michel's avatar
Caillat Michel committed
597
        self.__logger.debug("__addWCStoPNG: entering")
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
        im = Image.open(absPNGFilePath)
        info = PngImagePlugin.PngInfo()
        naxis2 = max(headerInfos["NAXIS1"], headerInfos["NAXIS2"])
        naxis1 = naxis2
        info.add_text("zTXtcomment",
            "SIMPLE  = T "+
            "\n BITPIX = "+str(headerInfos["BITPIX"])+
            "\n NAXIS = 2 "+
            "\n NAXIS1  = "+str(naxis1)+
            "\n NAXIS2  = "+str(naxis2)+
            "\n CRPIX1  = "+str(headerInfos["CRPIX1"])+
            "\n CRPIX2  = "+str(headerInfos["CRPIX2"])+
            "\n EQUINOX = 2000.0" +
            "\n CRVAL1  = "+str(headerInfos["CRVAL1"])+
            "\n CRVAL2  = "+str(headerInfos["CRVAL2"])+
            "\n CTYPE1  = "+str(headerInfos["CTYPE1"])+
            "\n CTYPE2  = "+str(headerInfos["CTYPE2"])+
            "\n RADESYS= "+str(headerInfos["RADESYS"])+
            "\n CD1_1   = "+str(headerInfos["CDELT1"])+
            "\n CD1_2   = -0.0 " +
            "\n CD2_1   = -0.0 " +
            "\n CD2_2 = " +str(headerInfos["CDELT2"]) )

        im.save(absPNGFilePath, "PNG", pnginfo=info)
Caillat Michel's avatar
Caillat Michel committed
622
        self.__logger.debug("__addWCStoPNG: exiting")
623

Caillat Michel's avatar
Caillat Michel committed
624
625
    def __getSpectrumAtiRAiDEC(self, iRA, iDEC):
        self.__logger.debug("__getSpectrumAtiRAiDEC: entering")
626
        result = self.__data[:, iDEC, iRA]
Caillat Michel's avatar
Caillat Michel committed
627
        self.__logger.debug("__getSpectrumAtiRAiDEC: exiting")
628
629
630
        return result

    def __createFITSSpectrumFromData(self, crds, crdsUnit, spectrum, spectrumUnit):
Caillat Michel's avatar
Caillat Michel committed
631
        self.__logger.debug("__createFITSSpectrumFromData: entering")
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
        header = self.__header
        #outmp = StringIO()
        outmp = BytesIO()
        #create table
        xcolumn = fits.Column(name = 'Frequency', format='E', unit = crdsUnit, array=crds)
        ycolumn = fits.Column(name = 'Flux', format='E', unit = spectrumUnit, array=spectrum)
        hdu = fits.TableHDU.from_columns(fits.ColDefs([xcolumn, ycolumn]))

        #create header and primary hdu
        hdr = fits.Header()
        hdr['DATE'] = header["DATE"]
        hdr['DATE-OBS'] = header["DATE-OBS"]
        hdr['TELESCOP'] = header["TELESCOP"]
        hdr['OBSERVER'] = header["OBSERVER"]
        hdr['RESTFRQ'] = header["RESTFRQ"]
        hdr['SPECSYS'] = header["SPECSYS"]
        hdr['COMMENT'] = 'Spectrum of a single pixel of the cube'
        primary_hdu = fits.PrimaryHDU(header = hdr)
        hdulist = fits.HDUList([primary_hdu,hdu])
        self.__logger.debug("type of hdlist = %s" % type(hdulist))
        hdulist.writeto(outmp,overwrite=True)
        fits_content = outmp.getvalue().decode("utf-8")
        outmp.close()
Caillat Michel's avatar
Caillat Michel committed
655
        self.__logger.debug("__createFITSSpectrumFromData: exiting")
656
657
658
        return fits_content

    def __createFITS0(self, iRA, iDEC):
Caillat Michel's avatar
Caillat Michel committed
659
        self.__logger.debug("__createFITS0: entering")
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
        result = {"status": False, "message": "", "result": None}
        try:
            header = self.__header
            #
            # Retrieve the data to be published via SAMP
            #
            # frequencies ( or velocities )
            crds = self.__getFreqsAtiRAiDEC(iRA, iDEC)
            spectrum = self.__getSpectrumAtiRAiDEC(iRA, iDEC)


            fits_content = self.__createFITSSpectrumFromData( crds, header["CUNIT3"], spectrum, header["BUNIT"])
            result = {"status": True, "message": "", "result": fits_content}
        except Exception as e:
            result["message"] = "An exception occurred with message '%s'" % str(e)
            self.__logger.debug("An exception occurred with message '%s'" % str(e))
Caillat Michel's avatar
Caillat Michel committed
676
677
            self.__logger.debug("Traceback: %s"  % traceback.format_exc())
        self.__logger.debug("__createFITS0: exiting")
678
679
        return result

680
681
    #===========================================================================
    # Public getters and accessors.
682
    #
Caillat Michel's avatar
Caillat Michel committed
683
684
    def getlastAccess(self):
        return {"status": True, "message": "", "result": self.__lastAccess}
685

686
    def getHeader(self):
Caillat Michel's avatar
Caillat Michel committed
687
        return {"status": True, "message": "", "result": json.dumps(dict(self.__header))}
688

689
690
    def getDimensions(self):
        return {"status": True, "message": "", "result": self.__data.shape}
691

692
    def __collectStatistics(self, key, data): # data is expected to be a numpy array
693
694
        self.__logger.debug("__collectStatistics : entering")
        if key in self.__statistics:
695
            self.__logger.debug("Statistics are already collected for key %s" % key)
696
        else:
697
            self.__logger.debug("Statistics have to be collected for key %s" % key)
698
699
700
701
702
703
704
705
706
707
708
709
710
711
            results = []
            t = dd(np.nanmin)(data); results.append(t)
            t = dd(np.nanmax)(data); results.append(t)
            t = dd(np.nanmean)(data); results.append(t)
            t = dd(np.nanstd)(data); results.append(t)
            t = dd(np.histogram)(data[~np.isnan(data)],bins=100 ); results.append(t)

            results = dask.compute(*results)
            self.__statistics[key]={}
            self.__statistics[key]["min"] = results[0].item()
            self.__statistics[key]["max"] = results[1].item()
            self.__statistics[key]["mean"] = results[2].item()
            self.__statistics[key]["std"] = results[3].item()
            self.__statistics[key]["histogram"] = [x.tolist() for x in results[4]]
712
713
714
715
716
            population = self.__statistics[key]["histogram"][0]
            bins = self.__statistics[key]["histogram"][1]
            aux = [0 for x in range(len(bins))]
            for i in range (1, len(aux)):
                aux[i] = aux[i-1]+population[i-1]
717
            normfactor = np.count_nonzero(~np.isnan(data)) #reduce(lambda a,b : a*b, self.__data.shape);
718
            self.__statistics[key]["cumuldist"] = list(map(lambda x: x/normfactor, aux));
719
720
721
722
        self.__logger.debug(self.__statistics[key]);
        self.__logger.debug("__collectStatistics : exiting")


Caillat Michel's avatar
Caillat Michel committed
723
    def __getSlice(self, iFREQ, step=1):
Caillat Michel's avatar
Caillat Michel committed
724
        self.__logger.debug("__getSlice: entering")
Caillat Michel's avatar
Caillat Michel committed
725

726
        result = None
Caillat Michel's avatar
Caillat Michel committed
727
728
729
        numDimensions = len(self.__data.shape)

        if numDimensions == 2:
730
            result = self.__data[0:self.__data.shape[0]:step, 0:self.__data.shape[1]:step]
Caillat Michel's avatar
Caillat Michel committed
731
732
733
        elif numDimensions == 3:
            if iFREQ == None:
                iFREQ = self.__data.shape[0] / 2
734
            result = self.__data[iFREQ, 0:self.__data.shape[1]:step, 0:self.__data.shape[2]:step]
Caillat Michel's avatar
Caillat Michel committed
735

736
737
738
        #
        # Collect some statistics if not already done
        #
739
        self.__collectStatistics("%d"%iFREQ, result.compute())
740
        self.__logger.debug("__getSlice: exiting")
741
        return result # A DASK array
742

Caillat Michel's avatar
Caillat Michel committed
743
744
    def getSlice(self, iFREQ, step=1):
        self.__logger.debug("getSlice: entering")
Caillat Michel's avatar
Caillat Michel committed
745
        result = dict()
Caillat Michel's avatar
Caillat Michel committed
746
        numDimensions = len(self.__data.shape)
747

Caillat Michel's avatar
Caillat Michel committed
748
749
750
751
752
753
        try :
            if numDimensions > 3 or numDimensions < 2:
                message = f"Can't process data with such a shape: {self.__data.shape}"
                result["status"]=False
                result["message"]= message
            else:
Caillat Michel's avatar
Caillat Michel committed
754
                x = self.__getSlice(iFREQ, step)
755
756
757
758
759
760
761
762
                self.__logger.debug(f"type self.__data = {type(self.__data)}, type x = {type(x)}")
                self.__logger.debug("Avant isnan")
                self.__logger.debug(x.shape)
                x = x.compute()
                b = np.isnan(x)
                self.__logger.debug(b.shape)
                x[b]=None
                self.__logger.debug("Apres isnan")
Caillat Michel's avatar
Caillat Michel committed
763
764
                result["status"]=True
                result["message"]=""
765
766
                result["result"]={}
                result["result"]["slice"]=x.tolist()
767
                result["result"]["statistics"]=self.__statistics["%d"%iFREQ]
Caillat Michel's avatar
Caillat Michel committed
768
769
770
771
        except Exception as e:
            result["message"]=f"An exception occurred with message {e}"
            result["status"]= False
            result["result"]=None
772

Caillat Michel's avatar
Caillat Michel committed
773
        self.__logger.debug("getSlice: exiting")
Caillat Michel's avatar
Caillat Michel committed
774
        return result
775

776
777
    def getSpectrum(self, iRA=None, iDEC=None, iFREQ0=None, iFREQ1=None):
        self.__logger.debug( "getSpectrum: entering")
Caillat Michel's avatar
Caillat Michel committed
778
779
780
781
782
783
784
785
786
787
        if iRA == None:
            iRA = 0
        if iDEC == None:
            iDEC = 0
        if iFREQ0 == None:
            iFREQ0 = 0
        if iFREQ1 == None:
            iFREQ1 = self.__data.shape[0]

        nonanArray = np.nan_to_num(self.__data[iFREQ0:iFREQ1, iDEC, iRA].compute())
788
        result = nonanArray.tolist()
789
        self.__logger.debug( "getSpectrum: exiting")
790
791
792
793

        return {"status": True, "message": "", "result": result}

    def getPixelValueAtiFreqiRAiDEC(self, iFreq, iRA, iDEC):
Caillat Michel's avatar
Caillat Michel committed
794
795
        self.__logger.debug("getPixelValueAtiFreqiRAiDEC: entering")
        try:
796
797
            data = self.__data
            shape=data.shape
Caillat Michel's avatar
Caillat Michel committed
798
            if (len(shape) == 3 ) and (0 <= iFreq < shape[0]) and ( 0 <= iRA < shape[1]) and (0 <= iDEC < shape[2]):
799
800
                result = {"status": True, "message": "", "result": self.__getPixelValueAtiFreqiRAiDEC(iFreq, iRA, iDEC)}
            else:
Caillat Michel's avatar
Caillat Michel committed
801
                result = {"status": False, "message": "Invalid coordinates '{0}, {1}, {2}' or data with inappropriate shape '{3}' ".format(iFreq, iRA, iDEC, shape), "result": None}
802
        except Exception as e:
Caillat Michel's avatar
Caillat Michel committed
803
            result = {"status": False, "message": "{0} - {1}".format(type(e), e.args), "result": None}
804

Caillat Michel's avatar
Caillat Michel committed
805
        self.__logger.debug("getPixelValueAtiFreqiRAiDEC: exiting")
806
807
        return result

Caillat Michel's avatar
Caillat Michel committed
808
    def getAverageSpectrum(self, iDEC0=None, iDEC1=None, iRA0=None, iRA1=None, retFITS=False):
Caillat Michel's avatar
Caillat Michel committed
809
        self.__logger.debug("getAverageSpectrum: entering")
Caillat Michel's avatar
Caillat Michel committed
810
811
812
813
814
815
816
817
818

        if iRA0 == None:
            iRA0 = 0
        if iRA1 == None:
            iRA1 = self.__data.shape[2]
        if iDEC0 == None:
            iDEC0 = 0
        if iDEC1 == None:
            iDEC1 = self.__data.shape[1]
819
820
821
822
823
824

        pi180 = math.pi / 180 / 4.86e-6

        averageSpectrum = None
        with_dask=True
        if with_dask:
Caillat Michel's avatar
Caillat Michel committed
825
            averageSpectrum = (dask.array.nansum(self.__data[:, iDEC0:iDEC1, iRA0:iRA1], (1,2)) / self.__convert * self.__cdelt).compute().tolist()
826
        else:
Caillat Michel's avatar
Caillat Michel committed
827
            averageSpectrum = np.nansum(self.__data[:, iDEC0:iDEC1, iRA0:iRA1], (1,2)) / self.__convert * self.__cdelt
Caillat Michel's avatar
Caillat Michel committed
828
        if retFITS:
Caillat Michel's avatar
Caillat Michel committed
829
            crds = self.__getFreqsAtiRAiDEC(iRA0, iDEC0)
830
831
            averageSpectrumFits = self.__createFITSSpectrumFromData(crds,self.__header["CUNIT3"], averageSpectrum, "Jy")
            result = {"averageSpectrum": averageSpectrum, "averageSpectrumFits": averageSpectrumFits}
Caillat Michel's avatar
Caillat Michel committed
832
        else:
833
834
            result = {"averageSpectrum": averageSpectrum, "averageSpectrumFits": None}

Caillat Michel's avatar
Caillat Michel committed
835
        result = {"status": True, "message": "", "result": result}
Caillat Michel's avatar
Caillat Michel committed
836
        self.__logger.debug("getAverageSpectrum: exiting")
Caillat Michel's avatar
Caillat Michel committed
837
        return result
838

Caillat Michel's avatar
Caillat Michel committed
839
    def getSumOverSliceRectArea(self, iFREQ, iRA0=None, iRA1=None, iDEC0=None, iDEC1=None):
Caillat Michel's avatar
Caillat Michel committed
840
        self.__logger.debug("getSumOnSliceRectArea: entering")
Caillat Michel's avatar
Caillat Michel committed
841
842
        if (len(self.__data.shape) == 2 and iFREQ >= 1) or (len(self.__data.shape) == 3 and iFREQ >= self.__data.shape[0]):
            result = {"status": False, "message": "Invalid slice index '%d'." % (iFREQ), "result": None }
843
        else:
Caillat Michel's avatar
Caillat Michel committed
844
            result = {"status": True, "message": "", "result": self.__getSumOverSliceRectArea_0(iFREQ, iRA0, iRA1, iDEC0, iDEC1)}
845

Caillat Michel's avatar
Caillat Michel committed
846
        self.__logger.debug("getSumOnSliceRectArea: exiting")
847
848
        return result

Caillat Michel's avatar
Caillat Michel committed
849
    def getAverage(self, iFREQ0=None, iFREQ1=None, iDEC0=None, iDEC1=None, iRA0=None, iRA1=None, retFITS=False):
Caillat Michel's avatar
Caillat Michel committed
850
        self.__logger.debug("getAverage: entering")
Caillat Michel's avatar
Caillat Michel committed
851
        result = self.__getAverage_0(iFREQ0, iFREQ1, iDEC0, iDEC1, iRA0, iRA1)
Caillat Michel's avatar
Caillat Michel committed
852
        self.__logger.debug("getAverage: exiting")
853
        return {"status": True, "message": "", "result": result.tolist()}
854

Caillat Michel's avatar
Caillat Michel committed
855
    def getOneSliceAsPNG (self, iFREQ,  ittName=__default_transformation_name, lutName=__default_palette_name, vmName=__default_video_mode_name):
Caillat Michel's avatar
Caillat Michel committed
856
        self.__logger.debug("getOneSliceAsPNG: entering.")
Caillat Michel's avatar
Caillat Michel committed
857
        relPNGFileDir = ('.').join(self.__relFITSFilePath.split('.')[:-1])
858
        absPNGFileDir = DataBlock.getPNGFilePrefix()+"/"+relPNGFileDir
859

860
        ittName = self.__getForcedTransformationName(ittName)
861
862
863
864

        status = None
        sliceData = None

865
        data = self.__data
866
867
868
        shape = data.shape
        self.__logger.debug(shape)

Caillat Michel's avatar
Caillat Michel committed
869
870
        if ( iFREQ >= shape[0] ):
            self.__logger.debug("No such slice index '%d' . Max. possible value is '%d'" %(iFREQ, shape[1]))
Caillat Michel's avatar
Caillat Michel committed
871
        elif ( len(shape) > 4 ):
872
            self.__logger.debug("Can't process data with more than 4 dimensions")
Caillat Michel's avatar
Caillat Michel committed
873
        else:
874
            sliceData = self.__getSlice(iFREQ)
875

Caillat Michel's avatar
Caillat Michel committed
876
            relPNGFilePath = ("%s/%d.%s.%s.%s.png" % (relPNGFileDir, iFREQ, ittName, lutName, vmName))
877
            absPNGFilePath = DataBlock.getPNGFilePrefix() + "/" + relPNGFilePath
878

Caillat Michel's avatar
Caillat Michel committed
879
880
            #if not os.path.exists(absPNGFilePath):
            if True:
881
882
                self.__logger.debug("File '%s' does not exists; it must be created..." % absPNGFilePath)

Caillat Michel's avatar
Caillat Michel committed
883
                try:
884
885
886
887
888
                    self.__PNGDir = absPNGFileDir
                    if not os.path.exists(self.__PNGDir):
                        os.makedirs(self.__PNGDir)
                except Exception as e:
                    self.__logger.debug("Failed to open '%s'. Message was '%s'" % (self.__PNGDir, e))
Caillat Michel's avatar
Caillat Michel committed
889
890
                    self.__logger.debug("Traceback: %s" %traceback.format_exc())
                    return {"status": False, "message": "Problem while creating the PNG file: '%s'" % e }
891
892

                self.__logger.debug("... in cache directory is %s" % (self.__PNGDir))
Caillat Michel's avatar
Caillat Michel committed
893
                try:
894
                    squaredSliceData = self.__squareSliceData( sliceData)
895
                    data_steps = self.__convertOneSlice2PNG_0(absPNGFilePath, squaredSliceData, ittName, lutName, vmName)
896
897

                    h = self.__header
Caillat Michel's avatar
Caillat Michel committed
898
                    if "CDELT1" in h and "CDELT2" in h:
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
                        x = {"BITPIX":   h["BITPIX"],
                            "NAXIS1":   squaredSliceData.shape[0],
                            "NAXIS2":   squaredSliceData.shape[0],
                            "CRPIX1":   h["CRPIX1"],
                            "CRPIX2":   h["CRPIX2"],
                            "CRVAL1":   h["CRVAL1"],
                            "CRVAL2":   h["CRVAL2"],
                            "CRTYPE1":  h["CTYPE1"],
                            "CRTYPE2":  h["CTYPE2"],
                            "RADESYS":  h["RADESYS"],
                            "CD1_1":    h["CDELT1"],
                            "CD2_2":    h["CDELT2"]}
                        self.__addWCStoPNG(absPNGFilePath, h)
                    status = True
                    self.__logger.debug("File '%s' has been created" % absPNGFilePath)

                except Exception as e:
Caillat Michel's avatar
Caillat Michel committed
916
                        self.__logger.debug("Failed to convert slice #%d. Error was '%r'" % (iFREQ, e))
Caillat Michel's avatar
Caillat Michel committed
917
918
                        self.__logger.debug("Traceback: %s" %traceback.format_exc())
                        return {"status": False, "message": "Problem while creating the PNG file: '%s'" % e }
919
920
921
922
            else:
                status = True
                self.__logger.debug("Using cached file '%s'" % absPNGFilePath)

923
        result = {"data_steps": data_steps, "path_to_png": relPNGFilePath, "statistics": self.__statistics["%d"%iFREQ]}
Caillat Michel's avatar
Caillat Michel committed
924
        self.__logger.debug("getOneSliceAsPNG: exiting.")
925
926
927

        return {"status": True, "message": "", "result": result}

Caillat Michel's avatar
Caillat Michel committed
928
    def getSummedSliceRangeAsPNG( self, iFREQ0, iFREQ1, ittName=__default_transformation_name, lutName=__default_palette_name, vmName=__default_video_mode_name):
Caillat Michel's avatar
Caillat Michel committed
929
        self.__logger.debug("getSummedSliceRangeAsPNG: entering.")
930
931
932
933

        relPNGFileDir = ('.').join(self.__relFITSFilePath.split('.')[:-1])
        absPNGFileDir = DataBlock.getPNGFilePrefix()+"/"+relPNGFileDir

934
        ittName = self.__getForcedTransformationName(ittName)
935

Caillat Michel's avatar
Caillat Michel committed
936
        relPNGFilePath = ("%s/%d-%d.%s.%s.%s.png" % (relPNGFileDir, iFREQ0, iFREQ1, ittName, lutName, vmName))
937
        absPNGFilePath = DataBlock.getPNGFilePrefix() + "/" + relPNGFilePath
938
        summedSliceRangeData = self.__getAverage_0(iFREQ0, iFREQ1)
939
940

        status = None
Caillat Michel's avatar
Caillat Michel committed
941
        #if not os.path.exists(absPNGFilePath):
942
943
        if True:
            self.__logger.debug("File '%s' does not exists; it must be created..." % absPNGFilePath)
Caillat Michel's avatar
Caillat Michel committed
944
            try:
945
946
947
948
949
950
                self.__PNGDir = absPNGFileDir
                if not os.path.exists(self.__PNGDir):
                    os.makedirs(self.__PNGDir)
                status = True
            except Exception as e:
                self.__logger.debug("Failed to open '%s'. Message was '%s'" % (self.__PNGDir, e))
Caillat Michel's avatar
Caillat Michel committed
951
952
                self.__logger.debug("Traceback: %s" %traceback.format_exc())
                return {"status": False, "message": "Problem while creating the PNG file: '%s'" % e }
953
954
955
956
957
958


            self.__logger.debug("... in cache directory is %s" % (self.__PNGDir))
            try:
                squaredData = self.__squareSliceData(summedSliceRangeData)

Caillat Michel's avatar
Caillat Michel committed
959
                data_steps = self.__convertSummedSliceRange2PNG(iFREQ0, iFREQ1, squaredData, ittName, lutName, vmName)
Caillat Michel's avatar
Caillat Michel committed
960
                h = self.__header
Caillat Michel's avatar
Caillat Michel committed
961
                if "CDELT1" in h and "CDELT2" in h:
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
                    x = {"BITPIX":   h["BITPIX"],
                    "NAXIS1":   squaredData.shape[0],
                    "NAXIS2":   squaredData.shape[0],
                    "CRPIX1":   h["CRPIX1"],
                    "CRPIX2":   h["CRPIX2"],
                    "CRVAL1":   h["CRVAL1"],
                    "CRVAL2":   h["CRVAL2"],
                    "CRTYPE1":  h["CTYPE1"],
                    "CRTYPE2":  h["CTYPE2"],
                    "RADESYS":  h["RADESYS"],
                    "CD1_1":    h["CDELT1"],
                    "CD2_2":    h["CDELT2"]}
                    self.__addWCStoPNG(absPNGFilePath, h)
                status = True
            except Exception as e:
Caillat Michel's avatar
Caillat Michel committed
977
978
979
980
                self.__logger.debug("Problem while creating the PNG file: '%s'" % e )
                self.__logger.debug("Traceback: %s" %traceback.format_exc())
                return {"status": False, "message": "Problem while creating the PNG file: '%s'" % e }
        else:
981
982
983
            status = True