Source code for nf2.data.download

"""Unified download command for NF2 example data sources."""

from __future__ import annotations

import argparse
import glob
import math
import os

import drms
from dateutil.parser import parse


DEFAULT_SHARP_SEGMENTS = "Br,Bp,Bt,Br_err,Bp_err,Bt_err"
DEFAULT_VECTOR_SEGMENTS = "Br,Bt,Bp"
DEFAULT_MR_POLFIL_SEGMENTS = "Mr_polfil"


def download_SHARP_series(download_dir, email, t_start, t_end=None, noaa_num=None, sharp_num=None,
                          cadence='720s', segments=DEFAULT_SHARP_SEGMENTS, series='sharp_cea_720s'):
    if sharp_num is None and noaa_num is None:
        raise ValueError('Either sharp_num or noaa_num must be provided.')
    os.makedirs(download_dir, exist_ok=True)
    client = drms.Client(email=email)
    if noaa_num is not None:
        sharp_num = find_HARP(t_start, noaa_num, client)
        print(f'Found HARP number {sharp_num} for NOAA number {noaa_num}')
    else:
        sharp_num = sharp_num

    if t_end is None:
        ds = f'hmi.{series}[{sharp_num}][{t_start.isoformat("_", timespec="seconds")}]{{{segments}}}'
    else:
        duration = (t_end - t_start).total_seconds()
        ds = f'hmi.{series}[{sharp_num}][{t_start.isoformat("_", timespec="seconds")}/{duration}s@{cadence}]{{{segments}}}'
    return download_ds(ds, download_dir, client)


def download_ds(ds, dir, client, process=None):
    os.makedirs(dir, exist_ok=True)
    r = client.export(ds, protocol='fits', process=process)
    r.wait()
    download_result = r.download(dir)
    return download_result


def find_HARP(start_time, noaa_num, client):
    ar_mapping = client.query('hmi.Mharp_720s[][%sZ]' % start_time.isoformat('_', timespec='seconds'),
                              key=['NOAA_AR', 'HARPNUM'])
    if len(ar_mapping) == 0:
        return None
    harpnum = ar_mapping[ar_mapping['NOAA_AR'] == int(noaa_num)]['HARPNUM']
    if len(harpnum) > 0:
        return harpnum.iloc[0]
    return None


def carrington_rotation_from_time(time):
    """Return the integer Carrington rotation containing ``time``."""
    from sunpy.coordinates.sun import carrington_rotation_number

    return math.floor(carrington_rotation_number(time))


