diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c488dcac..67628537 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: with: name: demos_usage path: | - notebooks/ + docs/source/notebooks/ test: needs: build diff --git a/.gitignore b/.gitignore index aa0cd0ed..d9bbdebd 100644 --- a/.gitignore +++ b/.gitignore @@ -316,10 +316,9 @@ cython_debug/ *.code-workspace docs/build -docs/source/results_introduction +**/results_introduction/ docs/jupyter_execute pedpy/_version.py docs/source/ZENODO.rst -notebooks/results_introduction # End of https://www.toptal.com/developers/gitignore/api/python,visualstudiocode,pycharm,jupyternotebooks .vscode diff --git a/docs/source/_static/icon_analysis.svg b/docs/source/_static/icon_analysis.svg new file mode 100644 index 00000000..f8af5d2e --- /dev/null +++ b/docs/source/_static/icon_analysis.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/source/_static/icon_fd.svg b/docs/source/_static/icon_fd.svg new file mode 100644 index 00000000..2698ccb6 --- /dev/null +++ b/docs/source/_static/icon_fd.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/source/_static/icon_fd_at_line.svg b/docs/source/_static/icon_fd_at_line.svg new file mode 100644 index 00000000..42436916 --- /dev/null +++ b/docs/source/_static/icon_fd_at_line.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/source/_static/icon_measurement_setup.svg b/docs/source/_static/icon_measurement_setup.svg new file mode 100644 index 00000000..562e204f --- /dev/null +++ b/docs/source/_static/icon_measurement_setup.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/docs/source/_static/icon_preprocessing.svg b/docs/source/_static/icon_preprocessing.svg new file mode 100644 index 00000000..1a22f69f --- /dev/null +++ b/docs/source/_static/icon_preprocessing.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/source/_static/icon_working_with_results.svg b/docs/source/_static/icon_working_with_results.svg new file mode 100644 index 00000000..c4b9e83e --- /dev/null +++ b/docs/source/_static/icon_working_with_results.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/docs/source/_static/user_guide_flow.svg b/docs/source/_static/user_guide_flow.svg new file mode 100644 index 00000000..b1db4ea2 --- /dev/null +++ b/docs/source/_static/user_guide_flow.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + Application Examples + + + + + Measurement Setup + + + + + + Pre-processing + + + + + + Analysis + + + + + + FD + + + + + + FD at line + + + + + + Working with Results + + diff --git a/docs/source/demo-data b/docs/source/demo-data deleted file mode 120000 index bb9863ed..00000000 --- a/docs/source/demo-data +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/demo-data \ No newline at end of file diff --git a/docs/source/fundamental_diagram.ipynb b/docs/source/fundamental_diagram.ipynb deleted file mode 120000 index a6c1a9b2..00000000 --- a/docs/source/fundamental_diagram.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/fundamental_diagram.ipynb \ No newline at end of file diff --git a/docs/source/fundamental_diagram_at_measurement_line.ipynb b/docs/source/fundamental_diagram_at_measurement_line.ipynb deleted file mode 120000 index 9a7c68ef..00000000 --- a/docs/source/fundamental_diagram_at_measurement_line.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/fundamental_diagram_at_measurement_line.ipynb \ No newline at end of file diff --git a/docs/source/getting_started.ipynb b/docs/source/getting_started.ipynb deleted file mode 120000 index 20e305e1..00000000 --- a/docs/source/getting_started.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/getting_started.ipynb \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index f565921b..4e0c3075 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,10 +6,9 @@ PedPy: Pedestrian Trajectory Analyzer :maxdepth: 1 :hidden: - Getting Started - User Guide + Getting Started + User Guide API Reference - Methods Developer Guide Cite PedPy Changelog @@ -52,7 +51,7 @@ Cite PedPy .. grid-item-card:: Getting Started :img-top: _static/getting_started.svg :class-card: intro-card - :link: getting_started + :link: notebooks/getting_started :link-type: doc :link-alt: To the getting started guide @@ -62,7 +61,7 @@ Cite PedPy .. grid-item-card:: User Guide :img-top: _static/user_guide.svg :class-card: intro-card - :link: user_guide + :link: user_guide/index :link-type: doc :link-alt: To the user guide diff --git a/docs/source/methods/index.rst b/docs/source/methods/index.rst deleted file mode 100644 index 8ab384f4..00000000 --- a/docs/source/methods/index.rst +++ /dev/null @@ -1,18 +0,0 @@ -************* -Methods -************* - -.. list-table:: - :widths: 20 80 - - * - :doc:`Fundamental Diagrams at Measurement Line <../fundamental_diagram_at_measurement_line>` - - Calculate pedestrian flow characteristics using trajectory data, following the methodology in - `Continuity equation and fundamental diagram of pedestrians `_. - This approach ensures consistency with the continuity equation when analyzing movement patterns - along measurement lines. - -.. toctree:: - :maxdepth: 1 - :hidden: - - Fundamental Diagrams at Measurement Line <../fundamental_diagram_at_measurement_line> diff --git a/notebooks/user_guide.ipynb b/docs/source/notebooks/analysis.ipynb similarity index 71% rename from notebooks/user_guide.ipynb rename to docs/source/notebooks/analysis.ipynb index 2fcb4717..96551441 100644 --- a/notebooks/user_guide.ipynb +++ b/docs/source/notebooks/analysis.ipynb @@ -1,106 +1,59 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "remove-input" - ] - }, - "outputs": [], - "source": [ - "import pathlib\n", - "import warnings\n", - "\n", - "import numpy as np\n", - "import pandas as pd\n", - "import shapely\n", - "\n", - "warnings.filterwarnings(\"ignore\")" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "# User Guide\n", - "\n", - "This user guide will showcase the capabilities of *PedPy*. \n", - "By following this guide you will learn how to set up your analysis and compute different metrics from the movement data.\n", - "This guide is designed as a [Jupyter notebook](https://jupyter.org/) where the individual cells can be executed in the given order, for trying it yourself you can copy the individual cells in a Python script and run it, or download the notebook {download}`here <./user_guide.ipynb>` it directly and run it locally.\n", - "\n", - "If you use *PedPy* in your work, please cite it using the following information from zenodo:\n", - "[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7386931.svg)](https://doi.org/10.5281/zenodo.7386931)\n", - "\n", - "This is a bottleneck experiment conducted at the University of Wuppertal in 2018.\n", - "You can see the basic setup of the experiment in the picture below:\n", - "\n", - "```{eval-rst}\n", - ".. figure:: demo-data/bottleneck/040_c_56_h-.png\n", - " :width: 400px\n", - " :align: center\n", - "```\n", - "\n", - "The data for this experiment is available {download}`here `, which belongs to this [experimental series](https://doi.org/10.34735/ped.2018.1) and is part of the publication [\\\"Crowds in front of bottlenecks at entrances from the perspective of physics and social psychology\\\"](https://doi.org/10.1098/rsif.2019.0871)." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "## Analysis set-up\n", + "# Analysis\n", "\n", - "The first step we will take, is to set up the analysis environment, this means define the areas where pedestrians can walk and put existing obstacles in it.\n", - "Also, we will define which areas are of interest for the later analysis." + "This notebook covers the analysis methods available in *PedPy*.\n", + "Before diving into the analysis, we need to set up the walkable area, measurement areas, and load trajectory data.\n", + "For more details on how to set up your analysis environment, see the {doc}`Measurement Setup ` notebook. " ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "### Walkable area\n", - "\n", - "In the beginning we will define the {class}`walkable area ` in which the pedestrian can move. \n", - "For the used bottleneck experiment was conducted in the following set up:\n", - "\n", + "## Measurement setup\n", "\n", - "```{eval-rst}\n", - ".. figure:: demo-data/bottleneck/experimental_setup.png\n", - " :width: 50 %\n", - " :align: center\n", - "```\n", - "\n", - "The run handled in this user guide had a bottleneck width of 0.5m and w=5.6m.\n", + "For showing the capabilities of *PedPy* we are a using experimental data of bottleneck experiments conducted in 2018 at the University of Wuppertal. \n", + "The data for this experiment is available {download}`here `, which belongs to this [experimental series](https://doi.org/10.34735/ped.2018.1) and is part of the publication [\"Crowds in front of bottlenecks at entrances from the perspective of physics and social psychology\"](https://doi.org/10.1098/rsif.2019.0871).\n", + "We are re-using the measurement setup of the [Getting Started Guide](getting_started) and load the trajectories from the provided txt-file. \n", "\n", - "Below is the code for creating such a {class}`walkable area `:" + "This results in the following measurement setup:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "remove-input" + ] }, "outputs": [], "source": [ - "from pedpy import WalkableArea\n", + "import pathlib\n", + "import warnings\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import pandas as pd\n", + "import shapely\n", + "\n", + "warnings.filterwarnings(\"ignore\")\n", + "\n", + "from pedpy import (\n", + " MeasurementArea,\n", + " MeasurementLine,\n", + " TrajectoryUnit,\n", + " WalkableArea,\n", + " load_trajectory,\n", + " plot_measurement_setup,\n", + ")\n", "\n", "walkable_area = WalkableArea(\n", - " # complete area\n", " [\n", " (3.5, -2),\n", " (3.5, 8),\n", @@ -108,7 +61,6 @@ " (-3.5, -2),\n", " ],\n", " obstacles=[\n", - " # left barrier\n", " [\n", " (-0.7, -1.1),\n", " (-0.25, -1.1),\n", @@ -121,7 +73,6 @@ " (-0.7, -0.3),\n", " (-0.7, -1.0),\n", " ],\n", - " # right barrier\n", " [\n", " (0.25, -1.1),\n", " (0.7, -1.1),\n", @@ -135,28 +86,41 @@ " (0.25, -1.1),\n", " ],\n", " ],\n", - ")" + ")\n", + "\n", + "measurement_area = MeasurementArea([(-0.4, 0.5), (0.4, 0.5), (0.4, 1.3), (-0.4, 1.3)])\n", + "measurement_line = MeasurementLine([(0.4, 0), (-0.4, 0)])\n", + "\n", + "traj = load_trajectory(\n", + " trajectory_file=pathlib.Path(\"demo-data/bottleneck/040_c_56_h-.txt\"),\n", + " default_unit=TrajectoryUnit.METER,\n", + ")\n", + "\n", + "plot_measurement_setup(\n", + " traj=traj,\n", + " walkable_area=walkable_area,\n", + " measurement_lines=[measurement_line],\n", + " ml_width=2,\n", + " measurement_areas=[measurement_area],\n", + " ma_line_width=2,\n", + " ma_alpha=0.2,\n", + ").set_aspect(\"equal\")\n", + "plt.show()" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "jupyter": { "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] + } }, - "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", + "## Density\n", "\n", - "from pedpy import plot_walkable_area\n", - "\n", - "plot_walkable_area(walkable_area=walkable_area).set_aspect(\"equal\")\n", - "plt.show()" + "Density is a fundamental metric in pedestrian dynamics.\n", + "As it indicated how much space is accessible to each pedestrian within a specific area.\n", + "High density can lead to reduced walking speeds, increased congestion, and even potential safety hazards." ] }, { @@ -167,15 +131,15 @@ } }, "source": [ - "### Prepare measurement details\n", + "### Classic density\n", + "\n", + "The classic approach to calculate the density $\\rho_{classic}(t)$ at a time $t$, is to count the number of pedestrians ($N(t)$) inside a specific space ($M$) and divide it by the area of that space ($A(M)$).\n", "\n", - "After we defined where the pedestrians can move, we now need to define in which regions we want to analyze in more details. \n", - "This regions can either be a specific line, an area, or the whole {class}`walkable area `.\n", + "$$\n", + "\\rho_{classic}(t) = {N(t) \\over A(M)}\n", + "$$\n", "\n", - "In case of this bottleneck the most interesting area is a little bit in front of the bottleneck (here 0.5m) and the line at the beginning of the bottleneck.\n", - "The area is slightly in front of the bottleneck as here the highest density occur. \n", - "In *PedPy* such areas are called {class}`~geometry.MeasurementArea` and the lines {class}`~geometry.MeasurementLine`.\n", - "Below you can see how to define these:" + "In *PedPy* this can be computed with:\n" ] }, { @@ -188,24 +152,25 @@ }, "outputs": [], "source": [ - "from pedpy import MeasurementArea, MeasurementLine\n", - "\n", - "measurement_area = MeasurementArea([(-0.4, 0.5), (0.4, 0.5), (0.4, 1.3), (-0.4, 1.3)])\n", + "from pedpy import compute_classic_density\n", "\n", - "measurement_line = MeasurementLine([(0.4, 0), (-0.4, 0)])" + "classic_density = compute_classic_density(traj_data=traj, measurement_area=measurement_area)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The corresponding measurement setup looks like:" + "The resulting time-series can be seen below:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -214,16 +179,9 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import plot_density\n", "\n", - "plot_measurement_setup(\n", - " walkable_area=walkable_area,\n", - " measurement_lines=[measurement_line],\n", - " ml_width=2,\n", - " measurement_areas=[measurement_area],\n", - " ma_line_width=2,\n", - " ma_alpha=0.2,\n", - ").set_aspect(\"equal\")\n", + "plot_density(density=classic_density, title=\"Classic density\")\n", "plt.show()" ] }, @@ -235,51 +193,46 @@ } }, "source": [ - "### Importing pedestrian movement data\n", + "(voronoi_density)=\n", + " ### Voronoi density\n", "\n", - "The pedestrian movement data in *PedPy* is called {class}`trajectory data`.\n", + "Another approach for calculating the density is to compute the [Voronoi tesselation](https://en.wikipedia.org/wiki/Voronoi_diagram) of the pedestrians positions at a given time $t$, the resulting Voronoi polygons ($V$) directly relate to the individual's density.\n", + "For a pedestrian $i$ the individual density is defined as:\n", "\n", - "*PedPy* works with {class}`trajectory data` which can be created from an import function for specific data files alternatively from a {class}`~pandas.DataFrame` with the following columns:\n", + "$$\n", + "\\rho_i(t) = {1 \\over A(V_i(t))}\n", + "$$\n", "\n", - "- \"id\": unique numeric identifier for each person\n", - "- \"frame\": index of video frame where the positions were extracted\n", - "- \"x\", \"y\": position of the person (in meter) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Loading from Pandas DataFrame\n", "\n", - "To construct the {class}`trajectory data` from a {class}`~pandas.DataFrame` you also need to provide the frame rate at which the data was recorded.\n", - "If you have both the construction of the {class}`trajectory data` can be done with:" + "#### Compute individual Voronoi Polygons\n", + "\n", + "The first step for computing the Voronoi density, is to compute the individual's Voronoi polygon.\n", + "As these polygons may become infinite for pedestrians at the edge of the crowd, these polygons are restricted by the {class}`walkable area `.\n", + "This cutting at the boundaries can lead to split Voronoi polygons. For each of the split polygons it is checked, in which the pedestrian is located. \n", + "This polygon then is assigned.\n", + "\n", + ":::{important}\n", + "As these Voronoi polygons work on the Euclidean distance, some unexpected artifacts may occur on non-convex {class}`walkable areas `. Please keep that in mind! How that may look like, you can see in the plots later in this guide.\n", + ":::\n", + "\n", + "##### Without cut-off\n", + "\n", + "The computation of the individual polygons can be done from the {class}`trajectory data` and {class}`walkable area ` with:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import TrajectoryData\n", - "\n", - "data = pd.DataFrame(\n", - " [[0, 1, 0, 0]],\n", - " columns=[\"id\", \"frame\", \"x\", \"y\"],\n", - ")\n", - "trajectory_data = TrajectoryData(data=data, frame_rate=25.0)" - ] - }, - { - "cell_type": "markdown", "metadata": { "jupyter": { "outputs_hidden": false } }, + "outputs": [], "source": [ - "Alternatively, the data can be also loaded from any file format that is supported by *Pandas*, see the [documentation](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/io.html) for more details.\n" + "from pedpy import compute_individual_voronoi_polygons\n", + "\n", + "individual = compute_individual_voronoi_polygons(traj_data=traj, walkable_area=walkable_area)" ] }, { @@ -290,46 +243,19 @@ } }, "source": [ - "#### Loading from text trajectory files\n", - "\n", - "*Pedpy* can load trajectories, if they are stored as the {class}`trajectory data` provided in the [Jülich Data Archive](https://ped.fz-juelich.de/da/doku.php) directly.\n", - "If you have text files in same format, you can load them in the same way too:\n", - "\n", - "- values are separated by any whitespace, e.g., space, tab\n", - "- file has at least 4 columns in the following order: \"id\", \"frame\", \"x\", \"y\"\n", - "- file may contain comment lines with `#` at in the beginning\n", + "##### With cut-off\n", "\n", - "For meaningful analysis (and loading of the trajectory file) you also need\n", - "- unit of the trajectory (m or cm)\n", - "- frame rate\n", - "\n", - "For recent trajectory they are encoded in the header of the file, for older you may need to lead the documentation and provide the information in the loading process.\n", - "\n", - "**Examples:**\n", - "With frame rate, but no unit\n", - "```\n", - "# description: UNI_CORR_500_01\n", - "# framerate: 25.00\n", - "#geometry: geometry.xml\n", - "\n", - "# PersID\tFrame\tX\tY\tZ\n", - "1\t98\t4.6012\t1.8909\t1.7600\n", - "1\t99\t4.5359\t1.8976\t1.7600\n", - "1\t100\t4.4470\t1.9304\t1.7600\n", - "...\n", - "```\n", + "When having a large {class}`walkable area ` or widely spread pedestrians the Voronoi polygons may become quite large.\n", + "In *PedPy* it is possible to restrict the size of the computed Polygons.\n", + "This can be done by defining a {class}`cut off `, which is essentially an approximated circle which gives the maximum extension of a single Voronoi polygon.\n", + "For the creation of the {class}`cut off `, we need to define how accurate we want to approximate the circle, the differences can be seen below:\n", "\n", - "No header at all:\n", - "```\n", - "1 27 164.834 780.844 168.937\n", - "1 28 164.835 771.893 168.937\n", - "1 29 163.736 762.665 168.937\n", - "1 30 161.967 753.088 168.937\n", - "...\n", + "```{eval-rst}\n", + ".. figure:: /images/voronoi_cutoff_differences.svg\n", + " :align: center\n", "```\n", "\n", - "If your data is structured in a different way please take a look at the next section.\n", - "Since the data we want to analyze is from the data archive, we can directly load the {class}`trajectory data` with *PedPy*: " + "Now, with that {class}`cut off ` the computation of the individual polygons becomes:" ] }, { @@ -342,12 +268,12 @@ }, "outputs": [], "source": [ - "from pedpy import TrajectoryUnit, load_trajectory\n", + "from pedpy import Cutoff, compute_individual_voronoi_polygons\n", "\n", - "traj = load_trajectory(\n", - " trajectory_file=pathlib.Path(\"demo-data/bottleneck/040_c_56_h-.txt\"),\n", - " default_unit=TrajectoryUnit.METER, # needs to be provided as it not defined in the file\n", - " # default_frame_rate=25., # can be ignored here as the frame rate is defined in the file\n", + "individual_cutoff = compute_individual_voronoi_polygons(\n", + " traj_data=traj,\n", + " walkable_area=walkable_area,\n", + " cut_off=Cutoff(radius=1.0, quad_segments=3),\n", ")" ] }, @@ -359,7 +285,9 @@ } }, "source": [ - "The loaded {class}`trajectory data` look like:" + "##### Comparison\n", + "\n", + "To get a better impression what the differences between the Voronoi polygons with and without the {class}`cut off ` are, take a look at the plot below:" ] }, { @@ -375,11 +303,59 @@ }, "outputs": [], "source": [ + "import matplotlib as mpl\n", "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import DENSITY_COL, FRAME_COL, ID_COL, plot_voronoi_cells\n", + "\n", + "frame = 600\n", + "\n", + "fig = plt.figure(f\"frame = {frame}\")\n", + "fig.suptitle(f\"frame = {frame}\")\n", + "\n", + "ax1 = fig.add_subplot(121, aspect=\"equal\")\n", + "ax1.set_title(\"w/o cutoff\")\n", + "plot_voronoi_cells(\n", + " voronoi_data=individual,\n", + " traj_data=traj,\n", + " frame=frame,\n", + " walkable_area=walkable_area,\n", + " color_by_column=DENSITY_COL,\n", + " axes=ax1,\n", + " show_colorbar=False,\n", + " vmin=0,\n", + " vmax=10,\n", + ")\n", + "\n", + "ax2 = fig.add_subplot(122, aspect=\"equal\")\n", + "ax2.set_title(\"w cutoff\")\n", + "\n", + "plot_voronoi_cells(\n", + " voronoi_data=individual_cutoff,\n", + " traj_data=traj,\n", + " frame=frame,\n", + " walkable_area=walkable_area,\n", + " color_by_column=DENSITY_COL,\n", + " axes=ax2,\n", + " show_colorbar=False,\n", + " vmin=0,\n", + " vmax=10,\n", + ")\n", + "cbar_ax = fig.add_axes([0.1, -0.05, 0.88, 0.05])\n", + "\n", + "norm = mpl.colors.Normalize(vmin=0, vmax=10)\n", + "sm = plt.cm.ScalarMappable(cmap=plt.get_cmap(\"YlGn\"), norm=norm)\n", + "sm.set_array([])\n", + "plt.colorbar(\n", + " sm,\n", + " cax=cbar_ax,\n", + " shrink=0.1,\n", + " label=\"$\\\\rho$ \\\\ 1/$m^2$\",\n", + " aspect=2,\n", + " orientation=\"horizontal\",\n", + ")\n", "\n", - "plot_trajectories(traj=traj).set_aspect(\"equal\")\n", + "fig.tight_layout()\n", "plt.show()" ] }, @@ -391,12 +367,19 @@ } }, "source": [ - "#### Loading from hdf5 trajectory files\n", + "#### Compute actual Voronoi density\n", "\n", - "For some experiments the [Jülich Data Archive](https://ped.fz-juelich.de/da/doku.php) also provides [HDF5](https://www.hdfgroup.org/HDF5) trajectory files, with a structure described [here](https://ped.fz-juelich.de/da/doku.php?id=info).\n", - "These data are from a different experiment, and are only used to demonstrate how to load HDF5 files, it can be downloaded {download}`here `.\n", + "From these individual data we can now compute the Voronoi density $\\rho_{voronoi}(t)$ in the known {class}`measurement area ` ($M$):\n", + "\n", + "$$\n", + " \\rho_{voronoi}(t) = { \\int\\int \\rho_{xy}(t) dxdy \\over A(M)},\n", + "$$\n", + "\n", + "where $\\rho_{xy}(t) = 1 / A(V_i(t))$ is the individual density of each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of the {class}`measurement area `.\n", + "\n", + "##### Without cut-off\n", "\n", - "To make the data usable for *PedPy* use:" + "First, we compute the Voronoi density in the {class}`measurement area ` without a {class}`cut off `:" ] }, { @@ -409,24 +392,20 @@ }, "outputs": [], "source": [ - "import pathlib\n", - "\n", - "from pedpy import (\n", - " TrajectoryData,\n", - " load_trajectory_from_ped_data_archive_hdf5,\n", - " load_walkable_area_from_ped_data_archive_hdf5,\n", - ")\n", - "\n", - "h5_file = pathlib.Path(\"demo-data/single_file/00_01a.h5\")\n", + "from pedpy import compute_voronoi_density\n", "\n", - "traj_h5 = load_trajectory_from_ped_data_archive_hdf5(trajectory_file=h5_file)\n", - "walkable_area_h5 = load_walkable_area_from_ped_data_archive_hdf5(trajectory_file=h5_file)" - ] + "density_voronoi, intersecting = compute_voronoi_density(\n", + " individual_voronoi_data=individual, measurement_area=measurement_area\n", + ")" + ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -435,52 +414,49 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import PEDPY_ORANGE, plot_density\n", "\n", - "plot_trajectories(traj=traj_h5, walkable_area=walkable_area_h5).set_aspect(\"equal\")\n", + "plot_density(density=density_voronoi, title=\"Voronoi density\", color=PEDPY_ORANGE)\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "source": [ - "#### Loading from Viswalk trajectory files\n", - "\n", - "It is also possible to load trajectory files from [Viswalk](https://www.ptvgroup.com/en/products/pedestrian-simulation-software-ptv-viswalk) directly into *PedPy*. \n", - "The expected format is a CSV file with `;` as delimiter, and it should contain at least the following columns: `NO`, `SIMSEC`, `COORDCENTX`, `COORDCENTY`.\n", - "Comment lines may start with a `*` and will be ignored.\n", + "##### With cut-off\n", "\n", - ":::{important}\n", - "Currently only Viswalk trajectory files, which use the simulation time (`SIMSEC`) are supported.\n", - ":::\n", - "\n", - "\n", - "To make the data usable for *PedPy* use:" + "Second, we compute it now from the individual {class}`cut off ` Voronoi polygons:\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ - "import pathlib\n", - "\n", - "from pedpy import (\n", - " TrajectoryData,\n", - " load_trajectory_from_viswalk,\n", - ")\n", - "\n", - "viswalk_file = pathlib.Path(\"demo-data/viswalk/example.pp\")\n", + "from pedpy import compute_voronoi_density\n", "\n", - "traj_viswalk = load_trajectory_from_viswalk(trajectory_file=viswalk_file)" + "density_voronoi_cutoff, intersecting_cutoff = compute_voronoi_density(\n", + " individual_voronoi_data=individual_cutoff, measurement_area=measurement_area\n", + ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -489,51 +465,37 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import PEDPY_GREY, plot_density\n", "\n", - "plot_trajectories(traj=traj_viswalk).set_aspect(\"equal\")\n", + "plot_density(\n", + " density=density_voronoi_cutoff,\n", + " title=\"Voronoi density with cut-off\",\n", + " color=PEDPY_GREY,\n", + ")\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Loading from Vadere trajectory files\n", - "\n", - "It is also possible to load trajectory files from [Vadere](https://www.vadere.org/) directly into *PedPy*. \n", - "The expected format is a CSV file with space character as delimiter, and it should contain at least the following columns: `pedestrianId`, `simTime`, `startX`, `startY`.\n", - "Comment lines may start with a `#` and will be ignored.\n", - "\n", - "\n", - "To make the data usable for *PedPy* use:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "source": [ - "import pathlib\n", - "\n", - "from pedpy import (\n", - " TrajectoryData,\n", - " load_trajectory_from_vadere,\n", - " load_walkable_area_from_vadere_scenario,\n", - ")\n", + "### Comparison\n", "\n", - "vadere_traj_file = pathlib.Path(\"demo-data/vadere/bottleneck/vadere_postvis.traj\")\n", - "vadere_scenario_file = pathlib.Path(\"demo-data/vadere/bottleneck/vadere_bottleneck.scenario\")\n", - "\n", - "traj_vadere = load_trajectory_from_vadere(trajectory_file=vadere_traj_file, frame_rate=24.0)\n", - "vadere_walkable_area = load_walkable_area_from_vadere_scenario(vadere_scenario_file=vadere_scenario_file, margin=1e-3)" + "Now we have obtained the mean density inside the {class}`measurement area ` with different methods.\n", + "To compare the results take a look at the following plot:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -542,48 +504,94 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import PEDPY_BLUE, PEDPY_GREY, PEDPY_ORANGE\n", "\n", - "plot_trajectories(traj=traj_vadere, walkable_area=vadere_walkable_area).set_aspect(\"equal\")\n", + "fig = plt.figure()\n", + "plt.title(\"Comparison of different density methods\")\n", + "plt.plot(\n", + " classic_density.frame,\n", + " classic_density.density,\n", + " label=\"classic\",\n", + " color=PEDPY_BLUE,\n", + ")\n", + "plt.plot(\n", + " density_voronoi.frame,\n", + " density_voronoi.density,\n", + " label=\"voronoi\",\n", + " color=PEDPY_ORANGE,\n", + ")\n", + "plt.plot(\n", + " density_voronoi_cutoff.frame,\n", + " density_voronoi_cutoff.density,\n", + " label=\"voronoi with cutoff\",\n", + " color=PEDPY_GREY,\n", + ")\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"$\\\\rho$ / 1/$m^2$\")\n", + "plt.grid()\n", + "plt.legend()\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "source": [ - "#### Loading from Pathfinder trajectory files\n", + "(passing_density)=\n", + " ### Passing density (individual)\n", + "\n", + "Another option to compute the individual density, is the passing density. \n", + "For the computation it needs a {class}`measurement line ` and the distance to a second \"virtual\" {class}`measurement line ` which form a \"virtual\" {class}`measurement area ` ($M$).\n", + "\n", + "```{eval-rst}\n", + ".. image:: /images/passing_area_from_lines.svg\n", + " :width: 80 %\n", + " :align: center\n", + "```\n", + "\n", + "For each pedestrians now the frames when they enter and leave the virtual {class}`measurement area ` is computed. \n", + "In this frame interval they have to be inside the {class}`measurement area ` continuously. \n", + "They also need to enter and leave the {class}`measurement area ` via different {class}`measurement lines `.\n", + "If leaving the area between the two lines, crossing the same line twice they will be ignored. \n", + "For a better understanding, see the image below, where red parts of the trajectories are the detected ones inside the area. \n", + "These frame intervals will be returned.\n", "\n", - "[Pathfinder](https://www.thunderheadeng.com/pathfinder/) produces trajectory data in two formats: csv and json.\n", + "```{eval-rst}\n", + ".. image:: /images/frames_in_area.svg\n", + " :width: 80 %\n", + " :align: center\n", + "```\n", "\n", - "Both formats are supported by *PedPy* using the following calls:" + "In this our example, we want to measure from the entrance of the bottleneck (top line) 1m towards the exit of the bottleneck (bottom line).\n", + "The set-up is shown below:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [ + "remove-input" + ] + }, "outputs": [], "source": [ - "import pathlib\n", - "\n", - "from pedpy import (\n", - " load_trajectory_from_pathfinder_csv,\n", - " load_trajectory_from_pathfinder_json,\n", - ")\n", + "from pedpy import compute_frame_range_in_area\n", "\n", - "traj_pathfinder_csv = load_trajectory_from_pathfinder_csv(\n", - " trajectory_file=pathlib.Path(\"demo-data/pathfinder/pathfinder.csv\")\n", - ")\n", - "traj_pathfinder_json = load_trajectory_from_pathfinder_json(\n", - " trajectory_file=pathlib.Path(\"demo-data/pathfinder/pathfinder.json\")\n", - ")" + "frames_in_area, used_area = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=1.0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -592,16 +600,19 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import plot_measurement_setup\n", "\n", - "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", - "plot_trajectories(traj=traj_pathfinder_csv, axes=ax[0]).set_aspect(\"equal\")\n", - "plot_trajectories(\n", - " traj=traj_pathfinder_json,\n", - " axes=ax[1],\n", + "plot_measurement_setup(\n", + " measurement_areas=[used_area],\n", + " measurement_lines=[\n", + " measurement_line,\n", + " MeasurementLine(shapely.offset_curve(measurement_line.line, 1.0)),\n", + " ],\n", + " ml_width=2,\n", + " ma_line_width=0,\n", + " ma_alpha=0.2,\n", + " walkable_area=walkable_area,\n", ").set_aspect(\"equal\")\n", - "ax[0].set_title(\"Pathfinder CSV\")\n", - "ax[1].set_title(\"Pathfinder JSON\")\n", "plt.show()" ] }, @@ -609,29 +620,55 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Loading from Crowd:it trajectory files\n", + "The passing density for each pedestrian, $\\rho_{passing}(i)$, represents the average number of pedestrians present in the same {class}`measurement area ` $M$ during the same time interval ($[t_{in}(i), t_{out}(i)]$) as pedestrian $i$, divided by the area of that {class}`measurement area ` $A(M)$.\n", + "\n", + "The computation is expressed as:\n", + "\n", + "$$\n", + " \\rho_{passing}(i) = {1 \\over {t_{out}(i) - t_{in}(i)}}\n", + " \\int_{t_{in}(i)}^{t_{out}(i)} \\frac{N(t)}{A(M)} \\, dt\n", + "$$\n", + "\n", + "where:\n", + "- $t_{in}(i) = f_{in}(i) / fps$ is the time when pedestrian $i$ crosses the first line, \n", + "- $t_{out}(i) = f_{out}(i) / fps$ is the time when they cross the second line,\n", + "- $f_{in}(i)$ and $f_{out}(i)$ are the respective frame indices for crossing the first and second lines, and \n", + "- $fps$ is the frame rate of the {class}`trajectory data`.\n", "\n", - "[Crowd:it](https://www.accu-rate.de/en/software/crowdit/) generates a trajectory file and a geometry file. *PedPy* reads both out of the box as follows:" + "To compute the passing density inside the bottleneck, the formula can be applied with the appropriate measurement area and time intervals:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "outputs": [], "source": [ - "import pathlib\n", - "\n", - "from pedpy import load_trajectory_from_crowdit, load_walkable_area_from_crowdit\n", + "from pedpy import compute_frame_range_in_area, compute_passing_density\n", "\n", - "traj_crowdit = load_trajectory_from_crowdit(trajectory_file=pathlib.Path(\"demo-data/crowdit/crowdit.csv.gz\"))\n", - "geometry_crowdit = load_walkable_area_from_crowdit(geometry_file=pathlib.Path(\"demo-data/crowdit/crowdit.floor\"))" + "frames_in_area, used_area = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=1.0)\n", + "passing_density = compute_passing_density(density_per_frame=classic_density, frames=frames_in_area)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This provides a single density value for each pedestrian.\n", + "The following plot illustrates how individual densities are distributed within the bottleneck:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -640,9 +677,9 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import plot_density_distribution\n", "\n", - "plot_trajectories(traj=traj_crowdit, walkable_area=geometry_crowdit).set_aspect(\"equal\")\n", + "plot_density_distribution(density=passing_density, title=\"Individual density inside bottleneck\")\n", "plt.show()" ] }, @@ -650,10 +687,24 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Loading from JuPedSim trajectory files\n", + "### Line Density\n", + "\n", + "Similar to the Voronoi density within a measurement area, the line density $\\rho_{line}$ can be employed to determine the density at a measurement line $l$. In this approach, pedestrian densities are weighted based on the proportion of the total length of the line $w$ relative to the length of the intersection between the line and the Voronoi cell $w_i(t)$. \n", + "\n", + "The line density is mathematically defined as:\n", + "\n", + "$$\n", + "\\rho_\\text{line}(t) = \\sum_{{i \\,|\\, V_i(t) \\cap l \\neq \\emptyset}} \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w},\n", + "$$\n", + "\n", + "where:\n", + "- $A_i(t)$ is the area of the Voronoi cell $V_i(t)$ for pedestrian $i$ at time $t$, \n", + "- $w_i(t)$ is the length of the intersection between the Voronoi cell $V_i(t)$ and the measurement line $l$, and\n", + "- $w$ is the total length of the measurement line $l$.\n", + "\n", + "The line density can also be applied in scenarios where pedestrians approach the line from both sides. This allows for the analysis of data corresponding to different \"species\" of pedestrians (e.g., different directional flows or groups). However, in this example, only one species is considered, as the line is approached from a single side.\n", "\n", - "[JuPedSim](https://jupedsim.org) produces trajectory data in SQLite format containing the trajectories as well as the geometry data in WKT format. \n", - "*PedPy* supports both natively:" + "For further details, refer to [this](fundamental_diagram_at_measurement_line) Notebook." ] }, { @@ -662,16 +713,19 @@ "metadata": {}, "outputs": [], "source": [ - "from pedpy import (\n", - " load_trajectory_from_jupedsim_sqlite,\n", - " load_walkable_area_from_jupedsim_sqlite,\n", - ")\n", + "from pedpy import compute_line_density, compute_species\n", "\n", - "traj_jupedsim = load_trajectory_from_jupedsim_sqlite(\n", - " trajectory_file=pathlib.Path(\"demo-data/jupedsim/trajectories.sqlite\")\n", + "species = compute_species(\n", + " trajectory_data=traj,\n", + " individual_voronoi_polygons=individual_cutoff,\n", + " frame_step=int(traj.frame_rate),\n", + " measurement_line=measurement_line,\n", ")\n", - "geometry_jupedsim = load_walkable_area_from_jupedsim_sqlite(\n", - " trajectory_file=pathlib.Path(\"demo-data/jupedsim/trajectories.sqlite\")\n", + "\n", + "line_density = compute_line_density(\n", + " individual_voronoi_polygons=individual_cutoff,\n", + " measurement_line=measurement_line,\n", + " species=species,\n", ")" ] }, @@ -685,73 +739,88 @@ }, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_trajectories\n", + "from pedpy import plot_density\n", "\n", - "plot_trajectories(traj=traj_jupedsim, walkable_area=geometry_jupedsim).set_aspect(\"equal\")\n", + "plot_density(density=line_density, title=\"density at measurement line\")\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "### Plot setup\n", + "## Speed\n", "\n", - "For a better overview of our created measurement setup, see the plot below:" + "A further important measure in pedestrian dynamics is the speed of the pedestrians.\n", + "Low speeds can indicate congestions or other obstructions in the flow of the crowd." ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "jupyter": { "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] + } }, - "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", + "### Individual speed\n", "\n", - "from pedpy import plot_measurement_setup\n", + "For computing the individuals speed at a specific frame $v_i(t)$, a specific frame step ($n$) is needed.\n", + "Together with the frame rate of the {class}`trajectory data` $fps$ the time frame $\\Delta t$ for computing the speed becomes:\n", "\n", - "plot_measurement_setup(\n", - " traj=traj,\n", - " walkable_area=walkable_area,\n", - " measurement_areas=[measurement_area],\n", - " measurement_lines=[\n", - " measurement_line,\n", - " ],\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - " ml_width=2,\n", - " ma_alpha=0.2,\n", - " ma_line_width=2,\n", - ").set_aspect(\"equal\")\n", - "plt.show()" + "$$\n", + " \\Delta t = 2 n / fps\n", + "$$\n", + "\n", + "This time step describes how many frames before and after the current position $X_{current}$ are used to compute the movement.\n", + "These positions are called $X_{future}$, $X_{past}$, respectively.\n", + "\n", + "```{eval-rst}\n", + ".. image:: /images/speed_both.svg\n", + " :width: 80 %\n", + " :align: center\n", + "```\n", + "\n", + "First computing the displacement between these positions $\\bar{X}$.\n", + "This then can be used to compute the speed with:\n", + "\n", + "\\begin{align}\n", + " \\bar{X} &= X_{future} - X_{past} \\\\\n", + " v_i(t) &= \\frac{\\bar{X}}{\\Delta t}\n", + "\\end{align}\n", + "\n", + "When getting closer to the start, or end of the {class}`trajectory data`, it is not possible to use the full range of the frame interval for computing the speed.\n", + "For these cases *PedPy* offers three different methods to compute the speed:\n", + "\n", + "1. exclude these parts\n", + "2. adaptively shrink the window in which the speed is computed\n", + "3. switch to one-sided window\n", + "\n", + "#### Exclude border\n", + "\n", + "When not enough frames available to compute the speed at the borders, for these parts no speed can be computed and they are ignored.\n" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": { "jupyter": { "outputs_hidden": false } }, + "outputs": [], "source": [ - "### Validate that trajectory is completely inside the walkable area.\n", + "from pedpy import SpeedCalculation, compute_individual_speed\n", + "\n", + "frame_step = 25\n", "\n", - "An important step before starting the analysis is to verify that all trajectories lie within the constructed {class}`walkable area `.\n", - "Otherwise, you might get errors.\n", - "*PedPy* provides a function to test your trajectories, and offers also a function to get all invalid trajectories:" + "individual_speed_exclude = compute_individual_speed(\n", + " traj_data=traj,\n", + " frame_step=frame_step,\n", + " compute_velocity=True,\n", + " speed_calculation=SpeedCalculation.BORDER_EXCLUDE,\n", + ")" ] }, { @@ -760,14 +829,31 @@ "metadata": { "jupyter": { "outputs_hidden": false - } + }, + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ - "from pedpy import get_invalid_trajectory, is_trajectory_valid\n", + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import PEDPY_GREEN\n", + "\n", + "ped_id = 25\n", + "\n", + "plt.figure()\n", + "plt.title(f\"Speed time-series of a pedestrian {ped_id} (border excluded)\")\n", + "single_individual_speed = individual_speed_exclude[individual_speed_exclude.id == ped_id]\n", + "plt.plot(\n", + " single_individual_speed.frame,\n", + " single_individual_speed.speed,\n", + " color=PEDPY_GREEN,\n", + ")\n", "\n", - "print(f\"Trajectory is valid: {is_trajectory_valid(traj_data=traj, walkable_area=walkable_area)}\")\n", - "get_invalid_trajectory(traj_data=traj, walkable_area=walkable_area)" + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"v / m/s\")\n", + "plt.show()" ] }, { @@ -778,10 +864,23 @@ } }, "source": [ - "**For demonstration purposes, wrongly place the obstacle s.th. some pedestrian walk through it!**\n", + "#### Adaptive border window\n", + "\n", + "In the adaptive approach, it is checked how many frames $n$ are available to from $X_{current}$ to the end of the trajectory.\n", + "This number is then used on both sides to create a smaller symmetric window, which yields $X_{past}$ and $X_{future}$.\n", + "Now with the same principles as before the individual speed $v_i(t)$ can be computed.\n", + "\n", + "```{eval-rst}\n", + ".. image:: /images/speed_border_adaptive_future.svg\n", + " :width: 46 %\n", + " \n", + ".. image:: /images/speed_border_adaptive_past.svg\n", + " :width: 46 %\n", + "```\n", "\n", - "We now create a faulty geometry, s.th. you can see how the result would like.\n", - "Therefore, the right obstacle will be moved a bit towards the center of the bottlneck:" + ":::{important}\n", + "As the time interval gets smaller to the ends of the individual trajectories, the oscillations in the speed increase here.\n", + ":::" ] }, { @@ -790,51 +889,17 @@ "metadata": { "jupyter": { "outputs_hidden": false - }, - "tags": [ - "hide-cell" - ] + } }, "outputs": [], "source": [ - "from pedpy import WalkableArea\n", + "from pedpy import SpeedCalculation, compute_individual_speed\n", "\n", - "walkable_area_faulty = WalkableArea(\n", - " # complete area\n", - " [\n", - " (3.5, -2),\n", - " (3.5, 8),\n", - " (-3.5, 8),\n", - " (-3.5, -2),\n", - " ],\n", - " obstacles=[\n", - " # left barrier\n", - " [\n", - " (-0.7, -1.1),\n", - " (-0.25, -1.1),\n", - " (-0.25, -0.15),\n", - " (-0.4, 0.0),\n", - " (-2.8, 0.0),\n", - " (-2.8, 6.7),\n", - " (-3.05, 6.7),\n", - " (-3.05, -0.3),\n", - " (-0.7, -0.3),\n", - " (-0.7, -1.0),\n", - " ],\n", - " # right barrier is too close to the middle\n", - " [\n", - " (0.15, -1.1),\n", - " (0.6, -1.1),\n", - " (0.6, -0.3),\n", - " (3.05, -0.3),\n", - " (3.05, 6.7),\n", - " (2.8, 6.7),\n", - " (2.8, 0.0),\n", - " (0.3, 0.0),\n", - " (0.15, -0.15),\n", - " (0.15, -1.1),\n", - " ],\n", - " ],\n", + "individual_speed_adaptive = compute_individual_speed(\n", + " traj_data=traj,\n", + " frame_step=frame_step,\n", + " compute_velocity=True,\n", + " speed_calculation=SpeedCalculation.BORDER_ADAPTIVE,\n", ")" ] }, @@ -842,6 +907,9 @@ "cell_type": "code", "execution_count": null, "metadata": { + "jupyter": { + "outputs_hidden": false + }, "tags": [ "hide-input" ] @@ -850,77 +918,20 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import PEDPY_RED\n", "\n", - "ax = plot_measurement_setup(\n", - " traj=traj,\n", - " walkable_area=walkable_area_faulty,\n", - " traj_alpha=0.5,\n", - " traj_width=1,\n", - " hole_color=\"lightgrey\",\n", + "plt.figure()\n", + "plt.title(f\"Speed time-series of an pedestrian {ped_id} (adaptive)\")\n", + "single_individual_speed = individual_speed_adaptive[individual_speed_adaptive.id == ped_id]\n", + "plt.plot(\n", + " single_individual_speed.frame,\n", + " single_individual_speed.speed,\n", + " color=PEDPY_RED,\n", ")\n", - "ax.set_xlim([-1, 1])\n", - "ax.set_ylim([-1, 1])\n", - "ax.set_xticks([-1, -0.5, 0, 0.5, 1])\n", - "ax.set_yticks([-1, -0.5, 0, 0.5, 1])\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If you get any invalid trajectories, you should check whether you constructed your {class}`walkable area ` correctly.\n", - "In some cases you will get such errors when you have head trajectories, and the pedestrian lean over the obstacles.\n", - "Then you need to prepare your data before you can start your analysis." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "from pedpy import get_invalid_trajectory, is_trajectory_valid\n", - "\n", - "print(f\"Trajectory is valid: {is_trajectory_valid(traj_data=traj, walkable_area=walkable_area_faulty)}\")\n", - "get_invalid_trajectory(traj_data=traj, walkable_area=walkable_area_faulty)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Analysis\n", - "\n", - "Now that we set up the analysis environment, we can start with the real analysis.\n", - "*PedPy* provides different methods to obtain multiple metric from the {class}`trajectory data`:\n", - "\n", - "- Density\n", - "- Speed\n", - "- Flow\n", - "- Neighborhood\n", - "- Distance/Time to entrance\n", - "- Profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "### Density\n", "\n", - "Density is a fundamental metric in pedestrian dynamics.\n", - "As it indicated how much space is accessible to each pedestrian within a specific area.\n", - "High density can lead to reduced walking speeds, increased congestion, and even potential safety hazards." + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"v / m/s\")\n", + "plt.show()" ] }, { @@ -931,15 +942,26 @@ } }, "source": [ - "#### Classic density\n", + "#### Single sided border window\n", "\n", - "The classic approach to calculate the density $\\rho_{classic}(t)$ at a time $t$, is to count the number of pedestrians ($N(t)$) inside a specific space ($M$) and divide it by the area of that space ($A(M)$).\n", + "In these cases, one of the end points to compute the movement becomes the current position $X_{current}$.\n", + "When getting too close to the start of the trajectory, the movement is computed from $X_{current}$ to $X_{future}$.\n", + "In the other case the movement is from $X_{past}$ to $X_{current}$.\n", "\n", "$$\n", - "\\rho_{classic}(t) = {N(t) \\over A(M)}\n", + " v_i(t) = {|{X_{future} - X_{current}|}\\over{ \\frac{1}{2} \\Delta t}} \\text{, or } v_i(t) = {|{X_{current} - X_{past}|}\\over{ \\frac{1}{2} \\Delta t}}\n", "$$\n", "\n", - "In *PedPy* this can be computed with:\n" + "```{eval-rst}\n", + ".. image:: /images/speed_border_single_sided_future.svg\n", + " :width: 46 %\n", + ".. image:: /images/speed_border_single_sided_past.svg\n", + " :width: 46 %\n", + "```\n", + "\n", + ":::{important}\n", + "As at the edges of the trajectories the time interval gets halved, there may occur some jumps computed speeds at this point.\n", + ":::" ] }, { @@ -952,16 +974,14 @@ }, "outputs": [], "source": [ - "from pedpy import compute_classic_density\n", + "from pedpy import SpeedCalculation, compute_individual_speed\n", "\n", - "classic_density = compute_classic_density(traj_data=traj, measurement_area=measurement_area)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The resulting time-series can be seen below:" + "individual_speed_single_sided = compute_individual_speed(\n", + " traj_data=traj,\n", + " frame_step=frame_step,\n", + " compute_velocity=True,\n", + " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", + ")" ] }, { @@ -979,9 +999,19 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_density\n", + "from pedpy import PEDPY_GREY\n", "\n", - "plot_density(density=classic_density, title=\"Classic density\")\n", + "plt.figure()\n", + "plt.title(f\"Speed time-series of an pedestrian {ped_id} (single sided)\")\n", + "single_individual_speed = individual_speed_single_sided[individual_speed_single_sided.id == ped_id]\n", + "plt.plot(\n", + " single_individual_speed.frame,\n", + " single_individual_speed.speed,\n", + " color=PEDPY_GREY,\n", + ")\n", + "\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"v / m/s\")\n", "plt.show()" ] }, @@ -993,31 +1023,9 @@ } }, "source": [ - "(voronoi_density)=\n", - " #### Voronoi density\n", - "\n", - "Another approach for calculating the density is to compute the [Voronoi tesselation](https://en.wikipedia.org/wiki/Voronoi_diagram) of the pedestrians positions at a given time $t$, the resulting Voronoi polygons ($V$) directly relate to the individual's density.\n", - "For a pedestrian $i$ the individual density is defined as:\n", - "\n", - "$$\n", - "\\rho_i(t) = {1 \\over A(V_i(t))}\n", - "$$\n", - "\n", - "\n", - "##### Compute individual Voronoi Polygons\n", - "\n", - "The first step for computing the Voronoi density, is to compute the individual's Voronoi polygon.\n", - "As these polygons may become infinite for pedestrians at the edge of the crowd, these polygons are restricted by the {class}`walkable area `.\n", - "This cutting at the boundaries can lead to split Voronoi polygons. For each of the split polygons it is checked, in which the pedestrian is located. \n", - "This polygon then is assigned.\n", - "\n", - ":::{important}\n", - "As these Voronoi polygons work on the Euclidean distance, some unexpected artifacts may occur on non-convex {class}`walkable areas `. Please keep that in mind! How that may look like, you can see in the plots later in this guide.\n", - ":::\n", - "\n", - "###### Without cut-off\n", + "#### Comparison\n", "\n", - "The computation of the individual polygons can be done from the {class}`trajectory data` and {class}`walkable area ` with:" + "To demonstrate the differences in the computed speeds, take a look at the following plot:\n" ] }, { @@ -1026,36 +1034,114 @@ "metadata": { "jupyter": { "outputs_hidden": false - } + }, + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ - "from pedpy import compute_individual_voronoi_polygons\n", + "import matplotlib.pyplot as plt\n", "\n", - "individual = compute_individual_voronoi_polygons(traj_data=traj, walkable_area=walkable_area)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "from pedpy import PEDPY_GREEN, PEDPY_GREY, PEDPY_RED\n", + "\n", + "fig, ax = plt.subplots(1, 3, gridspec_kw={\"width_ratios\": [2, 1, 1]}, sharey=True, figsize=(12, 5))\n", + "\n", + "fig.suptitle(\"Comparison of the different speed calculations at the borders\")\n", + "speed_exclude = individual_speed_exclude[individual_speed_exclude.id == ped_id]\n", + "speed_adaptive = individual_speed_adaptive[individual_speed_adaptive.id == ped_id]\n", + "speed_single_sided = individual_speed_single_sided[individual_speed_single_sided.id == ped_id]\n", + "\n", + "ax[0].plot(\n", + " speed_single_sided.frame,\n", + " speed_single_sided.speed,\n", + " color=PEDPY_GREY,\n", + " linewidth=3,\n", + " label=\"single sided\",\n", + ")\n", + "ax[0].plot(\n", + " speed_adaptive.frame,\n", + " speed_adaptive.speed,\n", + " color=PEDPY_RED,\n", + " linewidth=3,\n", + " label=\"adaptive\",\n", + ")\n", + "ax[0].plot(\n", + " speed_exclude.frame,\n", + " speed_exclude.speed,\n", + " color=PEDPY_GREEN,\n", + " linewidth=3,\n", + " label=\"excluded\",\n", + ")\n", + "ax[0].set_xlabel(\"frame\")\n", + "ax[0].set_ylabel(\"v / m/s\")\n", + "ax[0].legend()\n", + "\n", + "ax[1].plot(\n", + " speed_single_sided.frame[speed_single_sided.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " speed_single_sided.speed[speed_single_sided.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " color=PEDPY_GREY,\n", + " linewidth=3,\n", + ")\n", + "ax[1].plot(\n", + " speed_adaptive.frame[speed_adaptive.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " speed_adaptive.speed[speed_adaptive.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " color=PEDPY_RED,\n", + " linewidth=3,\n", + ")\n", + "ax[1].plot(\n", + " speed_exclude.frame[speed_exclude.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " speed_exclude.speed[speed_exclude.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", + " color=PEDPY_GREEN,\n", + " linewidth=3,\n", + ")\n", + "ax[1].set_xlabel(\"frame\")\n", + "\n", + "ax[2].plot(\n", + " speed_single_sided.frame[speed_single_sided.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " speed_single_sided.speed[speed_single_sided.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " color=PEDPY_GREY,\n", + " linewidth=3,\n", + ")\n", + "ax[2].plot(\n", + " speed_adaptive.frame[speed_adaptive.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " speed_adaptive.speed[speed_adaptive.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " color=PEDPY_RED,\n", + " linewidth=3,\n", + ")\n", + "ax[2].plot(\n", + " speed_exclude.frame[speed_exclude.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " speed_exclude.speed[speed_exclude.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + " color=PEDPY_GREEN,\n", + " linewidth=3,\n", + ")\n", + "\n", + "ax[2].set_xlabel(\"frame\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ - "###### With cut-off\n", + "#### Individual speed in specific movement direction\n", "\n", - "When having a large {class}`walkable area ` or widely spread pedestrians the Voronoi polygons may become quite large.\n", - "In *PedPy* it is possible to restrict the size of the computed Polygons.\n", - "This can be done by defining a {class}`cut off `, which is essentially an approximated circle which gives the maximum extension of a single Voronoi polygon.\n", - "For the creation of the {class}`cut off `, we need to define how accurate we want to approximate the circle, the differences can be seen below:\n", + "It is also possible to compute the individual speed in a specific direction $d$, for this the movement $\\bar{X}$ is projected onto the desired movement direction. $\\bar{X}$ and $\\Delta t$ are computed as described above. \n", + "Hence, the speed then becomes:\n", + "\n", + "$$\n", + " v_i(t) = {{|\\boldsymbol{proj}_d\\; \\bar{X}|} \\over {\\Delta t}}\n", + "$$\n", "\n", "```{eval-rst}\n", - ".. figure:: images/voronoi_cutoff_differences.svg\n", + ".. image:: /images/speed_movement_direction.svg\n", + " :width: 80 %\n", " :align: center\n", "```\n", "\n", - "Now, with that {class}`cut off ` the computation of the individual polygons becomes:" + ":::{important}\n", + "When using a specific direction, the computed speed may become negative.\n", + ":::" ] }, { @@ -1068,28 +1154,15 @@ }, "outputs": [], "source": [ - "from pedpy import Cutoff, compute_individual_voronoi_polygons\n", - "\n", - "individual_cutoff = compute_individual_voronoi_polygons(\n", + "individual_speed_direction = compute_individual_speed(\n", " traj_data=traj,\n", - " walkable_area=walkable_area,\n", - " cut_off=Cutoff(radius=1.0, quad_segments=3),\n", + " frame_step=5,\n", + " movement_direction=np.array([0, -1]),\n", + " compute_velocity=True,\n", + " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", ")" ] }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "###### Comparison\n", - "\n", - "To get a better impression what the differences between the Voronoi polygons with and without the {class}`cut off ` are, take a look at the plot below:" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1103,59 +1176,25 @@ }, "outputs": [], "source": [ - "import matplotlib as mpl\n", "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import DENSITY_COL, FRAME_COL, ID_COL, plot_voronoi_cells\n", - "\n", - "frame = 600\n", - "\n", - "fig = plt.figure(f\"frame = {frame}\")\n", - "fig.suptitle(f\"frame = {frame}\")\n", - "\n", - "ax1 = fig.add_subplot(121, aspect=\"equal\")\n", - "ax1.set_title(\"w/o cutoff\")\n", - "plot_voronoi_cells(\n", - " voronoi_data=individual,\n", - " traj_data=traj,\n", - " frame=frame,\n", - " walkable_area=walkable_area,\n", - " color_by_column=DENSITY_COL,\n", - " axes=ax1,\n", - " show_colorbar=False,\n", - " vmin=0,\n", - " vmax=10,\n", - ")\n", - "\n", - "ax2 = fig.add_subplot(122, aspect=\"equal\")\n", - "ax2.set_title(\"w cutoff\")\n", + "from pedpy import PEDPY_BLUE, PEDPY_GREEN, PEDPY_GREY, PEDPY_RED\n", "\n", - "plot_voronoi_cells(\n", - " voronoi_data=individual_cutoff,\n", - " traj_data=traj,\n", - " frame=frame,\n", - " walkable_area=walkable_area,\n", - " color_by_column=DENSITY_COL,\n", - " axes=ax2,\n", - " show_colorbar=False,\n", - " vmin=0,\n", - " vmax=10,\n", - ")\n", - "cbar_ax = fig.add_axes([0.1, -0.05, 0.88, 0.05])\n", + "colors = [PEDPY_BLUE, PEDPY_GREY, PEDPY_RED, PEDPY_GREEN]\n", + "ped_ids = [10, 20, 17, 70]\n", "\n", - "norm = mpl.colors.Normalize(vmin=0, vmax=10)\n", - "sm = plt.cm.ScalarMappable(cmap=plt.get_cmap(\"YlGn\"), norm=norm)\n", - "sm.set_array([])\n", - "plt.colorbar(\n", - " sm,\n", - " cax=cbar_ax,\n", - " shrink=0.1,\n", - " label=\"$\\\\rho$ \\\\ 1/$m^2$\",\n", - " aspect=2,\n", - " orientation=\"horizontal\",\n", - ")\n", + "fig = plt.figure()\n", + "plt.title(\"Velocity time-series of an excerpt of the pedestrians in a specific direction\")\n", + "for color, ped_id in zip(colors, ped_ids, strict=False):\n", + " single_individual_speed = individual_speed_direction[individual_speed_direction.id == ped_id]\n", + " plt.plot(\n", + " single_individual_speed.frame,\n", + " single_individual_speed.speed,\n", + " color=color,\n", + " )\n", "\n", - "fig.tight_layout()\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"v / m/s\")\n", "plt.show()" ] }, @@ -1167,19 +1206,25 @@ } }, "source": [ - "##### Compute actual Voronoi density\n", + "(mean_speed)=\n", + " ### Mean speed\n", "\n", - "From these individual data we can now compute the Voronoi density $\\rho_{voronoi}(t)$ in the known {class}`measurement area ` ($M$):\n", + "Now, that we have computed the individual's speed, we want to compute the mean speed in the already used {class}`measurement area ` $M$ closely in front of the bottleneck.\n", + "The mean speed is defined as\n", "\n", "$$\n", - " \\rho_{voronoi}(t) = { \\int\\int \\rho_{xy}(t) dxdy \\over A(M)},\n", + " v_{mean}(t) = {{1} \\over {N}} \\sum_{i \\in P_M} v_i(t), \n", "$$\n", "\n", - "where $\\rho_{xy}(t) = 1 / A(V_i(t))$ is the individual density of each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of the {class}`measurement area `.\n", + "where $P_M$ are all pedestrians inside the {class}`measurement area `, and $N$ the number of pedestrians inside the {class}`measurement area ` ($|P_M|$).\n", "\n", - "###### Without cut-off\n", + ":::{important}\n", + "The mean speed can only be computed when for each pedestrian inside the {class}`measurement area ` also a speed $v_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", + "Then some extra processing steps are needed, to avoid this use the single sided approach. \n", + ":::\n", "\n", - "First, we compute the Voronoi density in the {class}`measurement area ` without a {class}`cut off `:" + "This can be as follows with *PedPy*:\n", + " " ] }, { @@ -1192,10 +1237,12 @@ }, "outputs": [], "source": [ - "from pedpy import compute_voronoi_density\n", + "from pedpy import compute_mean_speed_per_frame\n", "\n", - "density_voronoi, intersecting = compute_voronoi_density(\n", - " individual_voronoi_data=individual, measurement_area=measurement_area\n", + "mean_speed = compute_mean_speed_per_frame(\n", + " traj_data=traj,\n", + " measurement_area=measurement_area,\n", + " individual_speed=individual_speed_single_sided,\n", ")" ] }, @@ -1214,23 +1261,21 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_ORANGE, plot_density\n", + "from pedpy import PEDPY_BLUE, plot_speed\n", "\n", - "plot_density(density=density_voronoi, title=\"Voronoi density\", color=PEDPY_ORANGE)\n", + "plot_speed(\n", + " speed=mean_speed.speed,\n", + " title=\"Mean speed in front of the bottleneck\",\n", + " color=PEDPY_BLUE,\n", + ")\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "###### With cut-off\n", - "\n", - "Second, we compute it now from the individual {class}`cut off ` Voronoi polygons:\n" + "The same can be now computed, using the speed in a movement direction as basis:" ] }, { @@ -1243,10 +1288,10 @@ }, "outputs": [], "source": [ - "from pedpy import compute_voronoi_density\n", - "\n", - "density_voronoi_cutoff, intersecting_cutoff = compute_voronoi_density(\n", - " individual_voronoi_data=individual_cutoff, measurement_area=measurement_area\n", + "mean_speed_direction = compute_mean_speed_per_frame(\n", + " traj_data=traj,\n", + " measurement_area=measurement_area,\n", + " individual_speed=individual_speed_direction,\n", ")" ] }, @@ -1265,12 +1310,12 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_GREY, plot_density\n", + "from pedpy import PEDPY_RED, plot_speed\n", "\n", - "plot_density(\n", - " density=density_voronoi_cutoff,\n", - " title=\"Voronoi density with cut-off\",\n", - " color=PEDPY_GREY,\n", + "plot_speed(\n", + " speed=mean_speed_direction.speed,\n", + " title=\"Mean speed in specific direction in front of the bottleneck\",\n", + " color=PEDPY_RED,\n", ")\n", "plt.show()" ] @@ -1283,106 +1328,52 @@ } }, "source": [ - "#### Comparison\n", + "(voronoi_speed)=\n", + " ### Voronoi speed\n", "\n", - "Now we have obtained the mean density inside the {class}`measurement area ` with different methods.\n", - "To compare the results take a look at the following plot:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", + "A further approach to compute average speed $v_{voronoi}(t)$ in an area by weighting the individuals speed by the size of their corresponding Voronoi polygon $V_i$ inside the {class}`measurement area ` $M$.\n", + "The individuals speed are weighted by the proportion of their Voronoi cell $V_i$ and the intersection with the {class}`measurement area ` $V_i \\cap M$.\n", "\n", - "from pedpy import PEDPY_BLUE, PEDPY_GREY, PEDPY_ORANGE\n", + "The Voronoi speed $v_{voronoi}(t)$ is defined as\n", "\n", - "fig = plt.figure()\n", - "plt.title(\"Comparison of different density methods\")\n", - "plt.plot(\n", - " classic_density.frame,\n", - " classic_density.density,\n", - " label=\"classic\",\n", - " color=PEDPY_BLUE,\n", - ")\n", - "plt.plot(\n", - " density_voronoi.frame,\n", - " density_voronoi.density,\n", - " label=\"voronoi\",\n", - " color=PEDPY_ORANGE,\n", - ")\n", - "plt.plot(\n", - " density_voronoi_cutoff.frame,\n", - " density_voronoi_cutoff.density,\n", - " label=\"voronoi with cutoff\",\n", - " color=PEDPY_GREY,\n", - ")\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"$\\\\rho$ / 1/$m^2$\")\n", - "plt.grid()\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "(passing_density)=\n", - " #### Passing density (individual)\n", + "$$\n", + " v_{voronoi}(t) = { \\int\\int v_{xy}(t) dxdy \\over A(M)},\n", + "$$\n", "\n", - "Another option to compute the individual density, is the passing density. \n", - "For the computation it needs a {class}`measurement line ` and the distance to a second \"virtual\" {class}`measurement line ` which form a \"virtual\" {class}`measurement area ` ($M$).\n", + "where $v_{xy}(t) = v_i(t)$ is the individual speed of each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of the {class}`measurement area `.\n", "\n", "```{eval-rst}\n", - ".. image:: /images/passing_area_from_lines.svg\n", - " :width: 80 %\n", + ".. image:: /images/voronoi_density.svg\n", + " :width: 60 %\n", " :align: center\n", "```\n", "\n", - "For each pedestrians now the frames when they enter and leave the virtual {class}`measurement area ` is computed. \n", - "In this frame interval they have to be inside the {class}`measurement area ` continuously. \n", - "They also need to enter and leave the {class}`measurement area ` via different {class}`measurement lines `.\n", - "If leaving the area between the two lines, crossing the same line twice they will be ignored. \n", - "For a better understanding, see the image below, where red parts of the trajectories are the detected ones inside the area. \n", - "These frame intervals will be returned.\n", - "\n", - "```{eval-rst}\n", - ".. image:: /images/frames_in_area.svg\n", - " :width: 80 %\n", - " :align: center\n", - "```\n", + ":::{important}\n", + "The Voronoi speed can only be computed when for each pedestrian inside the {class}`measurement area ` also a speed $v_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", + "Then some extra processing steps are needed, to avoid this use the single sided approach. \n", + ":::\n", "\n", - "In this our example, we want to measure from the entrance of the bottleneck (top line) 1m towards the exit of the bottleneck (bottom line).\n", - "The set-up is shown below:" + "This can be done in *PedPy* with:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "tags": [ - "remove-input" - ] + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ - "from pedpy import compute_frame_range_in_area\n", + "from pedpy import compute_voronoi_speed\n", "\n", - "frames_in_area, used_area = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=1.0)" + "voronoi_speed = compute_voronoi_speed(\n", + " traj_data=traj,\n", + " individual_voronoi_intersection=intersecting,\n", + " individual_speed=individual_speed_single_sided,\n", + " measurement_area=measurement_area,\n", + ")" ] }, { @@ -1400,19 +1391,13 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import PEDPY_ORANGE, plot_speed\n", "\n", - "plot_measurement_setup(\n", - " measurement_areas=[used_area],\n", - " measurement_lines=[\n", - " measurement_line,\n", - " MeasurementLine(shapely.offset_curve(measurement_line.line, 1.0)),\n", - " ],\n", - " ml_width=2,\n", - " ma_line_width=0,\n", - " ma_alpha=0.2,\n", - " walkable_area=walkable_area,\n", - ").set_aspect(\"equal\")\n", + "plot_speed(\n", + " speed=voronoi_speed.speed,\n", + " title=\"Voronoi speed in front of the bottleneck\",\n", + " color=PEDPY_ORANGE,\n", + ")\n", "plt.show()" ] }, @@ -1420,22 +1405,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The passing density for each pedestrian, $\\rho_{passing}(i)$, represents the average number of pedestrians present in the same {class}`measurement area ` $M$ during the same time interval ($[t_{in}(i), t_{out}(i)]$) as pedestrian $i$, divided by the area of that {class}`measurement area ` $A(M)$.\n", - "\n", - "The computation is expressed as:\n", - "\n", - "$$\n", - " \\rho_{passing}(i) = {1 \\over {t_{out}(i) - t_{in}(i)}}\n", - " \\int_{t_{in}(i)}^{t_{out}(i)} \\frac{N(t)}{A(M)} \\, dt\n", - "$$\n", - "\n", - "where:\n", - "- $t_{in}(i) = f_{in}(i) / fps$ is the time when pedestrian $i$ crosses the first line, \n", - "- $t_{out}(i) = f_{out}(i) / fps$ is the time when they cross the second line,\n", - "- $f_{in}(i)$ and $f_{out}(i)$ are the respective frame indices for crossing the first and second lines, and \n", - "- $fps$ is the frame rate of the {class}`trajectory data`.\n", - "\n", - "To compute the passing density inside the bottleneck, the formula can be applied with the appropriate measurement area and time intervals:" + "Analogously, this can be done with the speed in a specific direction with:" ] }, { @@ -1448,27 +1418,56 @@ }, "outputs": [], "source": [ - "from pedpy import compute_frame_range_in_area, compute_passing_density\n", + "voronoi_speed_direction = compute_voronoi_speed(\n", + " traj_data=traj,\n", + " individual_voronoi_intersection=intersecting,\n", + " individual_speed=individual_speed_direction,\n", + " measurement_area=measurement_area,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", "\n", - "frames_in_area, used_area = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=1.0)\n", - "passing_density = compute_passing_density(density_per_frame=classic_density, frames=frames_in_area)" + "from pedpy import PEDPY_GREY, plot_speed\n", + "\n", + "plot_speed(\n", + " speed=voronoi_speed.speed,\n", + " title=\"Voronoi velocity in specific direction in front of the bottleneck\",\n", + " color=PEDPY_GREY,\n", + ")\n", + "plt.show()" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "source": [ - "This provides a single density value for each pedestrian.\n", - "The following plot illustrates how individual densities are distributed within the bottleneck:" + "### Comparison mean speed vs Voronoi speed\n", + "\n", + "We now computed the speed with different methods, this plot shows what the different results look like compared to each other:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -1477,34 +1476,63 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_density_distribution\n", + "from pedpy import PEDPY_BLUE, PEDPY_GREY, PEDPY_ORANGE, PEDPY_RED\n", "\n", - "plot_density_distribution(density=passing_density, title=\"Individual density inside bottleneck\")\n", + "plt.figure(figsize=(8, 6))\n", + "plt.title(\"Comparison of different speed methods\")\n", + "plt.plot(\n", + " voronoi_speed.frame,\n", + " voronoi_speed.speed,\n", + " label=\"Voronoi\",\n", + " color=PEDPY_ORANGE,\n", + ")\n", + "plt.plot(\n", + " voronoi_speed_direction.frame,\n", + " voronoi_speed_direction.speed,\n", + " label=\"Voronoi direction\",\n", + " color=PEDPY_GREY,\n", + ")\n", + "plt.plot(\n", + " mean_speed.frame,\n", + " mean_speed.speed,\n", + " label=\"classic\",\n", + " color=PEDPY_BLUE,\n", + ")\n", + "plt.plot(\n", + " mean_speed_direction.frame,\n", + " mean_speed_direction.speed,\n", + " label=\"classic direction\",\n", + " color=PEDPY_RED,\n", + ")\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"v / m/s\")\n", + "plt.legend()\n", + "plt.grid()\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, "source": [ - "#### Line Density\n", - "\n", - "Similar to the Voronoi density within a measurement area, the line density $\\rho_{line}$ can be employed to determine the density at a measurement line $l$. In this approach, pedestrian densities are weighted based on the proportion of the total length of the line $w$ relative to the length of the intersection between the line and the Voronoi cell $w_i(t)$. \n", - "\n", - "The line density is mathematically defined as:\n", + "### Line speed\n", "\n", + "Similar to the Voronoi speed in a measuring area, the line speed can be used to determine the speed $v_{line}$ at a measurement line $l$. The speeds of individuals are weighted based on the proportion of the total length of the line $w$ to the length of the intersection between the line and the Voronoi cell $w_i(t)$.\n", + "The speed at the line is defined as:\n", "$$\n", - "\\rho_\\text{line}(t) = \\sum_{{i \\,|\\, V_i(t) \\cap l \\neq \\emptyset}} \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w},\n", + "v_\\text{line}(t) = \\sum_{i | A_i(t) \\cap l \\neq \\emptyset} v_i(t) \\cdot n_l \\cdot \\frac{w_i(t)}{w}\n", "$$\n", "\n", - "where:\n", - "- $A_i(t)$ is the area of the Voronoi cell $V_i(t)$ for pedestrian $i$ at time $t$, \n", - "- $w_i(t)$ is the length of the intersection between the Voronoi cell $V_i(t)$ and the measurement line $l$, and\n", - "- $w$ is the total length of the measurement line $l$.\n", + "The line speed only includes the speed orthogonal to the line.\n", + "Therefore, the speed of the pedestrians is multiplied by the normal vector of the measuring line $n_l$.\n", "\n", - "The line density can also be applied in scenarios where pedestrians approach the line from both sides. This allows for the analysis of data corresponding to different \"species\" of pedestrians (e.g., different directional flows or groups). However, in this example, only one species is considered, as the line is approached from a single side.\n", + "The line speed can also be used when pedestrians are approaching the line from both sides. In this case it is possible to analyze the data of both \"species\". In this example, there will be only one species as the line is only approached from one side. \n", "\n", - "For further details, refer to [this](fundamental_diagram_at_measurement_line) Notebook." + "For further details check out [this](fundamental_diagram_at_measurement_line) Notebook." ] }, { @@ -1513,95 +1541,48 @@ "metadata": {}, "outputs": [], "source": [ - "from pedpy import compute_line_density, compute_species\n", + "from pedpy import compute_line_speed\n", "\n", - "species = compute_species(\n", - " trajectory_data=traj,\n", - " individual_voronoi_polygons=individual_cutoff,\n", - " frame_step=int(traj.frame_rate),\n", + "line_speed = compute_line_speed(\n", + " individual_speed=individual_speed_single_sided,\n", + " species=species,\n", " measurement_line=measurement_line,\n", - ")\n", - "\n", - "line_density = compute_line_density(\n", " individual_voronoi_polygons=individual_cutoff,\n", - " measurement_line=measurement_line,\n", - " species=species,\n", ")" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [ + "hide-input" + ] + }, "outputs": [], "source": [ - "line_density" + "from pedpy import plot_speed\n", + "from pedpy.column_identifier import SPEED_COL\n", + "\n", + "plot_speed(speed=line_speed[SPEED_COL])\n", + "plt.show()" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "plot_density(density=line_density, title=\"density at measurement line\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Speed\n", - "\n", - "A further important measure in pedestrian dynamics is the speed of the pedestrians.\n", - "Low speeds can indicate congestions or other obstructions in the flow of the crowd." - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "#### Individual speed\n", + "### Passing speed (individual)\n", "\n", - "For computing the individuals speed at a specific frame $v_i(t)$, a specific frame step ($n$) is needed.\n", - "Together with the frame rate of the {class}`trajectory data` $fps$ the time frame $\\Delta t$ for computing the speed becomes:\n", + "With the same principles as described in [passing density](passing_density), the individual speeds $v^i_{passing}$ is defined as\n", "\n", "$$\n", - " \\Delta t = 2 n / fps\n", + " v^i_{passing} = \\frac{d}{t_{out}-t_{in}},\n", "$$\n", "\n", - "This time step describes how many frames before and after the current position $X_{current}$ are used to compute the movement.\n", - "These positions are called $X_{future}$, $X_{past}$, respectively.\n", - "\n", - "```{eval-rst}\n", - ".. image:: /images/speed_both.svg\n", - " :width: 80 %\n", - " :align: center\n", - "```\n", - "\n", - "First computing the displacement between these positions $\\bar{X}$.\n", - "This then can be used to compute the speed with:\n", - "\n", - "$$\\begin{align}\n", - " \\bar{X} &= X_{future} - X_{past} \\\\\n", - " v_i(t) &= \\frac{\\bar{X}}{\\Delta t}\n", - "\\end{align}$$\n", - "\n", - "When getting closer to the start, or end of the {class}`trajectory data`, it is not possible to use the full range of the frame interval for computing the speed.\n", - "For these cases *PedPy* offers three different methods to compute the speed:\n", - "\n", - "1. exclude these parts\n", - "2. adaptively shrink the window in which the speed is computed\n", - "3. switch to one-sided window\n", - "\n", - "##### Exclude border\n", + "where $d$ is the distance between the two {class}`measurement lines `.\n", "\n", - "When not enough frames available to compute the speed at the borders, for these parts no speed can be computed and they are ignored.\n" + "In *PedPy* this can be done with: " ] }, { @@ -1614,15 +1595,14 @@ }, "outputs": [], "source": [ - "from pedpy import SpeedCalculation, compute_individual_speed\n", - "\n", - "frame_step = 25\n", + "from pedpy import compute_frame_range_in_area, compute_passing_speed\n", "\n", - "individual_speed_exclude = compute_individual_speed(\n", - " traj_data=traj,\n", - " frame_step=frame_step,\n", - " compute_velocity=True,\n", - " speed_calculation=SpeedCalculation.BORDER_EXCLUDE,\n", + "passing_offset = 1.0\n", + "frames_in_area, _ = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=passing_offset)\n", + "passing_speed = compute_passing_speed(\n", + " frames_in_area=frames_in_area,\n", + " frame_rate=traj.frame_rate,\n", + " distance=passing_offset,\n", ")" ] }, @@ -1641,21 +1621,9 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_GREEN\n", - "\n", - "ped_id = 25\n", - "\n", - "plt.figure()\n", - "plt.title(f\"Speed time-series of a pedestrian {ped_id} (border excluded)\")\n", - "single_individual_speed = individual_speed_exclude[individual_speed_exclude.id == ped_id]\n", - "plt.plot(\n", - " single_individual_speed.frame,\n", - " single_individual_speed.speed,\n", - " color=PEDPY_GREEN,\n", - ")\n", + "from pedpy import plot_speed_distribution\n", "\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"v / m/s\")\n", + "plot_speed_distribution(speed=passing_speed, title=\"Individual speed in bottleneck\")\n", "plt.show()" ] }, @@ -1667,22 +1635,24 @@ } }, "source": [ - "##### Adaptive border window\n", - "\n", - "In the adaptive approach, it is checked how many frames $n$ are available to from $X_{current}$ to the end of the trajectory.\n", - "This number is then used on both sides to create a smaller symmetric window, which yields $X_{past}$ and $X_{future}$.\n", - "Now with the same principles as before the individual speed $v_i(t)$ can be computed.\n", + "## Flow\n", "\n", - "```{eval-rst}\n", - ".. image:: images/speed_border_adaptive_future.svg\n", - " :width: 46 %\n", - ".. image:: images/speed_border_adaptive_past.svg\n", - " :width: 46 %\n", - "```\n", + "Another important metric, when analyzing pedestrian flows is the flow itself.\n", + "It describes how many persons cross a line in a given time.\n", + "From this potential bottlenecks or congestion can be derived." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "source": [ + "### N-t diagram at bottleneck\n", "\n", - ":::{important}\n", - "As the time interval gets smaller to the ends of the individual trajectories, the oscillations in the speed increase here.\n", - ":::\n" + "To get a first impression of the flow at the bottleneck we look at the N-t diagram, which shows how many pedestrian have crossed the {class}`measurement line ` at a specific time." ] }, { @@ -1695,13 +1665,11 @@ }, "outputs": [], "source": [ - "from pedpy import SpeedCalculation, compute_individual_speed\n", + "from pedpy import compute_n_t\n", "\n", - "individual_speed_adaptive = compute_individual_speed(\n", + "nt, crossing = compute_n_t(\n", " traj_data=traj,\n", - " frame_step=frame_step,\n", - " compute_velocity=True,\n", - " speed_calculation=SpeedCalculation.BORDER_ADAPTIVE,\n", + " measurement_line=measurement_line,\n", ")" ] }, @@ -1720,19 +1688,9 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_RED\n", - "\n", - "plt.figure()\n", - "plt.title(f\"Speed time-series of an pedestrian {ped_id} (adaptive)\")\n", - "single_individual_speed = individual_speed_adaptive[individual_speed_adaptive.id == ped_id]\n", - "plt.plot(\n", - " single_individual_speed.frame,\n", - " single_individual_speed.speed,\n", - " color=PEDPY_RED,\n", - ")\n", + "from pedpy import plot_nt\n", "\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"v / m/s\")\n", + "plot_nt(nt=nt, title=\"N-t at bottleneck\")\n", "plt.show()" ] }, @@ -1744,26 +1702,52 @@ } }, "source": [ - "##### Single sided border window\n", + "### Flow at bottleneck\n", "\n", - "In these cases, one of the end points to compute the movement becomes the current position $X_{current}$.\n", - "When getting too close to the start of the trajectory, the movement is computed from $X_{current}$ to $X_{future}$.\n", - "In the other case the movement is from $X_{past}$ to $X_{current}$.\n", + "From the N-t data we then can compute the flow at the bottleneck.\n", + "\n", + "For the computation of the flow we look at frame intervals $\\Delta frame$ in which the flow is computed.\n", + "The first intervals starts, when the first person crossed the {class}`measurement line `.\n", + "The next interval always starts at the time when the last person in the previous frame interval crossed the line.\n", + "\n", + "```{eval-rst}\n", + ".. image:: /images/flow.svg\n", + " :align: center\n", + " :width: 80 %\n", + "```\n", + "\n", + "In each of the time interval it is checked, if any person has crossed the line, if yes, a flow $J$ can be computed.\n", + "From the first frame the line was crossed $f^{\\Delta frame}_1$, the last frame someone crossed the line $f^{\\Delta frame}_N$ the length of the frame interval $\\Delta f$ can be computed:\n", "\n", "$$\n", - " v_i(t) = {|{X_{future} - X_{current}|}\\over{ \\frac{1}{2} \\Delta t}} \\text{, or } v_i(t) = {|{X_{current} - X_{past}|}\\over{ \\frac{1}{2} \\Delta t}}\n", + " \\Delta f = f^{\\Delta frame}_N - f^{\\Delta frame}_1\n", + "$$\n", + "\n", + "This directly together with the frame rate of the trajectory $fps$ gives the time interval $\\Delta t$:\n", + "\n", + "$$\n", + " \\Delta t = \\Delta f / fps\n", + "$$\n", + "\n", + "Given the number of pedestrian crossing the line is given by $N^{\\Delta frame}$, the flow $J$ becomes:\n", + "\n", + "$$\n", + " J = \\frac{N^{\\Delta frame}}{\\Delta t}\n", "$$\n", "\n", "```{eval-rst}\n", - ".. image:: images/speed_border_single_sided_future.svg\n", - " :width: 46 %\n", - ".. image:: images/speed_border_single_sided_past.svg\n", - " :width: 46 %\n", + ".. image:: /images/flow_zoom.svg\n", + " :align: center\n", + " :width: 60 %\n", "```\n", "\n", - ":::{important}\n", - "As at the edges of the trajectories the time interval gets halved, there may occur some jumps computed speeds at this point.\n", - ":::" + "At the same time also the mean speed of the pedestrian when crossing the line is given by:\n", + "\n", + "$$\n", + " v_{crossing} = {1 \\over N^{\\Delta t} } \\sum^{N^{\\Delta t}}_{i=1} v_i(t)\n", + "$$\n", + "\n", + "To compute the flow and mean speed when passing the line with *PedPy* use:" ] }, { @@ -1776,13 +1760,15 @@ }, "outputs": [], "source": [ - "from pedpy import SpeedCalculation, compute_individual_speed\n", + "from pedpy import compute_flow\n", "\n", - "individual_speed_single_sided = compute_individual_speed(\n", - " traj_data=traj,\n", - " frame_step=frame_step,\n", - " compute_velocity=True,\n", - " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", + "delta_frame = 100\n", + "flow = compute_flow(\n", + " nt=nt,\n", + " crossing_frames=crossing,\n", + " individual_speed=individual_speed_single_sided,\n", + " delta_frame=delta_frame,\n", + " frame_rate=traj.frame_rate,\n", ")" ] }, @@ -1801,19 +1787,12 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_GREY\n", + "from pedpy import plot_flow\n", "\n", - "plt.figure()\n", - "plt.title(f\"Speed time-series of an pedestrian {ped_id} (single sided)\")\n", - "single_individual_speed = individual_speed_single_sided[individual_speed_single_sided.id == ped_id]\n", - "plt.plot(\n", - " single_individual_speed.frame,\n", - " single_individual_speed.speed,\n", - " color=PEDPY_GREY,\n", + "plot_flow(\n", + " flow=flow,\n", + " title=\"Crossing velocities at the corresponding flow at bottleneck\",\n", ")\n", - "\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"v / m/s\")\n", "plt.show()" ] }, @@ -1825,100 +1804,124 @@ } }, "source": [ - "##### Comparison\n", - "\n", - "To demonstrate the differences in the computed speeds, take a look at the following plot:\n" + "## Acceleration\n", + "\n" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": { "jupyter": { "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] + } }, - "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_GREEN, PEDPY_GREY, PEDPY_RED\n", + "### Individual acceleration\n", "\n", - "fig, ax = plt.subplots(1, 3, gridspec_kw={\"width_ratios\": [2, 1, 1]}, sharey=True, figsize=(12, 5))\n", + "Compute the individual acceleration for each pedestrian.\n", "\n", - "fig.suptitle(\"Comparison of the different speed calculations at the borders\")\n", - "speed_exclude = individual_speed_exclude[individual_speed_exclude.id == ped_id]\n", - "speed_adaptive = individual_speed_adaptive[individual_speed_adaptive.id == ped_id]\n", - "speed_single_sided = individual_speed_single_sided[individual_speed_single_sided.id == ped_id]\n", + "For computing the individuals' acceleration at a specific frame $a_i(t_k)$,\n", + "a specific frame step ($n$) is needed.\n", + "Together with the {class}`trajectory data` $fps$ of\n", + "the trajectory data $fps$ the time frame $\\Delta t$ for\n", + "computing the speed becomes:\n", "\n", - "ax[0].plot(\n", - " speed_single_sided.frame,\n", - " speed_single_sided.speed,\n", - " color=PEDPY_GREY,\n", - " linewidth=3,\n", - " label=\"single sided\",\n", - ")\n", - "ax[0].plot(\n", - " speed_adaptive.frame,\n", - " speed_adaptive.speed,\n", - " color=PEDPY_RED,\n", - " linewidth=3,\n", - " label=\"adaptive\",\n", - ")\n", - "ax[0].plot(\n", - " speed_exclude.frame,\n", - " speed_exclude.speed,\n", - " color=PEDPY_GREEN,\n", - " linewidth=3,\n", - " label=\"excluded\",\n", - ")\n", - "ax[0].set_xlabel(\"frame\")\n", - "ax[0].set_ylabel(\"v / m/s\")\n", - "ax[0].legend()\n", + "$$\n", + "\\Delta t = 2 n / fps\n", + "$$\n", "\n", - "ax[1].plot(\n", - " speed_single_sided.frame[speed_single_sided.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " speed_single_sided.speed[speed_single_sided.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " color=PEDPY_GREY,\n", - " linewidth=3,\n", - ")\n", - "ax[1].plot(\n", - " speed_adaptive.frame[speed_adaptive.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " speed_adaptive.speed[speed_adaptive.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " color=PEDPY_RED,\n", - " linewidth=3,\n", - ")\n", - "ax[1].plot(\n", - " speed_exclude.frame[speed_exclude.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " speed_exclude.speed[speed_exclude.frame < speed_single_sided.frame.min() + 3 * frame_step],\n", - " color=PEDPY_GREEN,\n", - " linewidth=3,\n", - ")\n", - "ax[1].set_xlabel(\"frame\")\n", + "This time step describes how many frames before and after the current\n", + "position $X(t_k)$ are used to compute the movement.\n", + "These positions are called $X(t_{k+n})$, $X(t_{k-n})$\n", + "respectively.\n", "\n", - "ax[2].plot(\n", - " speed_single_sided.frame[speed_single_sided.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", - " speed_single_sided.speed[speed_single_sided.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", - " color=PEDPY_GREY,\n", - " linewidth=3,\n", - ")\n", - "ax[2].plot(\n", - " speed_adaptive.frame[speed_adaptive.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", - " speed_adaptive.speed[speed_adaptive.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", - " color=PEDPY_RED,\n", - " linewidth=3,\n", - ")\n", - "ax[2].plot(\n", - " speed_exclude.frame[speed_exclude.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", - " speed_exclude.speed[speed_exclude.frame > speed_single_sided.frame.max() - 3 * frame_step],\n", + "In order to compute the acceleration at time $t_k$, we first calculate the\n", + "displacements $\\bar{X}$ around $t_{k+n}$ and $t_{k-n}$:\n", + "\n", + "$$\n", + "\\bar{X}(t_{k+n}) = X(t_{k+2n}) - X(t_{k})\n", + "$$\n", + "$$\n", + "\\bar{X}(t_{k-n}) = X(t_{k}) - X(t_{k-2n})\n", + "$$\n", + "\n", + "The acceleration is then calculated from the difference of the displacements\n", + "\n", + "$$\n", + "\\Delta\\bar{X}(t_k) = \\bar{X}(t_{k+n}) - \\bar{X}(t_{k-n})\n", + "$$\n", + "\n", + "divided by the square of the time interval $\\Delta t$:\n", + "\n", + "$$\n", + "a_i(t_k) = \\Delta\\bar{X}(t_k) / \\Delta t^{2}\n", + "$$\n", + "\n", + "When getting closer to the start, or end of the trajectory data, it is not\n", + "possible to use the full range of the frame interval for computing the\n", + "acceleration. For these cases *PedPy* offers a method to compute\n", + "the acceleration:\n", + "\n", + "**Exclude border:**\n", + "\n", + "When not enough frames available to compute the speed at the borders, for\n", + "these parts no acceleration can be computed and they are ignored. Use\n", + "`acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE`.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + } + }, + "outputs": [], + "source": [ + "from pedpy import AccelerationCalculation, compute_individual_acceleration\n", + "\n", + "frame_step = 25\n", + "\n", + "individual_acceleration_exclude = compute_individual_acceleration(\n", + " traj_data=traj,\n", + " frame_step=frame_step,\n", + " compute_acceleration_components=True,\n", + " acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "jupyter": { + "outputs_hidden": false + }, + "tags": [ + "hide-input" + ] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import PEDPY_GREEN\n", + "\n", + "ped_id = 50\n", + "\n", + "plt.figure()\n", + "plt.title(f\"Acceleration time-series of a pedestrian {ped_id} (border excluded)\")\n", + "single_individual_acceleration = individual_acceleration_exclude[individual_acceleration_exclude.id == ped_id]\n", + "plt.plot(\n", + " single_individual_acceleration.frame,\n", + " single_individual_acceleration.acceleration,\n", " color=PEDPY_GREEN,\n", - " linewidth=3,\n", ")\n", "\n", - "ax[2].set_xlabel(\"frame\")\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"a / $m/s^2$\")\n", "plt.show()" ] }, @@ -1926,41 +1929,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Individual speed in specific movement direction\n", + "### Individual acceleration in specific movement direction:\n", "\n", - "It is also possible to compute the individual speed in a specific direction $d$, for this the movement $\\bar{X}$ is projected onto the desired movement direction. $\\bar{X}$ and $\\Delta t$ are computed as described above. \n", - "Hence, the speed then becomes:\n", + "It is also possible to compute the individual acceleration in a specific direction\n", + "$d$, for this the movement $\\Delta\\bar{X}$ is projected onto the\n", + "desired movement direction. $\\Delta\\bar{X}$ and $\\Delta t$ are\n", + "computed as described above. Hence, the acceleration then becomes:\n", "\n", "$$\n", - " v_i(t) = {{|\\boldsymbol{proj}_d\\; \\bar{X}|} \\over {\\Delta t}}\n", + "a_i(t) = {{|\\boldsymbol{proj}_d\\; \\Delta\\bar{X}|} \\over {\\Delta t^{2}}}\n", "$$\n", "\n", - "```{eval-rst}\n", - ".. image:: images/speed_movement_direction.svg\n", - " :width: 80 %\n", - "```\n", - "\n", - ":::{important}\n", - "When using a specific direction, the computed speed may become negative.\n", - ":::" + "If `compute_acceleration_components` is `True` also $\\Delta\\bar{X}$ is returned." ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "individual_speed_direction = compute_individual_speed(\n", + "individual_acceleration_direction = compute_individual_acceleration(\n", " traj_data=traj,\n", " frame_step=5,\n", " movement_direction=np.array([0, -1]),\n", - " compute_velocity=True,\n", - " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", + " compute_acceleration_components=True,\n", + " acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE,\n", ")" ] }, @@ -1968,9 +1962,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -1985,65 +1976,89 @@ "ped_ids = [10, 20, 17, 70]\n", "\n", "fig = plt.figure()\n", - "plt.title(\"Velocity time-series of an excerpt of the pedestrians in a specific direction\")\n", + "plt.title(\"Acceleration time-series of an excerpt of the pedestrians in a specific direction\")\n", "for color, ped_id in zip(colors, ped_ids, strict=False):\n", - " single_individual_speed = individual_speed_direction[individual_speed_direction.id == ped_id]\n", + " single_individual_acceleration = individual_acceleration_direction[individual_acceleration_direction.id == ped_id]\n", " plt.plot(\n", - " single_individual_speed.frame,\n", - " single_individual_speed.speed,\n", + " single_individual_acceleration.frame,\n", + " single_individual_acceleration.acceleration,\n", " color=color,\n", " )\n", "\n", "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"v / m/s\")\n", + "plt.ylabel(\"a / $m/s^2$\")\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "(mean_speed)=\n", - " #### Mean speed\n", + "### Mean acceleration\n", "\n", - "Now, that we have computed the individual's speed, we want to compute the mean speed in the already used {class}`measurement area ` $M$ closely in front of the bottleneck.\n", - "The mean speed is defined as\n", + "Compute mean acceleration per frame inside a given measurement area.\n", + "\n", + "Computes the mean acceleration $a_{mean}(t)$ inside the measurement area from\n", + "the given individual acceleration data $a_i(t)$ (see\n", + "{func}`~acceleration_calculator.compute_individual_acceleration` for\n", + "details of the computation). The mean acceleration $a_{mean}$ is defined as\n", "\n", "$$\n", - " v_{mean}(t) = {{1} \\over {N}} \\sum_{i \\in P_M} v_i(t), \n", + "a_{mean}(t) = {{1} \\over {N}} \\sum_{i \\in P_M} a_i(t),\n", "$$\n", "\n", - "where $P_M$ are all pedestrians inside the {class}`measurement area `, and $N$ the number of pedestrians inside the {class}`measurement area ` ($|P_M|$).\n", - "\n", + "where $P_M$ are all pedestrians inside the measurement area, and\n", + "$N$ the number of pedestrians inside the measurement area (\n", + "$|P_M|$).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ ":::{important}\n", - "The mean speed can only be computed when for each pedestrian inside the {class}`measurement area ` also a speed $v_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", - "Then some extra processing steps are needed, to avoid this use the single sided approach. \n", - ":::\n", + "The mean acceleration can only be computed when for each pedestrian inside the {class}`measurement area ` also an acceleration $a_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", + "Therefore, further processing steps are needed to ensure that the trajectory data and the individual acceleration data overlap, e.g. as in the example below:\n", + ":::" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import TrajectoryData\n", "\n", - "This can be as follows with *PedPy*:\n", - " " + "traj_idx = pd.MultiIndex.from_frame(traj.data[[\"id\", \"frame\"]])\n", + "acc_idx = pd.MultiIndex.from_frame(individual_acceleration_exclude[[\"id\", \"frame\"]])\n", + "# get intersecting rows in id and frame\n", + "common_idx = traj_idx.intersection(acc_idx)\n", + "traj_common = traj.data.set_index([\"id\", \"frame\"]).reindex(common_idx).reset_index()\n", + "\n", + "fr = traj.frame_rate\n", + "traj_exclude = TrajectoryData(data=traj_common, frame_rate=fr)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the mean acceleration can be computed using *PedPy* with:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "from pedpy import compute_mean_speed_per_frame\n", + "from pedpy import compute_mean_acceleration_per_frame\n", "\n", - "mean_speed = compute_mean_speed_per_frame(\n", - " traj_data=traj,\n", + "mean_acceleration = compute_mean_acceleration_per_frame(\n", + " traj_data=traj_exclude,\n", " measurement_area=measurement_area,\n", - " individual_speed=individual_speed_single_sided,\n", + " individual_acceleration=individual_acceleration_exclude,\n", ")" ] }, @@ -2051,9 +2066,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -2062,11 +2074,11 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_BLUE, plot_speed\n", + "from pedpy import PEDPY_BLUE, plot_acceleration\n", "\n", - "plot_speed(\n", - " speed=mean_speed.speed,\n", - " title=\"Mean speed in front of the bottleneck\",\n", + "plot_acceleration(\n", + " acceleration=mean_acceleration.acceleration,\n", + " title=\"Mean acceleration in front of the bottleneck\",\n", " color=PEDPY_BLUE,\n", ")\n", "plt.show()" @@ -2082,17 +2094,13 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "mean_speed_direction = compute_mean_speed_per_frame(\n", - " traj_data=traj,\n", + "mean_acceleration_direction = compute_mean_acceleration_per_frame(\n", + " traj_data=traj_exclude,\n", " measurement_area=measurement_area,\n", - " individual_speed=individual_speed_direction,\n", + " individual_acceleration=individual_acceleration_direction,\n", ")" ] }, @@ -2100,9 +2108,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -2111,11 +2116,11 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_RED, plot_speed\n", + "from pedpy import PEDPY_RED, plot_acceleration\n", "\n", - "plot_speed(\n", - " speed=mean_speed_direction.speed,\n", - " title=\"Mean speed in specific direction in front of the bottleneck\",\n", + "plot_acceleration(\n", + " acceleration=mean_acceleration_direction.acceleration,\n", + " title=\"Mean acceleration in specific direction in front of the bottleneck\",\n", " color=PEDPY_RED,\n", ")\n", "plt.show()" @@ -2123,56 +2128,76 @@ }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "(voronoi_speed)=\n", - " #### Voronoi speed\n", + "### Voronoi acceleration\n", + "Compute the Voronoi acceleration per frame inside the measurement area.\n", "\n", - "A further approach to compute average speed $v_{voronoi}(t)$ in an area by weighting the individuals speed by the size of their corresponding Voronoi polygon $V_i$ inside the {class}`measurement area ` $M$.\n", - "The individuals speed are weighted by the proportion of their Voronoi cell $V_i$ and the intersection with the {class}`measurement area ` $V_i \\cap M$.\n", + "Computes the Voronoi acceleration $a_{voronoi}(t)$ inside the measurement\n", + "area $M$ from the given individual acceleration data $a_i(t)$ (see\n", + "{func}`~acceleration_calculator.compute_individual_acceleration` for\n", + "details of the computation) and their individual Voronoi intersection data\n", + "(from {func}`~density_calculator.compute_voronoi_density`).\n", + "The individuals' accelerations are weighted by the proportion of their Voronoi cell\n", + "$V_i$ and the intersection with the measurement area\n", + "$V_i \\cap M$.\n", "\n", - "The Voronoi speed $v_{voronoi}(t)$ is defined as\n", + "The Voronoi acceleration $a_{voronoi}(t)$ is defined as\n", "\n", "$$\n", - " v_{voronoi}(t) = { \\int\\int v_{xy}(t) dxdy \\over A(M)},\n", + "a_{voronoi}(t) = { \\int\\int a_{xy}(t) dxdy \\over A(M)},\n", "$$\n", "\n", - "where $v_{xy}(t) = v_i(t)$ is the individual speed of each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of the {class}`measurement area `.\n", - "\n", - "```{eval-rst}\n", - ".. image:: /images/voronoi_density.svg\n", - " :width: 60 %\n", - " :align: center\n", - "```\n", + "where $a_{xy}(t) = a_i(t)$ is the individual acceleration of\n", + "each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of\n", + "the measurement area.\n", "\n", + "This can be as follows with *PedPy*:" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ ":::{important}\n", - "The Voronoi speed can only be computed when for each pedestrian inside the {class}`measurement area ` also a speed $v_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", - "Then some extra processing steps are needed, to avoid this use the single sided approach. \n", - ":::\n", + "The Voronoi acceleration can only be computed when for each pedestrian inside the {class}`measurement area ` also an acceleration $a_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", "\n", - "This can be done in *PedPy* with:" + "Therefore, further processing steps are needed to ensure that the trajectory data, the individual acceleration dara as well as the intersecting Voronoi cell data overlap. We assume that the trajectory data is already overlapping (see example above). The intersecting voronoi cells can be filtered, e.g., as in the example below:\n", + ":::" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "from pedpy import compute_voronoi_speed\n", + "intersecting_idx = pd.MultiIndex.from_frame(intersecting[[\"id\", \"frame\"]])\n", + "acc_idx = pd.MultiIndex.from_frame(individual_acceleration_exclude[[\"id\", \"frame\"]])\n", + "# get intersecting rows in id and frame\n", + "common_idx = intersecting_idx.intersection(acc_idx)\n", + "intersecting_exclude = intersecting.set_index([\"id\", \"frame\"]).reindex(common_idx).reset_index()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now the Voronoi acceleration can be computed using *PedPy* with:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import compute_voronoi_acceleration\n", "\n", - "voronoi_speed = compute_voronoi_speed(\n", - " traj_data=traj,\n", - " individual_voronoi_intersection=intersecting,\n", - " individual_speed=individual_speed_single_sided,\n", + "voronoi_acceleration = compute_voronoi_acceleration(\n", + " traj_data=traj_exclude,\n", + " individual_voronoi_intersection=intersecting_exclude,\n", + " individual_acceleration=individual_acceleration_exclude,\n", " measurement_area=measurement_area,\n", ")" ] @@ -2181,9 +2206,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -2192,11 +2214,11 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_ORANGE, plot_speed\n", + "from pedpy import PEDPY_ORANGE, plot_acceleration\n", "\n", - "plot_speed(\n", - " speed=voronoi_speed.speed,\n", - " title=\"Voronoi speed in front of the bottleneck\",\n", + "plot_acceleration(\n", + " acceleration=voronoi_acceleration.acceleration,\n", + " title=\"Voronoi acceleration in front of the bottleneck\",\n", " color=PEDPY_ORANGE,\n", ")\n", "plt.show()" @@ -2206,23 +2228,19 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Analogously, this can be done with the speed in a specific direction with:" + "Analogously, this can be done with the acceleration in a specific direction with:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "voronoi_speed_direction = compute_voronoi_speed(\n", - " traj_data=traj,\n", - " individual_voronoi_intersection=intersecting,\n", - " individual_speed=individual_speed_direction,\n", + "voronoi_acceleration_direction = compute_voronoi_acceleration(\n", + " traj_data=traj_exclude,\n", + " individual_voronoi_intersection=intersecting_exclude,\n", + " individual_acceleration=individual_acceleration_direction,\n", " measurement_area=measurement_area,\n", ")" ] @@ -2231,9 +2249,6 @@ "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -2242,11 +2257,11 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_GREY, plot_speed\n", + "from pedpy import PEDPY_GREY, plot_acceleration\n", "\n", - "plot_speed(\n", - " speed=voronoi_speed.speed,\n", - " title=\"Voronoi velocity in specific direction in front of the bottleneck\",\n", + "plot_acceleration(\n", + " acceleration=voronoi_acceleration.acceleration,\n", + " title=\"Voronoi acceleration in specific direction in front of the bottleneck\",\n", " color=PEDPY_GREY,\n", ")\n", "plt.show()" @@ -2254,15 +2269,11 @@ }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "#### Comparison mean speed vs Voronoi speed\n", + "### Comparison mean acceleration vs Voronoi acceleration\n", "\n", - "We now computed the speed with different methods, this plot shows what the different results look like compared to each other:" + "We now computed the acceleration with different methods, this plot shows what the different results look like compared to each other:" ] }, { @@ -2280,33 +2291,33 @@ "from pedpy import PEDPY_BLUE, PEDPY_GREY, PEDPY_ORANGE, PEDPY_RED\n", "\n", "plt.figure(figsize=(8, 6))\n", - "plt.title(\"Comparison of different speed methods\")\n", + "plt.title(\"Comparison of different acceleration methods\")\n", "plt.plot(\n", - " voronoi_speed.frame,\n", - " voronoi_speed.speed,\n", + " voronoi_acceleration.frame,\n", + " voronoi_acceleration.acceleration,\n", " label=\"Voronoi\",\n", " color=PEDPY_ORANGE,\n", ")\n", "plt.plot(\n", - " voronoi_speed_direction.frame,\n", - " voronoi_speed_direction.speed,\n", + " voronoi_acceleration_direction.frame,\n", + " voronoi_acceleration_direction.acceleration,\n", " label=\"Voronoi direction\",\n", " color=PEDPY_GREY,\n", ")\n", "plt.plot(\n", - " mean_speed.frame,\n", - " mean_speed.speed,\n", + " mean_acceleration.frame,\n", + " mean_acceleration.acceleration,\n", " label=\"classic\",\n", " color=PEDPY_BLUE,\n", ")\n", "plt.plot(\n", - " mean_speed_direction.frame,\n", - " mean_speed_direction.speed,\n", + " mean_acceleration_direction.frame,\n", + " mean_acceleration_direction.acceleration,\n", " label=\"classic direction\",\n", " color=PEDPY_RED,\n", ")\n", "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"v / m/s\")\n", + "plt.ylabel(\"a / $m/s^2$\")\n", "plt.legend()\n", "plt.grid()\n", "plt.show()" @@ -2320,18 +2331,18 @@ } }, "source": [ - "#### Line speed\n", + "### Line flow\n", + "\n", + "The line flow is derived from the line speed and line density. It can is used to determine the flow at a measurement line $l$. The values of individuals are weighted based on the proportion of the total length of the line $w$ to the length of the intersection between the line and the Voronoi cell $w_i(t)$.\n", + "The flow at the line is defined as:\n", "\n", - "Similar to the Voronoi speed in a measuring area, the line speed can be used to determine the speed $v_{line}$ at a measurement line $l$. The speeds of individuals are weighted based on the proportion of the total length of the line $w$ to the length of the intersection between the line and the Voronoi cell $w_i(t)$.\n", - "The speed at the line is defined as:\n", "$$\n", - "v_\\text{line}(t) = \\sum_{i | A_i(t) \\cap l \\neq \\emptyset} v_i(t) \\cdot n_l \\cdot \\frac{w_i(t)}{w}\n", + "j_\\text{line}(t) = \\sum_{{i | A_i(t) \\cap l \\neq \\emptyset}} v_i(t) \\cdot n_l \\cdot \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w}\n", "$$\n", "\n", - "The line speed only includes the speed orthogonal to the line.\n", - "Therefore, the speed of the pedestrians is multiplied by the normal vector of the measuring line $n_l$.\n", + "The line flow only includes the speed orthogonal to the line. Therefore, the speed of the pedestrians $v_i(t)$ is multiplied by the normal vector of the measuring line $n_l$.\n", "\n", - "The line speed can also be used when pedestrians are approaching the line from both sides. In this case it is possible to analyze the data of both \"species\". In this example, there will be only one species as the line is only approached from one side. \n", + "The line flow can also be used when pedestrians are approaching the line from both sides.In this case it is possible to analyze the data of both \"species\". In this example there will be only one species as the line is only approached from one side. \n", "\n", "For further details check out [this](fundamental_diagram_at_measurement_line) Notebook." ] @@ -2342,13 +2353,13 @@ "metadata": {}, "outputs": [], "source": [ - "from pedpy import compute_line_speed\n", + "from pedpy import compute_line_flow\n", "\n", - "line_speed = compute_line_speed(\n", - " individual_speed=individual_speed_single_sided,\n", + "line_flow = compute_line_flow(\n", + " individual_voronoi_polygons=individual_cutoff,\n", " species=species,\n", " measurement_line=measurement_line,\n", - " individual_voronoi_polygons=individual_cutoff,\n", + " individual_speed=individual_speed_single_sided,\n", ")" ] }, @@ -2362,10 +2373,12 @@ }, "outputs": [], "source": [ - "from pedpy import plot_speed\n", - "from pedpy.column_identifier import SPEED_COL\n", + "from pedpy.column_identifier import FLOW_COL\n", "\n", - "plot_speed(speed=line_speed[SPEED_COL])\n", + "plt.plot(line_flow[FRAME_COL], line_flow[FLOW_COL])\n", + "plt.title(\"flow on line\")\n", + "plt.xlabel(\"frame\")\n", + "plt.ylabel(\"$J$\")\n", "plt.show()" ] }, @@ -2373,17 +2386,22 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Passing speed (individual)\n", + "## Neighborhood\n", "\n", - "With the same principles as described in [passing density](passing_density), the individual speeds $v^i_{passing}$ is defined as\n", - "\n", - "$$\n", - " v^i_{passing} = \\frac{d}{t_{out}-t_{in}},\n", - "$$\n", - "\n", - "where $d$ is the distance between the two {class}`measurement lines `.\n", + "To analyze which pedestrians are close to each other, it is possible to compute the neighbors of each pedestrian.\n", + "We define two pedestrians as neighbors if their Voronoi polygons ($V_i$, $V_j$) touch at some point, in case of *PedPy* they are touching if their distance is below 1mm.\n", + "As basis for the computation one can either use the uncut or cut Voronoi polygons.\n", + "When using the uncut Voronoi polygons, pedestrian may be detected as neighbors even when their distance is quite large in low density situation.\n", + "Therefore, it is recommended to use the cut Voronoi polygons, where the cut-off radius can be used to define a maximal distance between neighboring pedestrians." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Neighborhood computation\n", "\n", - "In *PedPy* this can be done with: " + "To compute the neighbors in *PedPy* use:" ] }, { @@ -2396,15 +2414,10 @@ }, "outputs": [], "source": [ - "from pedpy import compute_frame_range_in_area, compute_passing_speed\n", + "from pedpy import compute_neighbors\n", "\n", - "passing_offset = 1.0\n", - "frames_in_area, _ = compute_frame_range_in_area(traj_data=traj, measurement_line=measurement_line, width=passing_offset)\n", - "passing_speed = compute_passing_speed(\n", - " frames_in_area=frames_in_area,\n", - " frame_rate=traj.frame_rate,\n", - " distance=passing_offset,\n", - ")" + "neighbors = compute_neighbors(individual_cutoff, as_list=False)\n", + "neighbors[0:5]" ] }, { @@ -2422,164 +2435,104 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_speed_distribution\n", + "from pedpy import plot_neighborhood\n", "\n", - "plot_speed_distribution(speed=passing_speed, title=\"Individual speed in bottleneck\")\n", + "plot_neighborhood(\n", + " pedestrian_id=8,\n", + " voronoi_data=individual_cutoff,\n", + " frame=350,\n", + " neighbors=neighbors,\n", + " walkable_area=walkable_area,\n", + ").set_aspect(\"equal\")\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "### Flow\n", - "\n", - "Another important metric, when analyzing pedestrian flows is the flow itself.\n", - "It describes how many persons cross a line in a given time.\n", - "From this potential bottlenecks or congestion can be derived." + ":::{important}\n", + "For legacy reasons the function {func}`compute_neighbors ` works also without specifing `as_list` (defaults to `True`). \n", + "We highly discourage using this, as its result is harder to be used in further computations.\n", + "Use 'as_list=False' instead.\n", + "The default value may change in future versions of *PedPy*.\n", + ":::" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "hide-input", + "hide-cell" + ] }, + "outputs": [], "source": [ - "#### N-t diagram at bottleneck\n", - "\n", - "To get a first impression of the flow at the bottleneck we look at the N-t diagram, which shows how many pedestrian have crossed the {class}`measurement line ` at a specific time." + "neighbors_as_list = compute_neighbors(individual_cutoff)\n", + "neighbors_as_list[0:5]" ] }, { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], + "cell_type": "markdown", + "metadata": {}, "source": [ - "from pedpy import compute_n_t\n", + "### Distance to neighbors\n", "\n", - "nt, crossing = compute_n_t(\n", - " traj_data=traj,\n", - " measurement_line=measurement_line,\n", - ")" + "For computing the distance between neighbors, *PedPy* offers a dedicated function:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] - }, + "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_nt\n", + "from pedpy import compute_neighbor_distance\n", "\n", - "plot_nt(nt=nt, title=\"N-t at bottleneck\")\n", - "plt.show()" + "neighbor_distance = compute_neighbor_distance(traj_data=traj, neighborhood=neighbors)\n", + "neighbor_distance[0:5]" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "#### Flow at bottleneck\n", - "\n", - "From the N-t data we then can compute the flow at the bottleneck.\n", - "\n", - "For the computation of the flow we look at frame intervals $\\Delta frame$ in which the flow is computed.\n", - "The first intervals starts, when the first person crossed the {class}`measurement line `.\n", - "The next interval always starts at the time when the last person in the previous frame interval crossed the line.\n", - "\n", - "```{eval-rst}\n", - ".. image:: images/flow.svg\n", - " :align: center\n", - " :width: 80 %\n", - "```\n", - "\n", - "In each of the time interval it is checked, if any person has crossed the line, if yes, a flow $J$ can be computed.\n", - "From the first frame the line was crossed $f^{\\Delta frame}_1$, the last frame someone crossed the line $f^{\\Delta frame}_N$ the length of the frame interval $\\Delta f$ can be computed:\n", - "\n", - "$$\n", - " \\Delta f = f^{\\Delta frame}_N - f^{\\Delta frame}_1\n", - "$$\n", - "\n", - "This directly together with the frame rate of the trajectory $fps$ gives the time interval $\\Delta t$:\n", - "\n", - "$$\n", - " \\Delta t = \\Delta f / fps\n", - "$$\n", - "\n", - "Given the number of pedestrian crossing the line is given by $N^{\\Delta frame}$, the flow $J$ becomes:\n", - "\n", - "$$\n", - " J = \\frac{N^{\\Delta frame}}{\\Delta t}\n", - "$$\n", - "\n", - "```{eval-rst}\n", - ".. image:: images/flow_zoom.svg\n", - " :align: center\n", - " :width: 60 %\n", - "```\n", - "\n", - "At the same time also the mean speed of the pedestrian when crossing the line is given by:\n", + ":::{note}\n", + "The resulting {class}`~pandas.DataFrame` is symmetric. \n", + "If pedestrian A is a neighbor of pedestrian B, then pedestrian B is also a neighbor of pedestrian A.\n", + "Consequently, the distance between both appears twice in the DataFrame.\n", + ":::" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Distance to entrance/Time to entrance\n", "\n", - "$$\n", - " v_{crossing} = {1 \\over N^{\\Delta t} } \\sum^{N^{\\Delta t}}_{i=1} v_i(t)\n", - "$$\n", + "An indicator to detect congestions or jams in {class}`trajectory data` are distance/time to crossing.\n", + "It shows how much time until the crossing of the {class}`measurement line ` is left and how big the distance to that line is.\n", "\n", - "To compute the flow and mean speed when passing the line with *PedPy* use:" + "In *PedPy* this can be done with:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "from pedpy import compute_flow\n", + "from pedpy import compute_time_distance_line\n", "\n", - "delta_frame = 100\n", - "flow = compute_flow(\n", - " nt=nt,\n", - " crossing_frames=crossing,\n", - " individual_speed=individual_speed_single_sided,\n", - " delta_frame=delta_frame,\n", - " frame_rate=traj.frame_rate,\n", - ")" + "df_time_distance = compute_time_distance_line(traj_data=traj, measurement_line=measurement_line)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -2588,25 +2541,42 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_flow\n", + "from pedpy import plot_time_distance\n", "\n", - "plot_flow(\n", - " flow=flow,\n", - " title=\"Crossing velocities at the corresponding flow at bottleneck\",\n", + "plot_time_distance(\n", + " time_distance=df_time_distance,\n", + " title=\"Distance to entrance/Time to entrance\",\n", + " frame_rate=traj.frame_rate,\n", ")\n", "plt.show()" ] }, { "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also color the lines based on speed values. \n", + "To achieve this, simply provide a speed DataFrame as an argument to the function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "hide-input" + ] }, + "outputs": [], "source": [ - "### Acceleration\n", - "\n" + "speed = compute_individual_speed(traj_data=traj, frame_step=5)\n", + "plot_time_distance(\n", + " time_distance=df_time_distance,\n", + " title=\"Distance to entrance/Time to entrance - colored\",\n", + " frame_rate=traj.frame_rate,\n", + " speed=speed,\n", + ")\n", + "plt.show()" ] }, { @@ -2617,58 +2587,35 @@ } }, "source": [ - "#### Individual acceleration\n", - "\n", - "Compute the individual acceleration for each pedestrian.\n", - "\n", - "For computing the individuals' acceleration at a specific frame $a_i(t_k)$,\n", - "a specific frame step ($n$) is needed.\n", - "Together with the {class}`trajectory data` $fps$ of\n", - "the trajectory data $fps$ the time frame $\\Delta t$ for\n", - "computing the speed becomes:\n", - "\n", - "$$\n", - "\\Delta t = 2 n / fps\n", - "$$\n", - "\n", - "This time step describes how many frames before and after the current\n", - "position $X(t_k)$ are used to compute the movement.\n", - "These positions are called $X(t_{k+n})$, $X(t_{k-n})$\n", - "respectively.\n", - "\n", - "In order to compute the acceleration at time $t_k$, we first calculate the\n", - "displacements $\\bar{X}$ around $t_{k+n}$ and $t_{k-n}$:\n", - "\n", - "$$\n", - "\\bar{X}(t_{k+n}) = X(t_{k+2n}) - X(t_{k})\n", - "$$\n", - "$$\n", - "\\bar{X}(t_{k-n}) = X(t_{k}) - X(t_{k-2n})\n", - "$$\n", + "## Profiles\n", "\n", - "The acceleration is then calculated from the difference of the displacements\n", + "For the computation of the profiles the given {class}`walkable area ` or {class}`axis-aligned measurement area ` is divided into square grid cells.\n", + "Each of these grid cells is then used as a {class}`measurement area ` to compute the density and speed.\n", "\n", - "$$\n", - "\\Delta\\bar{X}(t_k) = \\bar{X}(t_{k+n}) - \\bar{X}(t_{k-n})\n", - "$$\n", + "When using the whole walkable area for the computing the profiles, the used grid will look like this:\n", + "```{eval-rst}\n", + ".. image:: /images/profile_grid.svg\n", + " :width: 70 %\n", + " :align: center\n", + "```\n", "\n", - "divided by the square of the time interval $\\Delta t$:\n", + ":::{note}\n", + "As this is a quite compute heavy operation, it is suggested to reduce the trajectories to the important areas and limit the input data to the most relevant frame interval. \n", "\n", - "$$\n", - "a_i(t_k) = \\Delta\\bar{X}(t_k) / \\Delta t^{2}\n", - "$$\n", + "In *PedPy* it's possible to pass an axis-aligned measurement area as option to the profile computation, this will retrict the computation to this area.\n", + ":::\n", "\n", - "When getting closer to the start, or end of the trajectory data, it is not\n", - "possible to use the full range of the frame interval for computing the\n", - "acceleration. For these cases *PedPy* offers a method to compute\n", - "the acceleration:\n", "\n", - "**Exclude border:**\n", + "As the profiles are computed on a square axis-aligned grid, the computation can only be restriced to axis-aligned measurement areas! \n", + "For an arbritraty shaped measurement area, internally the grid would be created for the bounding box of that measurement area. \n", + "We decided to make this step explicit and visible to the users.\n", + "The resulting grid of such an axis-aligned measurement area, could look like this:\n", "\n", - "When not enough frames available to compute the speed at the borders, for\n", - "these parts no acceleration can be computed and they are ignored. Use\n", - ":code:`acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE`.\n", - "\n" + "```{eval-rst}\n", + ".. image:: /images/profile_axis_aligned_measurement_area_with_grid.svg\n", + " :width: 70 %\n", + " :align: center\n", + "```\n" ] }, { @@ -2681,67 +2628,61 @@ }, "outputs": [], "source": [ - "from pedpy import AccelerationCalculation, compute_individual_acceleration\n", + "from pedpy import (\n", + " Cutoff,\n", + " SpeedCalculation,\n", + " compute_individual_speed,\n", + " compute_individual_voronoi_polygons,\n", + ")\n", "\n", - "frame_step = 25\n", + "individual_cutoff = compute_individual_voronoi_polygons(\n", + " traj_data=traj,\n", + " walkable_area=walkable_area,\n", + " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", + ")\n", "\n", - "individual_acceleration_exclude = compute_individual_acceleration(\n", + "individual_speed = compute_individual_speed(\n", " traj_data=traj,\n", - " frame_step=frame_step,\n", - " compute_acceleration_components=True,\n", - " acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE,\n", - ")" + " frame_step=5,\n", + " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", + ")\n", + "\n", + "profile_data = individual_speed.merge(individual_cutoff, on=[ID_COL, FRAME_COL])\n", + "profile_data = profile_data.merge(traj.data, on=[ID_COL, FRAME_COL])" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] - }, + "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", + "from pedpy import (\n", + " compute_grid_cell_polygon_intersection_area,\n", + " get_grid_cells,\n", + ")\n", "\n", - "from pedpy import PEDPY_GREEN\n", + "grid_size = 0.4\n", + "grid_cells, _, _ = get_grid_cells(walkable_area=walkable_area, grid_size=grid_size)\n", "\n", - "ped_id = 50\n", + "min_frame_profiles = 250 # We use here just an excerpt of the\n", + "max_frame_profiles = 400 # trajectory data to reduce compute time\n", "\n", - "plt.figure()\n", - "plt.title(f\"Acceleration time-series of a pedestrian {ped_id} (border excluded)\")\n", - "single_individual_acceleration = individual_acceleration_exclude[individual_acceleration_exclude.id == ped_id]\n", - "plt.plot(\n", - " single_individual_acceleration.frame,\n", - " single_individual_acceleration.acceleration,\n", - " color=PEDPY_GREEN,\n", - ")\n", + "profile_data = profile_data[profile_data.frame.between(min_frame_profiles, max_frame_profiles)]\n", "\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"a / $m/s^2$\")\n", - "plt.show()" + "# Compute the grid intersection area for the resorted profile data (they have the same sorting)\n", + "# for usage in multiple calls to not run the compute heavy operation multiple times\n", + "(\n", + " grid_cell_intersection_area,\n", + " resorted_profile_data,\n", + ") = compute_grid_cell_polygon_intersection_area(data=profile_data, grid_cells=grid_cells)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Individual acceleration in specific movement direction:\n", - "\n", - "It is also possible to compute the individual acceleration in a specific direction\n", - "$d$, for this the movement $\\Delta\\bar{X}$ is projected onto the\n", - "desired movement direction. $\\Delta\\bar{X}$ and $\\Delta t$ are\n", - "computed as described above. Hence, the acceleration then becomes:\n", - "\n", - "$$\n", - "a_i(t) = {{|\\boldsymbol{proj}_d\\; \\Delta\\bar{X}|} \\over {\\Delta t^{2}}}\n", - "$$\n", - "\n", - "If :code:`compute_acceleration_components` is `True` also $\\Delta\\bar{X}$ is returned." + "Create an axis-aligned measurement area to speed-up the profile computation. This can either be done directly from the bounding box of the desired measurement area:" ] }, { @@ -2750,76 +2691,16 @@ "metadata": {}, "outputs": [], "source": [ - "individual_acceleration_direction = compute_individual_acceleration(\n", - " traj_data=traj,\n", - " frame_step=5,\n", - " movement_direction=np.array([0, -1]),\n", - " compute_acceleration_components=True,\n", - " acceleration_calculation=AccelerationCalculation.BORDER_EXCLUDE,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_BLUE, PEDPY_GREEN, PEDPY_GREY, PEDPY_RED\n", - "\n", - "colors = [PEDPY_BLUE, PEDPY_GREY, PEDPY_RED, PEDPY_GREEN]\n", - "ped_ids = [10, 20, 17, 70]\n", - "\n", - "fig = plt.figure()\n", - "plt.title(\"Acceleration time-series of an excerpt of the pedestrians in a specific direction\")\n", - "for color, ped_id in zip(colors, ped_ids, strict=False):\n", - " single_individual_acceleration = individual_acceleration_direction[individual_acceleration_direction.id == ped_id]\n", - " plt.plot(\n", - " single_individual_acceleration.frame,\n", - " single_individual_acceleration.acceleration,\n", - " color=color,\n", - " )\n", - "\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"a / $m/s^2$\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Mean acceleration\n", - "\n", - "Compute mean acceleration per frame inside a given measurement area.\n", - "\n", - "Computes the mean acceleration $a_{mean}(t)$ inside the measurement area from\n", - "the given individual acceleration data $a_i(t)$ (see\n", - ":func:`~acceleration_calculator.compute_individual_acceleration` for\n", - "details of the computation). The mean acceleration $a_{mean}$ is defined as\n", - "\n", - "$$\n", - "a_{mean}(t) = {{1} \\over {N}} \\sum_{i \\in P_M} a_i(t),\n", - "$$\n", + "from pedpy import AxisAlignedMeasurementArea\n", "\n", - "where $P_M$ are all pedestrians inside the measurement area, and\n", - "$N$ the number of pedestrians inside the measurement area (\n", - "$|P_M|$).\n" + "profile_measurement_area = AxisAlignedMeasurementArea(-1.5, 0.5, 1.5, 2.8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - ":::{important}\n", - "The mean acceleration can only be computed when for each pedestrian inside the {class}`measurement area ` also an acceleration $a_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", - "Therefore, further processing steps are needed to ensure that the trajectory data and the individual acceleration data overlap, e.g. as in the example below:" + "Alternatively, an axis-aligned measurement can be created from a given measurement area:" ] }, { @@ -2828,67 +2709,111 @@ "metadata": {}, "outputs": [], "source": [ - "from pedpy import TrajectoryData\n", - "\n", - "traj_idx = pd.MultiIndex.from_frame(traj.data[[\"id\", \"frame\"]])\n", - "acc_idx = pd.MultiIndex.from_frame(individual_acceleration_exclude[[\"id\", \"frame\"]])\n", - "# get intersecting rows in id and frame\n", - "common_idx = traj_idx.intersection(acc_idx)\n", - "traj_common = traj.data.set_index([\"id\", \"frame\"]).reindex(common_idx).reset_index()\n", + "from pedpy import AxisAlignedMeasurementArea, MeasurementArea\n", "\n", - "fr = traj.frame_rate\n", - "traj_exclude = TrajectoryData(data=traj_common, frame_rate=fr)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the mean acceleration can be computed using *PedPy* with:" + "measurement_area = MeasurementArea([(-1.5, 0.5), (1.5, 0.5), (1.5, 2.8), (-1.5, 2.8)])\n", + "profile_measurement_area = AxisAlignedMeasurementArea.from_measurement_area(measurement_area=measurement_area)" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "tags": [ + "hide-input" + ] + }, "outputs": [], "source": [ - "from pedpy import compute_mean_acceleration_per_frame\n", + "from pedpy import plot_measurement_setup\n", "\n", - "mean_acceleration = compute_mean_acceleration_per_frame(\n", - " traj_data=traj_exclude,\n", - " measurement_area=measurement_area,\n", - " individual_acceleration=individual_acceleration_exclude,\n", - ")" + "plot_measurement_setup(\n", + " walkable_area=walkable_area,\n", + " measurement_areas=[profile_measurement_area],\n", + " ma_line_width=2,\n", + " ma_alpha=0.2,\n", + ").set_aspect(\"equal\")\n", + "plt.show()" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, + "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_BLUE, plot_acceleration\n", + "from pedpy import (\n", + " compute_grid_cell_polygon_intersection_area,\n", + " get_grid_cells,\n", + ")\n", "\n", - "plot_acceleration(\n", - " acceleration=mean_acceleration.acceleration,\n", - " title=\"Mean acceleration in front of the bottleneck\",\n", - " color=PEDPY_BLUE,\n", + "grid_cells_measurement_area, _, _ = get_grid_cells(\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", ")\n", - "plt.show()" + "\n", + "min_frame_profiles = 250 # We use here just an excerpt of the\n", + "max_frame_profiles = 400 # trajectory data to reduce compute time\n", + "\n", + "profile_data = profile_data[profile_data.frame.between(min_frame_profiles, max_frame_profiles)]\n", + "\n", + "# Compute the grid intersection area for the resorted profile data (they have the same sorting)\n", + "# for usage in multiple calls to not run the compute heavy operation multiple times\n", + "(\n", + " grid_cell_intersection_area_measurement_area,\n", + " resorted_profile_data_measurement_area,\n", + ") = compute_grid_cell_polygon_intersection_area(data=profile_data, grid_cells=grid_cells_measurement_area)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The same can be now computed, using the speed in a movement direction as basis:" + "### Speed Profiles\n", + "\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 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", + "\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_{\\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_{\\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 average speed of all pedestrians $P_c$ present within a grid cell $c$:\n", + "\n", + "$$\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_{\\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", + "$" ] }, { @@ -2897,10 +2822,37 @@ "metadata": {}, "outputs": [], "source": [ - "mean_acceleration_direction = compute_mean_acceleration_per_frame(\n", - " traj_data=traj_exclude,\n", - " measurement_area=measurement_area,\n", - " individual_acceleration=individual_acceleration_direction,\n", + "from pedpy import SpeedMethod, compute_speed_profile\n", + "\n", + "voronoi_speed_profile = compute_speed_profile(\n", + " data=resorted_profile_data,\n", + " walkable_area=walkable_area,\n", + " grid_intersections_area=grid_cell_intersection_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.VORONOI,\n", + ")\n", + "\n", + "arithmetic_speed_profile = compute_speed_profile(\n", + " data=resorted_profile_data,\n", + " walkable_area=walkable_area,\n", + " grid_intersections_area=grid_cell_intersection_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.ARITHMETIC,\n", + ")\n", + "\n", + "mean_speed_profile = compute_speed_profile(\n", + " data=profile_data,\n", + " 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", + " gaussian_width=0.5,\n", + " speed_method=SpeedMethod.GAUSSIAN,\n", ")" ] }, @@ -2916,1538 +2868,99 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import PEDPY_RED, plot_acceleration\n", + "from pedpy import plot_profiles\n", "\n", - "plot_acceleration(\n", - " acceleration=mean_acceleration_direction.acceleration,\n", - " title=\"Mean acceleration in specific direction in front of the bottleneck\",\n", - " color=PEDPY_RED,\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Voronoi acceleration\n", - "Compute the Voronoi acceleration per frame inside the measurement area.\n", - "\n", - "Computes the Voronoi acceleration $a_{voronoi}(t)$ inside the measurement\n", - "area $M$ from the given individual acceleration data $a_i(t)$ (see\n", - ":func:`~acceleration_calculator.compute_individual_acceleration` for\n", - "details of the computation) and their individual Voronoi intersection data\n", - "(from :func:`~density_calculator.compute_voronoi_density`).\n", - "The individuals' accelerations are weighted by the proportion of their Voronoi cell\n", - "$V_i$ and the intersection with the measurement area\n", - "$V_i \\cap M$.\n", - "\n", - "The Voronoi acceleration $a_{voronoi}(t)$ is defined as\n", - "\n", - "$$\n", - "a_{voronoi}(t) = { \\int\\int a_{xy}(t) dxdy \\over A(M)},\n", - "$$\n", - "\n", - "where $a_{xy}(t) = a_i(t)$ is the individual acceleration of\n", - "each pedestrian, whose $V_i(t) \\cap M$ and $A(M)$ the area of\n", - "the measurement area.\n", - "\n", - "This can be as follows with *PedPy*:" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ":::{important}\n", - "The Voronoi acceleration can only be computed when for each pedestrian inside the {class}`measurement area ` also an acceleration $a_i(t)$ is computed, when using the exclude or adaptive approach this might not be the case.\n", - "\n", - "Therefore, further processing steps are needed to ensure that the trajectory data, the individual acceleration dara as well as the intersecting Voronoi cell data overlap. We assume that the trajectory data is already overlapping (see example above). The intersecting voronoi cells can be filtered, e.g., as in the example below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "intersecting_idx = pd.MultiIndex.from_frame(intersecting[[\"id\", \"frame\"]])\n", - "acc_idx = pd.MultiIndex.from_frame(individual_acceleration_exclude[[\"id\", \"frame\"]])\n", - "# get intersecting rows in id and frame\n", - "common_idx = intersecting_idx.intersection(acc_idx)\n", - "intersecting_exclude = intersecting.set_index([\"id\", \"frame\"]).reindex(common_idx).reset_index()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the Voronoi acceleration can be computed using *PedPy* with:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_voronoi_acceleration\n", - "\n", - "voronoi_acceleration = compute_voronoi_acceleration(\n", - " traj_data=traj_exclude,\n", - " individual_voronoi_intersection=intersecting_exclude,\n", - " individual_acceleration=individual_acceleration_exclude,\n", - " measurement_area=measurement_area,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_ORANGE, plot_acceleration\n", - "\n", - "plot_acceleration(\n", - " acceleration=voronoi_acceleration.acceleration,\n", - " title=\"Voronoi acceleration in front of the bottleneck\",\n", - " color=PEDPY_ORANGE,\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Analogously, this can be done with the acceleration in a specific direction with:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "voronoi_acceleration_direction = compute_voronoi_acceleration(\n", - " traj_data=traj_exclude,\n", - " individual_voronoi_intersection=intersecting_exclude,\n", - " individual_acceleration=individual_acceleration_direction,\n", - " measurement_area=measurement_area,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_GREY, plot_acceleration\n", - "\n", - "plot_acceleration(\n", - " acceleration=voronoi_acceleration.acceleration,\n", - " title=\"Voronoi acceleration in specific direction in front of the bottleneck\",\n", - " color=PEDPY_GREY,\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Comparison mean acceleration vs Voronoi acceleration\n", - "\n", - "We now computed the acceleration with different methods, this plot shows what the different results look like compared to each other:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import PEDPY_BLUE, PEDPY_GREY, PEDPY_ORANGE, PEDPY_RED\n", - "\n", - "plt.figure(figsize=(8, 6))\n", - "plt.title(\"Comparison of different acceleration methods\")\n", - "plt.plot(\n", - " voronoi_acceleration.frame,\n", - " voronoi_acceleration.acceleration,\n", - " label=\"Voronoi\",\n", - " color=PEDPY_ORANGE,\n", - ")\n", - "plt.plot(\n", - " voronoi_acceleration_direction.frame,\n", - " voronoi_acceleration_direction.acceleration,\n", - " label=\"Voronoi direction\",\n", - " color=PEDPY_GREY,\n", - ")\n", - "plt.plot(\n", - " mean_acceleration.frame,\n", - " mean_acceleration.acceleration,\n", - " label=\"classic\",\n", - " color=PEDPY_BLUE,\n", - ")\n", - "plt.plot(\n", - " mean_acceleration_direction.frame,\n", - " mean_acceleration_direction.acceleration,\n", - " label=\"classic direction\",\n", - " color=PEDPY_RED,\n", - ")\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"a / $m/s^2$\")\n", - "plt.legend()\n", - "plt.grid()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "#### Line flow\n", - "\n", - "The line flow is derived from the line speed and line density. It can is used to determine the flow at a measurement line $l$. The values of individuals are weighted based on the proportion of the total length of the line $w$ to the length of the intersection between the line and the Voronoi cell $w_i(t)$.\n", - "The flow at the line is defined as:\n", - "\n", - "$$\n", - "j_\\text{line}(t) = \\sum_{{i | A_i(t) \\cap l \\neq \\emptyset}} v_i(t) \\cdot n_l \\cdot \\frac{1}{A_i(t)} \\cdot \\frac{w_i(t)}{w}\n", - "$$\n", - "\n", - "The line flow only includes the speed orthogonal to the line. Therefore, the speed of the pedestrians $v_i(t)$ is multiplied by the normal vector of the measuring line $n_l$.\n", - "\n", - "The line flow can also be used when pedestrians are approaching the line from both sides.In this case it is possible to analyze the data of both \"species\". In this example there will be only one species as the line is only approached from one side. \n", - "\n", - "For further details check out [this](fundamental_diagram_at_measurement_line) Notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_line_flow\n", - "\n", - "line_flow = compute_line_flow(\n", - " individual_voronoi_polygons=individual_cutoff,\n", - " species=species,\n", - " measurement_line=measurement_line,\n", - " individual_speed=individual_speed_single_sided,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "from pedpy.column_identifier import FLOW_COL\n", - "\n", - "plt.plot(line_flow[FRAME_COL], line_flow[FLOW_COL])\n", - "plt.title(\"flow on line\")\n", - "plt.xlabel(\"frame\")\n", - "plt.ylabel(\"$J$\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Neighborhood\n", - "\n", - "To analyze which pedestrians are close to each other, it is possible to compute the neighbors of each pedestrian.\n", - "We define two pedestrians as neighbors if their Voronoi polygons ($V_i$, $V_j$) touch at some point, in case of *PedPy* they are touching if their distance is below 1mm.\n", - "As basis for the computation one can either use the uncut or cut Voronoi polygons.\n", - "When using the uncut Voronoi polygons, pedestrian may be detected as neighbors even when their distance is quite large in low density situation.\n", - "Therefore, it is recommended to use the cut Voronoi polygons, where the cut-off radius can be used to define a maximal distance between neighboring pedestrians." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Neighborhood computation\n", - "\n", - "To compute the neighbors in *PedPy* use:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "from pedpy import compute_neighbors\n", - "\n", - "neighbors = compute_neighbors(individual_cutoff, as_list=False)\n", - "neighbors[0:5]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_neighborhood\n", - "\n", - "plot_neighborhood(\n", - " pedestrian_id=8,\n", - " voronoi_data=individual_cutoff,\n", - " frame=350,\n", - " neighbors=neighbors,\n", - " walkable_area=walkable_area,\n", - ").set_aspect(\"equal\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ":::{important}\n", - "For legacy reasons the function {func}`compute_neighbors ` works also without specifing {code}`as_list` (defaults to {code}`True`). \n", - "We highly discourage using this, as its result is harder to be used in further computations.\n", - "Use 'as_list=False' instead.\n", - "The default value may change in future versions of *PedPy*.\n", - ":::" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input", - "hide-cell" - ] - }, - "outputs": [], - "source": [ - "neighbors_as_list = compute_neighbors(individual_cutoff)\n", - "neighbors_as_list[0:5]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Distance to neighbors\n", - "\n", - "For computing the distance between neighbors, *PedPy* offers a dedicated function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_neighbor_distance\n", - "\n", - "neighbor_distance = compute_neighbor_distance(traj_data=traj, neighborhood=neighbors)\n", - "neighbor_distance[0:5]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - ":::{note}\n", - "The resulting {class}`~pandas.DataFrame` is symmetric. \n", - "If pedestrian A is a neighbor of pedestrian B, then pedestrian B is also a neighbor of pedestrian A.\n", - "Consequently, the distance between both appears twice in the DataFrame.\n", - ":::" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Distance to entrance/Time to entrance\n", - "\n", - "An indicator to detect congestions or jams in {class}`trajectory data` are distance/time to crossing.\n", - "It shows how much time until the crossing of the {class}`measurement line ` is left and how big the distance to that line is.\n", - "\n", - "In *PedPy* this can be done with:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_time_distance_line\n", - "\n", - "df_time_distance = compute_time_distance_line(traj_data=traj, measurement_line=measurement_line)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_time_distance\n", - "\n", - "plot_time_distance(\n", - " time_distance=df_time_distance,\n", - " title=\"Distance to entrance/Time to entrance\",\n", - " frame_rate=traj.frame_rate,\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can also color the lines based on speed values. \n", - "To achieve this, simply provide a speed DataFrame as an argument to the function:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "speed = compute_individual_speed(traj_data=traj, frame_step=5)\n", - "plot_time_distance(\n", - " time_distance=df_time_distance,\n", - " title=\"Distance to entrance/Time to entrance - colored\",\n", - " frame_rate=traj.frame_rate,\n", - " speed=speed,\n", - ")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "### Profiles\n", - "\n", - "For the computation of the profiles the given {class}`walkable area ` or {class}`axis-aligned measurement area ` is divided into square grid cells.\n", - "Each of these grid cells is then used as a {class}`measurement area ` to compute the density and speed.\n", - "\n", - "When using the whole walkable area for the computing the profiles, the used grid will look like this:\n", - "```{eval-rst}\n", - ".. image:: /images/profile_grid.svg\n", - " :width: 70 %\n", - " :align: center\n", - "```\n", - "\n", - ":::{note}\n", - "As this is a quite compute heavy operation, it is suggested to reduce the trajectories to the important areas and limit the input data to the most relevant frame interval. \n", - "\n", - "In *PedPy* it's possible to pass an axis-aligned measurement area as option to the profile computation, this will retrict the computation to this area.\n", - ":::\n", - "\n", - "\n", - "As the profiles are computed on a square axis-aligned grid, the computation can only be restriced to axis-aligned measurement areas! \n", - "For an arbritraty shaped measurement area, internally the grid would be created for the bounding box of that measurement area. \n", - "We decided to make this step explicit and visible to the users.\n", - "The resulting grid of such an axis-aligned measurement area, could look like this:\n", - "\n", - "```{eval-rst}\n", - ".. image:: /images/profile_axis_aligned_measurement_area_with_grid.svg\n", - " :width: 70 %\n", - " :align: center\n", - "```\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "from pedpy import (\n", - " Cutoff,\n", - " SpeedCalculation,\n", - " compute_individual_speed,\n", - " compute_individual_voronoi_polygons,\n", - ")\n", - "\n", - "individual_cutoff = compute_individual_voronoi_polygons(\n", - " traj_data=traj,\n", - " walkable_area=walkable_area,\n", - " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", - ")\n", - "\n", - "individual_speed = compute_individual_speed(\n", - " traj_data=traj,\n", - " frame_step=5,\n", - " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", - ")\n", - "\n", - "profile_data = individual_speed.merge(individual_cutoff, on=[ID_COL, FRAME_COL])\n", - "profile_data = profile_data.merge(traj.data, on=[ID_COL, FRAME_COL])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import (\n", - " compute_grid_cell_polygon_intersection_area,\n", - " get_grid_cells,\n", - ")\n", - "\n", - "grid_size = 0.4\n", - "grid_cells, _, _ = get_grid_cells(walkable_area=walkable_area, grid_size=grid_size)\n", - "\n", - "min_frame_profiles = 250 # We use here just an excerpt of the\n", - "max_frame_profiles = 400 # trajectory data to reduce compute time\n", - "\n", - "profile_data = profile_data[profile_data.frame.between(min_frame_profiles, max_frame_profiles)]\n", - "\n", - "# Compute the grid intersection area for the resorted profile data (they have the same sorting)\n", - "# for usage in multiple calls to not run the compute heavy operation multiple times\n", - "(\n", - " grid_cell_intersection_area,\n", - " resorted_profile_data,\n", - ") = compute_grid_cell_polygon_intersection_area(data=profile_data, grid_cells=grid_cells)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create an axis-aligned measurement area to speed-up the profile computation. This can either be done directly from the bounding box of the desired measurement area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import AxisAlignedMeasurementArea\n", - "\n", - "profile_measurement_area = AxisAlignedMeasurementArea(-1.5, 0.5, 1.5, 2.8)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Alternatively, an axis-aligned measurement can be created from a given measurement area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import AxisAlignedMeasurementArea, MeasurementArea\n", - "\n", - "measurement_area = MeasurementArea([(-1.5, 0.5), (1.5, 0.5), (1.5, 2.8), (-1.5, 2.8)])\n", - "profile_measurement_area = AxisAlignedMeasurementArea.from_measurement_area(measurement_area=measurement_area)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "from pedpy import plot_measurement_setup\n", - "\n", - "plot_measurement_setup(\n", - " walkable_area=walkable_area,\n", - " measurement_areas=[profile_measurement_area],\n", - " ma_line_width=2,\n", - " ma_alpha=0.2,\n", - ").set_aspect(\"equal\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import (\n", - " compute_grid_cell_polygon_intersection_area,\n", - " get_grid_cells,\n", - ")\n", - "\n", - "grid_cells_measurement_area, _, _ = get_grid_cells(\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_size=grid_size,\n", - ")\n", - "\n", - "min_frame_profiles = 250 # We use here just an excerpt of the\n", - "max_frame_profiles = 400 # trajectory data to reduce compute time\n", - "\n", - "profile_data = profile_data[profile_data.frame.between(min_frame_profiles, max_frame_profiles)]\n", - "\n", - "# Compute the grid intersection area for the resorted profile data (they have the same sorting)\n", - "# for usage in multiple calls to not run the compute heavy operation multiple times\n", - "(\n", - " grid_cell_intersection_area_measurement_area,\n", - " resorted_profile_data_measurement_area,\n", - ") = compute_grid_cell_polygon_intersection_area(data=profile_data, grid_cells=grid_cells_measurement_area)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Speed Profiles\n", - "\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 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", - "\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_{\\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_{\\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 average speed of all pedestrians $P_c$ present within a grid cell $c$:\n", - "\n", - "$$\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_{\\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", - "$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import SpeedMethod, compute_speed_profile\n", - "\n", - "voronoi_speed_profile = compute_speed_profile(\n", - " data=resorted_profile_data,\n", - " walkable_area=walkable_area,\n", - " grid_intersections_area=grid_cell_intersection_area,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.VORONOI,\n", - ")\n", - "\n", - "arithmetic_speed_profile = compute_speed_profile(\n", - " data=resorted_profile_data,\n", - " walkable_area=walkable_area,\n", - " grid_intersections_area=grid_cell_intersection_area,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.ARITHMETIC,\n", - ")\n", - "\n", - "mean_speed_profile = compute_speed_profile(\n", - " data=profile_data,\n", - " 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", - " gaussian_width=0.5,\n", - " speed_method=SpeedMethod.GAUSSIAN,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\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", - " profiles=voronoi_speed_profile,\n", - " axes=ax0,\n", - " label=\"v / m/s\",\n", - " vmin=0,\n", - " vmax=1.5,\n", - " title=\"Voronoi\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " profiles=arithmetic_speed_profile,\n", - " axes=ax1,\n", - " label=\"v / m/s\",\n", - " vmin=0,\n", - " vmax=1.5,\n", - " title=\"Arithmetic\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\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", - "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the same for a specific measurement area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import AxisAlignedMeasurementArea, SpeedMethod, compute_speed_profile\n", - "\n", - "voronoi_speed_profile = compute_speed_profile(\n", - " data=resorted_profile_data_measurement_area,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.VORONOI,\n", - ")\n", - "\n", - "arithmetic_speed_profile = compute_speed_profile(\n", - " data=resorted_profile_data_measurement_area,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.ARITHMETIC,\n", - ")\n", - "\n", - "mean_speed_profile = compute_speed_profile(\n", - " data=profile_data,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_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", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_size=grid_size,\n", - " gaussian_width=0.5,\n", - " speed_method=SpeedMethod.GAUSSIAN,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\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", - " measurement_area=profile_measurement_area,\n", - " profiles=voronoi_speed_profile,\n", - " axes=ax0,\n", - " label=\"v / m/s\",\n", - " vmin=0,\n", - " vmax=1.5,\n", - " title=\"Voronoi\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=arithmetic_speed_profile,\n", - " axes=ax1,\n", - " label=\"v / m/s\",\n", - " vmin=0,\n", - " vmax=1.5,\n", - " title=\"Arithmetic\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\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", - " measurement_area=profile_measurement_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", - "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Density Profiles\n", - "\n", - "Currently, it is possible to compute either the Voronoi, classic or Gaussian density profiles.\n", - "\n", - "**Voronoi density profile**\n", - "\n", - "In each cell the Voronoi speed $v_{voronoi}$ is defined as\n", - "\n", - "$$\n", - " v_{voronoi}(t) = { \\int\\int v_{xy} dxdy \\over A(M)},\n", - "$$ \n", - "where $v_{xy} = v_i$ is the individual speed of each pedestrian, whose $V_i \\cap M$ and $A(M)$ the area the grid cell.\n", - " \n", - "\n", - "**Classic density profile**\n", - "\n", - "In each cell the density $\\rho_{classic}$ is defined by \n", - "\n", - "$$ \n", - " \\rho_{classic} = {N \\over A(M)},\n", - "$$\n", - "where $N$ is the number of pedestrians inside the grid cell $M$ and the area of that grid cell ($A(M)$).\n", - "\n", - "\n", - "**Gaussian density profile**\n", - "\n", - "In each cell the density $\\rho_{gaussian}$ is defined by \n", - " \n", - "$$ \n", - " \\rho_{gaussian} = \\sum_{i=1}^{N}{\\delta (\\boldsymbol{r}_i - \\boldsymbol{c})},\n", - "$$\n", - "\n", - "where $\\boldsymbol{r}_i$ is the position of a pedestrian and $\\boldsymbol{c}$ is the center of the grid cell. Finally $\\delta(x)$ is approximated by a Gaussian\n", - "\n", - "$$ \n", - " \\delta(x) = \\frac{1}{\\sqrt{\\pi}a}\\exp[-x^2/a^2]\n", - "$$" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import DensityMethod, compute_density_profile\n", - "\n", - "# here it is important to use the resorted data, as it needs to be in the same ordering as \"grid_cell_intersection_area\"\n", - "voronoi_density_profile = compute_density_profile(\n", - " data=resorted_profile_data,\n", - " walkable_area=walkable_area,\n", - " grid_intersections_area=grid_cell_intersection_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.VORONOI,\n", - ")\n", - "\n", - "# here the unsorted data can be used\n", - "classic_density_profile = compute_density_profile(\n", - " data=profile_data,\n", - " walkable_area=walkable_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.CLASSIC,\n", - ")\n", - "\n", - "gaussian_density_profile = compute_density_profile(\n", - " data=profile_data,\n", - " walkable_area=walkable_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.GAUSSIAN,\n", - " gaussian_width=0.5,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\n", - "fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, layout=\"constrained\")\n", - "fig.set_size_inches(12, 5)\n", - "fig.suptitle(\"Density profile\")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " profiles=voronoi_density_profile,\n", - " axes=ax0,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Voronoi\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " profiles=classic_density_profile,\n", - " axes=ax1,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Classic\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " profiles=gaussian_density_profile,\n", - " axes=ax2,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Gaussian\",\n", - ")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the same for a specific measurement area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import DensityMethod, compute_density_profile\n", - "\n", - "# here it is important to use the resorted data, as it needs to be in the same ordering as \"grid_cell_intersection_area\"\n", - "voronoi_density_profile = compute_density_profile(\n", - " data=resorted_profile_data_measurement_area,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.VORONOI,\n", - ")\n", - "\n", - "# here the unsorted data can be used\n", - "classic_density_profile = compute_density_profile(\n", - " data=profile_data,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.CLASSIC,\n", - ")\n", - "\n", - "gaussian_density_profile = compute_density_profile(\n", - " data=profile_data,\n", - " walkable_area=walkable_area,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_size=grid_size,\n", - " density_method=DensityMethod.GAUSSIAN,\n", - " gaussian_width=0.5,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\n", - "fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, layout=\"constrained\")\n", - "fig.set_size_inches(12, 5)\n", - "fig.suptitle(\"Density profile\")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=voronoi_density_profile,\n", - " axes=ax0,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Voronoi\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=classic_density_profile,\n", - " axes=ax1,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Classic\",\n", - ")\n", - "cm = plot_profiles(\n", - " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=gaussian_density_profile,\n", - " axes=ax2,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", - " vmin=0,\n", - " vmax=8,\n", - " title=\"Gaussian\",\n", - ")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Density and Speed Profiles\n", - "\n", - "An other option is to compute both kinds of profile at the same time:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "from pedpy import compute_profiles\n", - "\n", - "min_frame_profiles = 250 # We use here just an excerpt of the\n", - "max_frame_profiles = 400 # trajectory data to reduce compute time\n", - "grid_size = 0.4\n", - "\n", - "density_profiles, speed_profiles = compute_profiles(\n", - " data=pd.merge(\n", - " individual_cutoff[individual_cutoff.frame.between(min_frame_profiles, max_frame_profiles)],\n", - " individual_speed[individual_speed.frame.between(min_frame_profiles, max_frame_profiles)],\n", - " on=[ID_COL, FRAME_COL],\n", - " ),\n", - " walkable_area=walkable_area.polygon,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.ARITHMETIC,\n", - " density_method=DensityMethod.VORONOI,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - }, - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\n", - "fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)\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", - " profiles=density_profiles,\n", + " profiles=voronoi_speed_profile,\n", " axes=ax0,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " label=\"v / m/s\",\n", " vmin=0,\n", - " vmax=12,\n", - " title=\"Density\",\n", + " vmax=1.5,\n", + " title=\"Voronoi\",\n", ")\n", "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " profiles=speed_profiles,\n", + " profiles=arithmetic_speed_profile,\n", " axes=ax1,\n", " label=\"v / m/s\",\n", " vmin=0,\n", - " vmax=2,\n", - " title=\"Speed\",\n", + " vmax=1.5,\n", + " title=\"Arithmetic\",\n", ")\n", - "fig.tight_layout(pad=2)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now the same for a specific measurement area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_profiles\n", - "\n", - "min_frame_profiles = 250 # We use here just an excerpt of the\n", - "max_frame_profiles = 400 # trajectory data to reduce compute time\n", - "grid_size = 0.4\n", - "\n", - "density_profiles, speed_profiles = compute_profiles(\n", - " data=pd.merge(\n", - " individual_cutoff[individual_cutoff.frame.between(min_frame_profiles, max_frame_profiles)],\n", - " individual_speed[individual_speed.frame.between(min_frame_profiles, max_frame_profiles)],\n", - " on=[ID_COL, FRAME_COL],\n", - " ),\n", - " walkable_area=walkable_area.polygon,\n", - " axis_aligned_measurement_area=profile_measurement_area,\n", - " grid_size=grid_size,\n", - " speed_method=SpeedMethod.ARITHMETIC,\n", - " density_method=DensityMethod.VORONOI,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_profiles\n", - "\n", - "fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)\n", "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=density_profiles,\n", - " axes=ax0,\n", - " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " profiles=gauss_speed_profile,\n", + " axes=ax2,\n", + " label=\"v / m/s\",\n", " vmin=0,\n", - " vmax=12,\n", - " title=\"Density\",\n", + " vmax=1.5,\n", + " title=\"Gauss\",\n", ")\n", "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " measurement_area=profile_measurement_area,\n", - " profiles=speed_profiles,\n", - " axes=ax1,\n", + " profiles=mean_speed_profile,\n", + " axes=ax3,\n", " label=\"v / m/s\",\n", - " vmin=0,\n", - " vmax=2,\n", - " title=\"Speed\",\n", - ")\n", - "fig.tight_layout(pad=2)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Spatial Analysis \n", - "This section corresponds to analysis method which can be used to characterise different crowds or group formations.\n", - "These methods may include measurement of the time-to-collision, pair-distribution function and measurement of crowd polarization.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Pair-distribution function (PDF)\n", - "\n", - "This method is inspired from condensed matter description and used in the work of [Cordes et al. (2023)](https://doi.org/10.1093/pnasnexus/pgae120) following [Karamousas et al. (2014)](https://doi.org/10.1103/PhysRevLett.113.238701).\n", - "The pair-distribution function (PDF): \n", - "\n", - "$$g(r)=P(r)/P_{Ni}(r)$$.\n", - "\n", - "\"Quantifies the probability that two interacting pedestrians are found a given distance r apart, renormalized by the probability $P_{Ni}$ of measuring this distance for pedestrians that do not interact.\"\n", - "\n", - "In this method, \"interacting pedestrians\" are defined as pedestrians that are present in the same spatial domain at the same time. One should also keep in mind that in its current implementation, the method does not take into account walls and corners, which should in theory block any \"interaction\" between pedestrians on opposite sides of the obstacles.\n", - "\n", - "The probability $P_{Ni}$ is approximated here by time randomising the original trajectory file. For this randomisation process, only the frame numbers of the trajectory file are shuffled. The created \"randomised trajectories\" contain random pedestrian positions, composed only of positions present in the original trajectory file. This method helps account for pedestrians' preferred space utilisation, which can be due to terrain features or social behaviours. One should note that the number of positions selected for each frame is also random during the creation of the randomised trajectory file. The random process should ensure a uniform distribution of positions for each frame. However, to smooth any noise that this method may induce, we recommend using a higher `randomisation_stacking` number (see details in the next section).\n", - "\n", - "The pair-distribution function of a given crowd recording can be computed using the following instructions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_pair_distribution_function\n", - "\n", - "# Compute pair distribution function\n", - "radius_bins, pair_distribution = compute_pair_distribution_function(\n", - " traj_data=traj, radius_bin_size=0.1, randomisation_stacking=1\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "fig, ax1 = plt.subplots(figsize=(5, 5))\n", - "ax1.plot(radius_bins, pair_distribution)\n", - "ax1.set_title(\"Pair Distribution Function\")\n", - "ax1.set_xlabel(\"$r$\", fontsize=16)\n", - "ax1.set_ylabel(\"$g(r)$\", fontsize=16)\n", - "ax1.grid(True, alpha=0.3)\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##### Parameters of the PDF\n", - "\n", - "The function `compute_pair_distribution_function` has two main parameters:\n", - "\n", - "- `radius_bin_size` is the size of the radius bins for which probability will be computed. On one hand a larger bin size results in smoother pdf but decreases the accuracy of the description, as more individuals can be detected in each bin. On the other hand, a smaller bin will increase the accuracy of the description but may lead to noisy or `Nan` values as each bin may not be populated (leading to invalid divisions). We suggest using a bin size value between 0.1 and 0.3 m as these values are close to order of magniture of a chest depth.\n", - "- `randomisation_stacking` is the number of time the data stacked before being shuffled in order to compute the probability $P_{Ni}$ of measuring given pair-wise distances for pedestrians that do not interact. Stacking the data multiple times helps harmonize the random positions more effectively, ensuring that the PDF converges to results that are independent of the randomization method." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First we show the influence of varying `radius_bin_size` on the result:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from pedpy import compute_pair_distribution_function\n", - "\n", - "radius_bin_sizes = [0.05, 0.1, 0.25, 0.5]\n", - "\n", - "varying_radius_bin_sizes = [\n", - " (\n", - " i,\n", - " compute_pair_distribution_function(\n", - " traj_data=traj,\n", - " radius_bin_size=radius_bin_size,\n", - " randomisation_stacking=1,\n", - " ),\n", - " )\n", - " for i, radius_bin_size in enumerate(radius_bin_sizes)\n", - "]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And now how `randomisation_stacking` influences the result:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from time import time\n", - "\n", - "from pedpy import (\n", - " compute_pair_distribution_function,\n", + " vmin=0,\n", + " vmax=1.5,\n", + " title=\"Mean\",\n", ")\n", - "\n", - "randomisation_stackings = [1, 3, 5]\n", - "\n", - "varying_randomisation_stacking = []\n", - "\n", - "for i, randomisation_stacking in enumerate(randomisation_stackings):\n", - " begin_time = time()\n", - "\n", - " pdf = compute_pair_distribution_function(\n", - " traj_data=traj,\n", - " radius_bin_size=0.15,\n", - " randomisation_stacking=randomisation_stacking,\n", - " )\n", - " end_time = time()\n", - "\n", - " varying_randomisation_stacking.append((i, pdf, end_time - begin_time))" + "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", + "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "These variations generate the following result:" + "Now the same for a specific measurement area:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, + "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from matplotlib.cm import twilight\n", - "\n", - "cmap = twilight\n", - "\n", - "fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(10, 5))\n", - "ax0.set_title(\"Effect of `radius_bin_size`\")\n", - "\n", - "for i, pdf in varying_radius_bin_sizes:\n", - " radius_bins, pair_distribution = pdf\n", - " ax0.plot(\n", - " radius_bins,\n", - " pair_distribution,\n", - " color=twilight(i / len(varying_radius_bin_sizes)),\n", - " label=\"$r_{bin}=$\" + str(radius_bin_sizes[i]),\n", - " )\n", - "\n", - "ax0.set_ylim((0, 1.3))\n", - "ax0.set_xlabel(\"$r$\", fontsize=16)\n", - "ax0.set_ylabel(\"$g(r)$\", fontsize=16)\n", - "ax0.grid(True, alpha=0.3)\n", - "ax0.legend(title=\"Bin sizes\")\n", - "\n", - "ax1.set_title(\"Effect of 'randomisation_stacking'\")\n", - "for i, pdf, _time in varying_randomisation_stacking:\n", - " radius_bins, pair_distribution = pdf\n", - " ax1.plot(\n", - " radius_bins,\n", - " pair_distribution,\n", - " color=cmap(i / len(varying_randomisation_stacking)),\n", - " label=str(randomisation_stackings[i]) + \" times: \" + str(np.round(_time, 2)) + \"s\",\n", - " )\n", - "ax1.set_ylim((0, 1.3))\n", - "ax1.set_ylabel(\"$g(r)$\", fontsize=16)\n", - "ax1.set_xlabel(\"$r$\", fontsize=16)\n", - "ax1.grid(True, alpha=0.3)\n", - "\n", - "fig.tight_layout()\n", - "ax1.legend(title=\"Nb of stacks: Execution time\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "## Preprocess the data\n", - "\n", - "Until now, we used complete trajectories, but sometimes not all the data is relevant for the analysis. \n", - "If the data comes from larger simulation or experiments you may be only interested in data close to your region of interest or data in a specific time range.\n", + "from pedpy import AxisAlignedMeasurementArea, SpeedMethod, compute_speed_profile\n", "\n", - "As *PedPy* builds up on *Pandas* as data container, the filtering methods from *Pandas* can also be used here.\n", - "More information on filtering and merging with *Pandas* can be found here: [filtering](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/indexing.html) & [merging](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/merging.html)." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Geometric filtering\n", + "voronoi_speed_profile = compute_speed_profile(\n", + " data=resorted_profile_data_measurement_area,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.VORONOI,\n", + ")\n", "\n", - "First, we want to filter the data by geometrical principles, therefor we combine the capabilities of *Pandas* and *Shapely*. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Data inside Polygon\n", + "arithmetic_speed_profile = compute_speed_profile(\n", + " data=resorted_profile_data_measurement_area,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.ARITHMETIC,\n", + ")\n", "\n", - "In the first case, we are only interested in {class}`trajectory data` inside the known {class}`measurement area `. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "import shapely\n", + "mean_speed_profile = compute_speed_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.MEAN,\n", + ")\n", "\n", - "bottleneck = shapely.Polygon([(0.25, 0), (0.25, -1), (-0.25, -1), (-0.25, 0)])\n", - "leaving_area = shapely.Polygon([(-3, -1), (-3, -2), (3, -2), (3, -1)])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_inside_ma = traj.data[shapely.within(traj.data.point, bottleneck)]" + "gauss_speed_profile = compute_speed_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", + " gaussian_width=0.5,\n", + " speed_method=SpeedMethod.GAUSSIAN,\n", + ")" ] }, { @@ -4462,21 +2975,51 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import plot_profiles\n", "\n", - "ax = plot_measurement_setup(\n", - " traj=TrajectoryData(data_inside_ma, frame_rate=traj.frame_rate),\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", + " measurement_area=profile_measurement_area,\n", + " profiles=voronoi_speed_profile,\n", + " axes=ax0,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=1.5,\n", + " title=\"Voronoi\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\n", + " profiles=arithmetic_speed_profile,\n", + " axes=ax1,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=1.5,\n", + " title=\"Arithmetic\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\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", - " measurement_areas=[MeasurementArea(bottleneck)],\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - " ml_width=1,\n", - " ma_alpha=0.1,\n", - " ma_line_width=1,\n", + " measurement_area=profile_measurement_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", - "ax.set_xlim([-0.75, 0.75])\n", - "ax.set_ylim([-1.5, 0.5])\n", - "ax.set_aspect(\"equal\")\n", + "plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=0, hspace=0.6)\n", "plt.show()" ] }, @@ -4484,115 +3027,77 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Data outside Polygon\n", + "### Density Profiles\n", "\n", - "Secondly, we want to filter the data, such that the result contains only data which is outside a given area. \n", - "In our case we want to remove all data behind the bottleneck, here called leaving area:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_outside_leaving_area = traj.data[~shapely.within(traj.data.point, leaving_area)]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", + "Currently, it is possible to compute either the Voronoi, classic or Gaussian density profiles.\n", "\n", - "from pedpy import plot_measurement_setup\n", + "**Voronoi density profile**\n", "\n", - "ax = plot_measurement_setup(\n", - " traj=TrajectoryData(data_outside_leaving_area, frame_rate=traj.frame_rate),\n", - " walkable_area=walkable_area,\n", - " measurement_areas=[MeasurementArea(leaving_area)],\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - " ml_width=1,\n", - " ma_alpha=0.1,\n", - " ma_line_width=1,\n", - ").set_aspect(\"equal\")\n", + "In each cell the Voronoi speed $v_{voronoi}$ is defined as\n", "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Data close to line\n", + "$$\n", + " v_{voronoi}(t) = { \\int\\int v_{xy} dxdy \\over A(M)},\n", + "$$ \n", + "where $v_{xy} = v_i$ is the individual speed of each pedestrian, whose $V_i \\cap M$ and $A(M)$ the area the grid cell.\n", + " \n", "\n", - "It is not only possible to check whether a point is within a given polygon, it is also possible to check if the distance to a given geometrical object is below a given threshold.\n", - "Here we want all the data that is within 1m of the {class}`measurement line ` at the entrance of the bottleneck: " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "data_close_ma = traj.data[shapely.dwithin(traj.data.point, measurement_line.line, 1)]" + "**Classic density profile**\n", + "\n", + "In each cell the density $\\rho_{classic}$ is defined by \n", + "\n", + "$$ \n", + " \\rho_{classic} = {N \\over A(M)},\n", + "$$\n", + "where $N$ is the number of pedestrians inside the grid cell $M$ and the area of that grid cell ($A(M)$).\n", + "\n", + "\n", + "**Gaussian density profile**\n", + "\n", + "In each cell the density $\\rho_{gaussian}$ is defined by \n", + " \n", + "$$ \n", + " \\rho_{gaussian} = \\sum_{i=1}^{N}{\\delta (\\boldsymbol{r}_i - \\boldsymbol{c})},\n", + "$$\n", + "\n", + "where $\\boldsymbol{r}_i$ is the position of a pedestrian and $\\boldsymbol{c}$ is the center of the grid cell. Finally $\\delta(x)$ is approximated by a Gaussian\n", + "\n", + "$$ \n", + " \\delta(x) = \\frac{1}{\\sqrt{\\pi}a}\\exp[-x^2/a^2]\n", + "$$" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, + "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", + "from pedpy import DensityMethod, compute_density_profile\n", "\n", - "from pedpy import plot_measurement_setup\n", + "# here it is important to use the resorted data, as it needs to be in the same ordering as \"grid_cell_intersection_area\"\n", + "voronoi_density_profile = compute_density_profile(\n", + " data=resorted_profile_data,\n", + " walkable_area=walkable_area,\n", + " grid_intersections_area=grid_cell_intersection_area,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.VORONOI,\n", + ")\n", "\n", - "ax = plot_measurement_setup(\n", - " traj=TrajectoryData(data_close_ma, frame_rate=traj.frame_rate),\n", + "# here the unsorted data can be used\n", + "classic_density_profile = compute_density_profile(\n", + " data=profile_data,\n", " walkable_area=walkable_area,\n", - " measurement_lines=[measurement_line],\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - " ml_width=1,\n", - " ma_alpha=0.1,\n", - " ma_line_width=1,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.CLASSIC,\n", ")\n", - "ax.set_xlim([-1.5, 1.5])\n", - "ax.set_ylim([-2.5, 1.5])\n", - "ax.set_aspect(\"equal\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Time based filtering\n", "\n", - "It is not only possible to filter the data by geometrical means, but also depending on time information.\n", - "In experiments, you might only be interested in steady state data, this can be also be achieved by slicing the data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "traj_data_frame_range = traj[300:600]" + "gaussian_density_profile = compute_density_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.GAUSSIAN,\n", + " gaussian_width=0.5,\n", + ")" ] }, { @@ -4607,14 +3112,38 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import plot_profiles\n", "\n", - "plot_measurement_setup(\n", - " traj=traj_data_frame_range,\n", + "fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, layout=\"constrained\")\n", + "fig.set_size_inches(12, 5)\n", + "fig.suptitle(\"Density profile\")\n", + "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - ").set_aspect(\"equal\")\n", + " profiles=voronoi_density_profile,\n", + " axes=ax0,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Voronoi\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=classic_density_profile,\n", + " axes=ax1,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Classic\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=gaussian_density_profile,\n", + " axes=ax2,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Gaussian\",\n", + ")\n", "\n", "plt.show()" ] @@ -4623,7 +3152,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Alternatively, you could also utilise the Pandas filtering methods:" + "Now the same for a specific measurement area:" ] }, { @@ -4632,66 +3161,41 @@ "metadata": {}, "outputs": [], "source": [ - "data_frame_range = traj.data[traj.data.frame.between(300, 600, inclusive=\"both\")]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "hide-input" - ] - }, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import DensityMethod, compute_density_profile\n", "\n", - "plot_measurement_setup(\n", - " traj=TrajectoryData(data_frame_range, frame_rate=traj.frame_rate),\n", + "# here it is important to use the resorted data, as it needs to be in the same ordering as \"grid_cell_intersection_area\"\n", + "voronoi_density_profile = compute_density_profile(\n", + " data=resorted_profile_data_measurement_area,\n", " walkable_area=walkable_area,\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - ").set_aspect(\"equal\")\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_intersections_area=grid_cell_intersection_area_measurement_area,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.VORONOI,\n", + ")\n", "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "### ID based filtering\n", + "# here the unsorted data can be used\n", + "classic_density_profile = compute_density_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.CLASSIC,\n", + ")\n", "\n", - "It is also possible to filter the data in a way that only specific pedestrians are contained:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], - "source": [ - "data_id = traj.data[traj.data.id == 20]" + "gaussian_density_profile = compute_density_profile(\n", + " data=profile_data,\n", + " walkable_area=walkable_area,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", + " density_method=DensityMethod.GAUSSIAN,\n", + " gaussian_width=0.5,\n", + ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - }, "tags": [ "hide-input" ] @@ -4700,26 +3204,52 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import plot_profiles\n", "\n", - "plot_measurement_setup(\n", - " traj=TrajectoryData(data_id, frame_rate=traj.frame_rate),\n", + "fig, (ax0, ax1, ax2) = plt.subplots(nrows=1, ncols=3, layout=\"constrained\")\n", + "fig.set_size_inches(12, 5)\n", + "fig.suptitle(\"Density profile\")\n", + "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - ").set_aspect(\"equal\")\n", + " measurement_area=profile_measurement_area,\n", + " profiles=voronoi_density_profile,\n", + " axes=ax0,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Voronoi\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\n", + " profiles=classic_density_profile,\n", + " axes=ax1,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Classic\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\n", + " profiles=gaussian_density_profile,\n", + " axes=ax2,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=8,\n", + " title=\"Gaussian\",\n", + ")\n", + "\n", "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "It is also possible to filter for multiple ids:" + "### Density and Speed Profiles\n", + "\n", + "An other option is to compute both kinds of profile at the same time:" ] }, { @@ -4732,8 +3262,23 @@ }, "outputs": [], "source": [ - "ids = [10, 20, 30, 40]\n", - "data_id = traj.data[traj.data.id.isin(ids)]" + "from pedpy import compute_profiles\n", + "\n", + "min_frame_profiles = 250 # We use here just an excerpt of the\n", + "max_frame_profiles = 400 # trajectory data to reduce compute time\n", + "grid_size = 0.4\n", + "\n", + "density_profiles, speed_profiles = compute_profiles(\n", + " data=pd.merge(\n", + " individual_cutoff[individual_cutoff.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " individual_speed[individual_speed.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " on=[ID_COL, FRAME_COL],\n", + " ),\n", + " walkable_area=walkable_area.polygon,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.ARITHMETIC,\n", + " density_method=DensityMethod.VORONOI,\n", + ")" ] }, { @@ -4751,56 +3296,36 @@ "source": [ "import matplotlib.pyplot as plt\n", "\n", - "from pedpy import plot_measurement_setup\n", + "from pedpy import plot_profiles\n", "\n", - "plot_measurement_setup(\n", - " traj=TrajectoryData(data_id, frame_rate=traj.frame_rate),\n", + "fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)\n", + "cm = plot_profiles(\n", " walkable_area=walkable_area,\n", - " traj_alpha=0.7,\n", - " traj_width=0.4,\n", - ").set_aspect(\"equal\")\n", - "\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "## What to do with the results?\n", - "\n", - "Now we have completed our analysis, what can be done to \n", - "\n", - "- access specific columns\n", - "- combine for further analysis\n", - "- save results" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Access specific columns\n", - "\n", - "In order to access a specific column of the computed results or the {class}`trajectory data` *PedPy* provides all the column names for access.\n", - "It is highly encouraged to use these identifiers instead of using the column names directly, as the identifier would be updated, if they change. \n", - "A list of all identifiers can be found in the [API reference](api_column_identifier)." + " profiles=density_profiles,\n", + " axes=ax0,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=12,\n", + " title=\"Density\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " profiles=speed_profiles,\n", + " axes=ax1,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=2,\n", + " title=\"Speed\",\n", + ")\n", + "fig.tight_layout(pad=2)\n", + "plt.show()" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "from pedpy import FRAME_COL, ID_COL, POINT_COL\n", - "\n", - "# Access with column identifier\n", - "traj.data[[ID_COL, FRAME_COL, POINT_COL]]" + "Now the same for a specific measurement area:" ] }, { @@ -4809,241 +3334,265 @@ "metadata": {}, "outputs": [], "source": [ - "# Access with column names\n", - "traj.data[[\"id\", \"frame\", \"point\"]]" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "### Combine multiple DataFrames\n", + "from pedpy import compute_profiles\n", + "\n", + "min_frame_profiles = 250 # We use here just an excerpt of the\n", + "max_frame_profiles = 400 # trajectory data to reduce compute time\n", + "grid_size = 0.4\n", "\n", - "From the analysis we have one {class}`~pandas.DataFrame` containing the {class}`trajectory data`:" + "density_profiles, speed_profiles = compute_profiles(\n", + " data=pd.merge(\n", + " individual_cutoff[individual_cutoff.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " individual_speed[individual_speed.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " on=[ID_COL, FRAME_COL],\n", + " ),\n", + " walkable_area=walkable_area.polygon,\n", + " axis_aligned_measurement_area=profile_measurement_area,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.ARITHMETIC,\n", + " density_method=DensityMethod.VORONOI,\n", + ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ - "traj.data" + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_profiles\n", + "\n", + "fig, (ax0, ax1) = plt.subplots(nrows=1, ncols=2)\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\n", + " profiles=density_profiles,\n", + " axes=ax0,\n", + " label=\"$\\\\rho$ / 1/$m^2$\",\n", + " vmin=0,\n", + " vmax=12,\n", + " title=\"Density\",\n", + ")\n", + "cm = plot_profiles(\n", + " walkable_area=walkable_area,\n", + " measurement_area=profile_measurement_area,\n", + " profiles=speed_profiles,\n", + " axes=ax1,\n", + " label=\"v / m/s\",\n", + " vmin=0,\n", + " vmax=2,\n", + " title=\"Speed\",\n", + ")\n", + "fig.tight_layout(pad=2)\n", + "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "source": [ - "and one containing the individual Voronoi data:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, - "outputs": [], + "metadata": {}, "source": [ - "individual" + "## Spatial Analysis \n", + "This section corresponds to analysis method which can be used to characterise different crowds or group formations.\n", + "These methods may include measurement of the time-to-collision, pair-distribution function and measurement of crowd polarization.\n" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "As both have the columns 'id' and 'frame' in common, these can be used to merge the dataframes.\n", - "See {func}`pandas.merge` and {meth}`pandas.DataFrame.merge` for more information on how {class}`DataFrames ` can be merged." + "### Pair-distribution function (PDF)\n", + "\n", + "This method is inspired from condensed matter description and used in the work of [Cordes et al. (2023)](https://doi.org/10.1093/pnasnexus/pgae120) following [Karamousas et al. (2014)](https://doi.org/10.1103/PhysRevLett.113.238701).\n", + "The pair-distribution function (PDF): \n", + "\n", + "$$g(r)=P(r)/P_{Ni}(r)$$.\n", + "\n", + "\"Quantifies the probability that two interacting pedestrians are found a given distance r apart, renormalized by the probability $P_{Ni}$ of measuring this distance for pedestrians that do not interact.\"\n", + "\n", + "In this method, \"interacting pedestrians\" are defined as pedestrians that are present in the same spatial domain at the same time. One should also keep in mind that in its current implementation, the method does not take into account walls and corners, which should in theory block any \"interaction\" between pedestrians on opposite sides of the obstacles.\n", + "\n", + "The probability $P_{Ni}$ is approximated here by time randomising the original trajectory file. For this randomisation process, only the frame numbers of the trajectory file are shuffled. The created \"randomised trajectories\" contain random pedestrian positions, composed only of positions present in the original trajectory file. This method helps account for pedestrians' preferred space utilisation, which can be due to terrain features or social behaviours. One should note that the number of positions selected for each frame is also random during the creation of the randomised trajectory file. The random process should ensure a uniform distribution of positions for each frame. However, to smooth any noise that this method may induce, we recommend using a higher `randomisation_stacking` number (see details in the next section).\n", + "\n", + "The pair-distribution function of a given crowd recording can be computed using the following instructions:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "data_with_voronoi_cells = traj.data.merge(intersecting, on=[ID_COL, FRAME_COL])\n", - "data_with_voronoi_cells" + "from pedpy import compute_pair_distribution_function\n", + "\n", + "# Compute pair distribution function\n", + "radius_bins, pair_distribution = compute_pair_distribution_function(\n", + " traj_data=traj, radius_bin_size=0.1, randomisation_stacking=1\n", + ")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ - "from pedpy import SPEED_COL\n", + "import matplotlib.pyplot as plt\n", "\n", - "data_with_voronoi_cells_speed = data_with_voronoi_cells.merge(\n", - " individual_speed[[ID_COL, FRAME_COL, SPEED_COL]], on=[ID_COL, FRAME_COL]\n", - ")\n", - "data_with_voronoi_cells_speed" + "fig, ax1 = plt.subplots(figsize=(5, 5))\n", + "ax1.plot(radius_bins, pair_distribution)\n", + "ax1.set_title(\"Pair Distribution Function\")\n", + "ax1.set_xlabel(\"$r$\", fontsize=16)\n", + "ax1.set_ylabel(\"$g(r)$\", fontsize=16)\n", + "ax1.grid(True, alpha=0.3)\n", + "plt.show()" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "### Save in files\n", + "#### Parameters of the PDF\n", + "\n", + "The function `compute_pair_distribution_function` has two main parameters:\n", "\n", - "For preserving the results on the disk, the results need to be saved on the disk.\n", - "This can be done with the build-in functions from *Pandas*." + "- `radius_bin_size` is the size of the radius bins for which probability will be computed. On one hand a larger bin size results in smoother pdf but decreases the accuracy of the description, as more individuals can be detected in each bin. On the other hand, a smaller bin will increase the accuracy of the description but may lead to noisy or `Nan` values as each bin may not be populated (leading to invalid divisions). We suggest using a bin size value between 0.1 and 0.3 m as these values are close to order of magniture of a chest depth.\n", + "- `randomisation_stacking` is the number of time the data stacked before being shuffled in order to compute the probability $P_{Ni}$ of measuring given pair-wise distances for pedestrians that do not interact. Stacking the data multiple times helps harmonize the random positions more effectively, ensuring that the PDF converges to results that are independent of the randomization method." ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "#### Create directories to store the results\n", - "\n", - "First we create a directory, where we want to save the results.\n", - "This step is optional!" + "First we show the influence of varying `radius_bin_size` on the result:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "pathlib.Path(\"results_introduction/profiles/speed\").mkdir(parents=True, exist_ok=True)\n", - "pathlib.Path(\"results_introduction/profiles/density\").mkdir(parents=True, exist_ok=True)\n", + "from pedpy import compute_pair_distribution_function\n", + "\n", + "radius_bin_sizes = [0.05, 0.1, 0.25, 0.5]\n", "\n", - "results_directory = pathlib.Path(\"results_introduction\")" + "varying_radius_bin_sizes = [\n", + " (\n", + " i,\n", + " compute_pair_distribution_function(\n", + " traj_data=traj,\n", + " radius_bin_size=radius_bin_size,\n", + " randomisation_stacking=1,\n", + " ),\n", + " )\n", + " for i, radius_bin_size in enumerate(radius_bin_sizes)\n", + "]" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "#### Save Pandas DataFrame (result from everything but profiles) as csv\n", - "\n", - "Now, a {class}`~pandas.DataFrame` can be saved as `csv` with:" + "And now how `randomisation_stacking` influences the result:" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "outputs": [], "source": [ - "import csv\n", + "from time import time\n", "\n", "from pedpy import (\n", - " DENSITY_COL,\n", - " INTERSECTION_COL,\n", - " POLYGON_COL,\n", - " X_COL,\n", - " Y_COL,\n", + " compute_pair_distribution_function,\n", ")\n", "\n", - "with open(results_directory / \"individual_result.csv\", \"w\") as individual_output_file:\n", - " individual_output_file.write(f\"#framerate:\t{traj.frame_rate}\\n\\n\")\n", - " data_with_voronoi_cells_speed[\n", - " [\n", - " ID_COL,\n", - " FRAME_COL,\n", - " X_COL,\n", - " Y_COL,\n", - " DENSITY_COL,\n", - " SPEED_COL,\n", - " POLYGON_COL,\n", - " INTERSECTION_COL,\n", - " ]\n", - " ].to_csv(\n", - " individual_output_file,\n", - " mode=\"a\",\n", - " header=True,\n", - " sep=\"\\t\",\n", - " index_label=False,\n", - " index=False,\n", - " quoting=csv.QUOTE_NONNUMERIC,\n", - " )" + "randomisation_stackings = [1, 3, 5]\n", + "\n", + "varying_randomisation_stacking = []\n", + "\n", + "for i, randomisation_stacking in enumerate(randomisation_stackings):\n", + " begin_time = time()\n", + "\n", + " pdf = compute_pair_distribution_function(\n", + " traj_data=traj,\n", + " radius_bin_size=0.15,\n", + " randomisation_stacking=randomisation_stacking,\n", + " )\n", + " end_time = time()\n", + "\n", + " varying_randomisation_stacking.append((i, pdf, end_time - begin_time))" ] }, { "cell_type": "markdown", - "metadata": { - "jupyter": { - "outputs_hidden": false - } - }, + "metadata": {}, "source": [ - "#### Save numpy arrays (result from profiles) as txt\n", - "\n", - "The profiles are returned as Numpy arrays, which also provide a build-in save function, which allows to save the arrays as txt format:" + "These variations generate the following result:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "jupyter": { - "outputs_hidden": false - } + "tags": [ + "hide-input" + ] }, "outputs": [], "source": [ - "results_directory_density = results_directory / \"profiles/density\"\n", - "results_directory_speed = results_directory / \"profiles/speed\"\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from matplotlib.cm import twilight\n", + "\n", + "cmap = twilight\n", + "\n", + "fig, (ax0, ax1) = plt.subplots(1, 2, figsize=(10, 5))\n", + "ax0.set_title(\"Effect of `radius_bin_size`\")\n", + "\n", + "for i, pdf in varying_radius_bin_sizes:\n", + " radius_bins, pair_distribution = pdf\n", + " ax0.plot(\n", + " radius_bins,\n", + " pair_distribution,\n", + " color=twilight(i / len(varying_radius_bin_sizes)),\n", + " label=\"$r_{bin}=$\" + str(radius_bin_sizes[i]),\n", + " )\n", + "\n", + "ax0.set_ylim((0, 1.3))\n", + "ax0.set_xlabel(\"$r$\", fontsize=16)\n", + "ax0.set_ylabel(\"$g(r)$\", fontsize=16)\n", + "ax0.grid(True, alpha=0.3)\n", + "ax0.legend(title=\"Bin sizes\")\n", "\n", - "for i in range(len(range(min_frame_profiles, min_frame_profiles + 10))):\n", - " frame = min_frame_profiles + i\n", - " np.savetxt(\n", - " results_directory_density / f\"density_frame_{frame:05d}.txt\",\n", - " density_profiles[i],\n", + "ax1.set_title(\"Effect of 'randomisation_stacking'\")\n", + "for i, pdf, _time in varying_randomisation_stacking:\n", + " radius_bins, pair_distribution = pdf\n", + " ax1.plot(\n", + " radius_bins,\n", + " pair_distribution,\n", + " color=cmap(i / len(varying_randomisation_stacking)),\n", + " label=str(randomisation_stackings[i]) + \" times: \" + str(np.round(_time, 2)) + \"s\",\n", " )\n", - " np.savetxt(\n", - " results_directory_speed / f\"speed_frame_{frame:05d}.txt\",\n", - " speed_profiles[i],\n", - " )" + "ax1.set_ylim((0, 1.3))\n", + "ax1.set_ylabel(\"$g(r)$\", fontsize=16)\n", + "ax1.set_xlabel(\"$r$\", fontsize=16)\n", + "ax1.grid(True, alpha=0.3)\n", + "\n", + "fig.tight_layout()\n", + "ax1.legend(title=\"Nb of stacks: Execution time\")\n", + "plt.show()" ] } ], diff --git a/notebooks/demo-data/bi-directional/bi_corr_400_b_03.txt b/docs/source/notebooks/demo-data/bi-directional/bi_corr_400_b_03.txt similarity index 100% rename from notebooks/demo-data/bi-directional/bi_corr_400_b_03.txt rename to docs/source/notebooks/demo-data/bi-directional/bi_corr_400_b_03.txt diff --git a/notebooks/demo-data/bi-directional/bi_corr_400_b_08.txt b/docs/source/notebooks/demo-data/bi-directional/bi_corr_400_b_08.txt similarity index 100% rename from notebooks/demo-data/bi-directional/bi_corr_400_b_08.txt rename to docs/source/notebooks/demo-data/bi-directional/bi_corr_400_b_08.txt diff --git a/notebooks/demo-data/bi-directional/bidirectional_data.pkl b/docs/source/notebooks/demo-data/bi-directional/bidirectional_data.pkl similarity index 100% rename from notebooks/demo-data/bi-directional/bidirectional_data.pkl rename to docs/source/notebooks/demo-data/bi-directional/bidirectional_data.pkl diff --git a/notebooks/demo-data/bottleneck/040_c_56_h-.png b/docs/source/notebooks/demo-data/bottleneck/040_c_56_h-.png similarity index 100% rename from notebooks/demo-data/bottleneck/040_c_56_h-.png rename to docs/source/notebooks/demo-data/bottleneck/040_c_56_h-.png diff --git a/notebooks/demo-data/bottleneck/040_c_56_h-.txt b/docs/source/notebooks/demo-data/bottleneck/040_c_56_h-.txt similarity index 100% rename from notebooks/demo-data/bottleneck/040_c_56_h-.txt rename to docs/source/notebooks/demo-data/bottleneck/040_c_56_h-.txt diff --git a/notebooks/demo-data/bottleneck/experimental_setup.png b/docs/source/notebooks/demo-data/bottleneck/experimental_setup.png similarity index 100% rename from notebooks/demo-data/bottleneck/experimental_setup.png rename to docs/source/notebooks/demo-data/bottleneck/experimental_setup.png diff --git a/notebooks/demo-data/crowdit/crowdit.csv.gz b/docs/source/notebooks/demo-data/crowdit/crowdit.csv.gz similarity index 100% rename from notebooks/demo-data/crowdit/crowdit.csv.gz rename to docs/source/notebooks/demo-data/crowdit/crowdit.csv.gz diff --git a/notebooks/demo-data/crowdit/crowdit.floor b/docs/source/notebooks/demo-data/crowdit/crowdit.floor similarity index 100% rename from notebooks/demo-data/crowdit/crowdit.floor rename to docs/source/notebooks/demo-data/crowdit/crowdit.floor diff --git a/notebooks/demo-data/jupedsim/trajectories.sqlite b/docs/source/notebooks/demo-data/jupedsim/trajectories.sqlite similarity index 100% rename from notebooks/demo-data/jupedsim/trajectories.sqlite rename to docs/source/notebooks/demo-data/jupedsim/trajectories.sqlite diff --git a/notebooks/demo-data/pathfinder/pathfinder.csv b/docs/source/notebooks/demo-data/pathfinder/pathfinder.csv similarity index 100% rename from notebooks/demo-data/pathfinder/pathfinder.csv rename to docs/source/notebooks/demo-data/pathfinder/pathfinder.csv diff --git a/notebooks/demo-data/pathfinder/pathfinder.json b/docs/source/notebooks/demo-data/pathfinder/pathfinder.json similarity index 100% rename from notebooks/demo-data/pathfinder/pathfinder.json rename to docs/source/notebooks/demo-data/pathfinder/pathfinder.json diff --git a/notebooks/demo-data/single_file/00_01a.h5 b/docs/source/notebooks/demo-data/single_file/00_01a.h5 similarity index 100% rename from notebooks/demo-data/single_file/00_01a.h5 rename to docs/source/notebooks/demo-data/single_file/00_01a.h5 diff --git a/notebooks/demo-data/single_file/n34_cam2.csv b/docs/source/notebooks/demo-data/single_file/n34_cam2.csv similarity index 100% rename from notebooks/demo-data/single_file/n34_cam2.csv rename to docs/source/notebooks/demo-data/single_file/n34_cam2.csv diff --git a/notebooks/demo-data/single_file/n56_cam1.csv b/docs/source/notebooks/demo-data/single_file/n56_cam1.csv similarity index 100% rename from notebooks/demo-data/single_file/n56_cam1.csv rename to docs/source/notebooks/demo-data/single_file/n56_cam1.csv diff --git a/notebooks/demo-data/uni-directional/comparison/method_a_uo.png b/docs/source/notebooks/demo-data/uni-directional/comparison/method_a_uo.png similarity index 100% rename from notebooks/demo-data/uni-directional/comparison/method_a_uo.png rename to docs/source/notebooks/demo-data/uni-directional/comparison/method_a_uo.png diff --git a/notebooks/demo-data/uni-directional/comparison/method_b_uo.png b/docs/source/notebooks/demo-data/uni-directional/comparison/method_b_uo.png similarity index 100% rename from notebooks/demo-data/uni-directional/comparison/method_b_uo.png rename to docs/source/notebooks/demo-data/uni-directional/comparison/method_b_uo.png diff --git a/notebooks/demo-data/uni-directional/comparison/method_c_uo.png b/docs/source/notebooks/demo-data/uni-directional/comparison/method_c_uo.png similarity index 100% rename from notebooks/demo-data/uni-directional/comparison/method_c_uo.png rename to docs/source/notebooks/demo-data/uni-directional/comparison/method_c_uo.png diff --git a/notebooks/demo-data/uni-directional/comparison/method_d_uo.png b/docs/source/notebooks/demo-data/uni-directional/comparison/method_d_uo.png similarity index 100% rename from notebooks/demo-data/uni-directional/comparison/method_d_uo.png rename to docs/source/notebooks/demo-data/uni-directional/comparison/method_d_uo.png diff --git a/notebooks/demo-data/uni-directional/geo.png b/docs/source/notebooks/demo-data/uni-directional/geo.png similarity index 100% rename from notebooks/demo-data/uni-directional/geo.png rename to docs/source/notebooks/demo-data/uni-directional/geo.png diff --git a/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_01.txt b/docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_01.txt similarity index 100% rename from notebooks/demo-data/uni-directional/traj_UNI_CORR_500_01.txt rename to docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_01.txt diff --git a/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_03.txt b/docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_03.txt similarity index 100% rename from notebooks/demo-data/uni-directional/traj_UNI_CORR_500_03.txt rename to docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_03.txt diff --git a/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_08.txt b/docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_08.txt similarity index 100% rename from notebooks/demo-data/uni-directional/traj_UNI_CORR_500_08.txt rename to docs/source/notebooks/demo-data/uni-directional/traj_UNI_CORR_500_08.txt diff --git a/notebooks/demo-data/uni-directional/unidirectional_data.pkl b/docs/source/notebooks/demo-data/uni-directional/unidirectional_data.pkl similarity index 100% rename from notebooks/demo-data/uni-directional/unidirectional_data.pkl rename to docs/source/notebooks/demo-data/uni-directional/unidirectional_data.pkl diff --git a/notebooks/demo-data/uni-directional/uo-050-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-050-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-050-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-050-180-180.txt diff --git a/notebooks/demo-data/uni-directional/uo-060-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-060-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-060-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-060-180-180.txt diff --git a/notebooks/demo-data/uni-directional/uo-070-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-070-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-070-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-070-180-180.txt diff --git a/notebooks/demo-data/uni-directional/uo-100-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-100-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-100-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-100-180-180.txt diff --git a/notebooks/demo-data/uni-directional/uo-145-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-145-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-145-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-145-180-180.txt diff --git a/notebooks/demo-data/uni-directional/uo-180-180-070.txt b/docs/source/notebooks/demo-data/uni-directional/uo-180-180-070.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-180-180-070.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-180-180-070.txt diff --git a/notebooks/demo-data/uni-directional/uo-180-180-095.txt b/docs/source/notebooks/demo-data/uni-directional/uo-180-180-095.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-180-180-095.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-180-180-095.txt diff --git a/notebooks/demo-data/uni-directional/uo-180-180-120.txt b/docs/source/notebooks/demo-data/uni-directional/uo-180-180-120.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-180-180-120.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-180-180-120.txt diff --git a/notebooks/demo-data/uni-directional/uo-180-180-180.txt b/docs/source/notebooks/demo-data/uni-directional/uo-180-180-180.txt similarity index 100% rename from notebooks/demo-data/uni-directional/uo-180-180-180.txt rename to docs/source/notebooks/demo-data/uni-directional/uo-180-180-180.txt diff --git a/notebooks/demo-data/vadere/bottleneck/bottleneck.scenario b/docs/source/notebooks/demo-data/vadere/bottleneck/bottleneck.scenario similarity index 100% rename from notebooks/demo-data/vadere/bottleneck/bottleneck.scenario rename to docs/source/notebooks/demo-data/vadere/bottleneck/bottleneck.scenario diff --git a/notebooks/demo-data/vadere/bottleneck/postvis.traj b/docs/source/notebooks/demo-data/vadere/bottleneck/postvis.traj similarity index 100% rename from notebooks/demo-data/vadere/bottleneck/postvis.traj rename to docs/source/notebooks/demo-data/vadere/bottleneck/postvis.traj diff --git a/notebooks/demo-data/vadere/bottleneck/vadere_bottleneck.scenario b/docs/source/notebooks/demo-data/vadere/bottleneck/vadere_bottleneck.scenario similarity index 100% rename from notebooks/demo-data/vadere/bottleneck/vadere_bottleneck.scenario rename to docs/source/notebooks/demo-data/vadere/bottleneck/vadere_bottleneck.scenario diff --git a/notebooks/demo-data/vadere/bottleneck/vadere_postvis.traj b/docs/source/notebooks/demo-data/vadere/bottleneck/vadere_postvis.traj similarity index 100% rename from notebooks/demo-data/vadere/bottleneck/vadere_postvis.traj rename to docs/source/notebooks/demo-data/vadere/bottleneck/vadere_postvis.traj diff --git a/notebooks/demo-data/viswalk/example.pp b/docs/source/notebooks/demo-data/viswalk/example.pp similarity index 100% rename from notebooks/demo-data/viswalk/example.pp rename to docs/source/notebooks/demo-data/viswalk/example.pp diff --git a/notebooks/fundamental_diagram.ipynb b/docs/source/notebooks/fundamental_diagram.ipynb similarity index 97% rename from notebooks/fundamental_diagram.ipynb rename to docs/source/notebooks/fundamental_diagram.ipynb index 9f1e03c4..2a4b683f 100644 --- a/notebooks/fundamental_diagram.ipynb +++ b/docs/source/notebooks/fundamental_diagram.ipynb @@ -4,7 +4,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Setup" + "# Fundamental Diagram" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup" ] }, { @@ -52,7 +59,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Load trajectories" + "### Load trajectories" ] }, { @@ -87,7 +94,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define geometry" + "### Define geometry" ] }, { @@ -124,7 +131,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define measurement areas and lines" + "### Define measurement areas and lines" ] }, { @@ -143,7 +150,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot trajectories, geometry, and measurement areas/lines" + "### Plot trajectories, geometry, and measurement areas/lines" ] }, { @@ -194,14 +201,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Method A" + "## Method A" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute n-t and flow" + "### Compute n-t and flow" ] }, { @@ -249,7 +256,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot n-t diagram" + "### Plot n-t diagram" ] }, { @@ -276,7 +283,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot fundamental diagram" + "### Plot fundamental diagram" ] }, { @@ -345,14 +352,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Method B" + "## Method B" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute individual speed and density while passing the measurement area" + "### Compute individual speed and density while passing the measurement area" ] }, { @@ -398,7 +405,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot fundamental diagram" + "### Plot fundamental diagram" ] }, { @@ -439,14 +446,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Method C" + "## Method C" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute density and speed" + "### Compute density and speed" ] }, { @@ -489,7 +496,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot time series data" + "### Plot time series data" ] }, { @@ -544,7 +551,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot fundamental diagram" + "### Plot fundamental diagram" ] }, { @@ -587,21 +594,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Method D" + "## Method D" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute density and speed" + "### Compute density and speed" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Without cutoff radius" + "#### Without cutoff radius" ] }, { @@ -654,7 +661,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### With cutoff radius" + "#### With cutoff radius" ] }, { @@ -708,7 +715,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot time-series data" + "### Plot time-series data" ] }, { @@ -783,7 +790,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot fundamental diagram" + "### Plot fundamental diagram" ] }, { @@ -843,14 +850,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Profiles" + "## Profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute speed and density profiles" + "### Compute speed and density profiles" ] }, { @@ -915,7 +922,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Plot profiles" + "### Plot profiles" ] }, { @@ -963,7 +970,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Plots" + "## Plots" ] }, { @@ -1045,13 +1052,6 @@ ")\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/notebooks/fundamental_diagram_at_measurement_line.ipynb b/docs/source/notebooks/fundamental_diagram_at_measurement_line.ipynb similarity index 100% rename from notebooks/fundamental_diagram_at_measurement_line.ipynb rename to docs/source/notebooks/fundamental_diagram_at_measurement_line.ipynb diff --git a/notebooks/getting_started.ipynb b/docs/source/notebooks/getting_started.ipynb similarity index 100% rename from notebooks/getting_started.ipynb rename to docs/source/notebooks/getting_started.ipynb diff --git a/docs/source/notebooks/measurement_setup.ipynb b/docs/source/notebooks/measurement_setup.ipynb new file mode 100644 index 00000000..f0de75ec --- /dev/null +++ b/docs/source/notebooks/measurement_setup.ipynb @@ -0,0 +1,622 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [], + "source": [ + "import pathlib\n", + "import warnings\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import shapely\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Analysis Setup\n", + "\n", + "This notebook covers the complete setup needed before starting an analysis with *PedPy*.\n", + "You will learn how to:\n", + "\n", + "- Define the **walkable area** where pedestrians can move\n", + "- Set up **measurement areas** and **measurement lines** for your analysis\n", + "- **Import trajectory data** from various file formats\n", + "\n", + "This guide uses a bottleneck experiment conducted at the University of Wuppertal in 2018 as a running example.\n", + "\n", + "```{eval-rst}\n", + ".. figure:: demo-data/bottleneck/040_c_56_h-.png\n", + " :width: 400px\n", + " :align: center\n", + "```\n", + "\n", + "The data for this experiment is available {download}`here `, which belongs to this [experimental series](https://doi.org/10.34735/ped.2018.1) and is part of the publication [\"Crowds in front of bottlenecks at entrances from the perspective of physics and social psychology\"](https://doi.org/10.1098/rsif.2019.0871)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Measurement Setup\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Walkable area\n", + "\n", + "In the beginning we will define the {class}`walkable area ` in which the pedestrian can move. \n", + "For the used bottleneck experiment was conducted in the following set up:\n", + "\n", + "\n", + "```{eval-rst}\n", + ".. figure:: demo-data/bottleneck/experimental_setup.png\n", + " :width: 50 %\n", + " :align: center\n", + "```\n", + "\n", + "The run handled in this user guide had a bottleneck width of 0.5m and w=5.6m.\n", + "\n", + "Below is the code for creating such a {class}`walkable area `:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import WalkableArea\n", + "\n", + "walkable_area = WalkableArea(\n", + " # complete area\n", + " [\n", + " (3.5, -2),\n", + " (3.5, 8),\n", + " (-3.5, 8),\n", + " (-3.5, -2),\n", + " ],\n", + " obstacles=[\n", + " # left barrier\n", + " [\n", + " (-0.7, -1.1),\n", + " (-0.25, -1.1),\n", + " (-0.25, -0.15),\n", + " (-0.4, 0.0),\n", + " (-2.8, 0.0),\n", + " (-2.8, 6.7),\n", + " (-3.05, 6.7),\n", + " (-3.05, -0.3),\n", + " (-0.7, -0.3),\n", + " (-0.7, -1.0),\n", + " ],\n", + " # right barrier\n", + " [\n", + " (0.25, -1.1),\n", + " (0.7, -1.1),\n", + " (0.7, -0.3),\n", + " (3.05, -0.3),\n", + " (3.05, 6.7),\n", + " (2.8, 6.7),\n", + " (2.8, 0.0),\n", + " (0.4, 0.0),\n", + " (0.25, -0.15),\n", + " (0.25, -1.1),\n", + " ],\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_walkable_area\n", + "\n", + "plot_walkable_area(walkable_area=walkable_area).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Measurement areas and measurement lines\n", + "\n", + "After we defined where the pedestrians can move, we now need to define in which regions we want to analyze in more details. \n", + "This regions can either be a specific line, an area, or the whole {class}`walkable area `.\n", + "\n", + "In case of this bottleneck the most interesting area is a little bit in front of the bottleneck (here 0.5m) and the line at the beginning of the bottleneck.\n", + "The area is slightly in front of the bottleneck as here the highest density occur. \n", + "In *PedPy* such areas are called {class}`~geometry.MeasurementArea` and the lines {class}`~geometry.MeasurementLine`.\n", + "Below you can see how to define these:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import MeasurementArea, MeasurementLine\n", + "\n", + "measurement_area = MeasurementArea([(-0.4, 0.5), (0.4, 0.5), (0.4, 1.3), (-0.4, 1.3)])\n", + "\n", + "measurement_line = MeasurementLine([(0.4, 0), (-0.4, 0)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The corresponding measurement setup looks like:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "plot_measurement_setup(\n", + " walkable_area=walkable_area,\n", + " measurement_lines=[measurement_line],\n", + " ml_width=2,\n", + " measurement_areas=[measurement_area],\n", + " ma_line_width=2,\n", + " ma_alpha=0.2,\n", + ").set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Importing pedestrian movement data\n", + "\n", + "The pedestrian movement data in *PedPy* is called {class}`trajectory data`.\n", + "\n", + "*PedPy* works with {class}`trajectory data` which can be created from an import function for specific data files alternatively from a {class}`~pandas.DataFrame` with the following columns:\n", + "\n", + "- \"id\": unique numeric identifier for each person\n", + "- \"frame\": index of video frame where the positions were extracted\n", + "- \"x\", \"y\": position of the person (in meter)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from Pandas DataFrame\n", + "\n", + "To construct the {class}`trajectory data` from a {class}`~pandas.DataFrame` you also need to provide the frame rate at which the data was recorded.\n", + "If you have both the construction of the {class}`trajectory data` can be done with:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import TrajectoryData\n", + "\n", + "data = pd.DataFrame(\n", + " [[0, 1, 0, 0]],\n", + " columns=[\"id\", \"frame\", \"x\", \"y\"],\n", + ")\n", + "trajectory_data = TrajectoryData(data=data, frame_rate=25.0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, the data can be also loaded from any file format that is supported by *Pandas*, see the [documentation](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/io.html) for more details.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from text trajectory files\n", + "\n", + "*Pedpy* can load trajectories, if they are stored as the {class}`trajectory data` provided in the [Jülich Data Archive](https://ped.fz-juelich.de/da/doku.php) directly.\n", + "If you have text files in same format, you can load them in the same way too:\n", + "\n", + "- values are separated by any whitespace, e.g., space, tab\n", + "- file has at least 4 columns in the following order: \"id\", \"frame\", \"x\", \"y\"\n", + "- file may contain comment lines with `#` at in the beginning\n", + "\n", + "For meaningful analysis (and loading of the trajectory file) you also need\n", + "- unit of the trajectory (m or cm)\n", + "- frame rate\n", + "\n", + "For recent trajectory they are encoded in the header of the file, for older you may need to lead the documentation and provide the information in the loading process.\n", + "\n", + "**Examples:**\n", + "With frame rate, but no unit\n", + "```\n", + "# description: UNI_CORR_500_01\n", + "# framerate: 25.00\n", + "#geometry: geometry.xml\n", + "\n", + "# PersIDFrameXYZ\n", + "1 984.60121.89091.7600\n", + "1 994.53591.89761.7600\n", + "1 1004.44701.93041.7600\n", + "...\n", + "```\n", + "\n", + "No header at all:\n", + "```\n", + "1 27 164.834 780.844 168.937\n", + "1 28 164.835 771.893 168.937\n", + "1 29 163.736 762.665 168.937\n", + "1 30 161.967 753.088 168.937\n", + "...\n", + "```\n", + "\n", + "If your data is structured in a different way please take a look at the next section.\n", + "Since the data we want to analyze is from the data archive, we can directly load the {class}`trajectory data` with *PedPy*:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import TrajectoryUnit, load_trajectory\n", + "\n", + "traj = load_trajectory(\n", + " trajectory_file=pathlib.Path(\"demo-data/bottleneck/040_c_56_h-.txt\"),\n", + " default_unit=TrajectoryUnit.METER, # needs to be provided as it not defined in the file\n", + " # default_frame_rate=25., # can be ignored here as the frame rate is defined in the file\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The loaded {class}`trajectory data` look like:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from hdf5 trajectory files\n", + "\n", + "For some experiments the [Jülich Data Archive](https://ped.fz-juelich.de/da/doku.php) also provides [HDF5](https://www.hdfgroup.org/HDF5) trajectory files, with a structure described [here](https://ped.fz-juelich.de/da/doku.php?id=info).\n", + "These data are from a different experiment, and are only used to demonstrate how to load HDF5 files, it can be downloaded {download}`here `.\n", + "\n", + "To make the data usable for *PedPy* use:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "\n", + "from pedpy import (\n", + " TrajectoryData,\n", + " load_trajectory_from_ped_data_archive_hdf5,\n", + " load_walkable_area_from_ped_data_archive_hdf5,\n", + ")\n", + "\n", + "h5_file = pathlib.Path(\"demo-data/single_file/00_01a.h5\")\n", + "\n", + "traj_h5 = load_trajectory_from_ped_data_archive_hdf5(trajectory_file=h5_file)\n", + "walkable_area_h5 = load_walkable_area_from_ped_data_archive_hdf5(trajectory_file=h5_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj_h5, walkable_area=walkable_area_h5).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from Viswalk trajectory files\n", + "\n", + "It is also possible to load trajectory files from [Viswalk](https://www.ptvgroup.com/en/products/pedestrian-simulation-software-ptv-viswalk) directly into *PedPy*. \n", + "The expected format is a CSV file with `;` as delimiter, and it should contain at least the following columns: `NO`, `SIMSEC`, `COORDCENTX`, `COORDCENTY`.\n", + "Comment lines may start with a `*` and will be ignored.\n", + "\n", + ":::{important}\n", + "Currently only Viswalk trajectory files, which use the simulation time (`SIMSEC`) are supported.\n", + ":::\n", + "\n", + "\n", + "To make the data usable for *PedPy* use:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "\n", + "from pedpy import (\n", + " TrajectoryData,\n", + " load_trajectory_from_viswalk,\n", + ")\n", + "\n", + "viswalk_file = pathlib.Path(\"demo-data/viswalk/example.pp\")\n", + "\n", + "traj_viswalk = load_trajectory_from_viswalk(trajectory_file=viswalk_file)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj_viswalk).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from Vadere trajectory files\n", + "\n", + "It is also possible to load trajectory files from [Vadere](https://www.vadere.org/) directly into *PedPy*. \n", + "The expected format is a CSV file with space character as delimiter, and it should contain at least the following columns: `pedestrianId`, `simTime`, `startX`, `startY`.\n", + "Comment lines may start with a `#` and will be ignored.\n", + "\n", + "\n", + "To make the data usable for *PedPy* use:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "\n", + "from pedpy import (\n", + " TrajectoryData,\n", + " load_trajectory_from_vadere,\n", + " load_walkable_area_from_vadere_scenario,\n", + ")\n", + "\n", + "vadere_traj_file = pathlib.Path(\"demo-data/vadere/bottleneck/vadere_postvis.traj\")\n", + "vadere_scenario_file = pathlib.Path(\"demo-data/vadere/bottleneck/vadere_bottleneck.scenario\")\n", + "\n", + "traj_vadere = load_trajectory_from_vadere(trajectory_file=vadere_traj_file, frame_rate=24.0)\n", + "vadere_walkable_area = load_walkable_area_from_vadere_scenario(vadere_scenario_file=vadere_scenario_file, margin=1e-3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj_vadere, walkable_area=vadere_walkable_area).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from Pathfinder trajectory files\n", + "\n", + "[Pathfinder](https://www.thunderheadeng.com/pathfinder/) produces trajectory data in two formats: csv and json.\n", + "\n", + "Both formats are supported by *PedPy* using the following calls:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "\n", + "from pedpy import (\n", + " load_trajectory_from_pathfinder_csv,\n", + " load_trajectory_from_pathfinder_json,\n", + ")\n", + "\n", + "traj_pathfinder_csv = load_trajectory_from_pathfinder_csv(\n", + " trajectory_file=pathlib.Path(\"demo-data/pathfinder/pathfinder.csv\")\n", + ")\n", + "traj_pathfinder_json = load_trajectory_from_pathfinder_json(\n", + " trajectory_file=pathlib.Path(\"demo-data/pathfinder/pathfinder.json\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "fig, ax = plt.subplots(1, 2, figsize=(10, 5))\n", + "plot_trajectories(traj=traj_pathfinder_csv, axes=ax[0]).set_aspect(\"equal\")\n", + "plot_trajectories(\n", + " traj=traj_pathfinder_json,\n", + " axes=ax[1],\n", + ").set_aspect(\"equal\")\n", + "ax[0].set_title(\"Pathfinder CSV\")\n", + "ax[1].set_title(\"Pathfinder JSON\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from Crowd:it trajectory files\n", + "\n", + "[Crowd:it](https://www.accu-rate.de/en/software/crowdit/) generates a trajectory file and a geometry file. *PedPy* reads both out of the box as follows:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "\n", + "from pedpy import load_trajectory_from_crowdit, load_walkable_area_from_crowdit\n", + "\n", + "traj_crowdit = load_trajectory_from_crowdit(trajectory_file=pathlib.Path(\"demo-data/crowdit/crowdit.csv.gz\"))\n", + "geometry_crowdit = load_walkable_area_from_crowdit(geometry_file=pathlib.Path(\"demo-data/crowdit/crowdit.floor\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj_crowdit, walkable_area=geometry_crowdit).set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Loading from JuPedSim trajectory files\n", + "\n", + "[JuPedSim](https://jupedsim.org) produces trajectory data in SQLite format containing the trajectories as well as the geometry data in WKT format. \n", + "*PedPy* supports both natively:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import (\n", + " load_trajectory_from_jupedsim_sqlite,\n", + " load_walkable_area_from_jupedsim_sqlite,\n", + ")\n", + "\n", + "traj_jupedsim = load_trajectory_from_jupedsim_sqlite(\n", + " trajectory_file=pathlib.Path(\"demo-data/jupedsim/trajectories.sqlite\")\n", + ")\n", + "geometry_jupedsim = load_walkable_area_from_jupedsim_sqlite(\n", + " trajectory_file=pathlib.Path(\"demo-data/jupedsim/trajectories.sqlite\")\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_trajectories\n", + "\n", + "plot_trajectories(traj=traj_jupedsim, walkable_area=geometry_jupedsim).set_aspect(\"equal\")\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/notebooks/preprocessing.ipynb b/docs/source/notebooks/preprocessing.ipynb new file mode 100644 index 00000000..b24f6b21 --- /dev/null +++ b/docs/source/notebooks/preprocessing.ipynb @@ -0,0 +1,559 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [], + "source": [ + "import pathlib\n", + "import warnings\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "import shapely\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pre-processing\n", + "\n", + "Before starting the actual analysis, it is important to verify and prepare your data.\n", + "This notebook covers:\n", + "\n", + "- **Validating** that trajectories lie within the walkable area\n", + "- **Filtering** data by geometry, time, or pedestrian ID\n", + "\n", + "First, we set up the data needed for the examples (see [Analysis Setup](measurement_setup) for details):\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import (\n", + " MeasurementArea,\n", + " MeasurementLine,\n", + " TrajectoryData,\n", + " TrajectoryUnit,\n", + " WalkableArea,\n", + " load_trajectory,\n", + ")\n", + "\n", + "walkable_area = WalkableArea(\n", + " [\n", + " (3.5, -2),\n", + " (3.5, 8),\n", + " (-3.5, 8),\n", + " (-3.5, -2),\n", + " ],\n", + " obstacles=[\n", + " [\n", + " (-0.7, -1.1),\n", + " (-0.25, -1.1),\n", + " (-0.25, -0.15),\n", + " (-0.4, 0.0),\n", + " (-2.8, 0.0),\n", + " (-2.8, 6.7),\n", + " (-3.05, 6.7),\n", + " (-3.05, -0.3),\n", + " (-0.7, -0.3),\n", + " (-0.7, -1.0),\n", + " ],\n", + " [\n", + " (0.25, -1.1),\n", + " (0.7, -1.1),\n", + " (0.7, -0.3),\n", + " (3.05, -0.3),\n", + " (3.05, 6.7),\n", + " (2.8, 6.7),\n", + " (2.8, 0.0),\n", + " (0.4, 0.0),\n", + " (0.25, -0.15),\n", + " (0.25, -1.1),\n", + " ],\n", + " ],\n", + ")\n", + "\n", + "measurement_area = MeasurementArea([(-0.4, 0.5), (0.4, 0.5), (0.4, 1.3), (-0.4, 1.3)])\n", + "measurement_line = MeasurementLine([(0.4, 0), (-0.4, 0)])\n", + "\n", + "traj = load_trajectory(\n", + " trajectory_file=pathlib.Path(\"demo-data/bottleneck/040_c_56_h-.txt\"),\n", + " default_unit=TrajectoryUnit.METER,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Trajectory validation\n", + "\n", + "An important step before starting the analysis is to verify that all trajectories lie within the constructed {class}`walkable area `.\n", + "Otherwise, you might get errors.\n", + "*PedPy* provides a function to test your trajectories, and offers also a function to get all invalid trajectories:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import get_invalid_trajectory, is_trajectory_valid\n", + "\n", + "print(f\"Trajectory is valid: {is_trajectory_valid(traj_data=traj, walkable_area=walkable_area)}\")\n", + "get_invalid_trajectory(traj_data=traj, walkable_area=walkable_area)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**For demonstration purposes, wrongly place the obstacle s.th. some pedestrian walk through it!**\n", + "\n", + "We now create a faulty geometry, s.th. you can see how the result would like.\n", + "Therefore, the right obstacle will be moved a bit towards the center of the bottlneck:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import WalkableArea\n", + "\n", + "walkable_area_faulty = WalkableArea(\n", + " # complete area\n", + " [\n", + " (3.5, -2),\n", + " (3.5, 8),\n", + " (-3.5, 8),\n", + " (-3.5, -2),\n", + " ],\n", + " obstacles=[\n", + " # left barrier\n", + " [\n", + " (-0.7, -1.1),\n", + " (-0.25, -1.1),\n", + " (-0.25, -0.15),\n", + " (-0.4, 0.0),\n", + " (-2.8, 0.0),\n", + " (-2.8, 6.7),\n", + " (-3.05, 6.7),\n", + " (-3.05, -0.3),\n", + " (-0.7, -0.3),\n", + " (-0.7, -1.0),\n", + " ],\n", + " # right barrier is too close to the middle\n", + " [\n", + " (0.15, -1.1),\n", + " (0.6, -1.1),\n", + " (0.6, -0.3),\n", + " (3.05, -0.3),\n", + " (3.05, 6.7),\n", + " (2.8, 6.7),\n", + " (2.8, 0.0),\n", + " (0.3, 0.0),\n", + " (0.15, -0.15),\n", + " (0.15, -1.1),\n", + " ],\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "ax = plot_measurement_setup(\n", + " traj=traj,\n", + " walkable_area=walkable_area_faulty,\n", + " traj_alpha=0.5,\n", + " traj_width=1,\n", + " hole_color=\"lightgrey\",\n", + ")\n", + "ax.set_xlim([-1, 1])\n", + "ax.set_ylim([-1, 1])\n", + "ax.set_xticks([-1, -0.5, 0, 0.5, 1])\n", + "ax.set_yticks([-1, -0.5, 0, 0.5, 1])\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you get any invalid trajectories, you should check whether you constructed your {class}`walkable area ` correctly.\n", + "In some cases you will get such errors when you have head trajectories, and the pedestrian lean over the obstacles.\n", + "Then you need to prepare your data before you can start your analysis.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import get_invalid_trajectory, is_trajectory_valid\n", + "\n", + "print(f\"Trajectory is valid: {is_trajectory_valid(traj_data=traj, walkable_area=walkable_area_faulty)}\")\n", + "get_invalid_trajectory(traj_data=traj, walkable_area=walkable_area_faulty)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filtering the data\n", + "\n", + "Until now, we used complete trajectories, but sometimes not all the data is relevant for the analysis. \n", + "If the data comes from larger simulation or experiments you may be only interested in data close to your region of interest or data in a specific time range.\n", + "\n", + "As *PedPy* builds up on *Pandas* as data container, the filtering methods from *Pandas* can also be used here.\n", + "More information on filtering and merging with *Pandas* can be found here: [filtering](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/indexing.html) & [merging](https://pandas.pydata.org/pandas-docs/version/2.0/user_guide/merging.html).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geometric filtering\n", + "\n", + "First, we want to filter the data by geometrical principles, therefor we combine the capabilities of *Pandas* and *Shapely*.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data inside Polygon\n", + "\n", + "In the first case, we are only interested in {class}`trajectory data` inside the known {class}`measurement area `.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shapely\n", + "\n", + "bottleneck = shapely.Polygon([(0.25, 0), (0.25, -1), (-0.25, -1), (-0.25, 0)])\n", + "leaving_area = shapely.Polygon([(-3, -1), (-3, -2), (3, -2), (3, -1)])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_inside_ma = traj.data[shapely.within(traj.data.point, bottleneck)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "ax = plot_measurement_setup(\n", + " traj=TrajectoryData(data_inside_ma, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " measurement_areas=[MeasurementArea(bottleneck)],\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + " ml_width=1,\n", + " ma_alpha=0.1,\n", + " ma_line_width=1,\n", + ")\n", + "ax.set_xlim([-0.75, 0.75])\n", + "ax.set_ylim([-1.5, 0.5])\n", + "ax.set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data outside Polygon\n", + "\n", + "Secondly, we want to filter the data, such that the result contains only data which is outside a given area. \n", + "In our case we want to remove all data behind the bottleneck, here called leaving area:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_outside_leaving_area = traj.data[~shapely.within(traj.data.point, leaving_area)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "ax = plot_measurement_setup(\n", + " traj=TrajectoryData(data_outside_leaving_area, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " measurement_areas=[MeasurementArea(leaving_area)],\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + " ml_width=1,\n", + " ma_alpha=0.1,\n", + " ma_line_width=1,\n", + ").set_aspect(\"equal\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Data close to line\n", + "\n", + "It is not only possible to check whether a point is within a given polygon, it is also possible to check if the distance to a given geometrical object is below a given threshold.\n", + "Here we want all the data that is within 1m of the {class}`measurement line ` at the entrance of the bottleneck:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_close_ma = traj.data[shapely.dwithin(traj.data.point, measurement_line.line, 1)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "ax = plot_measurement_setup(\n", + " traj=TrajectoryData(data_close_ma, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " measurement_lines=[measurement_line],\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + " ml_width=1,\n", + " ma_alpha=0.1,\n", + " ma_line_width=1,\n", + ")\n", + "ax.set_xlim([-1.5, 1.5])\n", + "ax.set_ylim([-2.5, 1.5])\n", + "ax.set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Time based filtering\n", + "\n", + "It is not only possible to filter the data by geometrical means, but also depending on time information.\n", + "In experiments, you might only be interested in steady state data, this can be also be achieved by slicing the data:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "traj_data_frame_range = traj[300:600]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "plot_measurement_setup(\n", + " traj=traj_data_frame_range,\n", + " walkable_area=walkable_area,\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + ").set_aspect(\"equal\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Alternatively, you could also utilise the Pandas filtering methods:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_frame_range = traj.data[traj.data.frame.between(300, 600, inclusive=\"both\")]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "plot_measurement_setup(\n", + " traj=TrajectoryData(data_frame_range, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + ").set_aspect(\"equal\")\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ID based filtering\n", + "\n", + "It is also possible to filter the data in a way that only specific pedestrians are contained:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_id = traj.data[traj.data.id == 20]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "plot_measurement_setup(\n", + " traj=TrajectoryData(data_id, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + ").set_aspect(\"equal\")\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It is also possible to filter for multiple ids:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ids = [10, 20, 30, 40]\n", + "data_id = traj.data[traj.data.id.isin(ids)]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "from pedpy import plot_measurement_setup\n", + "\n", + "plot_measurement_setup(\n", + " traj=TrajectoryData(data_id, frame_rate=traj.frame_rate),\n", + " walkable_area=walkable_area,\n", + " traj_alpha=0.7,\n", + " traj_width=0.4,\n", + ").set_aspect(\"equal\")\n", + "\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/readthedocs.ipynb b/docs/source/notebooks/readthedocs.ipynb similarity index 100% rename from docs/source/readthedocs.ipynb rename to docs/source/notebooks/readthedocs.ipynb diff --git a/docs/source/notebooks/working_with_results.ipynb b/docs/source/notebooks/working_with_results.ipynb new file mode 100644 index 00000000..1d9986a5 --- /dev/null +++ b/docs/source/notebooks/working_with_results.ipynb @@ -0,0 +1,375 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "remove-input" + ] + }, + "outputs": [], + "source": [ + "import pathlib\n", + "import warnings\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "warnings.filterwarnings(\"ignore\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Working with Results\n", + "\n", + "After completing an analysis with *PedPy*, you may want to:\n", + "\n", + "- **Access specific columns** of the computed results\n", + "- **Combine** multiple result DataFrames for further analysis\n", + "- **Save** results to disk\n", + "\n", + "First, we set up the data and compute some example results (see [Analysis Setup](measurement_setup) and [Analysis](analysis) for details):\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import (\n", + " FRAME_COL,\n", + " ID_COL,\n", + " MeasurementArea,\n", + " MeasurementLine,\n", + " SpeedCalculation,\n", + " TrajectoryUnit,\n", + " WalkableArea,\n", + " compute_individual_speed,\n", + " compute_individual_voronoi_polygons,\n", + " compute_profiles,\n", + " compute_voronoi_density,\n", + " load_trajectory,\n", + ")\n", + "\n", + "walkable_area = WalkableArea(\n", + " [\n", + " (3.5, -2),\n", + " (3.5, 8),\n", + " (-3.5, 8),\n", + " (-3.5, -2),\n", + " ],\n", + " obstacles=[\n", + " [\n", + " (-0.7, -1.1),\n", + " (-0.25, -1.1),\n", + " (-0.25, -0.15),\n", + " (-0.4, 0.0),\n", + " (-2.8, 0.0),\n", + " (-2.8, 6.7),\n", + " (-3.05, 6.7),\n", + " (-3.05, -0.3),\n", + " (-0.7, -0.3),\n", + " (-0.7, -1.0),\n", + " ],\n", + " [\n", + " (0.25, -1.1),\n", + " (0.7, -1.1),\n", + " (0.7, -0.3),\n", + " (3.05, -0.3),\n", + " (3.05, 6.7),\n", + " (2.8, 6.7),\n", + " (2.8, 0.0),\n", + " (0.4, 0.0),\n", + " (0.25, -0.15),\n", + " (0.25, -1.1),\n", + " ],\n", + " ],\n", + ")\n", + "\n", + "measurement_area = MeasurementArea([(-0.4, 0.5), (0.4, 0.5), (0.4, 1.3), (-0.4, 1.3)])\n", + "measurement_line = MeasurementLine([(0.4, 0), (-0.4, 0)])\n", + "\n", + "traj = load_trajectory(\n", + " trajectory_file=pathlib.Path(\"demo-data/bottleneck/040_c_56_h-.txt\"),\n", + " default_unit=TrajectoryUnit.METER,\n", + ")\n", + "\n", + "individual = compute_individual_voronoi_polygons(traj_data=traj, walkable_area=walkable_area)\n", + "density_voronoi, intersecting = compute_voronoi_density(\n", + " individual_voronoi_data=individual, measurement_area=measurement_area\n", + ")\n", + "individual_speed = compute_individual_speed(\n", + " traj_data=traj,\n", + " frame_step=5,\n", + " speed_calculation=SpeedCalculation.BORDER_SINGLE_SIDED,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Access specific columns\n", + "\n", + "In order to access a specific column of the computed results or the {class}`trajectory data` *PedPy* provides all the column names for access.\n", + "It is highly encouraged to use these identifiers instead of using the column names directly, as the identifier would be updated, if they change. \n", + "A list of all identifiers can be found in the [API reference](api_column_identifier).\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import FRAME_COL, ID_COL, POINT_COL\n", + "\n", + "# Access with column identifier\n", + "traj.data[[ID_COL, FRAME_COL, POINT_COL]]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Access with column names\n", + "traj.data[[\"id\", \"frame\", \"point\"]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Combine multiple DataFrames\n", + "\n", + "From the analysis we have one {class}`~pandas.DataFrame` containing the {class}`trajectory data`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "traj.data" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "and one containing the individual Voronoi data:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "individual" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As both have the columns 'id' and 'frame' in common, these can be used to merge the dataframes.\n", + "See {func}`pandas.merge` and {meth}`pandas.DataFrame.merge` for more information on how {class}`DataFrames ` can be merged.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_with_voronoi_cells = traj.data.merge(intersecting, on=[ID_COL, FRAME_COL])\n", + "data_with_voronoi_cells" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import SPEED_COL\n", + "\n", + "data_with_voronoi_cells_speed = data_with_voronoi_cells.merge(\n", + " individual_speed[[ID_COL, FRAME_COL, SPEED_COL]], on=[ID_COL, FRAME_COL]\n", + ")\n", + "data_with_voronoi_cells_speed" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Save in files\n", + "\n", + "For preserving the results on the disk, the results need to be saved on the disk.\n", + "This can be done with the build-in functions from *Pandas*.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create directories to store the results\n", + "\n", + "First we create a directory, where we want to save the results.\n", + "This step is optional!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pathlib.Path(\"results_introduction/profiles/speed\").mkdir(parents=True, exist_ok=True)\n", + "pathlib.Path(\"results_introduction/profiles/density\").mkdir(parents=True, exist_ok=True)\n", + "\n", + "results_directory = pathlib.Path(\"results_introduction\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Save Pandas DataFrame (result from everything but profiles) as csv\n", + "\n", + "Now, a {class}`~pandas.DataFrame` can be saved as `csv` with:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "\n", + "from pedpy import (\n", + " DENSITY_COL,\n", + " INTERSECTION_COL,\n", + " POLYGON_COL,\n", + " X_COL,\n", + " Y_COL,\n", + ")\n", + "\n", + "with open(results_directory / \"individual_result.csv\", \"w\") as individual_output_file:\n", + " individual_output_file.write(f\"#framerate:\\t{traj.frame_rate}\\n\\n\")\n", + " data_with_voronoi_cells_speed[\n", + " [\n", + " ID_COL,\n", + " FRAME_COL,\n", + " X_COL,\n", + " Y_COL,\n", + " DENSITY_COL,\n", + " SPEED_COL,\n", + " POLYGON_COL,\n", + " INTERSECTION_COL,\n", + " ]\n", + " ].to_csv(\n", + " individual_output_file,\n", + " mode=\"a\",\n", + " header=True,\n", + " sep=\"\\t\",\n", + " index_label=False,\n", + " index=False,\n", + " quoting=csv.QUOTE_NONNUMERIC,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Save numpy arrays (result from profiles) as txt\n", + "\n", + "The profiles are returned as Numpy arrays, which also provide a build-in save function, which allows to save the arrays as txt format:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pedpy import Cutoff, DensityMethod, SpeedMethod\n", + "\n", + "individual_cutoff = compute_individual_voronoi_polygons(\n", + " traj_data=traj,\n", + " walkable_area=walkable_area,\n", + " cut_off=Cutoff(radius=0.8, quad_segments=3),\n", + ")\n", + "\n", + "min_frame_profiles = 250\n", + "max_frame_profiles = 400\n", + "grid_size = 0.4\n", + "\n", + "density_profiles, speed_profiles = compute_profiles(\n", + " data=pd.merge(\n", + " individual_cutoff[individual_cutoff.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " individual_speed[individual_speed.frame.between(min_frame_profiles, max_frame_profiles)],\n", + " on=[ID_COL, FRAME_COL],\n", + " ),\n", + " walkable_area=walkable_area.polygon,\n", + " grid_size=grid_size,\n", + " speed_method=SpeedMethod.ARITHMETIC,\n", + " density_method=DensityMethod.VORONOI,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "results_directory_density = results_directory / \"profiles/density\"\n", + "results_directory_speed = results_directory / \"profiles/speed\"\n", + "\n", + "for i in range(len(range(min_frame_profiles, min_frame_profiles + 10))):\n", + " frame = min_frame_profiles + i\n", + " np.savetxt(\n", + " results_directory_density / f\"density_frame_{frame:05d}.txt\",\n", + " density_profiles[i],\n", + " )\n", + " np.savetxt(\n", + " results_directory_speed / f\"speed_frame_{frame:05d}.txt\",\n", + " speed_profiles[i],\n", + " )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/source/user_guide.ipynb b/docs/source/user_guide.ipynb deleted file mode 120000 index 8c6cab7c..00000000 --- a/docs/source/user_guide.ipynb +++ /dev/null @@ -1 +0,0 @@ -../../notebooks/user_guide.ipynb \ No newline at end of file diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst new file mode 100644 index 00000000..2f582977 --- /dev/null +++ b/docs/source/user_guide/index.rst @@ -0,0 +1,38 @@ +********** +User Guide +********** + +This user guide showcases the capabilities of *PedPy* through focused, topic-specific notebooks. +Each notebook can be followed independently or as part of a complete workflow. + + +Workflow Overview +================= + +.. raw:: html + + + Your browser does not support embedded SVG diagrams. Read the workflow description below. + + +TODO: some text describing what is shown in the different parts + +.. `Measurement Setup <../notebooks/measurement_setup.html>`_ introduces the geometric setup, measurement definitions, and how to load trajectory data from various sources. +.. The optional `Pre-processing <../notebooks/preprocessing.html>`_ notebook covers validation, cleaning, and filtering of the data. +.. `Analysis <../notebooks/analysis.html>`_ demonstrates how to compute core pedestrian metrics from processed data. +.. For result-focused workflows, `Fundamental Diagram (FD) <../notebooks/fundamental_diagram.html>`_ explains area-based +.. fundamental diagrams, and `FD at measurement line <../notebooks/fundamental_diagram_at_measurement_line.html>`_ focuses +.. on line-based evaluations. `Working with Results <../notebooks/working_with_results.html>`_ summarizes how to +.. access, combine, and save results. + + +.. toctree:: + :maxdepth: 2 + :hidden: + + ../notebooks/measurement_setup + ../notebooks/preprocessing + ../notebooks/analysis + ../notebooks/working_with_results + ../notebooks/fundamental_diagram + ../notebooks/fundamental_diagram_at_measurement_line