Esempio n. 1
0
def lrsubtract(ccd,
               fitting_sections=["[:, 50:100]", "[:, 924:974]"],
               method='median',
               sigclip_kw=dict(sigma=2, maxiters=5),
               dtype='float32',
               update_header=True,
               verbose=False):
    """Subtract left from right quadrants w/ vertical pattern removal."""
    _t = Time.now()

    nccd = vertical_correct(ccd=ccd,
                            fitting_sections=fitting_sections,
                            method=method,
                            sigclip_kw=sigclip_kw,
                            dtype=dtype,
                            return_pattern=False,
                            update_header=update_header)
    nx = nccd.data.shape[1]
    i_half = nx // 2
    nccd.data[:, i_half:] -= nccd.data[:, :i_half]
    if update_header:
        # add as history
        add_to_header(
            nccd.header,
            'h',
            t_ref=_t,
            verbose=verbose,
            s=f"Subtracted left half ({i_half} columns) from the right half.")
    # nccd = trim_image(nccd, fits_section=f"[{i_half + 1}:, :]")
    return nccd
Esempio n. 2
0
def fourier_lrsub(ccd,
                  cut_wavelength=200,
                  copy=True,
                  verbose_bpm=False,
                  verbose=False,
                  **kwargs):
    if copy:
        _ccd = ccd.copy()
    else:
        _ccd = ccd
    ccd_l = trim_ccd(_ccd, fits_section="[:512, :]", update_header=False)
    ccd_l = medfilt_bpm(ccd_l, verbose=verbose_bpm, **kwargs)
    ccd.header = ccd_l.header  # to add MBPM logs

    amp_comp = np.fft.rfft(ccd_l.data, axis=0)
    amp_comp[cut_wavelength:, :] = 0
    pattern_pure = np.fft.irfft(amp_comp, axis=0)
    pattern = np.tile(pattern_pure, 2)
    _ccd.data = _ccd.data - pattern
    add_to_header(
        _ccd.header,
        'h',
        verbose=verbose,
        s=("FFT(left half) to get pattern map (see FFTCUTWL for the cut wavelength); "
           + "subtracted from both left/right."))
    _ccd.header["FFTCUTWL"] = (cut_wavelength,
                               "FFT cut wavelength (amplitude[this:, :] = 0)")
    return _ccd
Esempio n. 3
0
    def _sub_lr(part_l, part_r, filt):
        # part_l = cr_reject_nic(part_l, crrej_kw=crrej_kw, verbose=verbose_crrej, add_process=False)
        add_to_header(
            part_l.header,
            'h',
            verbose=verbose_bpm,
            s=
            ("Median filter badpixel masking (MBPM) algorithm started running on the left half "
             +
             'to remove hot pixels on left; a prerequisite for the right frame - left frame" '
             +
             f"technique to remove wavy pattern. Using med_sub_clip = {med_sub_clip} "
             + f"and {dark_medfilt_bpm_kw}"))

        # To keep the header log of medfilt_bpm, I need this:
        _tmp = part_r.data
        part_r.data = part_l.data
        part_r = medfilt_bpm(part_r,
                             med_sub_clip=med_sub_clip,
                             **dark_medfilt_bpm_kw)

        _t = Time.now()
        part_r.data = (_tmp - part_r.data).astype(dtype)

        add_to_header(
            part_r.header,
            'h',
            t_ref=_t,
            verbose=verbose_bpm,
            s=("Left part (vertical subtracted and MBPMed) is subtracted from the right part "
               + "(only vertical subtracted)"))
        add_to_header(part_r.header,
                      'h',
                      verbose=verbose_bpm,
                      s="{:-^72s}".format(' DONE '),
                      fmt=None)
        return part_r
Esempio n. 4
0
def make_dark(dir_in,
              dir_out,
              dir_log=None,
              dark_object="DARK",
              combine='med',
              reject='sc',
              sigma=[3., 3.],
              dark_min=3,
              skip_if_exists=True,
              verbose=0,
              **kwargs):
    '''
    verbose : int
        Larger number means it becomes more verbose::
            * 0: print nothing
            * 1: Only very essential things
            * 2: + progress-like info (printing filter and exptime)
            * 3: + fits stack info (grouping) + header update
            * 4: + imcombine verbose
    '''
    dir_in, dir_out, dir_log = _set_dir_iol(dir_in, dir_out, dir_log)

    try:
        _summary = pd.read_csv(dir_log / f"summary_{dir_in.name}.csv")
    except FileNotFoundError:
        raise FileNotFoundError(
            f"Summary for {dir_in} is not found in {dir_log}." +
            " Try changing dir_in or dir_log.")

    if skip_if_exists:
        if verbose >= 1:
            print("Loading existing dark frames...")
        _, darks, darkpaths = _load_as_dict(dir_out, ["FILTER", "EXPTIME"],
                                            verbose=verbose >= 2)
        if darks is not None:  # if some darks already exist
            return darks, darkpaths
        # else, run the code below

    if verbose >= 1:
        print(
            "Making master dark of the night (grouping & combining dark frames)."
        )

    darks = group_combine(_summary,
                          type_key=["OBJECT"],
                          type_val=[dark_object],
                          group_key=["FILTER", "EXPTIME"],
                          combine=combine,
                          reject=reject,
                          sigma=sigma,
                          verbose=verbose - 1,
                          **kwargs)
    darkpaths = {}
    for k, dark_k in darks.items():
        filt, exptime = k
        darks[k].data[dark_k.data < dark_min] = 0
        add_to_header(
            darks[k].header,
            'h',
            verbose=verbose >= 3,
            s=f"Pixels with value < {dark_min:.2f} in combined dark are replaced with 0."
        )
        fstem = f"{filt.lower()}_mdark_{exptime:.1f}"
        darkpaths[k] = _save(darks[k], dir_out, fstem, return_path=True)

    return darks, darkpaths
Esempio n. 5
0
def _do_16bit_vertical(fpath,
                       dir_out,
                       skip_if_exists=True,
                       sigclip_kw=dict(sigma=2, maxiters=5),
                       fitting_sections=None,
                       method='median',
                       verbose=True,
                       show_progress=True):
    ''' Changes to 16-bit and correct the vertical pattern.
    dir_out : path-like
        The directory for the resulting FITS files to be saved, RELATIVE to ``self.dir_work``.

    verbose : int
        Larger number means it becomes more verbose::
            * 0: print nothing
            * 1: Only very essential things
            * 2: + verbose for summary CSV file
            * 3: + the HISTORY in each FITS file's header
    '''
    fpath = Path(fpath)
    ccd_orig = load_ccd(fpath)
    _t = Time.now()

    # == Set output stem =================================================================================== #
    outstem, _ = _set_fstem(ccd_orig.header)
    outstem += "-PROC-v"

    # == Skip if conditions meet =========================================================================== #
    if skip_if_exists and (dir_out / f"{outstem}.fits").exists():
        return

    # == First, change the bit ============================================================================= #
    ccd_nbit = ccd_orig.copy()
    ccd_nbit = CCDData_astype(ccd_nbit, dtype='int16')

    add_to_header(ccd_nbit.header,
                  'h',
                  verbose=verbose,
                  s="{:=^72s}".format(' Basic preprocessing start '))

    if ccd_orig.dtype != ccd_nbit.dtype:
        add_to_header(
            ccd_nbit.header,
            'h',
            t_ref=_t,
            verbose=verbose,
            s=f"Changed dtype (BITPIX): {ccd_orig.dtype} to {ccd_nbit.dtype}")

    # == Then check if identical =========================================================================== #
    # It takes < ~20 ms on MBP 15" [2018, macOS 10.14.6, i7-8850H (2.6 GHz; 6-core), RAM 16 GB (2400MHz
    # DDR4), Radeon Pro 560X (4GB)]
    #   ysBach 2020-05-15 16:06:08 (KST: GMT+09:00)
    np.testing.assert_almost_equal(ccd_orig.data - ccd_nbit.data,
                                   np.zeros(ccd_nbit.data.shape))

    # == Set counter ======================================================================================= #
    try:
        counter = ccd_nbit.header["COUNTER"]
    except KeyError:
        try:
            counter = fpath.stem.split('_')[1]
            counter = counter.split('.')[0]
            # e.g., hYYMMDD_dddd.object.pcr.fits
        except IndexError:  # e.g., test images (``h.fits```)
            counter = 9999
        ccd_nbit.header['COUNTER'] = (
            counter, "Image counter of the day, 1-indexing; 9999=TEST")

    # == Update warning-invoking parts ===================================================================== #
    try:
        ccd_nbit.header["MJD-STR"] = float(ccd_nbit.header["MJD-STR"])
        ccd_nbit.header["MJD-END"] = float(ccd_nbit.header["MJD-END"])
    except KeyError:
        pass

    # == vertical pattern subtraction ====================================================================== #
    ccd_nbit_v = vertical_correct(ccd_nbit,
                                  sigclip_kw=sigclip_kw,
                                  fitting_sections=fitting_sections,
                                  method=method,
                                  dtype='int16',
                                  return_pattern=False)
    update_process(ccd_nbit_v.header,
                   "v",
                   additional_comment=dict(v="vertical pattern"))

    _save(ccd_nbit_v, dir_out, outstem)
    return ccd_nbit_v
