"""
Image Cleaning Algorithms (identification of noisy pixels)
All algorithms return a boolean mask that is True for pixels surviving
the cleaning.
To get a zero-suppressed image and pixel
list, use ``image[mask], geom.pix_id[mask]``, or to keep the same
image size and just set unclean pixels to 0 or similar, use
``image[~mask] = 0``
"""
__all__ = [
    "tailcuts_clean",
    "bright_cleaning",
    "dilate",
    "mars_cleaning_1st_pass",
    "fact_image_cleaning",
    "apply_time_delta_cleaning",
    "apply_time_average_cleaning",
    "time_constrained_clean",
    "nsb_image_cleaning",
    "ImageCleaner",
    "TailcutsImageCleaner",
    "NSBImageCleaner",
    "MARSImageCleaner",
    "FACTImageCleaner",
    "TimeConstrainedImageCleaner",
]
from abc import abstractmethod
import numpy as np
from ctapipe.image.statistics import n_largest
from ..containers import CameraMonitoringContainer
from ..core import TelescopeComponent
from ..core.traits import (
    BoolTelescopeParameter,
    FloatTelescopeParameter,
    IntTelescopeParameter,
)
from .morphology import brightest_island, largest_island, number_of_islands
[docs]
def tailcuts_clean(
    geom,
    image,
    picture_thresh=7,
    boundary_thresh=5,
    keep_isolated_pixels=False,
    min_number_picture_neighbors=0,
):
    """Clean an image by selection pixels that pass a two-threshold
    tail-cuts procedure.  The picture and boundary thresholds are
    defined with respect to the pedestal dispersion. All pixels that
    have a signal higher than the picture threshold will be retained,
    along with all those above the boundary threshold that are
    neighbors of a picture pixel.
    To include extra neighbor rows of pixels beyond what are accepted, use the
    `ctapipe.image.dilate` function.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    image : np.ndarray
        pixel charges
    picture_thresh : float | np.ndarray
        threshold above which all pixels are retained
    boundary_thresh : float | np.ndarray
        threshold above which pixels are retained if they have a neighbor
        already above the picture_thresh
    keep_isolated_pixels : bool
        If True, pixels above the picture threshold will be included always,
        if not they are only included if a neighbor is in the picture or
        boundary
    min_number_picture_neighbors : int
        A picture pixel survives cleaning only if it has at least this number
        of picture neighbors. This has no effect in case keep_isolated_pixels is True
    Returns
    -------
    A boolean mask of selected pixels.
    """
    pixels_above_picture = image >= picture_thresh
    if keep_isolated_pixels or min_number_picture_neighbors == 0:
        pixels_in_picture = pixels_above_picture
    else:
        # Require at least min_number_picture_neighbors. Otherwise, the pixel
        #  is not selected
        number_of_neighbors_above_picture = geom.neighbor_matrix_sparse.dot(
            pixels_above_picture.view(np.byte)
        )
        pixels_in_picture = pixels_above_picture & (
            number_of_neighbors_above_picture >= min_number_picture_neighbors
        )
    # by broadcasting together pixels_in_picture (1d) with the neighbor
    # matrix (2d), we find all pixels that are above the boundary threshold
    # AND have any neighbor that is in the picture
    pixels_above_boundary = image >= boundary_thresh
    pixels_with_picture_neighbors = geom.neighbor_matrix_sparse.dot(pixels_in_picture)
    if keep_isolated_pixels:
        return (
            pixels_above_boundary & pixels_with_picture_neighbors
        ) | pixels_in_picture
    else:
        pixels_with_boundary_neighbors = geom.neighbor_matrix_sparse.dot(
            pixels_above_boundary
        )
        return (pixels_above_boundary & pixels_with_picture_neighbors) | (
            pixels_in_picture & pixels_with_boundary_neighbors
        ) 
[docs]
def bright_cleaning(image, threshold, fraction, n_pixels=3):
    """
    Clean an image by removing pixels below a fraction of the mean charge
    in the ``n_pixels`` brightest pixels.
    No pixels are removed instead if the mean charge of the brightest pixels
    are below a certain threshold.
    Parameters
    ----------
    image : np.ndarray
        pixel charges
    threshold : float
        Minimum average charge in the ``n_pixels`` brightest pixels to apply
        cleaning
    fraction : float
        Pixels below fraction * (average charge in the ``n_pixels`` brightest pixels)
        will be removed from the cleaned image
    n_pixels : int
        Consider this number of the brightest pixels to calculate the mean charge
    Returns
    -------
    A boolean mask of selected pixels.
    """
    mean_brightest_signal = np.mean(n_largest(n_pixels, image))
    if mean_brightest_signal < threshold:
        return np.ones(image.shape, dtype=bool)
    threshold_brightest = fraction * mean_brightest_signal
    mask = image >= threshold_brightest
    return mask 
[docs]
def mars_cleaning_1st_pass(
    geom,
    image,
    picture_thresh=7,
    boundary_thresh=5,
    keep_isolated_pixels=False,
    min_number_picture_neighbors=0,
):
    """
    Clean an image by selecting pixels that pass a three-threshold tail-cuts
    procedure.
    All thresholds are defined with respect to the pedestal
    dispersion. All pixels that have a signal higher than the core (picture)
    threshold will be retained, along with all those above the boundary
    threshold that are neighbors of a core pixel AND all those above
    the boundary threshold that are neighbors of a neighbor of a core pixel.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    image : np.ndarray
        pixel charges
    picture_thresh : float
        threshold above which all pixels are retained
    boundary_thresh : float
        threshold above which pixels are retained if
        they have a neighbor already above the ``picture_thresh``; it is then
        reapplied iteratively to the neighbor of the neighbor
    keep_isolated_pixels : bool
        If True, pixels above the picture threshold will be included always,
        if not they are only included if a neighbor is in the picture or
        boundary
    min_number_picture_neighbors : int
        A picture pixel survives cleaning only if it has at least this number
        of picture neighbors. This has no effect in case ``keep_isolated_pixels`` is True
    Returns
    -------
    A boolean mask of selected pixels.
    """
    pixels_from_tailcuts_clean = tailcuts_clean(
        geom,
        image,
        picture_thresh,
        boundary_thresh,
        keep_isolated_pixels,
        min_number_picture_neighbors,
    )  # this selects any core pixel and any of its first neighbors
    # At this point we don't know yet which ones should be kept.
    # In principle, the pixel thresholds should be hierarchical from core to
    # boundaries (this should be true for every type of particle triggering
    # the image), so we can just check which pixels have more than
    # boundary_thresh photo-electrons in the same image, but starting from
    # the mask we got from 'tailcuts_clean'.
    pixels_above_2nd_boundary = image >= boundary_thresh
    # and now it's the same as the last part of 'tailcuts_clean', but without
    # the core pixels, i.e. we start from the neighbors of the core pixels.
    pixels_with_previous_neighbors = geom.neighbor_matrix_sparse.dot(
        pixels_from_tailcuts_clean
    )
    if keep_isolated_pixels:
        return (
            pixels_above_2nd_boundary & pixels_with_previous_neighbors
        ) | pixels_from_tailcuts_clean
    else:
        pixels_with_2ndboundary_neighbors = geom.neighbor_matrix_sparse.dot(
            pixels_above_2nd_boundary
        )
        return (pixels_above_2nd_boundary & pixels_with_previous_neighbors) | (
            pixels_from_tailcuts_clean & pixels_with_2ndboundary_neighbors
        ) 
[docs]
def dilate(geom, mask):
    """
    Add one row of neighbors to the true values of a pixel mask and return
    the new mask.
    This can be used to include extra rows of pixels in a mask that was
    pre-computed, e.g. via `tailcuts_clean`.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    mask : np.ndarray
        input mask (array of booleans) to be dilated
    """
    return mask | geom.neighbor_matrix_sparse.dot(mask) 
[docs]
def apply_time_delta_cleaning(
    geom, mask, arrival_times, min_number_neighbors, time_limit
):
    """
    Identify all pixels from selection that have less than N
    neighbors that arrived within a given timeframe.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    mask : np.ndarray
        boolean mask of selected pixels before `apply_time_delta_cleaning`
    arrival_times : np.ndarray
        pixel timing information
    min_number_neighbors : int
        a selected pixel needs at least this number of (already selected) neighbors
        that arrived within a given time_limit to itself to survive the cleaning.
    time_limit : int | float
        arrival time limit for neighboring pixels
    Returns
    -------
    A boolean mask of selected pixels.
    """
    pixels_to_keep = mask.copy()
    time_diffs = np.abs(arrival_times[mask, None] - arrival_times)
    # neighboring pixels arriving in the time limit and previously selected
    valid_neighbors = (time_diffs < time_limit) & geom.neighbor_matrix[mask] & mask
    enough_neighbors = np.count_nonzero(valid_neighbors, axis=1) >= min_number_neighbors
    pixels_to_keep[pixels_to_keep] &= enough_neighbors
    return pixels_to_keep 