[docs] def download_hmi_sharp( download_dir, email, t_start, t_end=None, noaa_num=None, sharp_num=None, cadence='720s', segments=DEFAULT_SHARP_SEGMENTS, series='sharp_cea_720s', ): """Download an HMI SHARP or SHARP CEA vector magnetogram series. Parameters mirror the ``nf2-download --source hmi_sharp`` command. Provide either ``sharp_num`` or ``noaa_num``. """ return download_SHARP_series( download_dir=download_dir, email=email, t_start=t_start, t_end=t_end, noaa_num=noaa_num, sharp_num=sharp_num, cadence=cadence, segments=segments, series=series, )
[docs] def download_hmi_synoptic( download_dir, email, carrington_rotation=None, carrington_rotation_end=None, t_start=None, t_end=None, segments=DEFAULT_VECTOR_SEGMENTS, series='b_synoptic', include_mr_polfil=False, synoptic_product='vector', ): """Download HMI synoptic maps for one or more Carrington rotations. Rotations can be provided explicitly or inferred from ``t_start`` and optional ``t_end``. """ if carrington_rotation is None: if t_start is None: raise ValueError('Either carrington_rotation or t_start must be provided.') carrington_rotation = carrington_rotation_from_time(t_start) if carrington_rotation_end is None and t_end is not None: carrington_rotation_end = carrington_rotation_from_time(t_end) os.makedirs(download_dir, exist_ok=True) client = drms.Client(email=email) carrington_rotation_end = carrington_rotation if carrington_rotation_end is None else carrington_rotation_end results = [] for rotation in range(carrington_rotation, carrington_rotation_end + 1): if synoptic_product in {'mr_polfil', 'both'} or include_mr_polfil: results.append(download_ds( f'hmi.synoptic_mr_polfil_720s[{rotation}]{{{DEFAULT_MR_POLFIL_SEGMENTS}}}', download_dir, client, )) if synoptic_product in {'vector', 'both'}: results.append(download_ds(f'hmi.{series}[{rotation}]{{{segments}}}', download_dir, client)) return results
[docs] def download_hmi_full_disk( download_dir, email, t_start, t_end=None, cadence='720s', series='B_720s', segments='field,inclination,azimuth,disambig', convert_ptr=True, keep_coordinates=False, ): """Download HMI full-disk vector data, optionally converted to Br/Bt/Bp. Conversion uses the JSOC ``HmiB2ptr`` export process by default. """ os.makedirs(download_dir, exist_ok=True) client = drms.Client(email=email) if t_end is None: time_selector = t_start.isoformat('_', timespec='seconds') else: duration = (t_end - t_start).total_seconds() time_selector = f'{t_start.isoformat("_", timespec="seconds")}/{duration}s@{cadence}' process = {'HmiB2ptr': {'l': 1}} if convert_ptr else None segment_selector = '' if convert_ptr else f'{{{segments}}}' ds = f'hmi.{series}[{time_selector}]{segment_selector}' result = download_ds(ds, download_dir, client, process=process) if convert_ptr and not keep_coordinates: for path in glob.glob(os.path.join(download_dir, '*lat.fits')): os.remove(path) for path in glob.glob(os.path.join(download_dir, '*lon.fits')): os.remove(path) return result
def _add_common_args(parser): parser.add_argument('--download_dir', type=str, required=True) parser.add_argument('--email', type=str, required=True) def _parse_time(value): return parse(value) if value is not None else None def main(): parser = argparse.ArgumentParser(description='Download data for NF2 runs.') parser.add_argument( '--source', choices=['hmi_sharp', 'hmi_synoptic', 'hmi_full_disk'], default='hmi_sharp', help='Download source to use.', ) _add_common_args(parser) parser.add_argument('--sharp_num', type=int, default=None) parser.add_argument('--noaa_num', type=int, default=None) parser.add_argument('--t_start', type=str, default=None) parser.add_argument('--t_end', type=str, default=None) parser.add_argument('--cadence', type=str, default='720s') parser.add_argument('--series', type=str, default=None) parser.add_argument('--segments', type=str, default=None) parser.add_argument('--carrington_rotation', type=int, default=None) parser.add_argument('--carrington_rotation_end', type=int, default=None) parser.add_argument( '--synoptic_product', choices=['vector', 'mr_polfil', 'both'], default='vector', help='Synoptic product to download for --source hmi_synoptic.', ) parser.add_argument('--include_mr_polfil', action='store_true') parser.add_argument('--no_convert_ptr', action='store_true') parser.add_argument('--keep_coordinates', action='store_true') args = parser.parse_args() if args.source == 'hmi_sharp': if args.t_start is None: parser.error('--t_start is required for --source hmi_sharp') return download_hmi_sharp( download_dir=args.download_dir, email=args.email, t_start=_parse_time(args.t_start), t_end=_parse_time(args.t_end), noaa_num=args.noaa_num, sharp_num=args.sharp_num, cadence=args.cadence, segments=args.segments or DEFAULT_SHARP_SEGMENTS, series=args.series or 'sharp_cea_720s', ) if args.source == 'hmi_synoptic': if args.carrington_rotation is None and args.t_start is None: parser.error('Either --carrington_rotation or --t_start is required for --source hmi_synoptic') return download_hmi_synoptic( download_dir=args.download_dir, email=args.email, carrington_rotation=args.carrington_rotation, carrington_rotation_end=args.carrington_rotation_end, t_start=_parse_time(args.t_start), t_end=_parse_time(args.t_end), segments=args.segments or DEFAULT_VECTOR_SEGMENTS, series=args.series or 'b_synoptic', include_mr_polfil=args.include_mr_polfil, synoptic_product=args.synoptic_product, ) if args.t_start is None: parser.error('--t_start is required for --source hmi_full_disk') return download_hmi_full_disk( download_dir=args.download_dir, email=args.email, t_start=_parse_time(args.t_start), t_end=_parse_time(args.t_end), cadence=args.cadence, series=args.series or 'B_720s', segments=args.segments or 'field,inclination,azimuth,disambig', convert_ptr=not args.no_convert_ptr, keep_coordinates=args.keep_coordinates, ) if __name__ == '__main__': main()