Skip to content

Commit

Permalink
Merge pull request #107 from yupidevs/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
jmorgadov authored Oct 17, 2022
2 parents 5d38bd6 + 185e0f5 commit 648cbdc
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 57 deletions.
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
<p style="text-align:center;"><img src="logo.png" alt="Logo"></p>
<p align="center"><img src="logo.png" alt="Logo"></p>

Standing for *Yet Underused Path Instruments*, **yupi** is a set of tools designed
for collecting, generating and processing trajectory data.
for collecting, generating and processing trajectory data of any kind.

## **What does it offers?**

- **Convert raw data to trajectories** ... *different input manners*
- **I/O operations with trajectories** ... *json and csv serializers*
- **Trajectory extraction from video inputs** ... *even with moving camera*
- **Artificial trajectory generation** ... *several models implemented*
- **Trajectory basic operations** ... *rotation, shift, scaling, ...*
- **Trajectory transformations** ... *filters, resamples, ...*
- **Statistical calculations from trajectories ensembles** ... *turning angles histogram, velocity autocorrelation function, power spectral density, and much more ...*
- **Results visualization** ... *each statistical observable has a related plot function*
- **Spacial projection of trajectories** ... *for 2D and 3D trajectories*

## Installation

Current recommended installation method is via the pypi package:
Current recommended installation method is via PyPI:

```cmd
pip install yupi
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
author = "Gustavo Viera-López, Alfredo Reyes, Jorge Morgado, Ernesto Altshuler"

# The full version, including alpha/beta/rc tags
release = "0.11.1"
release = "0.11.2"


# -- General configuration ---------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "yupi"
version = "0.11.1"
version = "0.11.2"
description = "A package for tracking and analysing objects trajectories"
authors = [
"Gustavo Viera-López <[email protected]>",
Expand Down
12 changes: 11 additions & 1 deletion tests/test_trajectory/test_io.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os

import numpy as np
import pytest

from yupi import Trajectory
Expand Down Expand Up @@ -84,6 +83,17 @@ def test_json_serializer(traj):
compare_trajectories(traj, loaded_traj)
os.remove("t1.json")

ensemble = [traj, traj]
JSONSerializer.save_ensemble(ensemble, "ensemble.json", overwrite=True)

with pytest.raises(FileExistsError):
JSONSerializer.save_ensemble(ensemble, "ensemble.json")

loaded_trajs = JSONSerializer.load_ensemble("ensemble.json")
for t_1, t_2 in zip(ensemble, loaded_trajs):
compare_trajectories(t_1, t_2)
os.remove("ensemble.json")


@pytest.mark.parametrize("traj", trajectories())
def test_csv_serializer(traj):
Expand Down
2 changes: 1 addition & 1 deletion yupi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
"WindowType",
]

__version__ = "0.11.1"
__version__ = "0.11.2"
196 changes: 150 additions & 46 deletions yupi/core/serializers/json_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import json
import logging
from typing import List

import yupi._differentiation as diff
from yupi import Trajectory
Expand Down Expand Up @@ -40,6 +41,128 @@ def save(
file_name, overwrite=overwrite, extension=".json"
)

json_dict = JSONSerializer.to_json(traj)
encoding = "utf-8" if "encoding" not in kwargs else kwargs.pop("encoding")
with open(file_name, "w", encoding=encoding, **kwargs) as traj_file:
json.dump(json_dict, traj_file)

@staticmethod
def save_ensemble(
trajs: List[Trajectory], file_name: str, overwrite: bool = False, **kwargs
) -> None:
"""
Writes an ensemble to a file.
The main difference with the ``save`` method is that all the
trajectories are saved in a single file.
Parameters
----------
trajs : List[Trajectory]
The ensemble to write to the file.
file_name : str
The name of the file to write.
overwrite : bool
If True, overwrites the file if it already exists.
kwargs
Additional arguments to pass to the ``open`` function.
Encoding is set to UTF-8 as default.
"""
JSONSerializer.check_save_path(
file_name, overwrite=overwrite, extension=".json"
)

json_dicts = [JSONSerializer.to_json(traj) for traj in trajs]
encoding = "utf-8" if "encoding" not in kwargs else kwargs.pop("encoding")
with open(file_name, "w", encoding=encoding, **kwargs) as traj_file:
json.dump(json_dicts, traj_file)

@staticmethod
def load(file_name: str, **kwargs) -> Trajectory:
"""
Loads a trajectory from a file.
Parameters
----------
file_name : str
The name of the file to loaded.
kwargs : dict
Additional keyword arguments.
Encoding is set to UTF-8 as default.
Returns
-------
Trajectory
The trajectory loaded from the file.
"""
JSONSerializer.check_load_path(file_name, extension=".json")

encoding = "utf-8" if "encoding" not in kwargs else kwargs.pop("encoding")
with open(file_name, "r", encoding=encoding, **kwargs) as file:
data = json.load(file)

if "axes" not in data and "r" not in data:
raise LoadTrajectoryError(file_name, "No position data found.")
if "dt" not in data and "t" not in data:
raise LoadTrajectoryError(file_name, "No time data found.")
return JSONSerializer.from_json(data)

@staticmethod
def load_ensemble(file_name: str, **kwargs) -> List[Trajectory]:
"""
Loads an ensemble from a file.
The main difference with the ``load`` method is that all the
trajectories are loaded from a single file.
Parameters
----------
file_name : str
The name of the file to loaded.
kwargs : dict
Additional keyword arguments.
Encoding is set to UTF-8 as default.
Returns
-------
List[Trajectory]
The ensemble loaded from the file.
"""
JSONSerializer.check_load_path(file_name, extension=".json")

encoding = "utf-8" if "encoding" not in kwargs else kwargs.pop("encoding")
with open(file_name, "r", encoding=encoding, **kwargs) as file:
data = json.load(file)

if any("axes" not in traj and "r" not in traj for traj in data):
raise LoadTrajectoryError(
file_name, "No position data found for one or more trajectories."
)
if any("dt" not in traj and "t" not in traj for traj in data):
raise LoadTrajectoryError(
file_name, "No time data found for one or more trajectories."
)
return [JSONSerializer.from_json(traj) for traj in data]

@staticmethod
def to_json(traj: Trajectory) -> dict:
"""
Converts a trajectory to a JSON dictionary.
Parameters
----------
traj : Trajectory
The trajectory to convert.
Returns
-------
dict
The JSON dictionary.
"""

method = Trajectory.general_diff_est.get("method", diff.DiffMethod.LINEAR_DIFF)
window = Trajectory.general_diff_est.get("window_type", diff.WindowType.CENTRAL)
accuracy = Trajectory.general_diff_est.get("accuracy", 1)
Expand All @@ -59,63 +182,44 @@ def save(
json_dict["t_0"] = traj.t_0
else:
json_dict["t"] = traj.t.tolist()
kwargs["encoding"] = kwargs.get("encoding", "utf-8")
with open(file_name, "w", **kwargs) as traj_file:
json.dump(json_dict, traj_file)
return json_dict

@staticmethod
def load(file_name: str, **kwargs) -> Trajectory:
def from_json(json_traj: dict) -> Trajectory:
"""
Loads a trajectory from a file.
Converts a JSON dictionary to a trajectory.
Parameters
----------
file_name : str
The name of the file to loaded.
kwargs : dict
Additional keyword arguments.
Encoding is set to UTF-8 as default.
json_traj : dict
The JSON dictionary to convert.
Returns
-------
Trajectory
The trajectory loaded from the file.
The trajectory.
"""
JSONSerializer.check_load_path(file_name, extension=".json")
axes = json_traj.get("axes", None)
if axes is None:
logging.warning(
"Trajectory will be loaded but it seems to be saved in an old format. "
"Please consider updating it by using the JSONSerializer.save method. "
"Older format won't be supported in a future."
)
axes = list(json_traj["r"].values())
traj_id = json_traj["id"] if json_traj["id"] is not None else ""

kwargs["encoding"] = kwargs.get("encoding", "utf-8")
with open(file_name, "r", **kwargs) as file:
data = json.load(file)
diff_est = json_traj.get("diff_est", None)
if diff_est is None:
diff_est = Trajectory.general_diff_est
else:
diff_est["method"] = diff.DiffMethod(diff_est["method"])
diff_est["window_type"] = diff.WindowType(diff_est["window_type"])

if "axes" not in data and "r" not in data:
raise LoadTrajectoryError(file_name, "No position data found.")
if "dt" not in data and "t" not in data:
raise LoadTrajectoryError(file_name, "No time data found.")
t = json_traj.get("t", None)
dt = json_traj.get("dt", None)
t_0 = json_traj.get("t_0", 0.0)

axes = data.get("axes", None)
if axes is None:
logging.warning(
"Trajectory '%s' will be loaded but it seems to "
"be saved in an old format. Please consider updating it"
"by using the JSONSerializer.save method. Older format "
"won't be supported in a future.",
file_name,
)
axes = list(data["r"].values())
traj_id = data["id"]

diff_est = data.get("diff_est", None)
if diff_est is None:
diff_est = Trajectory.general_diff_est
else:
diff_est["method"] = diff.DiffMethod(diff_est["method"])
diff_est["window_type"] = diff.WindowType(diff_est["window_type"])

t = data.get("t", None)
dt = data.get("dt", None)
t_0 = data.get("t_0", 0.0)

return Trajectory(
axes=axes, t=t, dt=dt, t_0=t_0, traj_id=traj_id, diff_est=diff_est
)
return Trajectory(
axes=axes, t=t, dt=dt, t_0=t_0, traj_id=traj_id, diff_est=diff_est
)
6 changes: 3 additions & 3 deletions yupi/graphics/_trajs_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def plot_2d(
kwargs.pop("color")
traj_plot = plt.plot(x_data, y_data, line_style, **kwargs)
color = traj_plot[-1].get_color()
traj_id = traj.traj_id if traj.traj_id is not None else f"traj {i}"
traj_id = traj.traj_id if traj.traj_id else f"traj {i}"
plt.plot(
x_data[0],
y_data[0],
Expand Down Expand Up @@ -306,7 +306,7 @@ def plot_3d(
kwargs.pop("color")
traj_plot = ax.plot(x_data, y_data, z_data, line_style, **kwargs)
color = traj_plot[-1].get_color()
traj_id = traj.traj_id if traj.traj_id is not None else f"traj {i}"
traj_id = traj.traj_id if traj.traj_id else f"traj {i}"

ax.plot(
x_data[0],
Expand Down Expand Up @@ -445,7 +445,7 @@ def plot_vs_time(
kwargs.pop("color")
y_data = key(traj)
x_data = traj.t
traj_id = traj.traj_id if traj.traj_id is not None else f"traj {i}"
traj_id = traj.traj_id if traj.traj_id else f"traj {i}"
plt.plot(x_data, y_data, line_style, **kwargs, label=traj_id)
plt.xlabel(x_units)
if y_label is not None:
Expand Down
2 changes: 1 addition & 1 deletion yupi/trajectory.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def __init__(
t: Optional[Collection[float]] = None,
dt: Optional[float] = None,
t_0: float = 0.0,
traj_id: Optional[str] = None,
traj_id: str = "",
lazy: Optional[bool] = False,
diff_est: Optional[Dict[str, Any]] = None,
vel_est: Optional[Dict[str, Any]] = None,
Expand Down

0 comments on commit 648cbdc

Please sign in to comment.