GrisRun.py 16.9 KB
Newer Older
Carl Schaffer's avatar
Carl Schaffer committed
1
2
3
4
5
6
7
8
9
10
11
12
13
"""
Created on 2018-04-09
@author Carl Schaffer
@mail   carl.schaffer@leibniz-kis.de

class for retrieving run information for a set of GrisFitsFiles

If this file is called directly, it will attempt to create a GrisRun
instance for target passed as the first commandline argument. If no
argument is passed, it will attempt to process a dummy folder.
"""

import datetime
14
import re
15
16
from glob import glob  # get filenames in unix style
from os.path import join, dirname, abspath, isdir, basename
Carl Schaffer's avatar
Carl Schaffer committed
17

18
19
20
import astropy.io.fits as fitsio
import matplotlib.pyplot as plt
import numpy as np
21
22
23
from tqdm import tqdm

from kis_tools.util.locplot import make_loc_plot
24
25
26
from . import GrisFitsFile
from .util import get_observers
from ..util.util import reglob, gris_run_number, get_filter
Carl Schaffer's avatar
Carl Schaffer committed
27

Carl Schaffer's avatar
Carl Schaffer committed
28

Carl Schaffer's avatar
Carl Schaffer committed
29
30
class GrisRun(object):
    """Class to handle run-wise gris data
Carl Schaffer's avatar
Carl Schaffer committed
31

32
33
34
35
    Args:
      gris_fits_files: list of gris files to form one run

    Returns:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
36
      ris_run: GrisRun instance
Carl Schaffer's avatar
Carl Schaffer committed
37
    """
Carl Schaffer's avatar
Carl Schaffer committed
38

39
    # Patterns for files:
40
41
    _l0pattern = r"(-\d\d){0,1}"
    _l1pattern = r"(?:-\d\d){0,1}c[cm]"
42

Carl Schaffer's avatar
Carl Schaffer committed
43
44
    # List of keywords to extract from header, values are passed
    # through self.query
45
46
47
48
49
50
51
    pars_native = {
        "BITPIX": "BITPIX",
        "NAXIS": "NAXIS",
        "BTYPE": "BTYPE",
        "DATE-OBS": "date",
        "TELESCOP": "TELESCOP",
        "CAMERA": "CAMERA",
52
        "WAVELENG": ["WAVELENG", "AWAVLNTH"],
53
54
55
56
        "STATES": "states",
        "AOSYSTEM": "AOSYSTEM",
        "AO_LOCK": "AO_LOCK",
        "NSUMEXP": "NSUMEXP",
57
        "SERIES": ["NSERIES", "NMAPS"],
58
        "IOBS": "IOBS",
59
        "RSUN_ARC": "RSUN_ARC",
60
61
        "STEPS": "NSTEPS",
        "STEPSIZE": "STEPSIZE",
Carl Schaffer's avatar
Carl Schaffer committed
62
63
        "EXPTIME": "XPOSURE",
        "TARGET": "OBS_TRGT",
64
        "OBS_MODE": "obs_mode",
65
    }
Carl Schaffer's avatar
Carl Schaffer committed
66

Carl Schaffer's avatar
Carl Schaffer committed
67
    # list of keywords where a range is deduced from the input files, values are passed through GrisFitsFile.query
68
69
70
71
72
73
74
75
    pars_minmax = {
        "UT": ["DATE-BEG"],
        "FRIEDR0": "ATMOS_R0",
        "AZIMUT": "AZIMUT",
        "ELEVATIO": "ELEV_ANG",
        "SLITPOSX": "SLITPOSX",
        "SLITPOSY": "SLITPOSY",
    }
Carl Schaffer's avatar
Carl Schaffer committed
76

Carl Schaffer's avatar
Carl Schaffer committed
77
    # Format string for storing revision dates in DB
Carl Schaffer's avatar
Carl Schaffer committed
78
79
    date_formatstring = "%Y-%m-%d %H:%M:%S"

Carl Schaffer's avatar
Carl Schaffer committed
80
    def __init__(self, gris_fits_files):
81
        # check inputs
Carl Schaffer's avatar
Carl Schaffer committed
82
        if not gris_fits_files:
83
            raise ValueError("Empty list of files passed to GrisRun")