[docs]
def apply_time_average_cleaning(
    geom, mask, image, arrival_times, picture_thresh, time_limit
):
    """
    Extract all pixels that arrived within a given timeframe
    with respect to the time average of the pixels on the main island.
    In order to avoid removing signal pixels of large impact-parameter events,
    the time limit for bright pixels is doubled.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    mask : np.ndarray
        boolean mask of selected pixels before `apply_time_delta_cleaning`
    image : np.ndarray
        pixel charges
    arrival_times : np.ndarray
        pixel timing information
    picture_thresh : float
        threshold above which ``time_limit`` is extended twice its value
    time_limit : int | float
        arrival time limit w.r.t. the average time of the core pixels
    Returns
    -------
    A boolean mask of selected pixels.
    """
    mask = mask.copy()
    if np.count_nonzero(mask) > 0:
        # use main island (maximum charge) for time average calculation
        n_islands, island_labels = number_of_islands(geom, mask)
        mask_main = brightest_island(n_islands, island_labels, image)
        time_ave = np.average(arrival_times[mask_main], weights=image[mask_main] ** 2)
        time_diffs = np.abs(arrival_times[mask] - time_ave)
        time_limit_pixwise = np.where(
            image < (2 * picture_thresh), time_limit, time_limit * 2
        )[mask]
        mask[mask] &= time_diffs < time_limit_pixwise
    return mask 
[docs]
def fact_image_cleaning(
    geom,
    image,
    arrival_times,
    picture_threshold=4,
    boundary_threshold=2,
    min_number_neighbors=2,
    time_limit=5,
):
    """Clean an image by selection pixels that pass the fact cleaning procedure.
    Cleaning contains the following steps:
    1: Find pixels containing more photons than a threshold t1
    2: Remove pixels with less than N neighbors
    3: Add neighbors of the remaining pixels that are above a lower threshold t2
    4: Remove pixels with less than N neighbors arriving within a given timeframe
    5: Remove pixels with less than N neighbors
    6: Remove pixels with less than N neighbors arriving within a given timeframe
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    image : np.ndarray
        pixel charges
    arrival_times : np.ndarray
        pixel timing information
    picture_threshold : float | np.ndarray
        threshold above which all pixels are retained
    boundary_threshold : float | np.ndarray
        threshold above which pixels are retained if they have a neighbor
        already above the ``picture_thresh``
    min_number_neighbors : int
        Threshold to determine if a pixel survives cleaning steps.
        These steps include checks of neighbor arrival time and value
    time_limit : int | float
        arrival time limit for neighboring pixels
    Returns
    -------
    A boolean mask of selected pixels.
    References
    ----------
    See :cite:p:`phd-temme` and for implementation :cite:p:`fact-tools`.
    """
    # Step 1
    pixels_to_keep = image >= picture_threshold
    # Step 2
    number_of_neighbors_above_picture = geom.neighbor_matrix_sparse.dot(
        (pixels_to_keep).view(np.byte)
    )
    pixels_to_keep = pixels_to_keep & (
        number_of_neighbors_above_picture >= min_number_neighbors
    )
    # Step 3
    pixels_above_boundary = image >= boundary_threshold
    pixels_to_keep = dilate(geom, pixels_to_keep) & pixels_above_boundary
    # nothing else to do if min_number_neighbors <= 0
    if min_number_neighbors <= 0:
        return pixels_to_keep
    # Step 4
    pixels_to_keep = apply_time_delta_cleaning(
        geom, pixels_to_keep, arrival_times, min_number_neighbors, time_limit
    )
    # Step 5
    number_of_neighbors = geom.neighbor_matrix_sparse.dot(
        (pixels_to_keep).view(np.byte)
    )
    pixels_to_keep = pixels_to_keep & (number_of_neighbors >= min_number_neighbors)
    # Step 6
    pixels_to_keep = apply_time_delta_cleaning(
        geom, pixels_to_keep, arrival_times, min_number_neighbors, time_limit
    )
    return pixels_to_keep 
