GrisRun.py 16.8 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
Carl Schaffer's avatar
Carl Schaffer committed
112
        self.maps = list(set([gris_fitsfile.mapnumber for gris_fitsfile in self.files]))
Carl Schaffer's avatar
Carl Schaffer committed
113
        self.map_lengths = {}
114
        self.get_map_lengths()
Carl Schaffer's avatar
minor    
Carl Schaffer committed
115

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

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

        Args:

        Returns:

        """
Carl Schaffer's avatar
Carl Schaffer committed
131
        if self.is_parsed:
Carl Schaffer's avatar
Carl Schaffer committed
132
            return self.is_parsed
133

Carl Schaffer's avatar
Carl Schaffer committed
134
        progress_bar = tqdm(sorted(self.files, key=lambda x: x.filename, reverse=True))
135
        for fits_file in progress_bar:
Carl Schaffer's avatar
Carl Schaffer committed
136
            progress_bar.set_description(f"Parsing {basename(fits_file.filename)}")
137
            fits_file.parse()
Carl Schaffer's avatar
Carl Schaffer committed
138
            if fits_file.errors:
Carl Schaffer's avatar
Carl Schaffer committed
139
                # Get errors from file,move file to broken list
140
                for error in fits_file.errors:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
141
                    self.errors.append(error)
Carl Schaffer's avatar
Carl Schaffer committed
142
                self.files_broken.append(self.files.pop(self.files.index(fits_file)))
Carl Schaffer's avatar
Carl Schaffer committed
143
        if not self.files:
Carl Schaffer's avatar
Carl Schaffer committed
144
            self.errors.append("ERROR in %s: No fits files for run!" % (str(self)))
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
145
            return False
Carl Schaffer's avatar
Carl Schaffer committed
146

147
148
149
        # Extract observation properites from files
        pass

150
        # Set is parsed flag
Carl Schaffer's avatar
Carl Schaffer committed
151
152
        self.is_parsed = True

153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
    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

169
    @property
170
171
172
173
174
175
    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.
176

177
178
179
180
181
        Args:
            date (datetime): specific date defaults to None
        Returns:
            outstring (string): verbose date
        """
182
183
184
        if date is None:
            outstring = self.date.strftime("%d%b%y").lower()
            outstring += f".{int(self.runnumber):03d}"
185
        else:
186
            outstring = date.strftime("%d%b%y").lower()
187
188
189
190
191
        return outstring

    def matching_files(self, subfolder, pattern=None):
        query = self.verbose_name
        if not pattern:
192
            files = glob(join(self.path, subfolder, "*"))
193
194
195
196
197
        else:
            query += pattern
            files = reglob(join(self.path, subfolder), query)
        return files

198
199
200
201
202
    @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}"

203
204
    @property
    def files_l0(self):
205
        return self.matching_files("level0", self._l0pattern)
206
207
208

    @property
    def files_l1(self):
209
        return self.matching_files("level1", self._l1pattern)
210
211
212

    @property
    def files_l1_split(self):
213
        files = glob(join(self.path, "level1_split", "*.fits"))
Carl Schaffer's avatar
Carl Schaffer committed
214
        res = list(filter(lambda x: gris_run_number(x) == self.runnumber, files))
215
216
217
218
        return res

    @property
    def files_l1_split_mod(self):
219
        files = glob(join(self.path, "level1_split_modified", "*.fits"))
Carl Schaffer's avatar
Carl Schaffer committed
220
        res = list(filter(lambda x: gris_run_number(x) == self.runnumber, files))
221
222
223
224
        return res

    @property
    def files_l2(self):
225
        files = glob(join(self.path, "level2", "*"))
226
227
228
229
230
        res = list(filter(lambda x: self.verbose_name in basename(x), files))
        return res

    @property
    def files_l3(self):
231
        files = glob(join(self.path, "level3", "*"))
232
233
234
235
236
        res = list(filter(lambda x: self.verbose_name in basename(x), files))
        return res

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

Carl Schaffer's avatar
Carl Schaffer committed
239
240
    @property
    def context_folder(self):
