diff --git a/tests/pre_3_10/test_metrics_completeness.py b/tests/pre_3_10/test_metrics_completeness.py index fda2126f..2cf7ccb0 100644 --- a/tests/pre_3_10/test_metrics_completeness.py +++ b/tests/pre_3_10/test_metrics_completeness.py @@ -14,7 +14,7 @@ from vip_hci.metrics import completeness_curve from vip_hci.metrics import completeness_map from vip_hci.preproc import frame_crop -from vip_hci.psfsub import pca +from vip_hci.psfsub import pca, PCAParams @fixture(scope="module") @@ -62,6 +62,7 @@ def test_completeness_curve(get_cube_empty): starphot=starphot, plot=True, algo_dict={"imlib": "opencv"}, + algo_class=PCAParams, ) if np.allclose(comp_curve / expected_res, [1], atol=0.5): @@ -89,6 +90,7 @@ def test_completeness_map(get_cube_empty): ini_contrast=expected_res, starphot=starphot, algo_dict={"imlib": "opencv"}, + algo_class=PCAParams, ) if np.allclose(contrasts[:, -2] / expected_res, [1], atol=0.5): diff --git a/tests/pre_3_10/test_metrics_contrcurve.py b/tests/pre_3_10/test_metrics_contrcurve.py index b2c48037..cb7ff48b 100644 --- a/tests/pre_3_10/test_metrics_contrcurve.py +++ b/tests/pre_3_10/test_metrics_contrcurve.py @@ -16,7 +16,7 @@ from vip_hci.fm.utils_negfc import find_nearest from vip_hci.metrics import contrast_curve from vip_hci.preproc import frame_crop -from vip_hci.psfsub import pca +from vip_hci.psfsub import pca, PCAParams @fixture(scope="module") @@ -69,6 +69,7 @@ def test_contrast_curve(get_cube): transmission=trans, plot=True, debug=True, + algo_class=PCAParams, ) rad = np.array(cc["distance"]) diff --git a/vip_hci/invprob/andromeda.py b/vip_hci/invprob/andromeda.py index 3a7ea90c..2ec8fede 100644 --- a/vip_hci/invprob/andromeda.py +++ b/vip_hci/invprob/andromeda.py @@ -26,13 +26,13 @@ from dataclasses import dataclass from enum import Enum -from typing import Union +from typing import Union, List from ..var.filters import frame_filter_highpass, cube_filter_highpass from ..config.utils_conf import pool_map, iterable from ..var import dist_matrix -from ..var.object_utils import setup_parameters -from ..var.paramenum import OptMethod +from ..var.object_utils import setup_parameters, separate_kwargs_dict +from ..var.paramenum import OptMethod, ALGO_KEY from .utils_andro import ( calc_psf_shift_subpix, @@ -48,11 +48,54 @@ @dataclass class AndroParams: - r""" + """ Set of parameters for the ANDROMEDA algorithm. + See function `andromeda` below for the documentation. + """ + + cube: np.ndarray = None + oversampling_fact: float = None + angle_list: np.ndarray = None + psf: np.ndarray = None + filtering_fraction: float = 0.25 + min_sep: float = 0.5 + annuli_width: float = 1.0 + roa: float = 2 + opt_method: Enum = OptMethod.LSQ + nsmooth_snr: int = 18 + iwa: float = None + owa: float = None + precision: int = 50 + fast: Union[float, bool] = False + homogeneous_variance: bool = True + ditimg: float = 1.0 + ditpsf: float = None + tnd: float = 1.0 + total: bool = False + multiply_gamma: bool = True + nproc: int = 1 + verbose: bool = False + + +def andromeda(*all_args: List, **all_kwargs: dict): + r""" + Exoplanet detection in ADI sequences by maximum-likelihood approach. + + This is as implemented in [CAN15]_, itself inspired by the framework presented in + [MUG09]_. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the andromeda algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a AndroParams or a AndroParams + itself. + + Andromeda parameters + ---------- cube : 3d numpy ndarray Input cube. IDL parameter: ``IMAGES_1_INPUT`` @@ -147,45 +190,6 @@ class AndroParams: verbose : bool, optional Print some parameter values for control. IDL parameter: ``VERBOSE`` - """ - cube: np.ndarray = None - oversampling_fact: float = None - angle_list: np.ndarray = None - psf: np.ndarray = None - filtering_fraction: float = 0.25 - min_sep: float = 0.5 - annuli_width: float = 1.0 - roa: float = 2 - opt_method: Enum = OptMethod.LSQ - nsmooth_snr: int = 18 - iwa: float = None - owa: float = None - precision: int = 50 - fast: Union[float, bool] = False - homogeneous_variance: bool = True - ditimg: float = 1.0 - ditpsf: float = None - tnd: float = 1.0 - total: bool = False - multiply_gamma: bool = True - nproc: int = 1 - verbose: bool = False - - -def andromeda(algo_params: AndroParams = None, **class_params: dict): - """ - Exoplanet detection in ADI sequences by maximum-likelihood approach. - - This is as implemented in [CAN15]_, itself inspired by the framework presented in - [MUG09]_. - - Parameters - ---------- - algo_params: AndroParams - Dataclass retaining all the needed parameters for ANDROMEDA. - class_params: dict, optionnal - Set of parameters needed for an initialization of algo_params if none - was provided. Returns ------- @@ -261,8 +265,18 @@ def andromeda(algo_params: AndroParams = None, **class_params: dict): diam_tel = 8.0 : Telescope diameter [m] """ + class_params, other_options = separate_kwargs_dict( + initial_kwargs=all_kwargs, parent_class=AndroParams + ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in other_options.keys(): + algo_params = other_options[ALGO_KEY] + del other_options[ALGO_KEY] + if algo_params is None: - algo_params = AndroParams(**class_params) + algo_params = AndroParams(*all_args, **class_params) def info(msg, *fmt, **kwfmt): if algo_params.verbose: diff --git a/vip_hci/invprob/fmmf.py b/vip_hci/invprob/fmmf.py index 5c390bc5..53c5eb3d 100644 --- a/vip_hci/invprob/fmmf.py +++ b/vip_hci/invprob/fmmf.py @@ -69,8 +69,8 @@ from ..config import time_ini, timing from ..fm import cube_inject_companions from ..preproc.derotation import _find_indices_adi -from ..var.object_utils import setup_parameters -from ..var.paramenum import VarEstim, Imlib, Interpolation +from ..var.object_utils import setup_parameters, separate_kwargs_dict +from ..var.paramenum import VarEstim, Imlib, Interpolation, ALGO_KEY @dataclass @@ -78,6 +78,42 @@ class FMMFParams: """ Set of parameters for the FMMF algorithm. + See function `fmmf` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + psf: np.ndarray = None + fwhm: float = None + min_r: int = None + max_r: int = None + model: str = "KLIP" + var: Enum = VarEstim.FR + param: dict = field( + default_factory=lambda: {"ncomp": 20, "tolerance": 5e-3, "delta_rot": 0.5} + ) + crop: int = 5 + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + nproc: int = 1 + verbose: bool = True + + +def fmmf(*all_args, **all_kwargs: dict): + """ + Forward model matched filter generating SNR map and contrast map, using + either KLIP or LOCI as PSF subtraction techniques, as implemented in + [RUF17]_ and [DAH21a]_. + + Parameters + ---------- + all_args: list, optional + Positionnal arguments for the FMMF algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a FMMFParams or a FMMFParams + itself. + Parameters ---------- cube : numpy ndarray, 3d @@ -139,39 +175,6 @@ class FMMFParams: verbose: bool, optional If True provide a message each time an annulus has been treated. Default True. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - psf: np.ndarray = None - fwhm: float = None - min_r: int = None - max_r: int = None - model: str = "KLIP" - var: Enum = VarEstim.FR - param: dict = field( - default_factory=lambda: {"ncomp": 20, "tolerance": 5e-3, "delta_rot": 0.5} - ) - crop: int = 5 - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - nproc: int = 1 - verbose: bool = True - - -def fmmf(algo_params: FMMFParams = None, **class_params: dict): - """ - Forward model matched filter generating SNR map and contrast map, using - either KLIP or LOCI as PSF subtraction techniques, as implemented in - [RUF17]_ and [DAH21a]_. - - Parameters - ---------- - algo_params: FMMFParams - Dataclass retaining all the needed parameters for FMMF. - class_params: dict, optionnal - Set of parameters needed for an initialization of algo_params if none - was provided. Returns ------- @@ -183,8 +186,18 @@ def fmmf(algo_params: FMMFParams = None, **class_params: dict): the estimated standard deviation of the contrast). """ + class_params, other_options = separate_kwargs_dict( + initial_kwargs=all_kwargs, parent_class=FMMFParams + ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in other_options.keys(): + algo_params = other_options[ALGO_KEY] + del other_options[ALGO_KEY] + if algo_params is None: - algo_params = FMMFParams(**class_params) + algo_params = FMMFParams(*all_args, **class_params) start_time = time_ini(algo_params.verbose) if algo_params.crop >= 2 * round(algo_params.fwhm) + 1: diff --git a/vip_hci/metrics/completeness.py b/vip_hci/metrics/completeness.py index 804af0d1..2c00fb9b 100644 --- a/vip_hci/metrics/completeness.py +++ b/vip_hci/metrics/completeness.py @@ -187,6 +187,7 @@ def _estimate_snr_fc( return max_target - max_map, b +# TODO: Include algo_class modifications in any tutorial using this function def completeness_curve( cube, angle_list, @@ -210,6 +211,7 @@ def completeness_curve( object_name=None, fix_y_lim=(), figsize=vip_figsize, + algo_class=None, ): """ Function allowing the computation of completeness-based contrast curves with @@ -350,6 +352,7 @@ def completeness_curve( wedge=(0, 360), fc_snr=100, plot=False, + algo_class=algo_class, **algo_dict, ) ini_rads = np.array(ini_cc["distance"]) @@ -368,11 +371,8 @@ def completeness_curve( if verbose: print("Calculating initial SNR map with no injected companion...") - algo_params = signature(algo).parameters - param_name = next(iter(algo_params)) - class_name = algo_params[param_name].annotation - argl = [attr for attr in vars(class_name)] + argl = [attr for attr in vars(algo_class)] if "cube" in argl and "angle_list" in argl: if "fwhm" in argl: frame_fin = algo( @@ -699,6 +699,7 @@ def completeness_curve( return an_dist, cont_curve +# TODO: Include the algo_class in the metrics tutorial !! def completeness_map( cube, angle_list, @@ -713,6 +714,7 @@ def completeness_map( nproc=1, algo_dict={}, verbose=True, + algo_class=None, ): """ Function allowing the computation of two dimensional (radius and @@ -846,15 +848,11 @@ def completeness_map( # argl = getfullargspec(algo).args # does not work with recent object support - """Because all psfsub algorithms now take a single object as parameter, looking for - specific named arguments can be a puzzle. We have to first identify the object, - and look inside its attributes to find the arguments we need.""" + """Because all psfsub algorithms now take more vague parameters, looking for + specific named arguments can be a puzzle. We have to first identify the parameters + tied to the algorithm by looking at its object of parameters.""" - algo_params = signature(algo).parameters - param_name = next(iter(algo_params)) - class_name = algo_params[param_name].annotation - - argl = [attr for attr in vars(class_name)] + argl = [attr for attr in vars(algo_class)] if "cube" in argl and "angle_list" in argl and "verbose" in argl: if "fwhm" in argl: diff --git a/vip_hci/metrics/contrcurve.py b/vip_hci/metrics/contrcurve.py index 8818d368..ab7b48a6 100644 --- a/vip_hci/metrics/contrcurve.py +++ b/vip_hci/metrics/contrcurve.py @@ -26,6 +26,7 @@ from ..var import frame_center, dist +# TODO: Include algo_class modifications in any tutorial using this function def contrast_curve( cube, angle_list, @@ -56,6 +57,7 @@ def contrast_curve( frame_size=None, fix_y_lim=(), figsize=vip_figsize, + algo_class=None, **algo_dict, ): """Computes the contrast curve at a given confidence (``sigma``) level for @@ -234,6 +236,7 @@ def contrast_curve( fc_snr=fc_snr, full_output=True, verbose=verbose_thru, + algo_class=algo_class, **algo_dict, ) vector_radd = res_throug[3] @@ -556,6 +559,7 @@ def contrast_curve( return datafr +# TODO: Include algo_class modifications in any tutorial using this function def throughput( cube, angle_list, @@ -570,6 +574,7 @@ def throughput( fc_snr=100, full_output=False, verbose=True, + algo_class=None, **algo_dict, ): """Measures the throughput for chosen algorithm and input dataset (ADI or @@ -708,11 +713,8 @@ def throughput( start_time = time_ini() # *************************************************************************** # Compute noise in concentric annuli on the "empty frame" - algo_params = signature(algo).parameters - param_name = next(iter(algo_params)) - class_name = algo_params[param_name].annotation - argl = [attr for attr in vars(class_name)] + argl = [attr for attr in vars(algo_class)] if "cube" in argl and "angle_list" in argl and "verbose" in argl: if "fwhm" in argl: frame_nofc = algo( @@ -832,11 +834,8 @@ def throughput( timing(start_time) # *************************************************************** - algo_params = signature(algo).parameters - param_name = next(iter(algo_params)) - class_name = algo_params[param_name].annotation - arg = [attr for attr in vars(class_name)] + arg = [attr for attr in vars(algo_class)] if "cube" in arg and "angle_list" in arg and "verbose" in arg: if "fwhm" in arg: frame_fc = algo( @@ -937,11 +936,8 @@ def throughput( timing(start_time) # ************************************************************** - algo_params = signature(algo).parameters - param_name = next(iter(algo_params)) - class_name = algo_params[param_name].annotation - arg = [attr for attr in vars(class_name)] + arg = [attr for attr in vars(algo_class)] if "cube" in arg and "angle_list" in arg and "verbose" in arg: if "fwhm" in arg: frame_fc = algo( diff --git a/vip_hci/objects/ppandromeda.py b/vip_hci/objects/ppandromeda.py index a0c1c794..34fccfa4 100644 --- a/vip_hci/objects/ppandromeda.py +++ b/vip_hci/objects/ppandromeda.py @@ -2,9 +2,7 @@ """Module for the post-processing ANDROMEDA algorithm.""" __author__ = "Thomas Bédrine, Carlos Alberto Gomez Gonzalez, Ralf Farkas" -__all__ = [ - "AndroBuilder", -] +__all__ = ["AndroBuilder", "PPAndromeda"] from typing import Optional from dataclasses import dataclass @@ -73,7 +71,8 @@ def run( self.nproc = nproc params_dict = self._create_parameters_dict(AndroParams) - res = andromeda(algo_params=self) + all_params = {"algo_params": self} + res = andromeda(**all_params) self.contrast_map = res[0] self.likelihood_map = res[5] diff --git a/vip_hci/objects/ppfmmf.py b/vip_hci/objects/ppfmmf.py index ebd1019d..fbeb06c0 100644 --- a/vip_hci/objects/ppfmmf.py +++ b/vip_hci/objects/ppfmmf.py @@ -2,9 +2,7 @@ """Module for the post-processing FMMF algorithm.""" __author__ = "Thomas Bédrine" -__all__ = [ - "FMMFBuilder", -] +__all__ = ["FMMFBuilder", "PPFMMF"] from typing import Optional from dataclasses import dataclass @@ -60,7 +58,8 @@ def run( self.nproc = nproc params_dict = self._create_parameters_dict(FMMFParams) - res = fmmf(algo_params=self) + all_params = {"algo_params": self} + res = fmmf(**all_params) self.frame_final, self.snr_map = res diff --git a/vip_hci/objects/ppframediff.py b/vip_hci/objects/ppframediff.py index ce12b939..78a4cc2e 100644 --- a/vip_hci/objects/ppframediff.py +++ b/vip_hci/objects/ppframediff.py @@ -2,9 +2,7 @@ """Module for the post-processing frame differencing algorithm.""" __author__ = "Thomas Bédrine" -__all__ = [ - "FrameDiffBuilder", -] +__all__ = ["FrameDiffBuilder", "PPFrameDiff"] from dataclasses import dataclass from typing import Optional @@ -74,7 +72,9 @@ def run( self._explicit_dataset() params_dict = self._create_parameters_dict(FrameDiffParams) - res = frame_diff(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + + res = frame_diff(**all_params) self.frame_final = res diff --git a/vip_hci/objects/ppllsg.py b/vip_hci/objects/ppllsg.py index 637dfa4e..613a6b5e 100644 --- a/vip_hci/objects/ppllsg.py +++ b/vip_hci/objects/ppllsg.py @@ -2,9 +2,7 @@ """Module for the post-processing LLSG algorithm.""" __author__ = "Thomas Bédrine" -__all__ = [ - "LLSGBuilder", -] +__all__ = ["LLSGBuilder", "PPLLSG"] from typing import Optional from dataclasses import dataclass @@ -80,7 +78,9 @@ def run( params_dict = self._create_parameters_dict(LLSGParams) - res = llsg(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + + res = llsg(**all_params) self.frame_l = res[3] self.frame_s = res[4] self.frame_g = res[5] diff --git a/vip_hci/objects/pploci.py b/vip_hci/objects/pploci.py index 877d8258..0bca8b53 100644 --- a/vip_hci/objects/pploci.py +++ b/vip_hci/objects/pploci.py @@ -2,7 +2,7 @@ """Module for the post-processing LOCI algorithm.""" __author__ = "Thomas Bédrine" -__all__ = ["LOCIBuilder"] +__all__ = ["LOCIBuilder", "PPLOCI"] from typing import Optional @@ -77,7 +77,8 @@ def run( params_dict = self._create_parameters_dict(LOCIParams) - res = xloci(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + res = xloci(**all_params) self.cube_res, self.cube_der, self.frame_final = res diff --git a/vip_hci/objects/ppmediansub.py b/vip_hci/objects/ppmediansub.py index df4d78dd..459907a9 100644 --- a/vip_hci/objects/ppmediansub.py +++ b/vip_hci/objects/ppmediansub.py @@ -2,7 +2,7 @@ """Module for the post-processing median subtraction algorithm.""" __author__ = "Thomas Bédrine" -__all__ = ["MedianBuilder"] +__all__ = ["MedianBuilder", "PPMedianSub"] from typing import Optional from dataclasses import dataclass @@ -87,7 +87,9 @@ def run( params_dict = self._create_parameters_dict(MedsubParams) - res = median_sub(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + + res = median_sub(**all_params) self.cube_residuals, self.cube_residuals_der, self.frame_final = res diff --git a/vip_hci/objects/ppnmf.py b/vip_hci/objects/ppnmf.py index 7eb1bb7c..cbc8e2e3 100644 --- a/vip_hci/objects/ppnmf.py +++ b/vip_hci/objects/ppnmf.py @@ -2,9 +2,7 @@ """Module for the post-processing non-negative matrix factorization algorithm.""" __author__ = "Thomas Bédrine" -__all__ = [ - "NMFBuilder", -] +__all__ = ["NMFBuilder", "PPNMF"] from typing import Optional, List, Tuple, Union from dataclasses import dataclass, field @@ -100,13 +98,15 @@ def run( if verbose is not None: self.verbose = verbose + all_params = {"algo_params": self, **rot_options} + if runmode == "fullframe": # Annular NMF gives the default delta_rot, fullframe delta_rot must be int if not isinstance(self.delta_rot, float): self.delta_rot = DELTA_FF_DEFAULT params_dict = self._create_parameters_dict(NMFParams) - res = nmf(algo_params=self, **rot_options) + res = nmf(**all_params) ( self.nmf_reshaped, @@ -128,7 +128,7 @@ def run( else: params_dict = self._create_parameters_dict(NMFAnnParams) - res = nmf_annular(algo_params=self, **rot_options) + res = nmf_annular(**all_params) ( self.cube_residuals, diff --git a/vip_hci/objects/pppca.py b/vip_hci/objects/pppca.py index b69c1c56..d2180bbb 100644 --- a/vip_hci/objects/pppca.py +++ b/vip_hci/objects/pppca.py @@ -2,9 +2,7 @@ """Module for the post-processing PCA algorithm.""" __author__ = "Thomas Bédrine" -__all__ = [ - "PCABuilder", -] +__all__ = ["PCABuilder", "PPPCA"] from typing import Tuple, Optional, List from dataclasses import dataclass, field @@ -188,7 +186,9 @@ def run( params_dict = self._create_parameters_dict(PCAParams) - res = pca(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + + res = pca(**all_params) self._find_pca_mode(res=res) @@ -205,7 +205,9 @@ def run( params_dict = self._create_parameters_dict(PCAAnnParams) - res = pca_annular(algo_params=self, **rot_options) + all_params = {"algo_params": self, **rot_options} + + res = pca_annular(**all_params) self.cube_residuals, self.cube_residuals_der, self.frame_final = res diff --git a/vip_hci/psfsub/framediff.py b/vip_hci/psfsub/framediff.py index 818b7787..d4389ac9 100644 --- a/vip_hci/psfsub/framediff.py +++ b/vip_hci/psfsub/framediff.py @@ -9,11 +9,12 @@ from hciplot import plot_frames from multiprocessing import cpu_count from dataclasses import dataclass +from typing import List from enum import Enum from sklearn.metrics import pairwise_distances from ..var import get_annulus_segments from ..var.object_utils import setup_parameters, separate_kwargs_dict -from ..var.paramenum import Metric, Imlib, Interpolation, Collapse +from ..var.paramenum import Metric, Imlib, Interpolation, Collapse, ALGO_KEY from ..preproc import cube_derotate, cube_collapse, check_pa_vector from ..config import time_ini, timing from ..config.utils_conf import pool_map, iterable @@ -26,8 +27,50 @@ class FrameDiffParams: """ Set of parameters for the frame differencing module. + See the function `frame_diff` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + fwhm: float = 4 + metric: Enum = Metric.MANHATTAN + dist_threshold: int = 50 + n_similar: int = None + delta_rot: int = 0.5 + radius_int: int = 2 + asize: int = 4 + ncomp: int = None + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + nproc: int = 1 + verbose: bool = True + debug: bool = False + full_output: bool = False + + +def frame_diff(*all_args: List, **all_kwargs: dict): + """Run the frame differencing algorithm. + + It uses vector distance (depending on + ``metric``), using separately the pixels from different annuli of ``asize`` + width, to create pairs of most similar images. Then it performs pair-wise + subtraction and combines the residuals. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the frame diff algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a FrameDiffParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a FrameDiffParams named as + `algo_params`. + + Frame differencing parameters + ---------- cube : numpy ndarray, 3d Input cube. angle_list : numpy ndarray, 1d @@ -72,47 +115,6 @@ class FrameDiffParams: debug : bool, optional If True the distance matrices will be plotted and additional information will be given. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - fwhm: float = 4 - metric: Enum = Metric.MANHATTAN - dist_threshold: int = 50 - n_similar: int = None - delta_rot: int = 0.5 - radius_int: int = 2 - asize: int = 4 - ncomp: int = None - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - nproc: int = 1 - verbose: bool = True - debug: bool = False - full_output: bool = False - - -def frame_diff( - algo_params: FrameDiffParams = None, - **all_kwargs, -): - """Run the frame differencing algorithm. - - It uses vector distance (depending on - ``metric``), using separately the pixels from different annuli of ``asize`` - width, to create pairs of most similar images. Then it performs pair-wise - subtraction and combines the residuals. - - Parameters - ---------- - algo_params: FrameDiffParams or PostProc - Dataclass retaining all the needed parameters for frame differencing. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -124,8 +126,15 @@ def frame_diff( class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=FrameDiffParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = FrameDiffParams(**class_params) + algo_params = FrameDiffParams(*all_args, **class_params) global array array = algo_params.cube diff --git a/vip_hci/psfsub/llsg.py b/vip_hci/psfsub/llsg.py index 6195e60f..2b343af8 100644 --- a/vip_hci/psfsub/llsg.py +++ b/vip_hci/psfsub/llsg.py @@ -22,12 +22,13 @@ from multiprocessing import cpu_count from astropy.stats import median_absolute_deviation from dataclasses import dataclass +from typing import List from enum import Enum from ..config import time_ini, timing from ..preproc import cube_derotate, cube_collapse from ..var import get_annulus_segments, cube_filter_highpass from ..var.object_utils import setup_parameters, separate_kwargs_dict -from ..var.paramenum import Collapse, LowRankMode, AutoRankMode, ThreshMode +from ..var.paramenum import Collapse, LowRankMode, AutoRankMode, ThreshMode, ALGO_KEY from .svd import svd_wrapper, get_eigenvectors from ..config.utils_conf import pool_map, iterable @@ -37,8 +38,61 @@ class LLSGParams: """ Set of parameters for the LLSG algorithm. + See function `llsg` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + fwhm: float = None + rank: int = 10 + thresh: float = 1 + max_iter: int = 10 + low_rank_ref: bool = False + low_rank_mode: Enum = LowRankMode.SVD + auto_rank_mode: Enum = AutoRankMode.NOISE + residuals_tol: float = 1e-1 + cevr: float = 0.9 + thresh_mode: Enum = ThreshMode.SOFT + nproc: int = 1 + asize: int = None + n_segments: int = 4 + azimuth_overlap: int = None + radius_int: int = None + random_seed: int = None + high_pass: int = None + collapse: Enum = Collapse.MEDIAN + full_output: bool = False + verbose: bool = True + debug: bool = False + + +def llsg(*all_args: List, **all_kwargs: dict): + """Local Low-rank plus Sparse plus Gaussian-noise decomposition (LLSG) as + described in [GOM16]_. This first version of our algorithm aims at + decomposing ADI cubes into three terms L+S+G (low-rank, sparse and Gaussian + noise). Separating the noise from the S component (where the moving planet + should stay) allow us to increase the SNR of potential planets. + + The three tunable parameters are the *rank* or expected rank of the L + component, the ``thresh`` or threshold for encouraging sparsity in the S + component and ``max_iter`` which sets the number of iterations. The rest of + parameters can be tuned at the users own risk (do it if you know what you're + doing). + Parameters ---------- + all_args: list, optional + Positionnal arguments for the LLSG algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a LLSGParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a LLSGParams named as + `algo_params`. + + LLSG parameters + ---------- cube : numpy ndarray, 3d Input ADI cube. angle_list : numpy ndarray, 1d @@ -100,58 +154,6 @@ class LLSGParams: If True prints to stdout intermediate info. debug : bool, optional Whether to output some intermediate information. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - fwhm: float = None - rank: int = 10 - thresh: float = 1 - max_iter: int = 10 - low_rank_ref: bool = False - low_rank_mode: Enum = LowRankMode.SVD - auto_rank_mode: Enum = AutoRankMode.NOISE - residuals_tol: float = 1e-1 - cevr: float = 0.9 - thresh_mode: Enum = ThreshMode.SOFT - nproc: int = 1 - asize: int = None - n_segments: int = 4 - azimuth_overlap: int = None - radius_int: int = None - random_seed: int = None - high_pass: int = None - collapse: Enum = Collapse.MEDIAN - full_output: bool = False - verbose: bool = True - debug: bool = False - - -def llsg( - algo_params: LLSGParams = None, - **all_kwargs, -): - """Local Low-rank plus Sparse plus Gaussian-noise decomposition (LLSG) as - described in [GOM16]_. This first version of our algorithm aims at - decomposing ADI cubes into three terms L+S+G (low-rank, sparse and Gaussian - noise). Separating the noise from the S component (where the moving planet - should stay) allow us to increase the SNR of potential planets. - - The three tunable parameters are the *rank* or expected rank of the L - component, the ``thresh`` or threshold for encouraging sparsity in the S - component and ``max_iter`` which sets the number of iterations. The rest of - parameters can be tuned at the users own risk (do it if you know what you're - doing). - - Parameters - ---------- - algo_params: LLSGParams - Dataclass retaining all the needed parameters for LLSG. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -168,8 +170,15 @@ def llsg( class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=LLSGParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = LLSGParams(**class_params) + algo_params = LLSGParams(*all_args, **class_params) if algo_params.cube.ndim != 3: raise TypeError("Input array is not a cube (3d array)") diff --git a/vip_hci/psfsub/loci.py b/vip_hci/psfsub/loci.py index fc673bb2..883a0fe4 100644 --- a/vip_hci/psfsub/loci.py +++ b/vip_hci/psfsub/loci.py @@ -23,10 +23,18 @@ from sklearn.metrics import pairwise_distances from dataclasses import dataclass from enum import Enum -from typing import Tuple, Union +from typing import Tuple, Union, List from ..var import get_annulus_segments from ..var.object_utils import setup_parameters, separate_kwargs_dict -from ..var.paramenum import Metric, Adimsdi, Imlib, Interpolation, Collapse, Solver +from ..var.paramenum import ( + Metric, + Adimsdi, + Imlib, + Interpolation, + Collapse, + Solver, + ALGO_KEY, +) from ..preproc import cube_derotate, cube_collapse, check_pa_vector, check_scal_vector from ..preproc.rescaling import _find_indices_sdi from ..config import time_ini, timing @@ -40,8 +48,55 @@ class LOCIParams: """ Set of parameters for the LOCI algorithm. + See function `xloci` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + scale_list: np.ndarray = None + fwhm: float = 4 + metric: Enum = Metric.MANHATTAN + dist_threshold: int = 100 + delta_rot: Union[float, Tuple[float]] = (0.1, 1) + delta_sep: Union[float, Tuple[float]] = (0.1, 1) + radius_int: int = 0 + asize: int = 4 + n_segments: int = 4 + nproc: int = 1 + solver: Enum = Solver.LSTSQ + tol: float = 1e-2 + optim_scale_fact: float = 2 + adimsdi: Enum = Adimsdi.SKIPADI + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + verbose: bool = True + full_output: bool = False + + +def xloci(*all_args: List, **all_kwargs: dict): + """Locally Optimized Combination of Images (LOCI) algorithm as in [LAF07]_. + The PSF is modeled (for ADI and ADI+mSDI) with a least-square combination + of neighbouring frames (solving the equation a x = b by computing a vector + x of coefficients that minimizes the Euclidean 2-norm || b - a x ||^2). + + This algorithm is also compatible with IFS data to perform LOCI-SDI, in a + similar fashion as suggested in [PUE12]_ (albeit without dampening zones). + Parameters ---------- + all_args: list, optional + Positionnal arguments for the LOCI algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a LOCIParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a LOCIParams named as + `algo_params`. + + LOCI parameters + ---------- cube : numpy ndarray, 3d or 4d Input cube. angle_list : numpy ndarray, 1d @@ -124,49 +179,6 @@ class LOCIParams: full_output: bool, optional Whether to return the final median combined image only or with other intermediate arrays. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - scale_list: np.ndarray = None - fwhm: float = 4 - metric: Enum = Metric.MANHATTAN - dist_threshold: int = 100 - delta_rot: Union[float, Tuple[float]] = (0.1, 1) - delta_sep: Union[float, Tuple[float]] = (0.1, 1) - radius_int: int = 0 - asize: int = 4 - n_segments: int = 4 - nproc: int = 1 - solver: Enum = Solver.LSTSQ - tol: float = 1e-2 - optim_scale_fact: float = 2 - adimsdi: Enum = Adimsdi.SKIPADI - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - verbose: bool = True - full_output: bool = False - - -def xloci(algo_params: LOCIParams = None, **all_kwargs): - """Locally Optimized Combination of Images (LOCI) algorithm as in [LAF07]_. - The PSF is modeled (for ADI and ADI+mSDI) with a least-square combination - of neighbouring frames (solving the equation a x = b by computing a vector - x of coefficients that minimizes the Euclidean 2-norm || b - a x ||^2). - - This algorithm is also compatible with IFS data to perform LOCI-SDI, in a - similar fashion as suggested in [PUE12]_ (albeit without dampening zones). - - Parameters - ---------- - algo_params: LOCIParams - Dataclass retaining all the needed parameters for LOCI. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -181,8 +193,15 @@ def xloci(algo_params: LOCIParams = None, **all_kwargs): class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=LOCIParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = LOCIParams(**class_params) + algo_params = LOCIParams(*all_args, **class_params) global ARRAY ARRAY = algo_params.cube diff --git a/vip_hci/psfsub/medsub.py b/vip_hci/psfsub/medsub.py index f89b8b15..542b0979 100644 --- a/vip_hci/psfsub/medsub.py +++ b/vip_hci/psfsub/medsub.py @@ -37,10 +37,10 @@ from multiprocessing import cpu_count from dataclasses import dataclass from enum import Enum -from typing import Tuple, Union +from typing import Tuple, Union, List from ..config import time_ini, timing from ..var import get_annulus_segments, mask_circle -from ..var.paramenum import Imlib, Interpolation, Collapse +from ..var.paramenum import Imlib, Interpolation, Collapse, ALGO_KEY from ..var.object_utils import setup_parameters, separate_kwargs_dict from ..preproc import cube_derotate, cube_collapse, check_pa_vector, check_scal_vector from ..preproc import cube_rescaling_wavelengths as scwave @@ -54,8 +54,51 @@ class MedsubParams: """ Set of parameters for the median subtraction module. + See function `median_sub` below for documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + scale_list: np.ndarray = None + flux_sc_list: np.ndarray = None + fwhm: float = 4 + radius_int: int = 0 + asize: int = 4 + delta_rot: int = 1 + delta_sep: Union[float, Tuple[float]] = (0.1, 1) + mode: str = "fullfr" + nframes: int = 4 + sdi_only: bool = False + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + nproc: int = 1 + full_output: bool = False + verbose: bool = True + + +def median_sub(*all_args: List, **all_kwargs: dict): + """Implementation of a median subtraction algorithm for model PSF + subtraction in high-contrast imaging sequences. In the case of ADI, the + algorithm is based on [MAR06]_. The ADI+IFS method is an extension of this + basic idea to multi-spectral cubes. + + References: [MAR06]_ for median-ADI; [SPA02]_ and [THA07]_ for SDI. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the medsub algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a MedsubParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a MedsubParams named as + `algo_params`. + + Median subtraction parameters + ----------------------------- cube : numpy ndarray, 3d Input cube. angle_list : numpy ndarray, 1d @@ -118,46 +161,6 @@ class MedsubParams: intermediate arrays. verbose : bool, optional If True prints to stdout intermediate info. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - scale_list: np.ndarray = None - flux_sc_list: np.ndarray = None - fwhm: float = 4 - radius_int: int = 0 - asize: int = 4 - delta_rot: int = 1 - delta_sep: Union[float, Tuple[float]] = (0.1, 1) - mode: str = "fullfr" - nframes: int = 4 - sdi_only: bool = False - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - nproc: int = 1 - full_output: bool = False - verbose: bool = True - - -def median_sub(algo_params: MedsubParams = None, **all_kwargs): - """Implementation of a median subtraction algorithm for model PSF - subtraction in high-contrast imaging sequences. In the case of ADI, the - algorithm is based on [MAR06]_. The ADI+IFS method is an extension of this - basic idea to multi-spectral cubes. - - References: [MAR06]_ for median-ADI; [SPA02]_ and [THA07]_ for SDI. - - Parameters - ---------- - algo_params: MedsubParams or PostProc - Dataclass retaining all the needed parameters for median subtraction. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) - Returns ------- cube_out : numpy ndarray, 3d @@ -168,13 +171,19 @@ def median_sub(algo_params: MedsubParams = None, **all_kwargs): Median combination of the de-rotated cube. """ - # Separating the parameters of the ParamsObject from the optionnal rot_options class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=MedsubParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = MedsubParams(**class_params) + algo_params = MedsubParams(*all_args, **class_params) global ARRAY ARRAY = algo_params.cube.copy() diff --git a/vip_hci/psfsub/nmf_fullfr.py b/vip_hci/psfsub/nmf_fullfr.py index b17241fb..b718b5fc 100644 --- a/vip_hci/psfsub/nmf_fullfr.py +++ b/vip_hci/psfsub/nmf_fullfr.py @@ -19,7 +19,7 @@ from sklearn.decomposition import NMF from dataclasses import dataclass, field from enum import Enum -from typing import Tuple +from typing import Tuple, List from ..preproc import cube_derotate, cube_collapse from ..preproc.derotation import _compute_pa_thresh, _find_indices_adi from ..var import ( @@ -31,7 +31,7 @@ mask_circle, ) from ..var.object_utils import setup_parameters, separate_kwargs_dict -from ..var.paramenum import Collapse, HandleNeg, Initsvd +from ..var.paramenum import Collapse, HandleNeg, Initsvd, ALGO_KEY from ..config import timing, time_ini @@ -40,8 +40,50 @@ class NMFParams: """ Set of parameters for the NMF full-frame algorithm. + See function `nmf` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + cube_ref: np.ndarray = None + ncomp: int = 1 + scaling: Enum = None + max_iter: int = 10000 + random_state: int = None + mask_center_px: int = None + source_xy: Tuple[int] = None + delta_rot: float = 1 + fwhm: float = 4 + init_svd: Enum = Initsvd.NNDSVD + collapse: Enum = Collapse.MEDIAN + full_output: bool = False + verbose: bool = True + cube_sig: np.ndarray = None + handle_neg: Enum = HandleNeg.MASK + nmf_args: dict = field(default_factory=lambda: {}) + + +def nmf(*all_args: List, **all_kwargs: dict): + """Non Negative Matrix Factorization [LEE99]_ for ADI sequences [GOM17]_. + Alternative to the full-frame ADI-PCA processing that does not rely on SVD + or ED for obtaining a low-rank approximation of the datacube. This function + embeds the scikit-learn NMF algorithm solved through either the coordinate + descent or the multiplicative update method. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the NMF algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a NMFParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a NMFParams named as + `algo_params`. + + NMF parameters + ---------- cube : numpy ndarray, 3d Input cube. angle_list : numpy ndarray, 1d @@ -98,44 +140,6 @@ class NMFParams: nmf_args : dictionary, optional Additional arguments for scikit-learn NMF algorithm. See: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - cube_ref: np.ndarray = None - ncomp: int = 1 - scaling: Enum = None - max_iter: int = 10000 - random_state: int = None - mask_center_px: int = None - source_xy: Tuple[int] = None - delta_rot: float = 1 - fwhm: float = 4 - init_svd: Enum = Initsvd.NNDSVD - collapse: Enum = Collapse.MEDIAN - full_output: bool = False - verbose: bool = True - cube_sig: np.ndarray = None - handle_neg: Enum = HandleNeg.MASK - nmf_args: dict = field(default_factory=lambda: {}) - - -def nmf(algo_params: NMFParams = None, **all_kwargs): - """Non Negative Matrix Factorization [LEE99]_ for ADI sequences [GOM17]_. - Alternative to the full-frame ADI-PCA processing that does not rely on SVD - or ED for obtaining a low-rank approximation of the datacube. This function - embeds the scikit-learn NMF algorithm solved through either the coordinate - descent or the multiplicative update method. - - Parameters - ---------- - algo_params: NMFParams - Dataclass retaining all the needed parameters for NMF full-frame. - rot_options: dictionary, optional - Dictionary with optional keyword values for "nproc", "imlib", - "interpolation, "border_mode", "mask_val", "edge_blend", - "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -148,8 +152,15 @@ def nmf(algo_params: NMFParams = None, **all_kwargs): class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=NMFParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = NMFParams(**class_params) + algo_params = NMFParams(*all_args, **class_params) array = algo_params.cube.copy() if algo_params.verbose: diff --git a/vip_hci/psfsub/nmf_local.py b/vip_hci/psfsub/nmf_local.py index 20b1c429..af6a4fe0 100644 --- a/vip_hci/psfsub/nmf_local.py +++ b/vip_hci/psfsub/nmf_local.py @@ -17,19 +17,69 @@ from ..preproc.derotation import _find_indices_adi, _define_annuli from ..var import get_annulus_segments, matrix_scaling from ..var.object_utils import setup_parameters, separate_kwargs_dict -from ..var.paramenum import Initsvd, Imlib, Interpolation, HandleNeg, Collapse +from ..var.paramenum import Initsvd, Imlib, Interpolation, HandleNeg, Collapse, ALGO_KEY from ..config import timing, time_ini from ..config.utils_conf import pool_map, iterable -# TODO: update the doc of the params (some are missing) @dataclass class NMFAnnParams: """ Set of parameters for the NMF annular algorithm. + See function `nmf_annular` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + cube_ref: np.ndarray = None + radius_int: int = 0 + fwhm: float = 4 + asize: int = 4 + n_segments: int = 1 + delta_rot: Union[float, Tuple[float]] = (0.1, 1) + ncomp: int = 1 + init_svd: Enum = Initsvd.NNDSVD + nproc: int = 1 + min_frames_lib: int = 2 + max_frames_lib: int = 200 + scaling: Enum = None + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + full_output: bool = False + verbose: bool = True + theta_init: float = 0 + weights: List = None + cube_sig: np.ndarray = None + handle_neg: Enum = HandleNeg.MASK + max_iter: int = 1000 + random_state: int = None + nmf_args: dict = field(default_factory=lambda: {}) + + +# TODO: update the doc of the params (some are missing) +def nmf_annular(*all_args: List, **all_kwargs: dict): + """Non Negative Matrix Factorization in concentric annuli, for ADI/RDI + sequences. Alternative to the annular ADI-PCA processing that does not rely + on SVD or ED for obtaining a low-rank approximation of the datacube. + This function embeds the scikit-learn NMF algorithm solved through either + the coordinate descent or the multiplicative update method. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the NMF annular algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a NMFAnnParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a NMFAnnParams named as + `algo_params`.. + + NMF annular parameters + ---------- cube : numpy ndarray, 3d Input cube. angle_list : numpy ndarray, 1d @@ -112,52 +162,6 @@ class NMFAnnParams: nmf_args: dictionary, optional Additional arguments for scikit-learn NMF algorithm. See: https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.NMF.html - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - cube_ref: np.ndarray = None - radius_int: int = 0 - fwhm: float = 4 - asize: int = 4 - n_segments: int = 1 - delta_rot: Union[float, Tuple[float]] = (0.1, 1) - ncomp: int = 1 - init_svd: Enum = Initsvd.NNDSVD - nproc: int = 1 - min_frames_lib: int = 2 - max_frames_lib: int = 200 - scaling: Enum = None - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - full_output: bool = False - verbose: bool = True - theta_init: float = 0 - weights: List = None - cube_sig: np.ndarray = None - handle_neg: Enum = HandleNeg.MASK - max_iter: int = 1000 - random_state: int = None - nmf_args: dict = field(default_factory=lambda: {}) - - -def nmf_annular(algo_params: NMFAnnParams = None, **all_kwargs): - """Non Negative Matrix Factorization in concentric annuli, for ADI/RDI - sequences. Alternative to the annular ADI-PCA processing that does not rely - on SVD or ED for obtaining a low-rank approximation of the datacube. - This function embeds the scikit-learn NMF algorithm solved through either - the coordinate descent or the multiplicative update method. - - Parameters - ---------- - algo_params: NMFAnnParams - Dataclass retaining all the needed parameters for NMF annular. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "imlib", "interpolation, - "border_mode", "mask_val", "edge_blend", "interp_zeros", "ker" (see - documentation of ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -170,8 +174,15 @@ def nmf_annular(algo_params: NMFAnnParams = None, **all_kwargs): class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=NMFAnnParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = NMFAnnParams(**class_params) + algo_params = NMFAnnParams(*all_args, **class_params) if algo_params.verbose: global start_time diff --git a/vip_hci/psfsub/pca_fullfr.py b/vip_hci/psfsub/pca_fullfr.py index c65390b9..3a167122 100644 --- a/vip_hci/psfsub/pca_fullfr.py +++ b/vip_hci/psfsub/pca_fullfr.py @@ -44,12 +44,12 @@ import numpy as np from multiprocessing import cpu_count -from typing import Tuple, Union +from typing import Tuple, Union, List from dataclasses import dataclass from enum import Enum from .svd import svd_wrapper, SVDecomposer from .utils_pca import pca_incremental, pca_grid -from ..var.paramenum import SvdMode, Adimsdi, Interpolation, Imlib, Collapse +from ..var.paramenum import SvdMode, Adimsdi, Interpolation, Imlib, Collapse, ALGO_KEY from ..preproc.derotation import _find_indices_adi, _compute_pa_thresh from ..preproc import cube_rescaling_wavelengths as scwave from ..preproc import ( @@ -79,8 +79,75 @@ class PCAParams: """ Set of parameters for the PCA module. + See function `pca` below for the documentation. + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + cube_ref: np.ndarray = None + scale_list: np.ndarray = None + ncomp: Union[Tuple, float, int] = 1 + svd_mode: Enum = SvdMode.LAPACK + scaling: Enum = None + mask_center_px: int = None + source_xy: Tuple[int] = None + delta_rot: int = None + fwhm: float = 4 + adimsdi: Enum = Adimsdi.SINGLE + crop_ifs: bool = True + imlib: Enum = Imlib.VIPFFT + imlib2: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + collapse_ifs: Enum = Collapse.MEAN + ifs_collapse_range: Union[str, Tuple[int]] = "all" + mask_rdi: np.ndarray = None + check_memory: bool = True + batch: Union[int, float] = None + nproc: int = 1 + full_output: bool = False + verbose: bool = True + weights: np.ndarray = None + left_eigv: bool = False + cube_sig: np.ndarray = None + + +def pca(*all_args: List, **all_kwargs: dict): + """Full-frame PCA algorithm applied to PSF substraction. + + The reference PSF and the quasi-static speckle pattern are modeled using Principal + Component Analysis. Depending on the input parameters this PCA function can work in + ADI, RDI or mSDI (IFS data) mode. + + ADI: the target ``cube`` itself is used to learn the PCs and to obtain a + low-rank approximation model PSF (star + speckles). Both `cube_ref`` and + ``scale_list`` must be None. The full-frame ADI-PCA implementation is based + on [AMA12]_ and [SOU12]_. If ``batch`` is provided then the cube is processed + with incremental PCA as described in [GOM17]_. + + (ADI+)RDI: if a reference cube is provided (``cube_ref``), its PCs are used + to reconstruct the target frames to obtain the model PSF (star + speckles). + + (ADI+)mSDI (IFS data): if a scaling vector is provided (``scale_list``) and + the cube is a 4d array [# channels, # adi-frames, Y, X], it's assumed it + contains several multi-spectral frames acquired in pupil-stabilized mode. + A single or two stages PCA can be performed, depending on ``adimsdi``, as + explained in [CHR19]_. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the PCA algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a PCAParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a PCAParams named as + `algo_params`. + + PCA parameters + ---------- cube : str or numpy ndarray, 3d or 4d Input cube (ADI or ADI+mSDI). If 4D, the first dimension should be spectral. If a string is given, it must correspond to the path to the @@ -223,72 +290,6 @@ class PCAParams: cube_sig: numpy ndarray, opt Cube with estimate of significant authentic signals. If provided, this will subtracted before projecting cube onto reference cube. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - cube_ref: np.ndarray = None - scale_list: np.ndarray = None - ncomp: Union[Tuple, float, int] = 1 - svd_mode: Enum = SvdMode.LAPACK - scaling: Enum = None - mask_center_px: int = None - source_xy: Tuple[int] = None - delta_rot: int = None - fwhm: float = 4 - adimsdi: Enum = Adimsdi.SINGLE - crop_ifs: bool = True - imlib: Enum = Imlib.VIPFFT - imlib2: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - collapse_ifs: Enum = Collapse.MEAN - ifs_collapse_range: Union[str, Tuple[int]] = "all" - mask_rdi: np.ndarray = None - check_memory: bool = True - batch: Union[int, float] = None - nproc: int = 1 - full_output: bool = False - verbose: bool = True - weights: np.ndarray = None - left_eigv: bool = False - cube_sig: np.ndarray = None - - -def pca( - algo_params: PCAParams = None, - **all_kwargs: dict, -): - """Full-frame PCA algorithm applied to PSF substraction. - - The reference PSF and the quasi-static speckle pattern are modeled using Principal - Component Analysis. Depending on the input parameters this PCA function can work in - ADI, RDI or mSDI (IFS data) mode. - - ADI: the target ``cube`` itself is used to learn the PCs and to obtain a - low-rank approximation model PSF (star + speckles). Both `cube_ref`` and - ``scale_list`` must be None. The full-frame ADI-PCA implementation is based - on [AMA12]_ and [SOU12]_. If ``batch`` is provided then the cube is processed - with incremental PCA as described in [GOM17]_. - - (ADI+)RDI: if a reference cube is provided (``cube_ref``), its PCs are used - to reconstruct the target frames to obtain the model PSF (star + speckles). - - (ADI+)mSDI (IFS data): if a scaling vector is provided (``scale_list``) and - the cube is a 4d array [# channels, # adi-frames, Y, X], it's assumed it - contains several multi-spectral frames acquired in pupil-stabilized mode. - A single or two stages PCA can be performed, depending on ``adimsdi``, as - explained in [CHR19]_. - - Parameters - ---------- - algo_params: PCAParams - Dataclass retaining all the needed parameters for PCA. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Return ------- @@ -331,11 +332,20 @@ def pca( """ # Separating the parameters of the ParamsObject from the optionnal rot_options + print(f"kwargs : {all_kwargs}") + class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=PCAParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = PCAParams(**class_params) + algo_params = PCAParams(*all_args, **class_params) start_time = time_ini(algo_params.verbose) diff --git a/vip_hci/psfsub/pca_local.py b/vip_hci/psfsub/pca_local.py index 27bedfb6..21fe6298 100644 --- a/vip_hci/psfsub/pca_local.py +++ b/vip_hci/psfsub/pca_local.py @@ -28,7 +28,7 @@ from ..config import time_ini, timing from ..config.utils_conf import pool_map, iterable from ..var import get_annulus_segments, matrix_scaling -from ..var.paramenum import SvdMode, Imlib, Interpolation, Collapse +from ..var.paramenum import SvdMode, Imlib, Interpolation, Collapse, ALGO_KEY from ..var.object_utils import setup_parameters, separate_kwargs_dict from ..stats import descriptive_stats from .svd import get_eigenvectors @@ -41,8 +41,70 @@ class PCAAnnParams: """ Set of parameters for the annular PCA module. + + """ + + cube: np.ndarray = None + angle_list: np.ndarray = None + cube_ref: np.ndarray = None + scale_list: np.ndarray = None + radius_int: int = 0 + fwhm: float = 4 + asize: float = 4 + n_segments: Union[int, List[int], AUTO] = 1 + delta_rot: Union[float, Tuple[float]] = (0.1, 1) + delta_sep: Union[float, Tuple[float]] = (0.1, 1) + ncomp: Union[int, Tuple, np.ndarray, AUTO] = 1 + svd_mode: Enum = SvdMode.LAPACK + nproc: int = 1 + min_frames_lib: int = 2 + max_frames_lib: int = 200 + tol: float = 1e-1 + scaling: Enum = None + imlib: Enum = Imlib.VIPFFT + interpolation: Enum = Interpolation.LANCZOS4 + collapse: Enum = Collapse.MEDIAN + collapse_ifs: Enum = Collapse.MEAN + ifs_collapse_range: Union["all", Tuple[int]] = "all" + theta_init: int = 0 + weights: np.ndarray = None + cube_sig: np.ndarray = None + full_output: bool = False + verbose: bool = True + left_eigv: bool = False + + +def pca_annular(*all_args: List, **all_kwargs: dict): + """PCA model PSF subtraction for ADI, ADI+RDI or ADI+mSDI (IFS) data. + + The PCA model is computed locally in each annulus (or annular sectors according + to ``n_segments``). For each sector we discard reference frames taking into + account a parallactic angle threshold (``delta_rot``) and optionally a + radial movement threshold (``delta_sep``) for 4d cubes. + + For ADI+RDI data, it computes the principal components from the reference + library/cube, forcing pixel-wise temporal standardization. The number of + principal components can be automatically adjusted by the algorithm by + minimizing the residuals inside each patch/region. + + References: [AMA12]_ for PCA-ADI; [ABS13]_ for PCA-ADI in concentric annuli + considering a parallactic angle threshold; [CHR19]_ for PCA-ASDI and + PCA-SADI in one or two steps. + Parameters ---------- + all_args: list, optional + Positionnal arguments for the PCA annular algorithm. Full list of parameters + below. + all_kwargs: dictionary, optional + Mix of keyword arguments that can initialize a PCAAnnParams and the optional + 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", + "edge_blend", "interp_zeros", "ker" (see documentation of + ``vip_hci.preproc.frame_rotate``). Can also contain a PCAAnnParams named as + `algo_params`. + + PCA annular parameters + ---------- cube : numpy ndarray, 3d or 4d Input cube. angle_list : numpy ndarray, 1d @@ -143,64 +205,6 @@ class PCAAnnParams: cube_sig: numpy ndarray, opt Cube with estimate of significant authentic signals. If provided, this will be subtracted before projecting cube onto reference cube. - """ - - cube: np.ndarray = None - angle_list: np.ndarray = None - cube_ref: np.ndarray = None - scale_list: np.ndarray = None - radius_int: int = 0 - fwhm: float = 4 - asize: float = 4 - n_segments: Union[int, List[int], AUTO] = 1 - delta_rot: Union[float, Tuple[float]] = (0.1, 1) - delta_sep: Union[float, Tuple[float]] = (0.1, 1) - ncomp: Union[int, Tuple, np.ndarray, AUTO] = 1 - svd_mode: Enum = SvdMode.LAPACK - nproc: int = 1 - min_frames_lib: int = 2 - max_frames_lib: int = 200 - tol: float = 1e-1 - scaling: Enum = None - imlib: Enum = Imlib.VIPFFT - interpolation: Enum = Interpolation.LANCZOS4 - collapse: Enum = Collapse.MEDIAN - collapse_ifs: Enum = Collapse.MEAN - ifs_collapse_range: Union["all", Tuple[int]] = "all" - theta_init: int = 0 - weights: np.ndarray = None - cube_sig: np.ndarray = None - full_output: bool = False - verbose: bool = True - left_eigv: bool = False - - -def pca_annular(algo_params: PCAAnnParams = None, **all_kwargs): - """PCA model PSF subtraction for ADI, ADI+RDI or ADI+mSDI (IFS) data. - - The PCA model is computed locally in each annulus (or annular sectors according - to ``n_segments``). For each sector we discard reference frames taking into - account a parallactic angle threshold (``delta_rot``) and optionally a - radial movement threshold (``delta_sep``) for 4d cubes. - - For ADI+RDI data, it computes the principal components from the reference - library/cube, forcing pixel-wise temporal standardization. The number of - principal components can be automatically adjusted by the algorithm by - minimizing the residuals inside each patch/region. - - References: [AMA12]_ for PCA-ADI; [ABS13]_ for PCA-ADI in concentric annuli - considering a parallactic angle threshold; [CHR19]_ for PCA-ASDI and - PCA-SADI in one or two steps. - - Parameters - ---------- - algo_params: PCAAnnParams - Dataclass retaining all the needed parameters for annular PCA. - all_kwargs: dictionary, optional - Mix of the parameters that can initialize an algo_params and the optional - 'rot_options' dictionnary, with keyword values for "border_mode", "mask_val", - "edge_blend", "interp_zeros", "ker" (see documentation of - ``vip_hci.preproc.frame_rotate``) Returns ------- @@ -217,8 +221,15 @@ def pca_annular(algo_params: PCAAnnParams = None, **all_kwargs): class_params, rot_options = separate_kwargs_dict( initial_kwargs=all_kwargs, parent_class=PCAAnnParams ) + + # Extracting the object of parameters (if any) + algo_params = None + if ALGO_KEY in rot_options.keys(): + algo_params = rot_options[ALGO_KEY] + del rot_options[ALGO_KEY] + if algo_params is None: - algo_params = PCAAnnParams(**class_params) + algo_params = PCAAnnParams(*all_args, **class_params) global start_time start_time = time_ini() @@ -536,7 +547,7 @@ def _pca_adi_rdi( weights=None, cube_sig=None, left_eigv=False, - **rot_options + **rot_options, ): """PCA exploiting angular variability (ADI fashion).""" array = cube diff --git a/vip_hci/var/object_utils.py b/vip_hci/var/object_utils.py index 2f9e0b2c..d44779c3 100644 --- a/vip_hci/var/object_utils.py +++ b/vip_hci/var/object_utils.py @@ -5,6 +5,8 @@ import numpy as np +KWARGS_EXCEPTIONS = ["param"] + def filter_duplicate_keys(filter_item: any, ref_item: any, filter_in: bool = True): """ @@ -56,7 +58,7 @@ def setup_parameters( params_obj: object, fkt: Callable, as_list: bool = False, - show_params: bool = True, + show_params: bool = False, **add_params: dict, ) -> any: """ @@ -148,7 +150,7 @@ def separate_kwargs_dict(initial_kwargs: dict, parent_class: any) -> None: more_params = {} for key, value in initial_kwargs.items(): - if hasattr(parent_class, key): + if hasattr(parent_class, key) or key in KWARGS_EXCEPTIONS: class_params[key] = value else: more_params[key] = value diff --git a/vip_hci/var/paramenum.py b/vip_hci/var/paramenum.py index 6a5cb994..206f0f56 100644 --- a/vip_hci/var/paramenum.py +++ b/vip_hci/var/paramenum.py @@ -2,6 +2,8 @@ from enum import auto from enum import Enum +ALGO_KEY = "algo_params" + class SvdMode(str, Enum): """