[docs]
def time_constrained_clean(
    geom,
    image,
    arrival_times,
    picture_thresh=7,
    boundary_thresh=5,
    time_limit_core=4.5,
    time_limit_boundary=1.5,
    min_number_picture_neighbors=1,
):
    """
    Time constrained cleaning by MAGIC
    Cleaning contains the following steps:
    - Find core pixels (containing more photons than a picture threshold)
    - Remove pixels with less than N neighbors
    - Keep core pixels whose arrival times are within a time limit of the average time
    - Find boundary pixels (containing more photons than a boundary threshold)
    - Remove pixels with less than N neighbors arriving within a given timeframe
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    image : np.ndarray
        pixel charges
    arrival_times : np.ndarray
        pixel timing information
    picture_threshold : float | np.ndarray
        threshold above which all pixels are retained
    boundary_threshold : float | np.ndarray
        threshold above which pixels are retained if they have a neighbor
        already above the ``picture_thresh``
    time_limit_core : int | float
        arrival time limit of core pixels w.r.t the average time
    time_limit_boundary : int | float
        arrival time limit of boundary pixels w.r.t neighboring core pixels
    min_number_neighbors : int
        Threshold to determine if a pixel survives cleaning steps.
        These steps include checks of neighbor arrival time and value
    Returns
    -------
    A boolean mask of selected pixels.
    """
    # find core pixels that pass a picture threshold
    pixels_above_picture = image >= picture_thresh
    # require at least min_number_picture_neighbors
    number_of_neighbors_above_picture = geom.neighbor_matrix_sparse.dot(
        pixels_above_picture.view(np.byte)
    )
    pixels_in_picture = pixels_above_picture & (
        number_of_neighbors_above_picture >= min_number_picture_neighbors
    )
    # keep core pixels whose arrival times are within a certain time limit of the average
    mask_core = apply_time_average_cleaning(
        geom, pixels_in_picture, image, arrival_times, picture_thresh, time_limit_core
    )
    # find boundary pixels that pass a boundary threshold
    pixels_above_boundary = image >= boundary_thresh
    pixels_with_picture_neighbors = geom.neighbor_matrix_sparse.dot(mask_core)
    mask_boundary = (pixels_above_boundary & pixels_with_picture_neighbors) & np.invert(
        mask_core
    )
    # keep boundary pixels whose arrival times are within a certain time limit of the neighboring core pixels
    mask_boundary = mask_boundary.copy()
    time_diffs = np.abs(arrival_times[mask_boundary, None] - arrival_times)
    valid_neighbors = (
        (time_diffs < time_limit_boundary)
        & geom.neighbor_matrix[mask_boundary]
        & mask_core
    )
    enough_neighbors = (
        np.count_nonzero(valid_neighbors, axis=1) >= min_number_picture_neighbors
    )
    mask_boundary[mask_boundary] &= enough_neighbors
    return mask_core | mask_boundary 