Carl Schaffer's avatar
Carl Schaffer committed
241
        return join(self.path, "context_data")
Carl Schaffer's avatar
Carl Schaffer committed
242

243
244
    @property
    def map_saves(self):
245
        res = list(filter(lambda x: x[-1] == "m", self.files_l1))
246
        return res
Carl Schaffer's avatar
Carl Schaffer committed
247

248
249
    @property
    def coord_saves(self):
250
251
252
        res = list(
            filter(lambda x: f"{self.runnumber:03d}_coord_mu" in x, self.context_files)
        )
253
254
255
256
        return res

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

Carl Schaffer's avatar
Carl Schaffer committed
260
261
    @property
    def map_files(self):
262
        res = list(filter(lambda x: "maps" in x, self.files_l2))
Carl Schaffer's avatar
Carl Schaffer committed
263
264
265
266
        return res

    @property
    def spec_files(self):
267
        res = list(filter(lambda x: "cor_spec" in x, self.files_l2))
Carl Schaffer's avatar
Carl Schaffer committed
268
269
270
271
        return res

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

275
276
    @property
    def loc_previews(self):
277
        def filterfunc(x):
Carl Schaffer's avatar
Carl Schaffer committed
278
279
280
            return re.search(
                f"HMI_{self.verbose_name}\\.png|{self.runnumber}_location.png", x
            )
281

282
283
284
        res = list(filter(filterfunc, self.context_files))
        return res

285
286
287
288
289
    @property
    def spatial_uncertainties(self):
        """Average Uncertainties as described by WCS"""
        return np.mean(self.query("CSYER1")), np.mean(self.query("CSYER2"))

290
291
    @property
    def map_previews(self):
292
293
294
        def filterfunc(x):
            return re.match(f"{self.verbose_name}\\.png", basename(x))

295
296
297
298
299
        res = list(filter(filterfunc, self.context_files))
        return res

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

303
304
305
306
307
308
    @property
    def observers(self):
        if self.logfiles:
            observers = get_observers(self.logfiles[0])
        return observers

309
310
    @property
    def was_aborted(self):
311
        # determine number of maps found
312
313
        map_lengths = self.map_lengths
        n_maps = len(map_lengths)
314
315
316
317
318
319
320
321

        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)
322
323
        planned_maps = int(header["NMAPS"])
        planned_steps = int(header["NSTEPS"])
324
325
326
327
328
329
330

        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
331
        was_aborted = any([v != len_first_map for v in map_lengths.values()])
332
333
        return was_aborted

334
    def get_map_lengths(self):
335
        """Check number of slit positions and check if this number is as
336
        specified by the header
337
338
339
340
341

        Args:

        Returns:

342
        """
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
343
        for mapnumber in self.maps:
344
            slit_numbers = [
345
346
                f.slitposnumber for f in self.files if f.mapnumber == mapnumber
            ]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
347
            self.map_lengths[mapnumber] = max(slit_numbers)
348

Carl Schaffer's avatar
Carl Schaffer committed
349
350
351
352
    def get_previews(self):
        """look for preview files and store their paths in
        self.previews as 'key':path

353
354
355
356
        Args:

        Returns:

Carl Schaffer's avatar
Carl Schaffer committed
357
358
        """
        run = self.runnumber
Carl Schaffer's avatar
Carl Schaffer committed
359

Carl Schaffer's avatar
Carl Schaffer committed
360
        # retrieve splits_level1_folder from first fits file
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
361
        folder = dirname(self.files[0].filename)
Carl Schaffer's avatar
Carl Schaffer committed
362
363

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

Carl Schaffer's avatar
minor    
Carl Schaffer committed
367
        preview_map = []
368
369
        preview_loc = self.loc_previews
        preview_log = self.logfiles
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
370

Carl Schaffer's avatar
Carl Schaffer committed
371
        preview_map = glob(join(self.context_folder, f"???????.{run:03}.png"))
Carl Schaffer's avatar
Carl Schaffer committed
372
        if not preview_map:
Carl Schaffer's avatar
Carl Schaffer committed
373
            preview_map = glob(join(mapfolder, "context_data", f"*{run:03}_*.gif"))
Carl Schaffer's avatar
Carl Schaffer committed
374
        if not preview_map:
Carl Schaffer's avatar
Carl Schaffer committed
375
            preview_map = glob(join(mapfolder, "context_data", f"*{run:03}_???.png"))
Carl Schaffer's avatar
Carl Schaffer committed
376

377
        self.previews = {}
Carl Schaffer's avatar
Carl Schaffer committed
378
        if preview_loc:
379
            self.previews["PREVIEWLOC"] = preview_loc[0]
Carl Schaffer's avatar
Carl Schaffer committed
380
        if preview_log:
381
            self.previews["PREVIEWLOG"] = preview_log[0]
Carl Schaffer's avatar
Carl Schaffer committed
382
        if preview_map:
383
            self.previews["PREVIEWMAP"] = preview_map[0]
Carl Schaffer's avatar
Carl Schaffer committed
384

Carl Schaffer's avatar
Carl Schaffer committed
385
    def check_data_levels(self):
386
        """Check which data levels are present in parent directory
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
387

Carl Schaffer's avatar
Carl Schaffer committed
388
        todo : so far there is no checking whether a specific run is
Carl Schaffer's avatar
Carl Schaffer committed
389
390
391
        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!
392
393
394
395
396

        Args:

        Returns:

Carl Schaffer's avatar
Carl Schaffer committed
397
        """
Carl Schaffer's avatar
Carl Schaffer committed
398
        # Keys under which to store the result
399
        keys = ["DATALVL0", "DATALVL1", "DATALVL2", "DATALV1S"]
Carl Schaffer's avatar
Carl Schaffer committed
400
        # folders to check for, order MUST match 'keys' above!
401
        folders = ["level0", "level1", "level2", "level1_split"]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
402
403
404
        path = dirname(self.files[0].filename)
        path = abspath(path)
        directory = join(path, "..")
Carl Schaffer's avatar
Carl Schaffer committed
405
406

        # check if given folder is present and set run flags
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
407
408
409
        for k, folder in zip(keys, folders):
            path = join(directory, folder)
            folder_present = isdir(path)
Carl Schaffer's avatar
Carl Schaffer committed
410
            self.properties[k] = folder_present
Carl Schaffer's avatar
Carl Schaffer committed
411

412
413
        self.properties["CROSS_CORRELATION"] = bool(self.coord_saves)

Carl Schaffer's avatar
Carl Schaffer committed
414
    def __str__(self):
Carl Schaffer's avatar
Carl Schaffer committed
415
416
417
        outstring = (
            f"GrisRun#{self.runnumber:3d}  Date: {self.date.strftime('%Y-%m-%d')},"
        )
418
        outstring += f" {len(self.maps):3d} maps, {len(self.files):3d} split files"
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
419
        return outstring
Carl Schaffer's avatar
Carl Schaffer committed
420

Carl Schaffer's avatar
Carl Schaffer committed
421
422
423
424
    def __repr__(self):
        return str(self)

    def get_cube(self):
425
        """Retrieve data cube for run from slit files"""
Carl Schaffer's avatar
Carl Schaffer committed
426
        # Determine data shape and initialize numpy cube
Carl Schaffer's avatar
Carl Schaffer committed
427
        gris_fitsfile = fitsio.open(self.files[0].filename, ignore_missing_end=True)
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
428
429
430
431
        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
432
433
        nlambda = shape[-1]
        npol = shape[0]
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
434
        cube = np.ndarray([npol, n_x, n_y, nlambda])
Carl Schaffer's avatar
Carl Schaffer committed
435
436

        # get data into cube:
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
437
        for i, fitsfile in enumerate(self.files):
Carl Schaffer's avatar
Carl Schaffer committed
438
            data = fitsio.open(fitsfile.filename, ignore_missing_end=True)[0].data
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
439
            cube[:, i, :, :] = data
440
            data.close()
Carl Schaffer's avatar
Carl Schaffer committed
441
442
443
444
        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
445
        # check if cube has been made
446
        if not hasattr(self, "cube"):
Carl Schaffer's avatar
Carl Schaffer committed
447
448
449
            print("Missing cube, can't plot!")
            return

Carl Schaffer's avatar
Linting    
Carl Schaffer committed
450
        # average over spatial dimensions and slit positions
Carl Schaffer's avatar
Carl Schaffer committed
451
452
        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
453
            specs[i, :] = self.cube[i, :, :, :].mean(axis=0).mean(axis=0)
Carl Schaffer's avatar
Carl Schaffer committed
454
455
456
        self.specs = specs

    def plot_toti(self):
Carl Schaffer's avatar
Carl Schaffer committed
457
        """plot self.cube totI channel"""
458
459
        if not hasattr(self, "cube"):
            print("Missing cube, not plotting!")
Carl Schaffer's avatar
Carl Schaffer committed
460
            return
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
461
        toplot = self.cube[0, :, :, :].mean(axis=2)
Carl Schaffer's avatar
Carl Schaffer committed
462
463
        plt.imshow(
            np.rot90(toplot),
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
464
465
            vmax=np.percentile(toplot, 95),
            vmin=np.percentile(toplot, 10),
466
            cmap="gray",
Carl Schaffer's avatar
Carl Schaffer committed
467
        )
Carl Schaffer's avatar
Carl Schaffer committed
468
469
470
        plt.ion()
        plt.show()

471
472
    def plot_location(self):
        coords = self.calculate_bounding_box()
Carl Schaffer's avatar
Carl Schaffer committed
473
        date = min(self.query("obs_time"))
Carl Schaffer's avatar
Carl Schaffer committed
474
475
476
        fig, ax = make_loc_plot(
            coords[:2], coords[2:], date, uncertainties=self.spatial_uncertainties
        )
Carl Schaffer's avatar
Carl Schaffer committed
477
        return fig, ax
478

Carl Schaffer's avatar
Carl Schaffer committed
479
    def plot_specs(self):
Carl Schaffer's avatar
Carl Schaffer committed
480
        """plot average spectra for given run. requres average spectra and cube"""
Carl Schaffer's avatar
Carl Schaffer committed
481
        # check if mean spectra have been calculated
482
483
        if not hasattr(self, "specs"):
            print("Missing specs, not plotting!")
Carl Schaffer's avatar
Carl Schaffer committed
484
            return
Carl Schaffer's avatar
Linting    
Carl Schaffer committed
485

Carl Schaffer's avatar
Carl Schaffer committed
486
        for i in range(4):
487
            plt.plot(self.specs[i, :] / np.linalg.norm(self.specs[i, :]))
Carl Schaffer's avatar
Carl Schaffer committed
488
489
        plt.ion()
        plt.show()
490

491
492
493
494
495
496
497
498
499
    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]
Carl Schaffer's avatar
Carl Schaffer committed
500
501
502
503
        map_files = sorted(
            list(filter(lambda x: x.mapnumber == first_map_id, self.files)),
            key=lambda x: x.filename,
        )
504
505
        first_coords = map_files[0]._coords_from_wcs
        last_coords = map_files[-1]._coords_from_wcs
506
507
508
509
510
511
        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

512

Carl Schaffer's avatar
Carl Schaffer committed
513
# noinspection PyMethodOverriding
514
515
516
517
518
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)
519
        self.date = datetime.datetime.strptime(fn.split(".")[0], "%d%b%y")
520
521
522
        self.runnumber = runnumber
        self.map_lengths = {}
        self.files = []
523
        self.path = abspath(join(dirname(path), ".."))
524
525
        self.maps = []

Carl Schaffer's avatar
Carl Schaffer committed
526
    # noinspection PyMethodOverriding
527
528
    @property
    def verbose_name(self):
529
530
        outstring = self.date.strftime("%d%b%y").lower()
        outstring += f".{int(self.runnumber):03d}"
531
        return outstring
532
533
534
535
536


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