Carl Schaffer's avatar
Carl Schaffer committed
84

Carl Schaffer's avatar
Carl Schaffer committed
85
        # Setup data containers
Carl Schaffer's avatar
Carl Schaffer committed
86
87
88
89
90
        self.properties = {}
        self.files_broken = []
        self.errors = []
        self.is_parsed = False

91
92
93
        # check inputs:
        if not isinstance(gris_fits_files, list):
            print("Invalid input to GrisRun!")
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
94
95
96
97
            raise TypeError

        # check if inputs are already instances of GrisFitsFile, if
        # not, generate instances
98
        elif isinstance(gris_fits_files[0], str):
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
99
100
101
102
103
104
            gris_fits_files = [
                GrisFitsFile.GrisFitsFile(gff) for gff in gris_fits_files
            ]

        # store files sorted by filename
        self.files = sorted(gris_fits_files, key=lambda x: x.filename)
105

Carl Schaffer's avatar
Carl Schaffer committed
106
        # Extract run-wise parameters from first file of the run
Carl Schaffer's avatar
Carl Schaffer committed
107
108
109
        self.runnumber = self.files[0].runnumber
        self.properties["RUNNUMBER"] = self.runnumber
        self.date = self.files[0].date
110
        self.path = abspath(join(dirname(self.files[0].path), ".."))
Carl Schaffer's avatar
Carl Schaffer committed
111
        self.properties["DATE-OBS"] = self.date
112
        self.maps = list(
Carl Schaffer's avatar
Carl Schaffer committed
113
114
            set([gris_fitsfile.mapnumber for gris_fitsfile in self.files])
        )
Carl Schaffer's avatar
Carl Schaffer committed
115
        self.map_lengths = {}
116
        self.get_map_lengths()
Carl Schaffer's avatar
minor    
Carl Schaffer committed
117

Carl Schaffer's avatar
Carl Schaffer committed
118
        # Check data levels:
Carl Schaffer's avatar
Carl Schaffer committed
119
        self.check_data_levels()
Carl Schaffer's avatar
Carl Schaffer committed
120
        # Look for preview images and log files
Carl Schaffer's avatar
Carl Schaffer committed
121
122
123
124
125
        self.get_previews()

    def parse(self):
        """Performs all information gathering that requires opening
        individual fits files instead of only using info from their
126
127
128
129
130
131
132
        filenames

        Args:

        Returns:

        """
Carl Schaffer's avatar
Carl Schaffer committed
133
        if self.is_parsed:
Carl Schaffer's avatar
Carl Schaffer committed
134
            return self.is_parsed
135
136
137
138
139
140
141
142
143

        progress_bar = tqdm(
            sorted(self.files, key=lambda x: x.filename, reverse=True)
        )
        for fits_file in progress_bar:
            progress_bar.set_description(
                f"Parsing {basename(fits_file.filename)}"
            )
            fits_file.parse()
Carl Schaffer's avatar
Carl Schaffer committed
144
            if fits_file.errors:
Carl Schaffer's avatar
Carl Schaffer committed
145
                # Get errors from file,move file to broken list
146
                for error in fits_file.errors:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
147
                    self.errors.append(error)
148
149
150
                self.files_broken.append(
                    self.files.pop(self.files.index(fits_file))
                )
Carl Schaffer's avatar
Carl Schaffer committed
151
        if not self.files:
Carl Schaffer's avatar
Carl Schaffer committed
152
            self.errors.append(
Carl Schaffer's avatar
Carl Schaffer committed
153
154
                "ERROR in %s: No fits files for run!" % (str(self))
            )
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
155
            return False
Carl Schaffer's avatar
Carl Schaffer committed
156

157
158
159
        # Extract observation properites from files
        pass

160
        # Set is parsed flag
Carl Schaffer's avatar
Carl Schaffer committed
161
162
        self.is_parsed = True

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
    def query(self, attribute):
        """
        Get query attribute either from self or search constituents for that attribute
        Args:
            attribute: name of attribute to query

        Returns:
            res: value of given attribute if the current instance has the attribute, list of attributes for all
            constituents if there is no attribute in "self"
        """
        if hasattr(self, attribute):
            res = getattr(self, attribute)
        else:
            res = [f.query(attribute) for f in self.files]
        return res

179
    @property
180
181
182
183
184
185
    def verbose_name(self, date=None):
        """Make verbose name for GRIS runs, turns the date of the run,
        or a date passed as an argument into a string like 26aug14.001
        where the sectionafter the point is the runnumber of the
        corresponding run. If a specific date is passed, only the
        first section of the string is generated.
186

187
188
189
190
191
        Args:
            date (datetime): specific date defaults to None
        Returns:
            outstring (string): verbose date
        """
192
193
194
        if date is None:
            outstring = self.date.strftime("%d%b%y").lower()
            outstring += f".{int(self.runnumber):03d}"
195
        else:
196
            outstring = date.strftime("%d%b%y").lower()
197
198
199
200
201
        return outstring

    def matching_files(self, subfolder, pattern=None):
        query = self.verbose_name
        if not pattern:
202
            files = glob(join(self.path, subfolder, "*"))
203
204
205
206
207
        else:
            query += pattern
            files = reglob(join(self.path, subfolder), query)
        return files

208
209
210
211
212
    @property
    def obs_name(self):
        """Observation name unique within all data for the instrument"""
        return f"{self.date.strftime('%Y%m%d')}_{self.runnumber:03d}"

213
214
    @property
    def files_l0(self):
215
        return self.matching_files("level0", self._l0pattern)
216
217
218

    @property
    def files_l1(self):
219
        return self.matching_files("level1", self._l1pattern)
220
221
222

    @property
    def files_l1_split(self):
223
        files = glob(join(self.path, "level1_split", "*.fits"))
224
        res = list(
Carl Schaffer's avatar
Carl Schaffer committed
225
226
            filter(lambda x: gris_run_number(x) == self.runnumber, files)
        )
227
228
229
230
        return res

    @property
    def files_l1_split_mod(self):
231
        files = glob(join(self.path, "level1_split_modified", "*.fits"))
232
        res = list(
Carl Schaffer's avatar
Carl Schaffer committed
233
234
            filter(lambda x: gris_run_number(x) == self.runnumber, files)
        )
235
236
237
238
        return res

    @property
    def files_l2(self):
239
        files = glob(join(self.path, "level2", "*"))
240
241
242
243
244
        res = list(filter(lambda x: self.verbose_name in basename(x), files))
        return res

    @property
    def files_l3(self):
245
        files = glob(join(self.path, "level3", "*"))
246
247
248
249
250
        res = list(filter(lambda x: self.verbose_name in basename(x), files))
        return res

    @property
    def context_files(self):
251
        return self.matching_files("context_data")
Carl Schaffer's avatar
Carl Schaffer committed
252

Carl Schaffer's avatar
Carl Schaffer committed
253
254
255
256
    @property
    def context_folder(self):
        return join(self.path, 'context_data')

257
258
    @property
    def map_saves(self):
259
        res = list(filter(lambda x: x[-1] == "m", self.files_l1))
260
        return res
Carl Schaffer's avatar
Carl Schaffer committed
261

262
263
    @property
    def coord_saves(self):
264
        res = list(filter(lambda x: "coord_mu" in x, self.context_files))
265
266
267
268
        return res

    @property
    def mask_files(self):
Carl Schaffer's avatar
Carl Schaffer committed
269
        res = list(filter(lambda x: "pen" in x and ".sav" in x, self.files_l2))
270
271
        return res

Carl Schaffer's avatar
Carl Schaffer committed
272
273
    @property
    def map_files(self):
274
        res = list(filter(lambda x: "maps" in x, self.files_l2))
Carl Schaffer's avatar
Carl Schaffer committed
275
276
277
278
        return res

    @property
    def spec_files(self):
279
        res = list(filter(lambda x: "cor_spec" in x, self.files_l2))
Carl Schaffer's avatar
Carl Schaffer committed
280
281
282
283
        return res

    @property
    def stokes_files(self):
284
        res = list(filter(lambda x: "stok" in x, self.files_l2))
Carl Schaffer's avatar
Carl Schaffer committed
285
286
        return res

287
288
    @property
    def loc_previews(self):
289
        def filterfunc(x):
290
            return re.search(f"HMI_{self.verbose_name}\\.png|{self.runnumber}_location.png", x)
291

292
293
294
        res = list(filter(filterfunc, self.context_files))
        return res

295
296
297
298
299
    @property
    def spatial_uncertainties(self):
        """Average Uncertainties as described by WCS"""
        return np.mean(self.query("CSYER1")), np.mean(self.query("CSYER2"))

300
301
    @property
    def map_previews(self):
302
303
304
        def filterfunc(x):
            return re.match(f"{self.verbose_name}\\.png", basename(x))

305
306
307
308
309
        res = list(filter(filterfunc, self.context_files))
        return res

    @property
    def logfiles(self):
Carl Schaffer's avatar
Carl Schaffer committed
310
        files = glob(join(self.path, "????????.txt"))
311
312
        return files

313
314
315
316
317
318
    @property
    def observers(self):
        if self.logfiles:
            observers = get_observers(self.logfiles[0])
        return observers

319
320
    @property
    def was_aborted(self):
321
        # determine number of maps found
322
323
        map_lengths = self.map_lengths
        n_maps = len(map_lengths)
324
325
326
327
328
329
330
331

        if n_maps == 0:
            return None

        # check header for planned number of maps
        planned_maps = 0
        planned_steps = 0
        header = fitsio.getheader(self.files[0].path, ignore_missing_end=True)
332
333
        planned_maps = int(header["NMAPS"])
        planned_steps = int(header["NSTEPS"])
334
335
336
337
338
339
340

        if n_maps != planned_maps:
            return True

        len_first_map = map_lengths[sorted(map_lengths.keys())[0]]
        if len_first_map != planned_steps:
            return True
Carl Schaffer's avatar
Carl Schaffer committed
341
        was_aborted = any([v != len_first_map for v in map_lengths.values()])
342
343
        return was_aborted

344
    def get_map_lengths(self):
345
        """Check number of slit positions and check if this number is as
346
        specified by the header
347
348
349
350
351

        Args:

        Returns:

352
        """
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
353
        for mapnumber in self.maps:
354
            slit_numbers = [
355
356
                f.slitposnumber for f in self.files if f.mapnumber == mapnumber
            ]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
357
            self.map_lengths[mapnumber] = max(slit_numbers)
358

Carl Schaffer's avatar
Carl Schaffer committed
359
360
361
362
    def get_previews(self):
        """look for preview files and store their paths in
        self.previews as 'key':path

363
364
365
366
        Args:

        Returns:

Carl Schaffer's avatar
Carl Schaffer committed
367
368
        """
        run = self.runnumber
Carl Schaffer's avatar
Carl Schaffer committed
369

Carl Schaffer's avatar
Carl Schaffer committed
370
        # retrieve splits_level1_folder from first fits file
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
371
        folder = dirname(self.files[0].filename)
Carl Schaffer's avatar
Carl Schaffer committed
372
373

        # navigate to root folder:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
374
        folder = abspath(join(folder, "../"))
375
        mapfolder = folder.replace("/gris/", "/gris_maps/")
Carl Schaffer's avatar
Carl Schaffer committed
376

Carl Schaffer's avatar
minor    
Carl Schaffer committed
377
        preview_map = []
378
379
        preview_loc = self.loc_previews
        preview_log = self.logfiles
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
380

Carl Schaffer's avatar
Carl Schaffer committed
381
        preview_map = glob(
382
            join(self.context_folder, f"???????.{run:03}.png")
Carl Schaffer's avatar
Carl Schaffer committed
383
        )
Carl Schaffer's avatar
Carl Schaffer committed
384
        if not preview_map:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
385
            preview_map = glob(
Carl Schaffer's avatar
Carl Schaffer committed
386
387
                join(mapfolder, "context_data", f"*{run:03}_*.gif")
            )
Carl Schaffer's avatar
Carl Schaffer committed
388
        if not preview_map:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
389
            preview_map = glob(
Carl Schaffer's avatar
Carl Schaffer committed
390
391
                join(mapfolder, "context_data", f"*{run:03}_???.png")
            )
Carl Schaffer's avatar
Carl Schaffer committed
392

393
        self.previews = {}
Carl Schaffer's avatar
Carl Schaffer committed
394
        if preview_loc:
395
            self.previews["PREVIEWLOC"] = preview_loc[0]
Carl Schaffer's avatar
Carl Schaffer committed
396
        if preview_log:
397
            self.previews["PREVIEWLOG"] = preview_log[0]
Carl Schaffer's avatar
Carl Schaffer committed
398
        if preview_map:
399
            self.previews["PREVIEWMAP"] = preview_map[0]
Carl Schaffer's avatar
Carl Schaffer committed
400

Carl Schaffer's avatar
Carl Schaffer committed
401
    def check_data_levels(self):
402
        """Check which data levels are present in parent directory
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
403

Carl Schaffer's avatar
Carl Schaffer committed
404
        todo : so far there is no checking whether a specific run is
Carl Schaffer's avatar
Carl Schaffer committed
405
406
407
        contained in all folders, we assume that if the folder is
        present in the day, the corresponding run is contained in that
        day. this could be improved!
408
409
410
411
412

        Args:

        Returns:

Carl Schaffer's avatar
Carl Schaffer committed
413
        """
Carl Schaffer's avatar
Carl Schaffer committed
414
        # Keys under which to store the result
415
        keys = ["DATALVL0", "DATALVL1", "DATALVL2", "DATALV1S"]
Carl Schaffer's avatar
Carl Schaffer committed
416
        # folders to check for, order MUST match 'keys' above!
417
        folders = ["level0", "level1", "level2", "level1_split"]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
418
419
420
        path = dirname(self.files[0].filename)
        path = abspath(path)
        directory = join(path, "..")
Carl Schaffer's avatar
Carl Schaffer committed
421
422

        # check if given folder is present and set run flags
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
423
424
425
        for k, folder in zip(keys, folders):
            path = join(directory, folder)
            folder_present = isdir(path)
Carl Schaffer's avatar
Carl Schaffer committed
426
            self.properties[k] = folder_present
Carl Schaffer's avatar
Carl Schaffer committed
427
428

    def __str__(self):
429
430
        outstring = f"GrisRun#{self.runnumber:3d}  Date: {self.date.strftime('%Y-%m-%d')},"
        outstring += f" {len(self.maps):3d} maps, {len(self.files):3d} split files"
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
431
        return outstring
Carl Schaffer's avatar
Carl Schaffer committed
432

Carl Schaffer's avatar
Carl Schaffer committed
433
434
435
436
    def __repr__(self):
        return str(self)

    def get_cube(self):
437
        """Retrieve data cube for run from slit files"""
Carl Schaffer's avatar
Carl Schaffer committed
438
        # Determine data shape and initialize numpy cube
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
439
        gris_fitsfile = fitsio.open(
Carl Schaffer's avatar
Carl Schaffer committed
440
441
            self.files[0].filename, ignore_missing_end=True
        )
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
442
443
444
445
        shape = gris_fitsfile[0].data.shape
        gris_fitsfile.close()
        n_x = len(self.files)
        n_y = shape[1]
Carl Schaffer's avatar
Carl Schaffer committed
446
447
        nlambda = shape[-1]
        npol = shape[0]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
448
        cube = np.ndarray([npol, n_x, n_y, nlambda])
Carl Schaffer's avatar
Carl Schaffer committed
449
450

        # get data into cube:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
451
        for i, fitsfile in enumerate(self.files):
Carl Schaffer's avatar
Carl Schaffer committed
452
453
454
            data = fitsio.open(fitsfile.filename, ignore_missing_end=True)[
                0
            ].data
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
455
            cube[:, i, :, :] = data
456
            data.close()
Carl Schaffer's avatar
Carl Schaffer committed
457
458
459
460
        self.cube = cube

    def get_mean_specs(self):
        """Calculate mean spectra for Stokes I,Q,U and V"""
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
461
        # check if cube has been made
462
        if not hasattr(self, "cube"):
Carl Schaffer's avatar
Carl Schaffer committed
463
464
465
            print("Missing cube, can't plot!")
            return

Carl Schaffer's avatar
Linting    
Carl Schaffer committed
466
        # average over spatial dimensions and slit positions
Carl Schaffer's avatar
Carl Schaffer committed
467
468
        specs = np.ndarray([self.cube.shape[0], self.cube.shape[-1]])
        for i in range(specs.shape[0]):
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
469
            specs[i, :] = self.cube[i, :, :, :].mean(axis=0).mean(axis=0)