[docs]
def nsb_image_cleaning(
    geom,
    image,
    arrival_times,
    picture_thresh_min=8,
    boundary_thresh=4,
    min_number_picture_neighbors=2,
    keep_isolated_pixels=False,
    time_limit=None,
    time_num_neighbors=1,
    bright_cleaning_n_pixels=3,
    bright_cleaning_fraction=0.03,
    bright_cleaning_threshold=None,
    largest_island_only=False,
    pedestal_factor=2.5,
    pedestal_std=None,
):
    """
    Clean an image in 5 Steps:
    1) Get pixelwise picture thresholds for `tailcuts_clean` in step 2) from interleaved
       pedestal events if ``pedestal_std`` is not None.
    2) Apply `tailcuts_clean` algorithm.
    3) Apply `apply_time_delta_cleaning` algorithm if ``time_limit`` is not None.
    4) Apply `bright_cleaning` if ``bright_cleaning_threshold`` is not None.
    5) Get only `ctapipe.image.largest_island` if ``largest_island_only`` is
       set to true.
    Parameters
    ----------
    geom : `ctapipe.instrument.CameraGeometry`
        Camera geometry information
    image : np.ndarray
        Pixel charges
    arrival_times : np.ndarray
        Pixel timing information
    picture_thresh_min : float | np.ndarray
        Defines the minimum value used for the picture threshold for `tailcuts_clean`.
        The threshold used will be at least this value, or higher if ``pedestal_std``
        and ``pedestal_factor`` are set.
    boundary_thresh : float | np.ndarray
        Threshold above which pixels are retained if they have a neighbor
        already above the ``picture_thresh_min``. Used for `tailcuts_clean`.
    min_number_picture_neighbors : int
        A picture pixel survives cleaning only if it has at least this number
        of picture neighbors. This has no effect in case ``keep_isolated_pixels`` is True.
        Used for `tailcuts_clean`.
    keep_isolated_pixels : bool
        If True, pixels above the picture threshold will be included always,
        if not they are only included if a neighbor is in the picture or
        boundary. Used for `tailcuts_clean`.
    time_limit : float
        Time limit for the `apply_time_delta_cleaning`. Set to None if no
        `apply_time_delta_cleaning` should be applied.
    time_num_neighbors : int
        Used for `apply_time_delta_cleaning`.
        A selected pixel needs at least this number of (already selected) neighbors
        that arrived within a given ``time_limit`` to itself to survive this cleaning.
    bright_cleaning_n_pixels : int
        Consider this number of the brightest pixels for calculating the mean charge.
        Pixels below fraction * (mean charge in the ``bright_cleaning_n_pixels``
        brightest pixels) will be removed from the cleaned image. Set
        ``bright_cleaning_threshold`` to None if no `bright_cleaning` should be applied.
    bright_cleaning_fraction : float
        Fraction parameter for `bright_cleaning`. Pixels below
        fraction * (mean charge in the ``bright_cleaning_n_pixels`` brightest pixels)
        will be removed from the cleaned image. Set ``bright_cleaning_threshold`` to None
        if no `bright_cleaning` should be applied.
    bright_cleaning_threshold : float
        Threshold parameter for `bright_cleaning`. Minimum mean charge
        in the ``bright_cleaning_n_pixels`` brightest pixels to apply the cleaning.
        Set to None if no `bright_cleaning` should be applied.
    largest_island_only : bool
        Set to true to get only largest island.
    pedestal_factor : float
        Factor for interleaved pedestal cleaning. It is multiplied by the
        pedestal standard deviation for each pixel to calculate pixelwise picture
        threshold parameters for `tailcuts_clean` considering the current background.
        Has no effect if ``pedestal_std`` is set to None.
    pedestal_std : np.ndarray | None
        Pedestal standard deviation for each pixel. Used to calculate pixelwise picture
        threshold parameters for `tailcuts_clean` by multiplying it with ``pedestal_factor``
        and clip (limit) the product with ``picture_thresh_min``. If set to None, only
        ``picture_thresh_min`` is used to set the picture threshold for `tailcuts_clean`.
    Returns
    -------
    A boolean mask of selected pixels.
    """
    # Step 1
    picture_thresh = picture_thresh_min
    if pedestal_std is not None:
        pedestal_threshold = pedestal_std * pedestal_factor
        picture_thresh = np.clip(pedestal_threshold, picture_thresh_min, None)
    # Step 2
    mask = tailcuts_clean(
        geom,
        image,
        picture_thresh=picture_thresh,
        boundary_thresh=boundary_thresh,
        min_number_picture_neighbors=min_number_picture_neighbors,
        keep_isolated_pixels=keep_isolated_pixels,
    )
    # Check that at least one pixel survives tailcuts_clean
    if np.count_nonzero(mask) == 0:
        return mask
    # Step 3
    if time_limit is not None:
        mask = apply_time_delta_cleaning(
            geom,
            mask,
            arrival_times,
            min_number_neighbors=time_num_neighbors,
            time_limit=time_limit,
        )
    # Step 4
    if bright_cleaning_threshold is not None:
        mask &= bright_cleaning(
            image,
            bright_cleaning_threshold,
            bright_cleaning_fraction,
            bright_cleaning_n_pixels,
        )
    # Step 5
    if largest_island_only:
        _, island_labels = number_of_islands(geom, mask)
        mask = largest_island(island_labels)
    return mask 
[docs]
class ImageCleaner(TelescopeComponent):
    """
    Abstract class for all configurable Image Cleaning algorithms. Use
    ``ImageCleaner.from_name()`` to construct an instance of a particular algorithm
    """
[docs]
    @abstractmethod
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """
        Identify pixels with signal, and reject those with pure noise.
        Parameters
        ----------
        tel_id : int
            which telescope id in the subarray is being used (determines
            which cut is used)
        image : np.ndarray
            image pixel data corresponding to the camera geometry
        arrival_times : np.ndarray
            image of arrival time (not used in this method)
        monitoring : `ctapipe.containers.CameraMonitoringContainer`
            `ctapipe.containers.CameraMonitoringContainer` to make use of
            additional parameters from monitoring data e.g. sky pedestal std.
        Returns
        -------
        np.ndarray
            boolean mask of pixels passing cleaning
        """
        pass 
 