Esempio n. 6
0
def vertical_correct(ccd,
                     fitting_sections=None,
                     method='median',
                     sigclip_kw=dict(sigma=2, maxiters=5),
                     dtype='float32',
                     return_pattern=False,
                     update_header=True,
                     verbose=False):
    ''' Correct vertical strip patterns.

    Paramters
    ---------

    ccd : CCDData, HDU object, HDUList, or ndarray.
        The CCD to subtract the vertical pattern.

    fitting_sections : list of two str, optional.
        The sections to be used for the vertical pattern estimation. This must be identical to the
        usual FITS section (i.e., that used in SAO ds9 or IRAF, 1-indexing and last-index-inclusive),
        not in python. **Give it in the order of ``[<upper>, <lower>]`` in FITS y-coordinate.**

    method : str, optional.
        One of ``['med', 'avg', 'median', 'average', 'mean']``.

    sigma, maxiters : float and int, optional
        A sigma-clipping will be done to remove hot pixels and cosmic rays for estimating the vertical
        pattern. To turn sigma-clipping off, set ``maxiters=0``.

    sigclip_kw : dict, optional
        The keyword arguments for the sigma clipping.

    dtype : str, dtype, optional
        The data type to be returned.

    return_pattern : bool, optional.
        If `True`, the subtracted pattern will also be returned.
        Default is `False`.

    update_header : bool, optional.
        Whether to update the header if there is any.

    Return
    ------

    '''
    _t = Time.now()
    data, hdr = _parse_data_header(ccd)

    if fitting_sections is None:
        fitting_sections = VERTICALSECTS
    elif len(fitting_sections) != 2:
        raise ValueError("fitting_sections must have two elements.")

    if method in ['median', 'med']:
        methodstr = 'taking median'
        fun = np.median
        idx = 1
    elif method in ['average', 'avg', 'mean']:
        methodstr = 'taking average'
        fun = np.mean
        idx = 0
    else:
        raise ValueError(
            "method not understood; it must be one of [med, avg, median, average, mean]."
        )

    parts = []  # The two horizontal box areas from raw data
    strips = []  # The estimated patterns (1-D)
    for sect in fitting_sections:
        parts.append(data[fitsxy2py(sect)])

    try:
        if sigclip_kw["maxiters"] == 0:
            clipstr = "no clipping"
            for part in parts:
                strips.append(fun(part, axis=0))
    except KeyError:
        pass  # The user wants to use default value of maxiters in astropy.

    clipstr = f"sigma-clipping in astropy (v {astropy.__version__})"
    if sigclip_kw:
        clipstr += f", given {sigclip_kw}."
    else:
        clipstr += "."

    for part in parts:
        clip = sigma_clipped_stats(part, axis=0, **sigclip_kw)
        strips.append(clip[idx])

    ny, nx = data.shape
    vpattern = np.repeat(strips, ny / 2, axis=0)
    vsub = data - vpattern.astype(dtype)

    if update_header and hdr is not None:
        # add as history
        add_to_header(
            hdr,
            'h',
            verbose=verbose,
            t_ref=_t,
            s=f"Vertical pattern subtracted using {fitting_sections} by {methodstr} with {clipstr}"
        )

    try:
        nccd = CCDData(data=vsub, header=hdr)
    except ValueError:
        nccd = CCDData(data=vsub, header=hdr, unit='adu')

    nccd.data = nccd.data.astype(dtype)
    if return_pattern:
        return nccd, vpattern
    return nccd
Esempio n. 7
0
def prepare(fpath,
            outdir=Path('.'),
            kw_vertical=dict(sigclip_kw=dict(sigma=2, maxiters=5),
                             fitting_sections=None,
                             method='median',
                             update_header=True,
                             verbose=False),
            dir_vc=None,
            dir_vcfs=None,
            kw_fourier={
                'med_sub_clip': [-5, 5],
                'med_rat_clip': [0.5, 2],
                'std_rat_clip': [-5, 5]
            },
            save_nonpol=False,
            split=True,
            verbose=False):
    ''' Rename the original NHAO NIC image and convert to certain dtype.

    Note
    ----
    Original NHAO NIC image is in 32-bit integer, and is twice the size it should be. To save the
    storage, it is desirable to convert those to 16-bit. As the bias is not added to the FITS frame
    from NHAO NIC, the pixel value in the raw FITS file can be negative. Fortunately, however, the
    maximum pixel value when saturation occurs is only about 20k, and therefore using ``int16`` rather
    than ``uint16`` is enough.

    Here, not only reducing the size, the file names are updated using the original file name and
    header information:
        ``<FILTER (j, h, k)><System YYMMDD>_<COUNTER:04d>.fits``
    It is then updated to
        ``<FILTER (j, h, k)>_<System YYYYMMDD>_<COUNTER:04d>_<OBJECT>_<EXPTIME:.1f>_<POL-AGL1:04.1f>
          _<INSROT:+04.0f>_<IMGROT:+04.0f>_<PA:+06.1f>.fits``
    '''
    ccd_orig = load_ccd(fpath)
    _t = Time.now()
    ccd_nbit = ccd_orig.copy()
    ccd_nbit = CCDData_astype(ccd_nbit, dtype='int16')

    add_to_header(ccd_nbit.header,
                  'h',
                  verbose=verbose,
                  s="{:=^72s}".format(' Basic preprocessing start '))

    if ccd_orig.dtype != ccd_nbit.dtype:
        add_to_header(
            ccd_nbit.header,
            'h',
            t_ref=_t,
            verbose=verbose,
            s=f"Changed dtype (BITPIX): {ccd_orig.dtype} to {ccd_nbit.dtype}")

    # == First, check if identical ===================================================================== #
    # It takes < ~20 ms on MBP 15" [2018, macOS 10.14.6, i7-8850H (2.6 GHz; 6-core), RAM 16 GB (2400MHz
    # DDR4), Radeon Pro 560X (4GB)]
    #   ysBach 2020-05-15 16:06:08 (KST: GMT+09:00)
    np.testing.assert_almost_equal(ccd_orig.data - ccd_nbit.data,
                                   np.zeros(ccd_nbit.data.shape))

    # == Set counter =================================================================================== #
    try:
        counter = ccd_nbit.header["COUNTER"]
    except KeyError:
        try:
            counter = fpath.stem.split('_')[1]
            counter = counter.split('.')[0]
            # e.g., hYYMMDD_dddd.object.pcr.fits
        except IndexError:  # e.g., test images (``h.fits```)
            counter = 9999
        ccd_nbit.header['COUNTER'] = (
            counter, "Image counter of the day, 1-indexing; 9999=TEST")

    # == Update warning-invoking parts ================================================================= #
    try:
        ccd_nbit.header["MJD-STR"] = float(ccd_nbit.header["MJD-STR"])
        ccd_nbit.header["MJD-END"] = float(ccd_nbit.header["MJD-END"])
    except KeyError:
        pass

    # == Set output stem =============================================================================== #
    outstem, polmode = _set_fstem(ccd_nbit.header)

    # == vertical pattern subtraction ================================================================== #
    ccd_nbit_vc = vertical_correct(ccd_nbit,
                                   **kw_vertical,
                                   dtype='int16',
                                   return_pattern=False)

    if dir_vc is not None:
        _save(ccd_nbit_vc, dir_vc, outstem + "_vc")

    if polmode:
        # == Do Fourier pattern subtraction ============================================================ #
        ccd_nbit_vcfs = fourier_lrsub(ccd_nbit_vc,
                                      cut_wavelength=200,
                                      **kw_fourier)
        ccd_nbit_vcfs = CCDData_astype(ccd_nbit_vcfs, dtype='int16')

        if dir_vcfs is not None:
            _save(ccd_nbit_vcfs, dir_vcfs, outstem + "_vc_fs")

        if split:
            ccds = split_oe(ccd_nbit_vcfs, verbose=verbose)
            ccd_out = []
            for _ccd, oe in zip(ccds, 'oe'):
                _save(_ccd, outdir, outstem + f"_{oe:s}")
                ccd_out.append(_ccd)

        else:
            ccd_out = ccd_nbit_vcfs
            _save(ccd_out, outdir, outstem)

    else:
        ccd_out = ccd_nbit_vc
        if save_nonpol:
            _save(ccd_out, outdir, outstem)

    return ccd_out
