Skip to content

line_metric

Compute a set of line metrics for every row of the gdf.

Parameters:

Name Type Description Default
gdf GeoDataFrame

The input GeoDataFrame.

required
metrics Tuple[str, ...]

A Tuple/List of line metrics.

required
normalize bool

Flag whether to column (quantile) normalize the computed metrics or not.

False
parallel bool

Flag whether to use parallel apply operations when computing the diversities.

True
num_processes int

The number of processes to use when parallel=True. If -1, this will use all available cores.

1
col_prefix str

Prefix for the new column names.

None
create_copy bool

Flag whether to create a copy of the input gdf or not.

True
Note

Allowed shape metrics are:

  • tortuosity
  • average_turning_angle
  • length
  • major_axis_len
  • minor_axis_len
  • major_axis_angle
  • minor_axis_angle

Raises:

Type Description
ValueError

If an illegal metric is given.

Returns:

Type Description
GeoDataFrame

gpd.GeoDataFrame: The input geodataframe with computed shape metric columns added.

Examples:

>>> from shapely.geometry import LineString
>>> from histolytics.spatial_geom.line_metrics import line_metric
>>> import geopandas as gpd
>>> lines = [
...     LineString([(i, i) for i in range(12)]),
...     LineString([(i, 0 if i % 2 == 0 else 1) for i in range(12)]),
...     LineString([(0, i) for i in range(12)]),
>>> ]
>>> gdf = gpd.GeoDataFrame(geometry=lines)
>>> gdf = line_metric(gdf, metrics=["tortuosity", "length"], parallel=True)
>>> print(gdf.head(3))
                                                geometry  tortuosity     length
    0  LINESTRING (0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6,...    1.000000  15.556349
    1  LINESTRING (0 0, 1 1, 2 0, 3 1, 4 0, 5 1, 6 0,...    1.408406  15.556349
    2  LINESTRING (0 0, 0 1, 0 2, 0 3, 0 4, 0 5, 0 6,...    1.000000  11.000000
Source code in src/histolytics/spatial_geom/line_metrics.py
def line_metric(
    gdf: gpd.GeoDataFrame,
    metrics: Tuple[str, ...],
    normalize: bool = False,
    parallel: bool = True,
    num_processes: int = 1,
    col_prefix: str = None,
    create_copy: bool = True,
) -> gpd.GeoDataFrame:
    """Compute a set of line metrics for every row of the gdf.

    Parameters:
        gdf (gpd.GeoDataFrame):
            The input GeoDataFrame.
        metrics (Tuple[str, ...]):
            A Tuple/List of line metrics.
        normalize (bool):
            Flag whether to column (quantile) normalize the computed metrics or not.
        parallel (bool):
            Flag whether to use parallel apply operations when computing the diversities.
        num_processes (int):
            The number of processes to use when parallel=True. If -1,
            this will use all available cores.
        col_prefix (str):
            Prefix for the new column names.
        create_copy (bool):
            Flag whether to create a copy of the input gdf or not.

    Note:
        Allowed shape metrics are:

        - `tortuosity`
        - `average_turning_angle`
        - `length`
        - `major_axis_len`
        - `minor_axis_len`
        - `major_axis_angle`
        - `minor_axis_angle`

    Raises:
        ValueError:
            If an illegal metric is given.

    Returns:
        gpd.GeoDataFrame:
            The input geodataframe with computed shape metric columns added.

    Examples:
        >>> from shapely.geometry import LineString
        >>> from histolytics.spatial_geom.line_metrics import line_metric
        >>> import geopandas as gpd
        >>> lines = [
        ...     LineString([(i, i) for i in range(12)]),
        ...     LineString([(i, 0 if i % 2 == 0 else 1) for i in range(12)]),
        ...     LineString([(0, i) for i in range(12)]),
        >>> ]
        >>> gdf = gpd.GeoDataFrame(geometry=lines)
        >>> gdf = line_metric(gdf, metrics=["tortuosity", "length"], parallel=True)
        >>> print(gdf.head(3))
                                                        geometry  tortuosity     length
            0  LINESTRING (0 0, 1 1, 2 2, 3 3, 4 4, 5 5, 6 6,...    1.000000  15.556349
            1  LINESTRING (0 0, 1 1, 2 0, 3 1, 4 0, 5 1, 6 0,...    1.408406  15.556349
            2  LINESTRING (0 0, 0 1, 0 2, 0 3, 0 4, 0 5, 0 6,...    1.000000  11.000000

    """
    if not isinstance(metrics, (list, tuple)):
        raise ValueError(f"`metrics` must be a list or tuple. Got: {type(metrics)}.")

    allowed = list(LINE_SHAPE_LOOKUP.keys())
    if not all(m in allowed for m in metrics):
        raise ValueError(
            f"Illegal metric in `metrics`. Got: {metrics}. Allowed metrics: {allowed}."
        )

    if create_copy:
        gdf = gdf.copy()

    if col_prefix is None:
        col_prefix = ""
    else:
        col_prefix += "_"

    met = list(metrics)
    if "length" in metrics:
        gdf[f"{col_prefix}length"] = gdf.length
        if normalize:
            gdf[f"{col_prefix}length"] = col_norm(gdf[f"{col_prefix}length"])
        met.remove("length")

    for metric in met:
        gdf[f"{col_prefix}{metric}"] = gdf_apply(
            gdf,
            LINE_SHAPE_LOOKUP[metric],
            columns=["geometry"],
            parallel=parallel,
            num_processes=num_processes,
        )
        if normalize:
            gdf[f"{col_prefix}{metric}"] = col_norm(gdf[f"{col_prefix}{metric}"])

    return gdf