[docs]
class TailcutsImageCleaner(ImageCleaner):
    """
    Clean images using the standard picture/boundary technique. See
    `ctapipe.image.tailcuts_clean`.
    """
    picture_threshold_pe = FloatTelescopeParameter(
        default_value=10.0, help="top-level threshold in photoelectrons"
    ).tag(config=True)
    boundary_threshold_pe = FloatTelescopeParameter(
        default_value=5.0, help="second-level threshold in photoelectrons"
    ).tag(config=True)
    min_picture_neighbors = IntTelescopeParameter(
        default_value=2, help="Minimum number of neighbors above threshold to consider"
    ).tag(config=True)
    keep_isolated_pixels = BoolTelescopeParameter(
        default_value=False,
        help="If False, pixels with less neighbors than ``min_picture_neighbors`` are"
        "removed.",
    ).tag(config=True)
[docs]
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """
        Apply standard picture-boundary cleaning. See `ImageCleaner.__call__()`
        """
        return tailcuts_clean(
            self.subarray.tel[tel_id].camera.geometry,
            image,
            picture_thresh=self.picture_threshold_pe.tel[tel_id],
            boundary_thresh=self.boundary_threshold_pe.tel[tel_id],
            min_number_picture_neighbors=self.min_picture_neighbors.tel[tel_id],
            keep_isolated_pixels=self.keep_isolated_pixels.tel[tel_id],
        ) 
 
[docs]
class NSBImageCleaner(TailcutsImageCleaner):
    """
    Clean images based on lstchains image cleaning technique described in
    :cite:p:`lst1-crab-paper`. See `ctapipe.image.nsb_image_cleaning`.
    """
    time_limit = FloatTelescopeParameter(
        default_value=2,
        help="Time limit for the `apply_time_delta_cleaning`. Set to None if no"
        " `apply_time_delta_cleaning` should be applied.",
        allow_none=True,
    ).tag(config=True)
    time_num_neighbors = IntTelescopeParameter(
        default_value=1,
        help="Used for `apply_time_delta_cleaning`."
        " A selected pixel needs at least this number of (already selected) neighbors"
        " that arrived within a given `time_limit` to itself to survive this cleaning.",
    ).tag(config=True)
    bright_cleaning_n_pixels = IntTelescopeParameter(
        default_value=3,
        help="Consider this number of the brightest pixels for calculating the"
        " mean charge. Pixels below fraction * (mean charge in the"
        " ``bright_cleaning_n_pixels`` brightest pixels) will be removed from the"
        " cleaned image. Set ``bright_cleaning_threshold`` to None if no"
        " `bright_cleaning` should be applied.",
    ).tag(config=True)
    bright_cleaning_fraction = FloatTelescopeParameter(
        default_value=0.03,
        help="Fraction parameter for `bright_cleaning`. Pixels below"
        " fraction * (mean charge in the ``bright_cleaning_n_pixels`` brightest pixels)"
        " will be removed from the cleaned image. Set ``bright_cleaning_threshold`` to"
        " None if no `bright_cleaning` should be applied.",
    ).tag(config=True)
    bright_cleaning_threshold = FloatTelescopeParameter(
        default_value=267,
        help="Threshold parameter for `bright_cleaning`. Minimum mean charge"
        " in the ``bright_cleaning_n_pixels`` brightest pixels to apply the cleaning."
        " Set to None if no `bright_cleaning` should be applied.",
        allow_none=True,
    ).tag(config=True)
    largest_island_only = BoolTelescopeParameter(
        default_value=False, help="Set to true to get only largest island"
    ).tag(config=True)
    pedestal_factor = FloatTelescopeParameter(
        default_value=2.5,
        help="Factor for interleaved pedestal cleaning. It is multiplied by the"
        " pedestal standard deviation for each pixel to calculate pixelwise upper limit"
        " picture thresholds and clip them with ``picture_thresh_pe`` of"
        " `TailcutsImageCleaner` for `tailcuts_clean` considering the current background."
        " If no pedestal standard deviation is given, interleaved pedestal cleaning is"
        " not applied and ``picture_thresh_pe`` of `TailcutsImageCleaner` is used alone"
        " instead.",
    ).tag(config=True)