Esempio n. 8
0
def reorganize_fits(fpath,
                    outdir=Path('.'),
                    dtype='int16',
                    fitting_sections=None,
                    method='median',
                    sigclip_kw=dict(sigma=2, maxiters=5),
                    med_sub_clip=[-5, 5],
                    sub_lr=True,
                    dark_medfilt_bpm_kw=dict(med_rat_clip=None,
                                             std_rat_clip=None,
                                             std_model='std',
                                             logical='and',
                                             sigclip_kw=dict(sigma=2,
                                                             maxiters=5,
                                                             std_ddof=1)),
                    update_header=True,
                    save_nonpol=False,
                    verbose_bpm=False,
                    split=True,
                    verbose=False):
    ''' Rename the original NHAO NIC image and convert to certain dtype.

    Parameters
    ----------
    fpath : path-like
        Path to the original image FITS file.

    outdir : None, path-like
        The top directory for the new file to be saved. If `None`,
        nothing will be saved.

    Note
    ----
    Original NHAO NIC image is in 32-bit integer, and is twice the size it should be. To save the
    storage, it is desirable to convert those to 16-bit. As the bias is not added to the FITS frame
    from NHAO NIC, the pixel value in the raw FITS file can be negative. Fortunately, however, the
    maximum pixel value when saturation occurs is only about 20k, and therefore using ``int16`` rather
    than ``uint16`` is enough.

    Here, not only reducing the size, the file names are updated using the original file name and
    header information:
        ``<FILTER (j, h, k)><System YYMMDD>_<COUNTER:04d>.fits``
    It is then updated to
        ``<FILTER (j, h, k)>_<System YYYYMMDD>_<COUNTER:04d>_<OBJECT>_<EXPTIME:.1f>_<POL-AGL1:04.1f>
          _<INSROT:+04.0f>_<IMGROT:+04.0f>_<PA:+06.1f>.fits``
    '''
    def _sub_lr(part_l, part_r, filt):
        # part_l = cr_reject_nic(part_l, crrej_kw=crrej_kw, verbose=verbose_crrej, add_process=False)
        add_to_header(
            part_l.header,
            'h',
            verbose=verbose_bpm,
            s=
            ("Median filter badpixel masking (MBPM) algorithm started running on the left half "
             +
             'to remove hot pixels on left; a prerequisite for the right frame - left frame" '
             +
             f"technique to remove wavy pattern. Using med_sub_clip = {med_sub_clip} "
             + f"and {dark_medfilt_bpm_kw}"))

        # To keep the header log of medfilt_bpm, I need this:
        _tmp = part_r.data
        part_r.data = part_l.data
        part_r = medfilt_bpm(part_r,
                             med_sub_clip=med_sub_clip,
                             **dark_medfilt_bpm_kw)

        _t = Time.now()
        part_r.data = (_tmp - part_r.data).astype(dtype)

        add_to_header(
            part_r.header,
            'h',
            t_ref=_t,
            verbose=verbose_bpm,
            s=("Left part (vertical subtracted and MBPMed) is subtracted from the right part "
               + "(only vertical subtracted)"))
        add_to_header(part_r.header,
                      'h',
                      verbose=verbose_bpm,
                      s="{:-^72s}".format(' DONE '),
                      fmt=None)
        return part_r

    fpath = Path(fpath)
    ccd_orig = load_ccd(fpath)
    _t = Time.now()
    ccd_nbit = ccd_orig.copy()
    ccd_nbit = CCDData_astype(ccd_nbit, dtype=dtype)

    add_to_header(ccd_nbit.header,
                  'h',
                  verbose=verbose,
                  s="{:=^72s}".format(' Basic preprocessing start '))

    if ccd_orig.dtype != ccd_nbit.dtype:
        add_to_header(
            ccd_nbit.header,
            'h',
            t_ref=_t,
            verbose=verbose,
            s=f"Changed dtype (BITPIX): {ccd_orig.dtype} to {ccd_nbit.dtype}")

    # == First, check if identical ========================================================================= #
    # It takes < ~20 ms on MBP 15" [2018, macOS 10.14.6, i7-8850H (2.6 GHz; 6-core), RAM 16 GB (2400MHz
    # DDR4), Radeon Pro 560X (4GB)]
    #   ysBach 2020-05-15 16:06:08 (KST: GMT+09:00)
    np.testing.assert_almost_equal(ccd_orig.data - ccd_nbit.data,
                                   np.zeros(ccd_nbit.data.shape))

    # == Set counter ======================================================================================= #
    try:
        counter = ccd_nbit.header["COUNTER"]
    except KeyError:
        try:
            counter = fpath.stem.split('_')[1]
            counter = counter.split('.')[0]
            # e.g., hYYMMDD_dddd.object.pcr.fits
        except IndexError:  # e.g., test images (``h.fits```)
            counter = 9999
        ccd_nbit.header['COUNTER'] = (
            counter, "Image counter of the day, 1-indexing; 9999=TEST")

    # == Set output stem =================================================================================== #
    hdr = ccd_nbit.header
    outstem, polmode = _set_fstem(hdr)

    # == Update warning-invoking parts ===================================================================== #
    try:
        ccd_nbit.header["MJD-STR"] = float(hdr["MJD-STR"])
        ccd_nbit.header["MJD-END"] = float(hdr["MJD-END"])
    except KeyError:
        pass

    # == Simple process to remove artifacts... ============================================================= #
    ccd_nbit = vertical_correct(ccd_nbit,
                                fitting_sections=fitting_sections,
                                method=method,
                                sigclip_kw=sigclip_kw,
                                dtype='float32',
                                return_pattern=False,
                                update_header=update_header,
                                verbose=verbose)

    if polmode:
        filt = infer_filter(ccd_nbit, filt=None, verbose=verbose)
        if split:
            ccds_l = split_oe(ccd_nbit,
                              filt=filt,
                              right_half=True,
                              verbose=verbose)
            ccds_r = split_oe(ccd_nbit,
                              filt=filt,
                              right_half=False,
                              verbose=verbose)
            ccd_nbit = []
            for i, oe in enumerate(['o', 'e']):
                if sub_lr:
                    part_l = ccds_l[i]
                    part_r = ccds_r[i]
                    _ccd_nbit = _sub_lr(part_l, part_r, filt)
                else:
                    _ccd_nbit = CCDData_astype(ccds_r[i], dtype=dtype)
                _save(_ccd_nbit, outdir, outstem + f"_{oe:s}")
                ccd_nbit.append(_ccd_nbit)

        else:
            # left half and right half
            part_l = trim_ccd(ccd_nbit, NICSECTS["left"], verbose=verbose)
            part_r = trim_ccd(ccd_nbit, NICSECTS["right"], verbose=verbose)
            if sub_lr:
                part_l = ccds_l[i]
                part_r = ccds_r[i]
                _ccd_nbit = _sub_lr(part_l, part_r, filt)
            else:
                _ccd_nbit = CCDData_astype(ccds_r[i], dtype=dtype)
            ccd_nbit = part_r.copy()
            _save(ccd_nbit, outdir, outstem)

    else:
        if save_nonpol:
            _save(ccd_nbit, outdir, outstem)

    return ccd_nbit