Carl Schaffer's avatar
Carl Schaffer committed
470
471
472
        self.specs = specs

    def plot_toti(self):
473
474
        """ plot self.cube totI channel

Carl Schaffer's avatar
Linting    
Carl Schaffer committed
475
        """
476
477
        if not hasattr(self, "cube"):
            print("Missing cube, not plotting!")
Carl Schaffer's avatar
Carl Schaffer committed
478
            return
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
479
        toplot = self.cube[0, :, :, :].mean(axis=2)
Carl Schaffer's avatar
Carl Schaffer committed
480
481
        plt.imshow(
            np.rot90(toplot),
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
482
483
            vmax=np.percentile(toplot, 95),
            vmin=np.percentile(toplot, 10),
484
            cmap="gray",
Carl Schaffer's avatar
Carl Schaffer committed
485
        )
Carl Schaffer's avatar
Carl Schaffer committed
486
487
488
        plt.ion()
        plt.show()

489
490
    def plot_location(self):
        coords = self.calculate_bounding_box()
Carl Schaffer's avatar
Carl Schaffer committed
491
492
        date = min(self.query("obs_time"))
        fig, ax = make_loc_plot(coords[:2], coords[2:], date, uncertainties=self.spatial_uncertainties)
Carl Schaffer's avatar
Carl Schaffer committed
493
        return fig, ax
494

Carl Schaffer's avatar
Carl Schaffer committed
495
    def plot_specs(self):
496
        """plot average spectra for given run. requres average spectra and cube
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
497
        """
Carl Schaffer's avatar
Carl Schaffer committed
498
        # check if mean spectra have been calculated
499
500
        if not hasattr(self, "specs"):
            print("Missing specs, not plotting!")
Carl Schaffer's avatar
Carl Schaffer committed
501
            return
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
502

Carl Schaffer's avatar
Carl Schaffer committed
503
        for i in range(4):
504
            plt.plot(self.specs[i, :] / np.linalg.norm(self.specs[i, :]))
Carl Schaffer's avatar
Carl Schaffer committed
505
506
        plt.ion()
        plt.show()
507

508
509
510
511
512
513
514
515
516
517
    def calculate_bounding_box(self):
        """
        Determine the bounding box for the run. The box is calculated as the area covered by the first map in the
        observation. If there are subsequent maps, they are ignored.

        Returns:
            xmin,xmax,ymin,ymax : Helioprojective coordinates in arcsec
        """
        first_map_id = self.maps[0]
        map_files = sorted(list(filter(lambda x: x.mapnumber == first_map_id, self.files)), key=lambda x: x.filename)
518
519
        first_coords = map_files[0]._coords_from_wcs
        last_coords = map_files[-1]._coords_from_wcs
520
521
522
523
524
525
        x_vals = np.concatenate([first_coords[:, 0], last_coords[:, 0]]).value
        y_vals = np.concatenate([first_coords[:, 1], last_coords[:, 1]]).value
        xmin, xmax = min(x_vals), max(x_vals)
        ymin, ymax = min(y_vals), max(y_vals)
        return xmin, xmax, ymin, ymax

526

Carl Schaffer's avatar
Carl Schaffer committed
527
# noinspection PyMethodOverriding
528
529
530
531
532
class EmptyGrisRun(GrisRun):
    def __init__(self, path):
        """run constructed from a path to a single l1 or l0 filename"""
        runnumber = gris_run_number(path)
        fn = basename(path)
533
        self.date = datetime.datetime.strptime(fn.split(".")[0], "%d%b%y")
534
535
536
        self.runnumber = runnumber
        self.map_lengths = {}
        self.files = []
537
        self.path = abspath(join(dirname(path), ".."))
538
539
        self.maps = []

Carl Schaffer's avatar
Carl Schaffer committed
540
    # noinspection PyMethodOverriding
541
542
    @property
    def verbose_name(self):
543
544
        outstring = self.date.strftime("%d%b%y").lower()
        outstring += f".{int(self.runnumber):03d}"
545
        return outstring
546
547
548
549
550


if __name__ == "__main__":
    files = glob("/dat/sdc/gris/20150426/level1_split/*_001_???_*.fits")
    gr = GrisRun(files)
551
    props = gr.parse()