[docs]
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """
        Apply NSB image cleaning used by lstchain. See `ImageCleaner.__call__()`
        """
        pedestal_std = None
        if monitoring is not None:
            pedestal_std = monitoring.pixel_statistics.sky_pedestal_image.std
        return nsb_image_cleaning(
            self.subarray.tel[tel_id].camera.geometry,
            image,
            arrival_times,
            picture_thresh_min=self.picture_threshold_pe.tel[tel_id],
            boundary_thresh=self.boundary_threshold_pe.tel[tel_id],
            min_number_picture_neighbors=self.min_picture_neighbors.tel[tel_id],
            keep_isolated_pixels=self.keep_isolated_pixels.tel[tel_id],
            time_limit=self.time_limit.tel[tel_id],
            time_num_neighbors=self.time_num_neighbors.tel[tel_id],
            bright_cleaning_n_pixels=self.bright_cleaning_n_pixels.tel[tel_id],
            bright_cleaning_fraction=self.bright_cleaning_fraction.tel[tel_id],
            bright_cleaning_threshold=self.bright_cleaning_threshold.tel[tel_id],
            largest_island_only=self.largest_island_only.tel[tel_id],
            pedestal_factor=self.pedestal_factor.tel[tel_id],
            pedestal_std=pedestal_std,
        ) 
 
[docs]
class MARSImageCleaner(TailcutsImageCleaner):
    """
    1st-pass MARS-like Image cleaner (See `ctapipe.image.mars_cleaning_1st_pass`)
    """
[docs]
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """
        Apply MARS-style image cleaning. See `ImageCleaner.__call__()`
        """
        return mars_cleaning_1st_pass(
            self.subarray.tel[tel_id].camera.geometry,
            image,
            picture_thresh=self.picture_threshold_pe.tel[tel_id],
            boundary_thresh=self.boundary_threshold_pe.tel[tel_id],
            min_number_picture_neighbors=self.min_picture_neighbors.tel[tel_id],
            keep_isolated_pixels=False,
        ) 
 
[docs]
class FACTImageCleaner(TailcutsImageCleaner):
    """
    Clean images using the FACT technique. See `ctapipe.image.fact_image_cleaning`
    for algorithm details.
    """
    time_limit_ns = FloatTelescopeParameter(
        default_value=5.0, help="arrival time limit for neighboring pixels, in ns"
    ).tag(config=True)
[docs]
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """Apply FACT-style image cleaning. see ImageCleaner.__call__()"""
        return fact_image_cleaning(
            geom=self.subarray.tel[tel_id].camera.geometry,
            image=image,
            arrival_times=arrival_times,
            picture_threshold=self.picture_threshold_pe.tel[tel_id],
            boundary_threshold=self.boundary_threshold_pe.tel[tel_id],
            min_number_neighbors=self.min_picture_neighbors.tel[tel_id],
            time_limit=self.time_limit_ns.tel[tel_id],
        ) 
 
[docs]
class TimeConstrainedImageCleaner(TailcutsImageCleaner):
    """
    MAGIC-like Image cleaner with timing information (See
    `ctapipe.image.time_constrained_clean`).
    """
    time_limit_core_ns = FloatTelescopeParameter(
        default_value=4.5,
        help="arrival time limit for neighboring core pixels, in ns",
    ).tag(config=True)
    time_limit_boundary_ns = FloatTelescopeParameter(
        default_value=1.5,
        help="arrival time limit for neighboring boundary pixels, in ns",
    ).tag(config=True)
[docs]
    def __call__(
        self,
        tel_id: int,
        image: np.ndarray,
        arrival_times: np.ndarray = None,
        *,
        monitoring: CameraMonitoringContainer = None,
    ) -> np.ndarray:
        """
        Apply MAGIC-like image cleaning with timing information. See `ImageCleaner.__call__()`
        """
        return time_constrained_clean(
            self.subarray.tel[tel_id].camera.geometry,
            image,
            arrival_times=arrival_times,
            picture_thresh=self.picture_threshold_pe.tel[tel_id],
            boundary_thresh=self.boundary_threshold_pe.tel[tel_id],
            min_number_picture_neighbors=self.min_picture_neighbors.tel[tel_id],
            time_limit_core=self.time_limit_core_ns.tel[tel_id],
            time_limit_boundary=self.time_limit_boundary_ns.tel[tel_id],
        )