From 588a45c43dfb5461490b24ff6f06ecf60edb4881 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Sun, 17 Mar 2024 12:16:36 +0100 Subject: [PATCH 01/15] Add Gauss speed profile --- pedpy/methods/profile_calculator.py | 162 +++++++++++++++++++++++----- 1 file changed, 137 insertions(+), 25 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 29732240..20e252fe 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -10,6 +10,7 @@ Each of these grid cells is then used as a :class:`~geometry.MeasurementArea` in which the mean speed and density can be computed with different methods. """ + from enum import Enum, auto from typing import Any, List, Optional, Tuple @@ -68,6 +69,29 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods is the number of pedestrians inside :math:`P_M` (:math:`|P_M|`). """ + GAUSSIAN = auto() + r"""Compute Gaussian speed profile. + + In each cell the weighted speed :math:`s_{gaussian}` is calculated as + + .. math:: + + v_{c} = \frac{\sum_{i=1}^{N}{\big(w_i(\delta)\cdot v_i\big)}}{\sum_{i=1}^{N} w_i}, + + where :math:`v_i` is the speed of a pedestrian and :math:`w_i` are weights depending on + the pedestrian's distance :math:`\delta` from its position (:math:`\boldsymbol{r}_i`) to the center of the grid (:math:`\boldsymbol{c}`) cell: + + :math:`\delta = \boldsymbol{r}_i - \boldsymbol{c}`. + + The weights :math:`w_i` are calculated by a Gaussian as follows: + + .. math:: + + w_i = \frac{1} {\sigma \cdot \sqrt{2\pi}} \exp\big(-\frac{\delta^2}{2\sigma^2}\big), + + where :math: `\sigma` is derived from FWHM as: :math:`\sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}` + """ + class DensityMethod(Enum): # pylint: disable=too-few-public-methods """Method used to compute the density profile.""" @@ -392,39 +416,44 @@ def compute_speed_profile( speed_method: SpeedMethod, grid_intersections_area: Optional[npt.NDArray[np.float64]] = None, fill_value: float = np.nan, + gaussian_width: float = 0.5, ) -> List[npt.NDArray[np.float64]]: - """Compute the speed profile. + """Computes the speed profile for pedestrians within a specified walkable area using various methods. + + This function calculates speed profiles based on pedestrian speed data across a grid within a walkable area. + The method of computation can be selected among several options, including classic (:attr::`SpeedMethod.CLASSIC`), Gaussian(:attr:`SpeedMethod.GAUSSIAN`), Voronoi (`:attr:SpeedMethod.VORONOI`), + and arithmetic mean methods (`:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. Args: - data: Data from which the profiles are computes. - The DataFrame must contain a `frame` and a `speed` (result from - :func:`~speed_calculator.compute_individual_speed`) column. - For computing density profiles, it must contain a `polygon` column - (from :func:`~method_utils.compute_individual_voronoi_polygons`) - when using the `DensityMethod.VORONOI`. When computing the classic - density profile (`DensityMethod.CLASSIC`) the DataFrame needs to - contain the columns 'x' and 'y'. Computing the speed - profiles needs a `polygon` column (from - :func:`~method_utils.compute_individual_voronoi_polygons`) when - using the `SpeedMethod.VORONOI` or `SpeedMethod.ARITHMETIC`. + data: A pandas DataFrame containing `frame` and pedestrian `speed` (result from + :func:`~speed_calculator.compute_individual_speed`). + Depending on `speed_method`, additional columns 'x', 'y', or 'polygon' might be required. + When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) the DataFrame needs to contain the columns 'x' and 'y'. + + `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when + using the `:attr:SpeedMethod.VORONOI` or `:attr:`SpeedMethod.ARITHMETIC`. + For getting a DataFrame containing all the needed data, you can merge the results of the different function on the 'id' and - 'frame' columns (see :func:`pandas.DataFrame.merge` and - :func:`pandas.merge`). - walkable_area: geometry for which the profiles are - computed - grid_size: resolution of the grid used for computing the - profiles - speed_method: speed method used to compute the + 'frame' columns (see :func:`pandas.DataFrame.merge` and :func:`pandas.merge`). + + walkable_area: The geometric area within which the speed profiles are computed. + grid_size: The resolution of the grid used for computing the + profiles, expressed in the same units as the `walkable_area`. + speed_method: The speed method used to compute the speed profile - grid_intersections_area: intersection of grid cells with the Voronoi - polygons (result from - :func:`compute_grid_cell_polygon_intersection_area`) - fill_value: fill value for cells with no pedestrians inside when using - `SpeedMethod.MEAN` (default = `np.nan`) + grid_intersections_area: (Optional) intersection areas of grid cells with Voronoi polygons, required for some speed methods. + polygons (result from :func:`compute_grid_cell_polygon_intersection_area`) + fill_value: fill value for cells with no pedestrians inside when using `SpeedMethod.MEAN` (default = `np.nan`) + gaussian_width: (Optional) The full width at half maximum (FWHM) for Gaussian weights, required when using + :attr:`SpeedMethod.GAUSSIAN` (default = 0.5). Returns: - List of speed profiles + A list of NumPy arrays, each representing the speed profile for a different grid cell within the walkable area. + + Note: + The choice of `speed_method` significantly impacts the required data format and the interpretation of results. + Refer to the documentation of `:attr:SpeedMethod` for details on each method's requirements and use cases. """ grid_cells, rows, cols = get_grid_cells( walkable_area=walkable_area, grid_size=grid_size @@ -471,6 +500,16 @@ def compute_speed_profile( grid_size=grid_size, fill_value=fill_value, ) + elif speed_method == SpeedMethod.GAUSSIAN: + grid_center = np.vectorize(shapely.centroid)(grid_cells) + center_x = shapely.get_x(grid_center[:cols]) + center_y = shapely.get_y(grid_center[::cols]) + speed = _compute_gaussian_speed_profile( + frame_data=frame_data, + center_x=center_x, + center_y=center_y, + fwhm=gaussian_width, + ) else: raise ValueError("speed method not accepted") @@ -479,6 +518,79 @@ def compute_speed_profile( return speed_profiles +def _compute_gaussian_speed_profile( + *, + frame_data: pandas.DataFrame, + center_x: npt.NDArray[np.float64], + center_y: npt.NDArray[np.float64], + fwhm: float, +) -> npt.NDArray[np.float64]: + """Computes a Gaussian-weighted speed profile. + + For a set of pedestrian positions and speeds relative to a grid defined by center_x and center_y coordinates, + this function calculates the Euclidean distance from each grid center to each pedestrian position, + applies a Gaussian kernel to these distances using the specified full width at half maximum (FWHM), + normalizes these weights so that the sum across all agents for each grid cell equals 1, and then + calculates the weighted average speed at each grid cell based on these normalized weights. + + Args: + - frame_data (pd.DataFrame): A pandas DataFrame containing the columns 'x', 'y', and 'speed', + representing the x and y positions of the pedestrians and their speeds, respectively. + - center_x (npt.NDArray[np.float64]): A NumPy array of x-coordinates for the grid centers. + - center_y (npt.NDArray[np.float64]): A NumPy array of y-coordinates for the grid centers. + - fwhm (float): The full width at half maximum (FWHM) parameter for the Gaussian kernel, controlling + the spread of the Gaussian function. + + Returns: + - np.ndarray: A 2D NumPy array where each element represents the weighted average speed of pedestrians + at each grid cell, with the shape (len(center_x), len(center_y)). The array is transposed + to align with the expected grid orientation. + """ + + def _compute_gaussian_weights( + x: npt.NDArray[np.float64], fwhm: float + ) -> npt.NDArray[np.float64]: + """Computes the Gaussian density for given values and FWHM. + + The Gaussian density is defined as: + G(x) = 1 / (sigma * sqrt(2 * pi)) * exp(-x^2 / (2 * sigma^2)) + where sigma is derived from FWHM as: + sigma = FWHM / (2 * sqrt(2 * ln(2))) + + Args: + - x: Value(s) for which the Gaussian should be computed. + - fwhm: Full width at half maximum, a measure of spread. + + Returns: + - Gaussian density corresponding to the given values and FWHM. + """ + sigma = fwhm / (2 * np.sqrt(2 * np.log(2))) + return ( + 1 + / (sigma * np.sqrt(2 * np.pi)) + * np.exp(-(x**2) / (2 * sigma**2)) + ) + + # pedestrians' position and speed + positions_x = frame_data.x.values + positions_y = frame_data.y.values + speeds = frame_data.speed.values + # distance from each grid center coordinates to the pedestrian positions + distance_x = np.subtract.outer(center_x, positions_x) + distance_y = np.subtract.outer(center_y, positions_y) + distance_x_expanded = np.expand_dims(distance_x, axis=1) + distance_y_expanded = np.expand_dims(distance_y, axis=0) + # combine distances along x/y-axes into a single Euclidean distance + distance = np.sqrt(distance_x_expanded**2 + distance_y_expanded**2) + # calculate the Gaussian weights based on the distances and the given fwhm + weights = _compute_gaussian_weights(distance, fwhm) + # ensure that weights sum to 1 across all agents for each grid cell. + normalized_weights = weights / np.sum(weights, axis=2, keepdims=True) + # calculate the weighted speeds + weighted_speeds = np.tensordot(normalized_weights, speeds, axes=([2], [0])) + return np.array(weighted_speeds.T) + + def _compute_arithmetic_voronoi_speed_profile( *, frame_data: pandas.DataFrame, From c89e890daaa182b1f0afc5ca162811fe3ebbf529 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Sun, 17 Mar 2024 12:48:05 +0100 Subject: [PATCH 02/15] ENhance documentation of methode --- pedpy/methods/profile_calculator.py | 30 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 20e252fe..380a2cfe 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -72,7 +72,7 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods GAUSSIAN = auto() r"""Compute Gaussian speed profile. - In each cell the weighted speed :math:`s_{gaussian}` is calculated as + In each cell the weighted speed :math:`v_{c}` is calculated as .. math:: @@ -81,7 +81,8 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods where :math:`v_i` is the speed of a pedestrian and :math:`w_i` are weights depending on the pedestrian's distance :math:`\delta` from its position (:math:`\boldsymbol{r}_i`) to the center of the grid (:math:`\boldsymbol{c}`) cell: - :math:`\delta = \boldsymbol{r}_i - \boldsymbol{c}`. + .. math:: + \delta = \boldsymbol{r}_i - \boldsymbol{c}. The weights :math:`w_i` are calculated by a Gaussian as follows: @@ -89,7 +90,7 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods w_i = \frac{1} {\sigma \cdot \sqrt{2\pi}} \exp\big(-\frac{\delta^2}{2\sigma^2}\big), - where :math: `\sigma` is derived from FWHM as: :math:`\sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}` + where :math:`\sigma` is derived from FWHM as: :math:`\sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}` """ @@ -421,22 +422,19 @@ def compute_speed_profile( """Computes the speed profile for pedestrians within a specified walkable area using various methods. This function calculates speed profiles based on pedestrian speed data across a grid within a walkable area. - The method of computation can be selected among several options, including classic (:attr::`SpeedMethod.CLASSIC`), Gaussian(:attr:`SpeedMethod.GAUSSIAN`), Voronoi (`:attr:SpeedMethod.VORONOI`), - and arithmetic mean methods (`:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. + The method of computation can be selected among several options, including classic (:attr:`SpeedMethod.CLASSIC`), Gaussian (:attr:`SpeedMethod.GAUSSIAN`), Voronoi (:attr:`SpeedMethod.VORONOI`), + and arithmetic mean methods (:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. Args: data: A pandas DataFrame containing `frame` and pedestrian `speed` (result from :func:`~speed_calculator.compute_individual_speed`). Depending on `speed_method`, additional columns 'x', 'y', or 'polygon' might be required. - When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) the DataFrame needs to contain the columns 'x' and 'y'. - - `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when - using the `:attr:SpeedMethod.VORONOI` or `:attr:`SpeedMethod.ARITHMETIC`. - - For getting a DataFrame containing all the needed data, you can - merge the results of the different function on the 'id' and - 'frame' columns (see :func:`pandas.DataFrame.merge` and :func:`pandas.merge`). - + When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) the DataFrame needs to contain the columns 'x' and 'y'. + `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when + using the `:attr:SpeedMethod.VORONOI` or `:attr:`SpeedMethod.ARITHMETIC`. + For getting a DataFrame containing all the needed data, you can + merge the results of the different function on the 'id' and + 'frame' columns (see :func:`pandas.DataFrame.merge` and :func:`pandas.merge`). walkable_area: The geometric area within which the speed profiles are computed. grid_size: The resolution of the grid used for computing the profiles, expressed in the same units as the `walkable_area`. @@ -444,14 +442,14 @@ def compute_speed_profile( speed profile grid_intersections_area: (Optional) intersection areas of grid cells with Voronoi polygons, required for some speed methods. polygons (result from :func:`compute_grid_cell_polygon_intersection_area`) - fill_value: fill value for cells with no pedestrians inside when using `SpeedMethod.MEAN` (default = `np.nan`) + fill_value: fill value for cells with no pedestrians inside when using :attr:`SpeedMethod.MEAN` (default = `np.nan`) gaussian_width: (Optional) The full width at half maximum (FWHM) for Gaussian weights, required when using :attr:`SpeedMethod.GAUSSIAN` (default = 0.5). Returns: A list of NumPy arrays, each representing the speed profile for a different grid cell within the walkable area. - Note: + Note: The choice of `speed_method` significantly impacts the required data format and the interpretation of results. Refer to the documentation of `:attr:SpeedMethod` for details on each method's requirements and use cases. """ From 26d20c66961499c11714fc63a2ba39765da0f5f7 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Sun, 17 Mar 2024 13:22:06 +0100 Subject: [PATCH 03/15] Fixing typing of documentation --- pedpy/methods/profile_calculator.py | 436 +++++++++++++++++++++------- 1 file changed, 328 insertions(+), 108 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 380a2cfe..11d2dbdf 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -78,8 +78,10 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods v_{c} = \frac{\sum_{i=1}^{N}{\big(w_i(\delta)\cdot v_i\big)}}{\sum_{i=1}^{N} w_i}, - where :math:`v_i` is the speed of a pedestrian and :math:`w_i` are weights depending on - the pedestrian's distance :math:`\delta` from its position (:math:`\boldsymbol{r}_i`) to the center of the grid (:math:`\boldsymbol{c}`) cell: + where :math:`v_i` is the speed of a pedestrian and :math:`w_i` are weights + depending on the pedestrian's distance :math:`\delta` from its position + (:math:`\boldsymbol{r}_i`) to the center of the grid + (:math:`\boldsymbol{c}`) cell: .. math:: \delta = \boldsymbol{r}_i - \boldsymbol{c}. @@ -90,7 +92,9 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods w_i = \frac{1} {\sigma \cdot \sqrt{2\pi}} \exp\big(-\frac{\delta^2}{2\sigma^2}\big), - where :math:`\sigma` is derived from FWHM as: :math:`\sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}` + where :math:`\sigma` is derived from FWHM as: + ..math:: + \sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}. """ @@ -133,8 +137,10 @@ class DensityMethod(Enum): # pylint: disable=too-few-public-methods \rho_{gaussian} = \sum_{i=1}^{N}{\delta (\boldsymbol{r}_i - \boldsymbol{c})}, - where :math:`\boldsymbol{r}_i` is the position of a pedestrian and :math:`\boldsymbol{c}` - is the center of the grid cell. Finally :math:`\delta(x)` is approximated by a Gaussian + where :math:`\boldsymbol{r}_i` is the position of a pedestrian and + :math:`\boldsymbol{c}` + is the center of the grid cell. Finally :math:`\delta(x)` is approximated + by a Gaussian .. math:: @@ -151,9 +157,9 @@ def compute_profiles( speed_method: SpeedMethod, density_method: DensityMethod = DensityMethod.VORONOI, gaussian_width: Optional[float] = None, - # pylint: disable=unused-argument + # pylint: disable=unused-argument,too-many-arguments **kwargs: Any, -) -> Tuple[List[npt.NDArray[np.float64]], List[npt.NDArray[np.float64]]]: +) -> Tuple[List[npt.NDArray[np.float64]], List[npt.NDArray[np.float64]],]: """Computes the density and speed profiles. .. note:: @@ -194,12 +200,21 @@ def compute_profiles( Returns: List of density profiles, List of speed profiles """ - grid_cells, _, _ = get_grid_cells( - walkable_area=walkable_area, grid_size=grid_size + ( + grid_cells, + _, + _, + ) = get_grid_cells( + walkable_area=walkable_area, + grid_size=grid_size, ) - grid_intersections_area, internal_data = _compute_grid_polygon_intersection( - data=data, grid_cells=grid_cells + ( + grid_intersections_area, + internal_data, + ) = _compute_grid_polygon_intersection( + data=data, + grid_cells=grid_cells, ) density_profiles = compute_density_profile( @@ -219,7 +234,10 @@ def compute_profiles( grid_size=grid_size, ) - return density_profiles, speed_profiles + return ( + density_profiles, + speed_profiles, + ) def compute_density_profile( @@ -230,14 +248,14 @@ def compute_density_profile( density_method: DensityMethod, grid_intersections_area: Optional[npt.NDArray[np.float64]] = None, gaussian_width: Optional[float] = None, + # pylint: disable=too-many-arguments ) -> List[npt.NDArray[np.float64]]: """Compute the density profile. Args: data: Data from which the profiles are computes. The DataFrame must contain a `frame` column. It must contain - a `polygon` column (from - :func:`~method_utils.compute_individual_voronoi_polygons`) + a `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) when using the :attr:`DensityMethod.VORONOI`. When computing the classic density profile (:attr:`DensityMethod.CLASSIC`) or Gaussian density profile (:attr:`DensityMethod.GAUSSIAN`) the @@ -262,8 +280,13 @@ def compute_density_profile( Returns: List of density profiles """ - grid_cells, rows, cols = get_grid_cells( - walkable_area=walkable_area, grid_size=grid_size + ( + grid_cells, + rows, + cols, + ) = get_grid_cells( + walkable_area=walkable_area, + grid_size=grid_size, ) grid_center = np.vectorize(shapely.centroid)(grid_cells) @@ -273,7 +296,10 @@ def compute_density_profile( data_grouped_by_frame = data.groupby(FRAME_COL) density_profiles = [] - for frame, frame_data in data_grouped_by_frame: + for ( + frame, + frame_data, + ) in data_grouped_by_frame: if density_method == DensityMethod.VORONOI: if grid_intersections_area is None: raise RuntimeError( @@ -282,7 +308,8 @@ def compute_density_profile( ) grid_intersections_area_frame = grid_intersections_area[ - :, data_grouped_by_frame.indices[frame] + :, + data_grouped_by_frame.indices[frame], ] density = _compute_voronoi_density_profile( @@ -311,7 +338,12 @@ def compute_density_profile( else: raise ValueError("density method not accepted.") - density_profiles.append(density.reshape(rows, cols)) + density_profiles.append( + density.reshape( + rows, + cols, + ) + ) return density_profiles @@ -338,13 +370,35 @@ def _compute_classic_density_profile( walkable_area: WalkableArea, grid_size: float, ) -> npt.NDArray[np.float64]: - min_x, min_y, max_x, max_y = walkable_area.bounds - - x_coords = np.arange(min_x, max_x + grid_size, grid_size) - y_coords = np.arange(min_y, max_y + grid_size, grid_size) + ( + min_x, + min_y, + max_x, + max_y, + ) = walkable_area.bounds + + x_coords = np.arange( + min_x, + max_x + grid_size, + grid_size, + ) + y_coords = np.arange( + min_y, + max_y + grid_size, + grid_size, + ) - hist, _, _ = np.histogram2d( - x=frame_data.x, y=frame_data.y, bins=[x_coords, y_coords] + ( + hist, + _, + _, + ) = np.histogram2d( + x=frame_data.x, + y=frame_data.y, + bins=[ + x_coords, + y_coords, + ], ) hist = hist / (grid_size**2) @@ -362,7 +416,9 @@ def _compute_gaussian_density_profile( center_y: npt.NDArray[np.float64], width: float, ) -> npt.NDArray[np.float64]: - def _gaussian_full_width_half_maximum(width: float) -> float: + def _gaussian_full_width_half_maximum( + width: float, + ) -> float: """Computes the full width at half maximum. Fast lookup for: @@ -377,7 +433,8 @@ def _gaussian_full_width_half_maximum(width: float) -> float: return width * 0.6005612 def _compute_gaussian_density( - x: npt.NDArray[np.float64], fwhm: float + x: npt.NDArray[np.float64], + fwhm: float, ) -> npt.NDArray[np.float64]: """Computes the Gaussian density. @@ -397,15 +454,30 @@ def _compute_gaussian_density( positions_y = frame_data.y.values # distance from each grid center x/y coordinates to the pedestrian positions - distance_x = np.add.outer(-center_x, positions_x) - distance_y = np.add.outer(-center_y, positions_y) + distance_x = np.add.outer( + -center_x, + positions_x, + ) + distance_y = np.add.outer( + -center_y, + positions_y, + ) fwhm = _gaussian_full_width_half_maximum(width) - gauss_density_x = _compute_gaussian_density(distance_x, fwhm) - gauss_density_y = _compute_gaussian_density(distance_y, fwhm) + gauss_density_x = _compute_gaussian_density( + distance_x, + fwhm, + ) + gauss_density_y = _compute_gaussian_density( + distance_y, + fwhm, + ) - gauss_density = np.matmul(gauss_density_x, np.transpose(gauss_density_y)) + gauss_density = np.matmul( + gauss_density_x, + np.transpose(gauss_density_y), + ) return np.array(gauss_density.T) @@ -418,50 +490,79 @@ def compute_speed_profile( grid_intersections_area: Optional[npt.NDArray[np.float64]] = None, fill_value: float = np.nan, gaussian_width: float = 0.5, + # pylint: disable=too-many-arguments ) -> List[npt.NDArray[np.float64]]: - """Computes the speed profile for pedestrians within a specified walkable area using various methods. - - This function calculates speed profiles based on pedestrian speed data across a grid within a walkable area. - The method of computation can be selected among several options, including classic (:attr:`SpeedMethod.CLASSIC`), Gaussian (:attr:`SpeedMethod.GAUSSIAN`), Voronoi (:attr:`SpeedMethod.VORONOI`), - and arithmetic mean methods (:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. + """Computes the speed profile for pedestrians within a specified walkable + area using various methods. + + This function calculates speed profiles based on pedestrian speed data + across a grid within a walkable area. + The method of computation can be selected among several options, + including classic (:attr:`SpeedMethod.CLASSIC`), + Gaussian (:attr:`SpeedMethod.GAUSSIAN`), + Voronoi (:attr:`SpeedMethod.VORONOI`), + and arithmetic mean methods (:attr:`SpeedMethod.ARITHMETIC`), + each suitable for different analysis contexts. Args: - data: A pandas DataFrame containing `frame` and pedestrian `speed` (result from - :func:`~speed_calculator.compute_individual_speed`). - Depending on `speed_method`, additional columns 'x', 'y', or 'polygon' might be required. - When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) the DataFrame needs to contain the columns 'x' and 'y'. - `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when - using the `:attr:SpeedMethod.VORONOI` or `:attr:`SpeedMethod.ARITHMETIC`. + data: A pandas DataFrame containing `frame` and pedestrian `speed` + (result from :func:`~speed_calculator.compute_individual_speed`). + Depending on `speed_method`, additional columns 'x', 'y', or 'polygon' + might be required. + When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) + or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) + the DataFrame needs to contain the columns 'x' and 'y'. + `polygon` column + (from :func:`~method_utils.compute_individual_voronoi_polygons`) is + required when using the `:attr:SpeedMethod.VORONOI` or + `:attr:`SpeedMethod.ARITHMETIC`. For getting a DataFrame containing all the needed data, you can merge the results of the different function on the 'id' and - 'frame' columns (see :func:`pandas.DataFrame.merge` and :func:`pandas.merge`). - walkable_area: The geometric area within which the speed profiles are computed. + 'frame' columns (see :func:`pandas.DataFrame.merge` and + :func:`pandas.merge`). + walkable_area: The geometric area within which the speed profiles are + computed. grid_size: The resolution of the grid used for computing the profiles, expressed in the same units as the `walkable_area`. speed_method: The speed method used to compute the speed profile - grid_intersections_area: (Optional) intersection areas of grid cells with Voronoi polygons, required for some speed methods. - polygons (result from :func:`compute_grid_cell_polygon_intersection_area`) - fill_value: fill value for cells with no pedestrians inside when using :attr:`SpeedMethod.MEAN` (default = `np.nan`) - gaussian_width: (Optional) The full width at half maximum (FWHM) for Gaussian weights, required when using - :attr:`SpeedMethod.GAUSSIAN` (default = 0.5). + grid_intersections_area: (Optional) intersection areas of grid cells + with Voronoi polygons, required for some speed methods. + polygons (result from + :func:`compute_grid_cell_polygon_intersection_area`) + fill_value: fill value for cells with no pedestrians inside when + using :attr:`SpeedMethod.MEAN` (default = `np.nan`) + gaussian_width: (Optional) The full width at half maximum (FWHM) for + Gaussian weights, required when using :attr:`SpeedMethod.GAUSSIAN` + (default = 0.5). Returns: - A list of NumPy arrays, each representing the speed profile for a different grid cell within the walkable area. + A list of NumPy arrays, each representing the speed profile for a + different grid cell within the walkable area. Note: - The choice of `speed_method` significantly impacts the required data format and the interpretation of results. - Refer to the documentation of `:attr:SpeedMethod` for details on each method's requirements and use cases. + The choice of `speed_method` significantly impacts the required data + format and the interpretation of results. + Refer to the documentation of `:attr:SpeedMethod` for details on each + method's requirements and use cases. """ - grid_cells, rows, cols = get_grid_cells( - walkable_area=walkable_area, grid_size=grid_size + ( + grid_cells, + rows, + cols, + ) = get_grid_cells( + walkable_area=walkable_area, + grid_size=grid_size, ) data_grouped_by_frame = data.groupby(FRAME_COL) speed_profiles = [] - for frame, frame_data in data_grouped_by_frame: + for ( + frame, + frame_data, + ) in data_grouped_by_frame: if speed_method == SpeedMethod.VORONOI: if grid_intersections_area is None: raise RuntimeError( @@ -469,7 +570,8 @@ def compute_speed_profile( "`grid_intersections_area`." ) grid_intersections_area_frame = grid_intersections_area[ - :, data_grouped_by_frame.indices[frame] + :, + data_grouped_by_frame.indices[frame], ] speed = _compute_voronoi_speed_profile( @@ -484,7 +586,8 @@ def compute_speed_profile( "`grid_intersections_area`." ) grid_intersections_area_frame = grid_intersections_area[ - :, data_grouped_by_frame.indices[frame] + :, + data_grouped_by_frame.indices[frame], ] speed = _compute_arithmetic_voronoi_speed_profile( @@ -511,7 +614,12 @@ def compute_speed_profile( else: raise ValueError("speed method not accepted") - speed_profiles.append(speed.reshape(rows, cols)) + speed_profiles.append( + speed.reshape( + rows, + cols, + ) + ) return speed_profiles @@ -525,28 +633,36 @@ def _compute_gaussian_speed_profile( ) -> npt.NDArray[np.float64]: """Computes a Gaussian-weighted speed profile. - For a set of pedestrian positions and speeds relative to a grid defined by center_x and center_y coordinates, - this function calculates the Euclidean distance from each grid center to each pedestrian position, - applies a Gaussian kernel to these distances using the specified full width at half maximum (FWHM), - normalizes these weights so that the sum across all agents for each grid cell equals 1, and then - calculates the weighted average speed at each grid cell based on these normalized weights. + For a set of pedestrian positions and speeds relative to a grid defined by + center_x and center_y coordinates, this function calculates the Euclidean + distance from each grid center to each pedestrian position, applies a + Gaussian kernel to these distances using the specified full width at half + maximum (FWHM), normalizes these weights so that the sum across all agents + for each grid cell equals 1, and then calculates the weighted average + speed at each grid cell based on these normalized weights. Args: - - frame_data (pd.DataFrame): A pandas DataFrame containing the columns 'x', 'y', and 'speed', - representing the x and y positions of the pedestrians and their speeds, respectively. - - center_x (npt.NDArray[np.float64]): A NumPy array of x-coordinates for the grid centers. - - center_y (npt.NDArray[np.float64]): A NumPy array of y-coordinates for the grid centers. - - fwhm (float): The full width at half maximum (FWHM) parameter for the Gaussian kernel, controlling - the spread of the Gaussian function. + - frame_data (pd.DataFrame): A pandas DataFrame containing the columns + 'x', 'y', and 'speed', representing the x and + y positions of the pedestrians and their speeds + , respectively. + - center_x (npt.NDArray[np.float64]): A NumPy array of x-coordinates for the + grid centers. + - center_y (npt.NDArray[np.float64]): A NumPy array of y-coordinates for the + grid centers. + - fwhm (float): The full width at half maximum (FWHM) parameter for the + Gaussian kernel, controlling the spread of the Gaussian function. Returns: - - np.ndarray: A 2D NumPy array where each element represents the weighted average speed of pedestrians - at each grid cell, with the shape (len(center_x), len(center_y)). The array is transposed - to align with the expected grid orientation. + - np.ndarray: A 2D NumPy array where each element represents the weighted + average speed of pedestrians at each grid cell, with the shape + (len(center_x), len(center_y)). The array is transposed to align with + the expected grid orientation. """ def _compute_gaussian_weights( - x: npt.NDArray[np.float64], fwhm: float + x: npt.NDArray[np.float64], + fwhm: float, ) -> npt.NDArray[np.float64]: """Computes the Gaussian density for given values and FWHM. @@ -574,18 +690,44 @@ def _compute_gaussian_weights( positions_y = frame_data.y.values speeds = frame_data.speed.values # distance from each grid center coordinates to the pedestrian positions - distance_x = np.subtract.outer(center_x, positions_x) - distance_y = np.subtract.outer(center_y, positions_y) - distance_x_expanded = np.expand_dims(distance_x, axis=1) - distance_y_expanded = np.expand_dims(distance_y, axis=0) + distance_x = np.subtract.outer( + center_x, + positions_x, + ) + distance_y = np.subtract.outer( + center_y, + positions_y, + ) + distance_x_expanded = np.expand_dims( + distance_x, + axis=1, + ) + distance_y_expanded = np.expand_dims( + distance_y, + axis=0, + ) # combine distances along x/y-axes into a single Euclidean distance distance = np.sqrt(distance_x_expanded**2 + distance_y_expanded**2) # calculate the Gaussian weights based on the distances and the given fwhm - weights = _compute_gaussian_weights(distance, fwhm) + weights = _compute_gaussian_weights( + distance, + fwhm, + ) # ensure that weights sum to 1 across all agents for each grid cell. - normalized_weights = weights / np.sum(weights, axis=2, keepdims=True) + normalized_weights = weights / np.sum( + weights, + axis=2, + keepdims=True, + ) # calculate the weighted speeds - weighted_speeds = np.tensordot(normalized_weights, speeds, axes=([2], [0])) + weighted_speeds = np.tensordot( + normalized_weights, + speeds, + axes=( + [2], + [0], + ), + ) return np.array(weighted_speeds.T) @@ -605,12 +747,20 @@ def _compute_arithmetic_voronoi_speed_profile( Returns: Arithmetic mean speed per grid cell """ - cells_with_peds = np.where(grid_intersections_area > 1e-16, 1, 0) + cells_with_peds = np.where( + grid_intersections_area > 1e-16, + 1, + 0, + ) accumulated_speed = np.sum( - cells_with_peds * frame_data.speed.values, axis=1 + cells_with_peds * frame_data.speed.values, + axis=1, + ) + num_peds = np.count_nonzero( + cells_with_peds, + axis=1, ) - num_peds = np.count_nonzero(cells_with_peds, axis=1) speed = np.divide( accumulated_speed, num_peds, @@ -638,7 +788,10 @@ def _compute_voronoi_speed_profile( Voronoi speed per grid cell """ speed = ( - np.sum(grid_intersections_area * frame_data.speed.values, axis=1) + np.sum( + grid_intersections_area * frame_data.speed.values, + axis=1, + ) ) / grid_area return speed @@ -651,24 +804,56 @@ def _compute_mean_speed_profile( grid_size: float, fill_value: float, ) -> npt.NDArray[np.float64]: - min_x, min_y, max_x, max_y = walkable_area.bounds - - x_coords = np.arange(min_x, max_x + grid_size, grid_size) - y_coords = np.arange(min_y, max_y + grid_size, grid_size) + ( + min_x, + min_y, + max_x, + max_y, + ) = walkable_area.bounds + + x_coords = np.arange( + min_x, + max_x + grid_size, + grid_size, + ) + y_coords = np.arange( + min_y, + max_y + grid_size, + grid_size, + ) - hist, _, _ = np.histogram2d( - x=frame_data.x, y=frame_data.y, bins=[x_coords, y_coords] + ( + hist, + _, + _, + ) = np.histogram2d( + x=frame_data.x, + y=frame_data.y, + bins=[ + x_coords, + y_coords, + ], ) - hist_speed, _, _ = np.histogram2d( + ( + hist_speed, + _, + _, + ) = np.histogram2d( x=frame_data.x, y=frame_data.y, - bins=[x_coords, y_coords], + bins=[ + x_coords, + y_coords, + ], weights=frame_data.speed, ) speed = np.divide( hist_speed, hist, - out=np.full(shape=hist.shape, fill_value=float(fill_value)), + out=np.full( + shape=hist.shape, + fill_value=float(fill_value), + ), where=hist != 0, ) @@ -680,8 +865,10 @@ def _compute_mean_speed_profile( def compute_grid_cell_polygon_intersection_area( - *, data: pandas.DataFrame, grid_cells: npt.NDArray[shapely.Polygon] -) -> Tuple[npt.NDArray[np.float64], pandas.DataFrame]: + *, + data: pandas.DataFrame, + grid_cells: npt.NDArray[shapely.Polygon], +) -> Tuple[npt.NDArray[np.float64], pandas.DataFrame,]: """Computes the intersection area of the grid cells with the Voronoi polygons. .. note:: @@ -711,10 +898,17 @@ def compute_grid_cell_polygon_intersection_area( Tuple containing first the grid cell-polygon intersection areas, and second the reordered data by 'frame', which needs to be used in the next steps. """ - grid_intersections_area, used_data = _compute_grid_polygon_intersection( - data=data, grid_cells=grid_cells + ( + grid_intersections_area, + used_data, + ) = _compute_grid_polygon_intersection( + data=data, + grid_cells=grid_cells, + ) + return ( + grid_intersections_area, + used_data, ) - return grid_intersections_area, used_data def _compute_grid_polygon_intersection( @@ -728,16 +922,27 @@ def _compute_grid_polygon_intersection( grid_intersections_area = shapely.area( shapely.intersection( - np.array(grid_cells)[:, np.newaxis], - np.array(internal_data.polygon)[np.newaxis, :], + np.array(grid_cells)[ + :, + np.newaxis, + ], + np.array(internal_data.polygon)[ + np.newaxis, + :, + ], ) ) - return grid_intersections_area, internal_data + return ( + grid_intersections_area, + internal_data, + ) def get_grid_cells( - *, walkable_area: WalkableArea, grid_size: float -) -> Tuple[npt.NDArray[shapely.Polygon], int, int]: + *, + walkable_area: WalkableArea, + grid_size: float, +) -> Tuple[npt.NDArray[shapely.Polygon], int, int,]: """Creates a list of square grid cells covering the geometry. .. image:: /images/profile_grid.svg @@ -759,15 +964,30 @@ def get_grid_cells( max_x = bounds[2] max_y = bounds[3] - x_coords = np.arange(min_x, max_x + grid_size, grid_size) - y_coords = np.arange(max_y, min_y - grid_size, -grid_size) + x_coords = np.arange( + min_x, + max_x + grid_size, + grid_size, + ) + y_coords = np.arange( + max_y, + min_y - grid_size, + -grid_size, + ) grid_cells = [] for j in range(len(y_coords) - 1): for i in range(len(x_coords) - 1): grid_cell = shapely.box( - x_coords[i], y_coords[j], x_coords[i + 1], y_coords[j + 1] + x_coords[i], + y_coords[j], + x_coords[i + 1], + y_coords[j + 1], ) grid_cells.append(grid_cell) - return np.array(grid_cells), len(y_coords) - 1, len(x_coords) - 1 + return ( + np.array(grid_cells), + len(y_coords) - 1, + len(x_coords) - 1, + ) From 865152e8ba014d4437d38f5ead29e68214cda216 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Sun, 17 Mar 2024 13:48:51 +0100 Subject: [PATCH 04/15] Typing fixes for latex --- pedpy/methods/profile_calculator.py | 49 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 11d2dbdf..dcda5d68 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -92,8 +92,9 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods w_i = \frac{1} {\sigma \cdot \sqrt{2\pi}} \exp\big(-\frac{\delta^2}{2\sigma^2}\big), - where :math:`\sigma` is derived from FWHM as: - ..math:: + where :math:`\sigma` is derived from FWHM as: + + .. math:: \sigma = \frac{FWHM}{2\sqrt{2\ln(2)}}. """ @@ -498,43 +499,41 @@ def compute_speed_profile( This function calculates speed profiles based on pedestrian speed data across a grid within a walkable area. The method of computation can be selected among several options, - including classic (:attr:`SpeedMethod.CLASSIC`), + including mean (:attr:`SpeedMethod.MEAN`), Gaussian (:attr:`SpeedMethod.GAUSSIAN`), Voronoi (:attr:`SpeedMethod.VORONOI`), and arithmetic mean methods (:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. + When computing the Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) + the DataFrame needs to contain the columns 'x' and 'y'. Args: data: A pandas DataFrame containing `frame` and pedestrian `speed` - (result from :func:`~speed_calculator.compute_individual_speed`). - Depending on `speed_method`, additional columns 'x', 'y', or 'polygon' - might be required. - When computing the classic speed profile (:attr:`SpeedMethod.CLASSIC`)) - or Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) - the DataFrame needs to contain the columns 'x' and 'y'. - `polygon` column - (from :func:`~method_utils.compute_individual_voronoi_polygons`) is - required when using the `:attr:SpeedMethod.VORONOI` or - `:attr:`SpeedMethod.ARITHMETIC`. - For getting a DataFrame containing all the needed data, you can - merge the results of the different function on the 'id' and - 'frame' columns (see :func:`pandas.DataFrame.merge` and - :func:`pandas.merge`). + (result from :func:`~speed_calculator.compute_individual_speed`). + Depending on `speed_method`, additional columns 'x', 'y', or + 'polygon' might be required. `polygon` column + (from :func:`~method_utils.compute_individual_voronoi_polygons`) + is required when using the `:attr:SpeedMethod.VORONOI` or + `:attr:`SpeedMethod.ARITHMETIC`. + For getting a DataFrame containing all the needed data, you can + merge the results of the different function on the 'id' and + 'frame' columns (see :func:`pandas.DataFrame.merge` and + :func:`pandas.merge`). walkable_area: The geometric area within which the speed profiles are - computed. + computed. grid_size: The resolution of the grid used for computing the profiles, expressed in the same units as the `walkable_area`. speed_method: The speed method used to compute the speed profile grid_intersections_area: (Optional) intersection areas of grid cells - with Voronoi polygons, required for some speed methods. - polygons (result from - :func:`compute_grid_cell_polygon_intersection_area`) + with Voronoi polygons, required for some speed methods. + polygons (result from + :func:`compute_grid_cell_polygon_intersection_area`) fill_value: fill value for cells with no pedestrians inside when - using :attr:`SpeedMethod.MEAN` (default = `np.nan`) + using :attr:`SpeedMethod.MEAN` (default = `np.nan`) gaussian_width: (Optional) The full width at half maximum (FWHM) for - Gaussian weights, required when using :attr:`SpeedMethod.GAUSSIAN` - (default = 0.5). + Gaussian weights, required when using :attr:`SpeedMethod.GAUSSIAN` + (default = 0.5). Returns: A list of NumPy arrays, each representing the speed profile for a @@ -543,7 +542,7 @@ def compute_speed_profile( Note: The choice of `speed_method` significantly impacts the required data format and the interpretation of results. - Refer to the documentation of `:attr:SpeedMethod` for details on each + Refer to the documentation of :attr:`SpeedMethod` for details on each method's requirements and use cases. """ ( From a8807a8d1290a0550273a5217b790aca8a4d9484 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Sun, 17 Mar 2024 14:07:26 +0100 Subject: [PATCH 05/15] fix broken reference links in docstring --- pedpy/methods/profile_calculator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index dcda5d68..02eb37b8 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -510,14 +510,14 @@ def compute_speed_profile( Args: data: A pandas DataFrame containing `frame` and pedestrian `speed` (result from :func:`~speed_calculator.compute_individual_speed`). - Depending on `speed_method`, additional columns 'x', 'y', or - 'polygon' might be required. `polygon` column + Depending on `speed_method`, additional columns `x`, `y`, or + `polygon` might be required. `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) - is required when using the `:attr:SpeedMethod.VORONOI` or - `:attr:`SpeedMethod.ARITHMETIC`. + is required when using the :attr:`SpeedMethod.VORONOI` or + :attr:`SpeedMethod.ARITHMETIC`. For getting a DataFrame containing all the needed data, you can - merge the results of the different function on the 'id' and - 'frame' columns (see :func:`pandas.DataFrame.merge` and + merge the results of the different function on the `id` and + `frame` columns (see :func:`pandas.DataFrame.merge` and :func:`pandas.merge`). walkable_area: The geometric area within which the speed profiles are computed. From 818a272469b57495a0fa17782670c6655a013e81 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Tue, 19 Mar 2024 20:42:59 +0100 Subject: [PATCH 06/15] Add documentation is guide for speed profile --- notebooks/user_guide.ipynb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index 6ed249d5..1834e504 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2386,6 +2386,18 @@ "\n", "$$\n", " v_{mean} = \\frac{1}{N} \\sum_{i \\in P_M} v_i\n", + "$$\n", + "\n", + "**Gauss speed profiles**\n", + "\n", + "Calculates a speed profile based on Gaussian weights for an array of pedestrian locations and velocities. The speed, weighted at a grid cell $c$ considering its distance $\\delta = \\boldsymbol{r}_i - \\boldsymbol{c}$ from an agent, is determined as follows:\n", + " \n", + "$$ \n", + " v_{c} = \\frac{\\sum_{i=1}^{N}{\\big(w_i(\\delta)\\cdot v_i\\big)}}{\\sum_{i=1}^{N} w_i},\n", + "$$\n", + "with \n", + "$$\n", + " w_i = \\frac{1} {\\sigma \\cdot \\sqrt{2\\pi}} \\exp\\big(-\\frac{\\delta^2}{2\\sigma^2}\\big)\\; \\text{and}\\; \\sigma = \\frac{FWHM}{2\\sqrt{2\\ln(2)}}.\n", "$$" ] }, @@ -2418,6 +2430,13 @@ " walkable_area=walkable_area,\n", " grid_size=grid_size,\n", " speed_method=SpeedMethod.MEAN,\n", + ")\n", + "\n", + "gauss_speed_profile = compute_speed_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.GAUSSIAN,\n", ")" ] }, From 043488a47b06ccbd556cff9b3517fce5d6aef524 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 20 Mar 2024 08:01:13 +0100 Subject: [PATCH 07/15] Changes to docstrings --- pedpy/methods/profile_calculator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 02eb37b8..a9313b92 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -76,7 +76,7 @@ class SpeedMethod(Enum): # pylint: disable=too-few-public-methods .. math:: - v_{c} = \frac{\sum_{i=1}^{N}{\big(w_i(\delta)\cdot v_i\big)}}{\sum_{i=1}^{N} w_i}, + v_{c} = \frac{\sum_{i=1}^{N}{\big(w_i\cdot v_i\big)}}{\sum_{i=1}^{N} w_i}, where :math:`v_i` is the speed of a pedestrian and :math:`w_i` are weights depending on the pedestrian's distance :math:`\delta` from its position @@ -504,8 +504,6 @@ def compute_speed_profile( Voronoi (:attr:`SpeedMethod.VORONOI`), and arithmetic mean methods (:attr:`SpeedMethod.ARITHMETIC`), each suitable for different analysis contexts. - When computing the Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) - the DataFrame needs to contain the columns 'x' and 'y'. Args: data: A pandas DataFrame containing `frame` and pedestrian `speed` @@ -514,7 +512,9 @@ def compute_speed_profile( `polygon` might be required. `polygon` column (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when using the :attr:`SpeedMethod.VORONOI` or - :attr:`SpeedMethod.ARITHMETIC`. + :attr:`SpeedMethod.ARITHMETIC`. + When computing the Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) + the DataFrame needs to contain the columns `x` and `y`. For getting a DataFrame containing all the needed data, you can merge the results of the different function on the `id` and `frame` columns (see :func:`pandas.DataFrame.merge` and From db4ad7f38f28e3b3fde5dcf706a66ef7294e7225 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 20 Mar 2024 08:26:29 +0100 Subject: [PATCH 08/15] Update guide to plot gaussian speed profile --- notebooks/user_guide.ipynb | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index 1834e504..86239b38 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2396,9 +2396,8 @@ " v_{c} = \\frac{\\sum_{i=1}^{N}{\\big(w_i(\\delta)\\cdot v_i\\big)}}{\\sum_{i=1}^{N} w_i},\n", "$$\n", "with \n", - "$$\n", - " w_i = \\frac{1} {\\sigma \\cdot \\sqrt{2\\pi}} \\exp\\big(-\\frac{\\delta^2}{2\\sigma^2}\\big)\\; \\text{and}\\; \\sigma = \\frac{FWHM}{2\\sqrt{2\\ln(2)}}.\n", - "$$" + "$w_i = \\frac{1} {\\sigma \\cdot \\sqrt{2\\pi}} \\exp\\big(-\\frac{\\delta^2}{2\\sigma^2}\\big)\\; \\text{and}\\; \\sigma = \\frac{FWHM}{2\\sqrt{2\\ln(2)}}.\n", + "$" ] }, { @@ -2436,6 +2435,7 @@ " data=profile_data,\n", " walkable_area=walkable_area,\n", " grid_size=grid_size,\n", + " gaussian_width=0.5,\n", " speed_method=SpeedMethod.GAUSSIAN,\n", ")" ] @@ -2453,7 +2453,7 @@ "from pedpy import plot_profiles\n", "import matplotlib.pyplot as plt\n", "\n", - "fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, layout=\"constrained\")\n", + "fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2, layout=\"constrained\")\n", "fig.set_size_inches(10, 5)\n", "fig.suptitle(\"Speed profile\")\n", "cm = plot_profiles(\n", @@ -2476,14 +2476,22 @@ ")\n", "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " profiles=mean_speed_profile,\n", + " profiles=gauss_speed_profile,\n", " axes=ax2,\n", " label=\"v / m/s\",\n", " vmin=0,\n", " vmax=1.5,\n", + " title=\"Gauss\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=mean_speed_profile,\n", + " axes=ax3,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=1.5,\n", " title=\"Mean\",\n", ")\n", - "\n", "plt.show()" ] }, @@ -3320,7 +3328,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.12.2" } }, "nbformat": 4, From df17b7055dba51cf64a84710c02d4b8e6f8ff1eb Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 20 Mar 2024 09:04:21 +0100 Subject: [PATCH 09/15] adjust subplots spacing --- notebooks/user_guide.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index 86239b38..3be21442 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2453,8 +2453,7 @@ "from pedpy import plot_profiles\n", "import matplotlib.pyplot as plt\n", "\n", - "fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2, layout=\"constrained\")\n", - "fig.set_size_inches(10, 5)\n", + "fig, ((ax0, ax1), (ax2, ax3)) = plt.subplots(nrows=2, ncols=2)\n", "fig.suptitle(\"Speed profile\")\n", "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", @@ -2492,6 +2491,7 @@ " vmax=1.5,\n", " title=\"Mean\",\n", ")\n", + "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", "plt.show()" ] }, From 8614de40afab246bfa39e33ac79f59200b54a659 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 20 Mar 2024 09:21:00 +0100 Subject: [PATCH 10/15] Make documentation for speed profiles using uniform notation --- notebooks/user_guide.ipynb | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index 3be21442..574b94bd 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2361,39 +2361,44 @@ "source": [ "#### Speed Profiles\n", "\n", - "Currently, it is possible to compute either the Voronoi or arithmetic speed profiles.\n", + "This documentation describes the methods available for computing speed profiles within a specified area, focusing on pedestrian movements. Four distinct methods are detailed: Voronoi, Arithmetic, Mean, and Gauss speed profiles.\n", "\n", "**Voronoi speed profile**\n", "\n", - "The Voronoi speed is computed as a weighted mean of the pedestrian's speed, whose Voronoi cell ($V_i$) intersects with the grid cell ($M$). The weighted is given by the proportion of the Voronoi, which lies inside the gird cell.\n", - "\n", + "The Voronoi speed profile $v_{\\text{voronoi}}$ is computed from a weighted mean of pedestrian speeds.\n", + " It utilizes the area overlap between a pedestrian's Voronoi cell ($V_i$) and the grid cell ($c$). The weight corresponds to the fraction of the Voronoi cell residing within the grid cell, thereby integrating speed across this intersection:\n", "$$\n", - " v_{voronoi}(t) = { \\int\\int v_{xy} dxdy \\over A(M)},\n", + " v_{\\text{voronoi}} = { \\int\\int v_{xy} dxdy \\over A(c)},\n", "$$\n", "\n", + "where $A(c)$ represents the area of the grid cell $c$.\n", "\n", "**Arithmetic Voronoi speed profile**\n", "\n", - "The arithmetic Voronoi speed $v_{arithmetic}$ is computed as the mean of each pedestrian's speed ($v_i$), whose Voronoi cell $V_i$ intersects with grid cell $M$:\n", + "The arithmetic Voronoi speed $v_{\\text{arithmetic}}$ is computed as the mean of each pedestrian's speed ($v_i$), whose Voronoi cell $V_i$ intersects with grid cell $c$:\n", "\n", "$$\n", - " v_{arithmetic} = \\frac{1}{N} \\sum_{i \\in V_i \\cap P_M} v_i,\n", + " v_{\\text{arithmetic}} = \\frac{1}{N} \\sum_{i \\in V_i \\cap P_c} v_i,\n", "$$\n", "\n", + "with $N$ being the total number of pedestrians whose Voronoi cells overlap with grid cell $c$.\n", + "\n", "**Mean speed profile**\n", "\n", - "The mean speed profile is computed by the mean speed of all pedestrians $P_M$ inside the grid cell $M$:\n", + "The mean speed profile is computed by the average speed of all pedestrians $P_c$ present within a grid cell $c$:\n", "\n", "$$\n", - " v_{mean} = \\frac{1}{N} \\sum_{i \\in P_M} v_i\n", + " v_{\\text{mean}} = \\frac{1}{N} \\sum_{i \\in P_c} v_i\n", "$$\n", "\n", + "where $N$ denotes the number of pedestrians in grid cell $c$.\n", + "\n", "**Gauss speed profiles**\n", "\n", "Calculates a speed profile based on Gaussian weights for an array of pedestrian locations and velocities. The speed, weighted at a grid cell $c$ considering its distance $\\delta = \\boldsymbol{r}_i - \\boldsymbol{c}$ from an agent, is determined as follows:\n", " \n", "$$ \n", - " v_{c} = \\frac{\\sum_{i=1}^{N}{\\big(w_i(\\delta)\\cdot v_i\\big)}}{\\sum_{i=1}^{N} w_i},\n", + " v_{\\text{gauss}} = \\frac{\\sum_{i=1}^{N}{\\big(w_i\\cdot v_i\\big)}}{\\sum_{i=1}^{N} w_i},\n", "$$\n", "with \n", "$w_i = \\frac{1} {\\sigma \\cdot \\sqrt{2\\pi}} \\exp\\big(-\\frac{\\delta^2}{2\\sigma^2}\\big)\\; \\text{and}\\; \\sigma = \\frac{FWHM}{2\\sqrt{2\\ln(2)}}.\n", From 134eeef074c811842be3fbbb546de7633da458ac Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 20 Mar 2024 09:29:06 +0100 Subject: [PATCH 11/15] Add missing line --- notebooks/user_guide.ipynb | 1 + 1 file changed, 1 insertion(+) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index 574b94bd..a545ac98 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2367,6 +2367,7 @@ "\n", "The Voronoi speed profile $v_{\\text{voronoi}}$ is computed from a weighted mean of pedestrian speeds.\n", " It utilizes the area overlap between a pedestrian's Voronoi cell ($V_i$) and the grid cell ($c$). The weight corresponds to the fraction of the Voronoi cell residing within the grid cell, thereby integrating speed across this intersection:\n", + " \n", "$$\n", " v_{\\text{voronoi}} = { \\int\\int v_{xy} dxdy \\over A(c)},\n", "$$\n", From 849807e8268446da9b14a31a03d8cc7d17cb0fbf Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Fri, 22 Mar 2024 14:39:49 +0100 Subject: [PATCH 12/15] Fix CI Black formatting --- notebooks/user_guide.ipynb | 4 +++- pedpy/methods/profile_calculator.py | 11 +++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/notebooks/user_guide.ipynb b/notebooks/user_guide.ipynb index a545ac98..5c51448d 100644 --- a/notebooks/user_guide.ipynb +++ b/notebooks/user_guide.ipynb @@ -2497,7 +2497,9 @@ " vmax=1.5,\n", " title=\"Mean\",\n", ")\n", - "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", + "plt.subplots_adjust(\n", + " left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6\n", + ")\n", "plt.show()" ] }, diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index a9313b92..8b0a11c7 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -493,8 +493,7 @@ def compute_speed_profile( gaussian_width: float = 0.5, # pylint: disable=too-many-arguments ) -> List[npt.NDArray[np.float64]]: - """Computes the speed profile for pedestrians within a specified walkable - area using various methods. + """Computes the speed profile for pedestrians within an area. This function calculates speed profiles based on pedestrian speed data across a grid within a walkable area. @@ -508,13 +507,13 @@ def compute_speed_profile( Args: data: A pandas DataFrame containing `frame` and pedestrian `speed` (result from :func:`~speed_calculator.compute_individual_speed`). - Depending on `speed_method`, additional columns `x`, `y`, or + Depending on `speed_method`, additional columns `x`, `y`, or `polygon` might be required. `polygon` column - (from :func:`~method_utils.compute_individual_voronoi_polygons`) + (from :func:`~method_utils.compute_individual_voronoi_polygons`) is required when using the :attr:`SpeedMethod.VORONOI` or :attr:`SpeedMethod.ARITHMETIC`. When computing the Gaussian profile (:attr:`SpeedMethod.GAUSSIAN`) - the DataFrame needs to contain the columns `x` and `y`. + the DataFrame needs to contain the columns `x` and `y`. For getting a DataFrame containing all the needed data, you can merge the results of the different function on the `id` and `frame` columns (see :func:`pandas.DataFrame.merge` and @@ -527,7 +526,7 @@ def compute_speed_profile( speed profile grid_intersections_area: (Optional) intersection areas of grid cells with Voronoi polygons, required for some speed methods. - polygons (result from + polygons (result from :func:`compute_grid_cell_polygon_intersection_area`) fill_value: fill value for cells with no pedestrians inside when using :attr:`SpeedMethod.MEAN` (default = `np.nan`) From 36a7c7f8e9adc2b98772d1c362f144cabb06befd Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Thu, 28 Mar 2024 06:54:35 +0100 Subject: [PATCH 13/15] black formatting --- pedpy/methods/profile_calculator.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index 15d26d12..e395431a 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -162,10 +162,7 @@ def compute_profiles( gaussian_width: Optional[float] = None, # pylint: disable=unused-argument,too-many-arguments **kwargs: Any, -) -> Tuple[ - List[npt.NDArray[np.float64]], - List[npt.NDArray[np.float64]], -]: +) -> Tuple[List[npt.NDArray[np.float64]], List[npt.NDArray[np.float64]],]: """Computes the density and speed profiles. .. note:: @@ -663,7 +660,9 @@ def _compute_gaussian_weights( """ sigma = fwhm / (2 * np.sqrt(2 * np.log(2))) return ( - 1 / (sigma * np.sqrt(2 * np.pi)) * np.exp(-(x**2) / (2 * sigma**2)) + 1 + / (sigma * np.sqrt(2 * np.pi)) + * np.exp(-(x**2) / (2 * sigma**2)) ) # pedestrians' position and speed @@ -849,10 +848,7 @@ def compute_grid_cell_polygon_intersection_area( *, data: pandas.DataFrame, grid_cells: npt.NDArray[shapely.Polygon], -) -> Tuple[ - npt.NDArray[np.float64], - pandas.DataFrame, -]: +) -> Tuple[npt.NDArray[np.float64], pandas.DataFrame,]: """Computes the intersection area of the grid cells with the Voronoi polygons. .. note:: @@ -926,11 +922,7 @@ def get_grid_cells( *, walkable_area: WalkableArea, grid_size: float, -) -> Tuple[ - npt.NDArray[shapely.Polygon], - int, - int, -]: +) -> Tuple[npt.NDArray[shapely.Polygon], int, int,]: """Creates a list of square grid cells covering the geometry. .. image:: /images/profile_grid.svg From b0463454140a3692d0d65dd0965a965ee5943aa5 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Thu, 4 Apr 2024 07:17:11 +0200 Subject: [PATCH 14/15] small changes to docstrings --- pedpy/methods/profile_calculator.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index e395431a..d1eb11b8 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -498,9 +498,9 @@ def compute_speed_profile( the DataFrame needs to contain the columns `x` and `y`. For getting a DataFrame containing all the needed data, you can merge the results of the different function on the `id` and - `frame` columns (see :func:`pandas.DataFrame.merge` and + `frame` columns (see :meth:`pandas.DataFrame.merge` and :func:`pandas.merge`). - walkable_area: geometry for which the speed profiles are + walkable_area (WalkableArea): geometry for which the speed profiles are computed. grid_size: The resolution of the grid used for computing the profiles, expressed in the same units as the `walkable_area`. @@ -517,8 +517,7 @@ def compute_speed_profile( (default = 0.5). Returns: - A list of NumPy arrays, each representing the speed profile for a - different grid cell within the walkable area. + A list of NumPy arrays, each representing the speed profile per frame. Note: The choice of `speed_method` significantly impacts the required data @@ -592,7 +591,7 @@ def compute_speed_profile( fwhm=gaussian_width, ) else: - raise ValueError("speed method not accepted") + raise ValueError("Speed method not accepted.") speed_profiles.append( speed.reshape( From 8a4c7976ac9058c3ef2e402c6bea92a84dcd02b1 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Thu, 4 Apr 2024 10:16:40 +0200 Subject: [PATCH 15/15] update docstring --- pedpy/methods/profile_calculator.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pedpy/methods/profile_calculator.py b/pedpy/methods/profile_calculator.py index d1eb11b8..487d77ac 100644 --- a/pedpy/methods/profile_calculator.py +++ b/pedpy/methods/profile_calculator.py @@ -507,8 +507,7 @@ def compute_speed_profile( speed_method: The speed method used to compute the speed profile grid_intersections_area: (Optional) intersection areas of grid cells - with Voronoi polygons, required for some speed methods. - polygons (result from + with Voronoi polygons (result from :func:`compute_grid_cell_polygon_intersection_area`) fill_value: fill value for cells with no pedestrians inside when using :attr:`SpeedMethod.MEAN` (default = `np.nan`)