diff --git a/setup.py b/setup.py index 5b1efe398bce00eb78f360d058f58a4bd6c2a371..d865cbc5eab3c6a0705bee103be9398b25749741 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,8 @@ entry_points = { "console_scripts": [ "robofish-io-validate=robofish.io.app:validate", "robofish-io-print=robofish.io.app:print", + # TODO: This should be called robofish-evaluate which is not possible because of the package name (guess) ask moritzs + "robofish-io-evaluate=robofish.evaluate.app:evaluate", ] } setup( diff --git a/src/robofish/evaluate/__init__.py b/src/robofish/evaluate/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7538468bcf3e5ba77245595b9ea593703754339a --- /dev/null +++ b/src/robofish/evaluate/__init__.py @@ -0,0 +1,12 @@ +import sys +import logging + +import robofish.io + +from robofish.evaluate.evaluate import * +import robofish.evaluate.app + +# TODO: REMOVE +# logging.getLogger().setLevel(logging.INFO) + +assert (3, 7) <= sys.version_info < (4, 0), "Unsupported Python version" diff --git a/src/robofish/evaluate/app.py b/src/robofish/evaluate/app.py new file mode 100644 index 0000000000000000000000000000000000000000..a6f9db8c9567b0f8f734c6cec2fc5eb14b35960c --- /dev/null +++ b/src/robofish/evaluate/app.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# ----------------------------------------------------------- +# Functions available to be used in the commandline to evaluate robofish.io files +# +# Dec 2020 Andreas Gerken, Berlin, Germany +# Released under GNU 3.0 License +# email andi.gerken@gmail.com +# ----------------------------------------------------------- + +import robofish.evaluate + +import argparse + + +def evaluate(args=None): + """This function can be used to print hdf5 files from the command line + + Returns: + A human readable print of a given hdf5 file. + """ + parser = argparse.ArgumentParser(description="TODO") + + parser.add_argument("analysis_type", type=str, choices=["speed"]) + parser.add_argument( + "paths", + type=str, + nargs="+", + help="The paths to io/hdf5 files. Multiple paths can be given which will be shown in different colors", + ) + + if args is None: + args = parser.parse_args() + + if args.analysis_type == "speed": + robofish.evaluate.evaluate.evaluate_speed(args.paths) diff --git a/src/robofish/evaluate/evaluate.py b/src/robofish/evaluate/evaluate.py new file mode 100644 index 0000000000000000000000000000000000000000..6dfb7e992691278c980fef60384f8ae34dbabdc1 --- /dev/null +++ b/src/robofish/evaluate/evaluate.py @@ -0,0 +1,32 @@ +import robofish.evaluate +import robofish.io +import matplotlib.pyplot as plt +import seaborn as sns + +import numpy as np + + +def evaluate_speed(paths): + + files_per_path = [robofish.io.read_multiple_files(p) for p in paths] + speeds = [] + for files in files_per_path: + path_speeds = [] + for p, file in files.items(): + poses = file.get_poses_array() + for e_poses in poses: + e_speeds = np.linalg.norm(np.diff(e_poses[:, :2], axis=0), axis=1) + path_speeds.extend(e_speeds) + + speeds.append(path_speeds) + + plt.hist(speeds, bins=20, label=paths, density=True, range=[0, 1]) + # sns.displot(speeds, label=paths, multiple="layer", kind="kde") + plt.title("Agent speeds") + plt.xlabel("Speed (cm/timestep)") + plt.ylabel("Frequency (logscale)") + plt.ticklabel_format(useOffset=False) + # plt.xscale("log", nonpositive="clip") + plt.legend() + plt.tight_layout() + plt.show() diff --git a/src/robofish/io/__init__.py b/src/robofish/io/__init__.py index 950066754815a39ce0ea460b91f52f080cfe4a9f..ece44fae3c0ac82b7c4910774e9176acde55ecb5 100644 --- a/src/robofish/io/__init__.py +++ b/src/robofish/io/__init__.py @@ -1,6 +1,11 @@ # SPDX-License-Identifier: LGPL-3.0-or-later import sys +import logging + +# TODO: REMOVE +logging.getLogger().setLevel(logging.INFO) + from robofish.io.file import File from robofish.io.validation import * from robofish.io.io import * diff --git a/src/robofish/io/file.py b/src/robofish/io/file.py index 42ad38a4dc0ce4ffc6d6692c3147c8b9d44f8e96..b6b27bee887f9a56460482108de693a94a6cd9aa 100644 --- a/src/robofish/io/file.py +++ b/src/robofish/io/file.py @@ -223,8 +223,11 @@ class File(h5py.File): names = self["entities"].keys() n = len(names) - timesteps = max( - [self["entities"][e_name]["poses"].shape[0] for e_name in names] + + timesteps = ( + 0 + if n == 0 + else max([self["entities"][e_name]["poses"].shape[0] for e_name in names]) ) # Initialize poses output array diff --git a/src/robofish/io/io.py b/src/robofish/io/io.py index 8056182992eca725b6e147e6399bdbe2f25b2d17..d053e167f247fc5b01b2a05c31dcda87b2966465 100644 --- a/src/robofish/io/io.py +++ b/src/robofish/io/io.py @@ -4,6 +4,17 @@ from typing import Union, Iterable from pathlib import Path import logging +import numpy as np + +# Optional pandas series support +list_types = (list, np.ndarray) +try: + import pandas + + list_types += (pandas.core.series.Series,) +except ImportError: + pass + def now_iso8061(): return datetime.datetime.now(datetime.timezone.utc).isoformat( @@ -29,16 +40,16 @@ def read_multiple_files( logging.info(f"Reading files from path {paths}") - try: - iter(paths) - except TypeError: + if not isinstance(paths, list_types): paths = [paths] + paths = [Path(p) for p in paths] sf_dict = {} for path in paths: if path.is_dir(): + logging.info("found dir %s" % path) # Find all hdf5 files in folder files = [] for ext in ("hdf", "hdf5", "h5", "he5"): @@ -51,7 +62,8 @@ def read_multiple_files( sf_dict.update( {file: robofish.io.File(path=file, strict_validate=strict_validate)} ) - elif path is not None: + elif path is not None and path.exists(): + logging.info("found file %s" % path) sf_dict[path] = robofish.io.File(path=path, strict_validate=strict_validate) return sf_dict