diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 72987a493862d328184a1e6b0a7416f883cfec31..5aac0a8d8d63491843bde749576154a3f6746c48 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -85,7 +85,7 @@ deploy centos[cpy37]:
   script:
     - ./ci/deploy.py
 
-deploy centos[cpy38]:
+.deploy centos[cpy38]:
   extends: .centos
   stage: deploy
   only:
@@ -95,7 +95,7 @@ deploy centos[cpy38]:
   script:
     - ./ci/deploy.py
 
-deploy windows[cpy37]:
+.deploy windows[cpy37]:
   extends: .centos
   stage: deploy
   only:
diff --git a/README.md b/README.md
index 824733e47983e1ae070dbc5b12f841304739e96d..45a4a5e1bc6d653c2d9160863391bddacc6a393a 100644
--- a/README.md
+++ b/README.md
@@ -14,33 +14,54 @@ This repository implements an easy to use interface, to create, save, load, and
 
 ## Installation
 
-Clone and install the repository:
-
+Quick variant:
 ```
-git clone https://git.imp.fu-berlin.de/bioroboticslab/robofish/io.git
-pip install ./io
+pip install robofish-trackviewer robofish-io --extra-index-url https://git.imp.fu-berlin.de/api/v4/projects/6392/packages/pypi/simple
 ```
 
+Better variant:
+- Follow instructions at [Artifacts repository](https://git.imp.fu-berlin.de/bioroboticslab/robofish/artifacts)
+- ```pip install robofish-io```
+
+
 ## Usage
 We show a simple example below. More examples can be found in ```examples/```
 
 ```python
 import robofish.io
-import numpy
-
-f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0)
+import numpy as np
 
-# Create a single robot with 40 poses
-# (x,y, x_orientation, y_orientation) and specified time points
-f.create_entity(category="robot", name="robot", poses=numpy.zeros((40, 4)))
+filename = "example.hdf5"
 
-# Create 2 fishes with 100 poses
-# (x,y, x_orientation, y_orientation) and 40ms timesteps
-f.create_multiple_entities("fish", poses=numpy.zeros((2, 100, 4)))
+f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0)
+f.attrs["experiment_setup"] = "This is a simple example with made up data."
+
+# Create a single robot with 30 timesteps
+# positions are passed separately
+# orientations are passed as with two columns -> orientation_x and orientation_y
+f.create_entity(
+    category="robot",
+    name="robot",
+    positions=np.zeros((100, 2)),
+    orientations=np.ones((100, 2)) * [0, 1],
+)
+
+# Create fishes with 30 poses (x, y, orientation_rad)
+poses = np.zeros((100, 3))
+poses[:, 0] = np.arange(-50, 50)
+poses[:, 1] = np.arange(-50, 50)
+poses[:, 2] = np.arange(0, 2 * np.pi, step=2 * np.pi / 100)
+fish = f.create_entity("fish", poses=poses)
+fish.attrs["species"] = "My rotating spaghetti fish"
+fish.attrs["fish_standard_length_cm"] = 10
 
 # Show and save the file
 print(f)
-f.save("example.hdf5")
+
+# Saving also validates the file
+f.save(filename)
+print(f"Saved to {filename}")
+
 ```
 
 
diff --git a/examples/example_readme.py b/examples/example_readme.py
index a568f8a26514e185f2d7fa32d0239d82d2c0a236..4f861169441d509cba0e8b8565a474ab8a463b40 100644
--- a/examples/example_readme.py
+++ b/examples/example_readme.py
@@ -4,24 +4,30 @@ import numpy as np
 filename = "example.hdf5"
 
 f = robofish.io.File(world_size_cm=[100, 100], frequency_hz=25.0)
+f.attrs["experiment_setup"] = "This is a simple example with made up data."
 
-# Create a single robot with 20 timesteps
-# (x,y, x_orientation, y_orientation) and specified time points
-
+# Create a single robot with 30 timesteps
+# positions are passed separately
+# orientations are passed as with two columns -> orientation_x and orientation_y
 f.create_entity(
     category="robot",
     name="robot",
-    positions=np.ones((20, 2)) * 10,
-    orientations=np.zeros((20, 1)),
+    positions=np.zeros((100, 2)),
+    orientations=np.ones((100, 2)) * [0, 1],
 )
 
-# Create 2 fishes with 20 poses
-poses = np.zeros((2, 20, 4))
-poses[:, :, 3] = 1  # all fishes pointing upwards
-poses[1, :, :2] = -20  # move first fish to x,y -20,-20
-f.create_multiple_entities("fish", poses=poses)
+# Create fishes with 30 poses (x, y, orientation_rad)
+poses = np.zeros((100, 3))
+poses[:, 0] = np.arange(-50, 50)
+poses[:, 1] = np.arange(-50, 50)
+poses[:, 2] = np.arange(0, 2 * np.pi, step=2 * np.pi / 100)
+fish = f.create_entity("fish", poses=poses)
+fish.attrs["species"] = "My rotating spaghetti fish"
+fish.attrs["fish_standard_length_cm"] = 10
 
 # Show and save the file
 print(f)
+
+# Saving also validates the file
 f.save(filename)
 print(f"Saved to {filename}")
diff --git a/src/conversion_scripts/convert_moritz.py b/src/conversion_scripts/convert_moritz.py
index 994286e80553ec0ec024cd84b6a2cf6df177dff1..f6a82af6e400ae4f154f5011c0308d001b5aee02 100644
--- a/src/conversion_scripts/convert_moritz.py
+++ b/src/conversion_scripts/convert_moritz.py
@@ -63,7 +63,6 @@ def format_mm_data_folder(input, output):
             # The original coordinate system had the origin in the top left and the
             # y axis pointing "down". We transform it
 
-            print(poses.shape)
             # Move x axis, to be centered
             poses[:, 0] -= world[0] / 2
             # Invert and move y axis (world_y - pose_y) - world_y / 2
diff --git a/src/robofish/evaluate/app.py b/src/robofish/evaluate/app.py
index b000a3b2d5d2c433bf050f63723ebdc6dc05115f..cd829d77593b7512701fb34beb617b5856a694c5 100644
--- a/src/robofish/evaluate/app.py
+++ b/src/robofish/evaluate/app.py
@@ -19,11 +19,20 @@ def evaluate(args=None):
     Returns:
         A human readable print of a given hdf5 file.
     """
+
+    function_dict = {
+        "speed": robofish.evaluate.evaluate.evaluate_speed,
+        "turn": robofish.evaluate.evaluate.evaluate_turn,
+        "tank_positions": robofish.evaluate.evaluate.evaluate_tankpositions,
+        "trajectories": robofish.evaluate.evaluate.evaluate_trajectories,
+        "follow_iid": robofish.evaluate.evaluate.evaluate_follow_iid,
+    }
+
     parser = argparse.ArgumentParser(
         description="This tool can be used to evaluate files from different sources. When different sources are passed, they will be plotted in different colors. With the first argument 'analysis_type', the type of analysis can be chosen."
     )
 
-    parser.add_argument("analysis_type", type=str, choices=["speed"])
+    parser.add_argument("analysis_type", type=str, choices=function_dict.keys())
     parser.add_argument(
         "paths",
         type=str,
@@ -38,14 +47,16 @@ def evaluate(args=None):
         default=None,
     )
     parser.add_argument(
-        "--save_folder",
+        "--save_path",
         type=str,
-        help="Output folder for saving resulting graphics",
+        help="Filename for saving resulting graphics",
         default=None,
     )
 
     if args is None:
         args = parser.parse_args()
 
-    if args.analysis_type == "speed":
-        robofish.evaluate.evaluate.evaluate_speed(args.paths)
+    if args.analysis_type in function_dict:
+        function_dict[args.analysis_type](args.paths, args.names, args.save_path)
+    else:
+        print(f"Evaluation function not found {args.analysis_type}")
diff --git a/src/robofish/evaluate/evaluate.py b/src/robofish/evaluate/evaluate.py
index da606d6516cbbe62f8861d660219974a1ed15ff0..c635a7345fd0d55e2b438b6df0f0b1e02b7c15e0 100644
--- a/src/robofish/evaluate/evaluate.py
+++ b/src/robofish/evaluate/evaluate.py
@@ -15,19 +15,20 @@ def evaluate_speed(paths, names=None, save_path=None, ignore_fish=None):
     for k in range(len(files_per_path)):
         path_speeds = []
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
+            poses = file.get_poses()
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
                     e_speeds = np.linalg.norm(np.diff(poses[i, :, :2], axis=0), axis=1)
+                    e_speeds *= file.get_frequency()
                     path_speeds.extend(e_speeds)
         speeds.append(path_speeds)
 
     if names is None:
         names = paths
 
-    plt.hist(speeds, bins=20, label=names, density=True, range=[0, 2])
+    plt.hist(speeds, bins=20, label=names, density=True)
     plt.title("Agent speeds")
-    plt.xlabel("Speed (cm/timestep)")
+    plt.xlabel("Speed [cm/s]")
     plt.ylabel("Frequency")
     plt.ticklabel_format(useOffset=False)
     plt.legend()
@@ -48,21 +49,28 @@ def evaluate_turn(paths, names=None, save_path=None, ignore_fish=None):
     for k in range(len(files_per_path)):
         path_turns = []
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
+            poses = file.get_poses()
+
+            # Todo check if all frequencies are the same
+            frequency = file.get_frequency()
+
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
                     # convert ori_x, ori_y to radians
                     ori_rad = np.arctan2(poses[i, :, 2], poses[i, :, 3])
                     e_turns = ori_rad[1:] - ori_rad[:-1]
+                    # e_turns *= file.get_frequency()
+                    e_turns *= 180 / np.pi
                     path_turns.extend(e_turns)
         turns.append(path_turns)
 
     if names is None:
         names = paths
 
-    plt.hist(turns, bins=40, label=names, density=True, range=[-np.pi, np.pi])
+    # TODO: Quantil range
+    plt.hist(turns, bins=40, label=names, density=True, range=[-30, 30])
     plt.title("Agent turns")
-    plt.xlabel("Change in orientation (radians)")
+    plt.xlabel("Change in orientation [Degree / timestep at %dhz]" % frequency)
     plt.ylabel("Frequency")
     plt.ticklabel_format(useOffset=False)
     plt.legend()
@@ -82,12 +90,12 @@ def evaluate_orientation(paths, names=None, save_path=None, ignore_fish=None):
         ignore_fish = [[] for i in range(len(paths))]
     for k in range(len(files_per_path)):
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
+            poses = file.get_poses()
             to_keep = list(
                 set([i for i in range(len(paths))]).difference(set(ignore_fish[k]))
             )
             poses = poses[to_keep].reshape((len(to_keep) * len(poses[0]), 4))
-            world_size = file.attrs["world size"]
+            world_size = file.attrs["world_size_cm"]
             world_bounds = [
                 -world_size[0] / 2,
                 -world_size[1] / 2,
@@ -108,6 +116,9 @@ def evaluate_orientation(paths, names=None, save_path=None, ignore_fish=None):
     if len(orientations) == 1:
         ax = [ax]
 
+    if names is None:
+        names = paths
+
     for i in range(len(orientations)):
         orientation = orientations[i]
         s_1, x_edges, y_edges, bnr = orientation[0]
@@ -143,7 +154,7 @@ def evaluate_relativeOrientation(paths, names=None, save_path=None, ignore_fish=
     for k in range(len(files_per_path)):
         path_orientations = []
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
+            poses = file.get_poses()
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
                     for j in range(len(poses)):
@@ -187,10 +198,10 @@ def evaluate_distanceToWall(paths, names=None, save_path=None, ignore_fish=None)
     for k in range(len(files_per_path)):
         path_distances = []
         for p, file in files_per_path[k].items():
-            worldBoundsX.append(file.attrs["world size"][0])
-            worldBoundsY.append(file.attrs["world size"][1])
-            poses = file.get_poses_array()
-            world_size = file.attrs["world size"]
+            worldBoundsX.append(file.attrs["world_size_cm"][0])
+            worldBoundsY.append(file.attrs["world_size_cm"][1])
+            poses = file.get_poses()
+            world_size = file.attrs["world_size_cm"]
             world_bounds = [
                 -world_size[0] / 2,
                 -world_size[1] / 2,
@@ -257,8 +268,8 @@ def evaluate_tankpositions(paths, names=None, save_path=None, ignore_fish=None):
     for k in range(len(files_per_path)):
         path_x_pos, path_y_pos = [], []
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
-            world_bounds.append(file.attrs["world size"])
+            poses = file.get_poses()
+            world_bounds.append(file.attrs["world_size_cm"])
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
                     path_x_pos.extend(poses[i, :, 0])
@@ -269,6 +280,8 @@ def evaluate_tankpositions(paths, names=None, save_path=None, ignore_fish=None):
     fig, ax = plt.subplots(1, len(x_pos), figsize=(8 * len(x_pos), 8))
     if len(x_pos) == 1:
         ax = [ax]
+    if names is None:
+        names = paths
 
     for i in range(len(x_pos)):
         ax[i].set_title("Tankpositions (" + names[i] + ")")
@@ -296,8 +309,8 @@ def evaluate_trajectories(paths, names=None, save_path=None, ignore_fish=None):
         ignore_fish = [[] for i in range(len(paths))]
     for k in range(len(files_per_path)):
         for p, file in files_per_path[k].items():
-            poses = file.get_poses_array()
-            world_bounds.append(file.attrs["world size"])
+            poses = file.get_poses()
+            world_bounds.append(file.attrs["world_size_cm"])
             to_keep = list(
                 set([i for i in range(len(paths))]).difference(set(ignore_fish[k]))
             )
@@ -316,6 +329,8 @@ def evaluate_trajectories(paths, names=None, save_path=None, ignore_fish=None):
     fig, ax = plt.subplots(1, len(pos), figsize=(len(pos) * 8, 8))
     if len(pos) == 1:
         ax = [ax]
+    if names is None:
+        names = paths
 
     for i in range(len(pos)):
         sns.set_style("white", {"axes.linewidth": 2, "axes.edgecolor": "black"})
@@ -364,9 +379,9 @@ def evaluate_positionVec(paths, names=None, save_path=None, ignore_fish=None):
     for k in range(len(files_per_path)):
         path_posVec = []
         for p, file in files_per_path[k].items():
-            worldBoundsX.append(file.attrs["world size"][0])
-            worldBoundsY.append(file.attrs["world size"][1])
-            poses = file.get_poses_array()
+            worldBoundsX.append(file.attrs["world_size_cm"][0])
+            worldBoundsY.append(file.attrs["world_size_cm"][1])
+            poses = file.get_poses()
             # calculate posVec for every fish combination
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
@@ -419,9 +434,9 @@ def evaluate_follow_iid(paths, names=None, save_path=None, ignore_fish=None):
     for k in range(len(files_per_path)):
         path_follow, path_iid = [], []
         for p, file in files_per_path[k].items():
-            worldBoundsX.append(file.attrs["world size"][0])
-            worldBoundsY.append(file.attrs["world size"][1])
-            poses = file.get_poses_array()
+            worldBoundsX.append(file.attrs["world_size_cm"][0])
+            worldBoundsY.append(file.attrs["world_size_cm"][1])
+            poses = file.get_poses()
             for i in range(len(poses)):
                 if not i in ignore_fish[k]:
                     for j in range(len(poses)):
diff --git a/src/robofish/io/file.py b/src/robofish/io/file.py
index 326776393247ae135763c07741940cd159a7f482..deefcc9a771d6e54966224be4be1dc1abbb69c16 100644
--- a/src/robofish/io/file.py
+++ b/src/robofish/io/file.py
@@ -206,6 +206,10 @@ class File(h5py.File):
             self["samplings"].attrs["default"] = self.default_sampling
         return name
 
+    def get_frequency(self):
+        default_sampling = self["samplings"].attrs["default"]
+        return self["samplings"][default_sampling].attrs["frequency_hz"]
+
     def create_entity(
         self,
         category: str,