Skip to content

Add dilation/erosion and morphological operation (open - close) #103

Description

@gjeanner

Je propose d’ajouter des opérations de dilatation/érosion et de morphologie simple (ouverture/fermeture) pour les objets portion.Interval. L’objectif est de permettre des extensions ou réductions d’intervalles directement via apply tout en restant cohérent avec l’esprit de portion.

Motivation :
Actuellement, il n’existe pas d’API native pour décaler facilement les bornes d’un intervalle de manière abstraite (temps, distance, etc.). L’implémentation proposée s’appuie sur apply, ce qui reste simple et performant.

Points clés :

  • L’érosion est obtenue via dilate_intervals avec magnitude < 0.
  • Ouverture/fermeture morphologique = deux dilatations successives.
  • Conserve la simplicité et la flexibilité de portion tout en ajoutant des fonctionnalités utiles.

Questions / discussion :

  • Serait-il pertinent d’ajouter ces méthodes directement à l’API Interval ?
    -Devrait-on prévoir des tests unitaires spécifiques pour ces nouvelles opérations ?
import portion as P
from portion import Interval, closed
from datetime import timedelta


##### Dilatation (et érosion via magnitude négative) #####
def _shift_interval(interval: Interval, magnitude: float, bound: str):
    """Décale un intervalle selon la magnitude et le côté choisi.

    Parameters
    ----------
    interval : Interval
        Intervalle à décaler.
    magnitude : float
        Quantité de décalage (abstraite, e.g., temps, distance...).
    bound : str
        Côté à décaler : 'both', 'low', ou 'high'.

    Returns
    -------
    Interval
        L'intervalle décalé.
    """
    if bound == "both":
        return closed(interval.lower - magnitude, interval.upper + magnitude)
    elif bound == "low":
        return closed(interval.lower - magnitude, interval.upper)
    elif bound == "high":
        return closed(interval.lower, interval.upper + magnitude)
    else:
        raise ValueError(f"Invalid value for 'bound': {bound!r}. Expected 'both', 'low', or 'high'.")


def dilate_intervals(intervals: Interval, magnitude: int | float, bound: str = "both") -> Interval:
    """Applique une dilatation (ou érosion si magnitude négative) à un ensemble d'intervalles.

    Parameters
    ----------
    intervals : Interval
        Collection d'intervalles à modifier.
    magnitude : float
        Quantité de dilatation (positive) ou d'érosion (négative).
    bound : str, optional
        Côté à modifier : 'both', 'low', ou 'high'. Par défaut 'both'.

    Returns
    -------
    Interval
        Les intervalles modifiés.
    """
    if not isinstance(magnitude, (int, float, timedelta)):
        raise TypeError(f"'magnitude' must be numeric, not {type(magnitude).__name__}.")
    return intervals.apply(lambda x: _shift_interval(x, magnitude, bound))


##### Opérations morphologiques simplifiées #####
def open_intervals(interval: Interval, magnitude: int | float) -> Interval:
    """Ouverture morphologique : érosion puis dilatation."""
    # open = dilate(dilate(interval, -magnitude), +magnitude)
    result = dilate_intervals(dilate_intervals(interval, -magnitude), +magnitude)
    valid_intervals = P.empty()
    for inter in result:
        if inter.lower < inter.upper:
            valid_intervals |= P.closed(inter.lower, inter.upper)
    return valid_intervals


def close_intervals(interval: Interval, magnitude: int | float) -> Interval:
    """Fermeture morphologique : dilatation puis érosion."""
    # close = dilate(dilate(interval, +magnitude), -magnitude)
    result = dilate_intervals(dilate_intervals(interval, +magnitude), -magnitude)
    valid_intervals = P.empty()
    for inter in result:
        if inter.lower < inter.upper:
            valid_intervals |= P.closed(inter.lower, inter.upper)
    return valid_intervals
 

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussionLet's discuss about it!enhancementNew feature or requestquestionIssue is a question

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions