From b0ea2152f5f26c34007f5ff6390eecb8770bb26b Mon Sep 17 00:00:00 2001 From: Andi Gerken <andi.gerken@gmail.com> Date: Wed, 30 Mar 2022 14:01:45 +0000 Subject: [PATCH] Changed storage of calculated data from implicit to explicit. --- setup.py | 3 +- src/robofish/io/__init__.py | 6 -- src/robofish/io/app.py | 45 ++++++++- src/robofish/io/entity.py | 157 ++++++++++++++++++------------- src/robofish/io/file.py | 46 ++++++++- tests/resources/nan_test.hdf5 | Bin 12296 -> 15576 bytes tests/resources/valid_1.hdf5 | Bin 65536 -> 23272 bytes tests/resources/valid_2.hdf5 | Bin 15672 -> 22488 bytes tests/robofish/io/test_app_io.py | 10 +- 9 files changed, 186 insertions(+), 81 deletions(-) diff --git a/setup.py b/setup.py index 48573d8..a9ebe9c 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,8 @@ entry_points = { "robofish-io-validate=robofish.io.app:validate", "robofish-io-print=robofish.io.app:print_file", "robofish-io-render=robofish.io.app:render", - "robofish-io-clear-calculated-data=robofish.io.app:clear_calculated_data", + # "robofish-io-clear-calculated-data=robofish.io.app:clear_calculated_data", + "robofish-io-update-calculated-data=robofish.io.app:update_calculated_data", # TODO: This should be called robofish-evaluate which is not possible because of the package name (guess) ask moritz "robofish-io-evaluate=robofish.evaluate.app:evaluate", ] diff --git a/src/robofish/io/__init__.py b/src/robofish/io/__init__.py index b14b319..5b054c2 100644 --- a/src/robofish/io/__init__.py +++ b/src/robofish/io/__init__.py @@ -20,9 +20,3 @@ import robofish.io.app if not ((3, 7) <= sys.version_info < (4, 0)): logging.warning("Unsupported Python version") - -warn_when_unable_to_store = True - - -def disable_warning_when_unable_to_store(): - robofish.io.warn_when_unable_to_store = False diff --git a/src/robofish/io/app.py b/src/robofish/io/app.py index 15b6ebe..c6a9385 100644 --- a/src/robofish/io/app.py +++ b/src/robofish/io/app.py @@ -15,6 +15,7 @@ from robofish.io import utils import argparse import logging +import warnings def print_file(args=None): @@ -53,9 +54,9 @@ def print_file(args=None): return not valid -def clear_calculated_data(args=None): +def update_calculated_data(args=None): parser = argparse.ArgumentParser( - description="This function clears calculated data from robofish.io files." + description="This function updates all calculated data from files." ) parser.add_argument( @@ -76,8 +77,42 @@ def clear_calculated_data(args=None): for fp in files: print(f"File {fp}") - with robofish.io.File(fp, "r+") as f: - f.clear_calculated_data() + try: + with robofish.io.File(fp, "r+", validate_poses_hash=False) as f: + f.update_calculated_data(verbose=True) + except Exception as e: + warnings.warn(f"The file {fp} could not be updated.") + print(e) + + +# This should not be neccessary since the data will always have the calculated data by default. +# def clear_calculated_data(args=None): +# parser = argparse.ArgumentParser( +# description="This function clears calculated data from robofish.io files." +# ) + +# parser.add_argument( +# "path", +# type=str, +# nargs="+", +# help="The path to one or multiple files and/or folders.", +# ) +# if args is None: +# args = parser.parse_args() + +# files_per_path = utils.get_all_files_from_paths(args.path) +# files = [ +# f for f_in_path in files_per_path for f in f_in_path +# ] # Concatenate all files to one list + +# assert len(files) > 0, f"No files found in path {args.path}." + +# for fp in files: +# print(f"File {fp}") +# with robofish.io.File( +# fp, "r+", validate_poses_hash=False, store_calculated_data=False +# ) as f: +# f.clear_calculated_data() def validate(args=None): @@ -162,7 +197,7 @@ def render(args=None): default_options = { "linewidth": 2, - "speedup": 4, + "speedup": 1, "trail": 100, "entity_scale": 0.2, "fixed_view": False, diff --git a/src/robofish/io/entity.py b/src/robofish/io/entity.py index fb0b1a2..3375f37 100644 --- a/src/robofish/io/entity.py +++ b/src/robofish/io/entity.py @@ -60,6 +60,8 @@ class Entity(h5py.Group): if outlines is not None: entity.create_outlines(outlines, sampling) + entity.update_calculated_data() + return entity @classmethod @@ -141,36 +143,6 @@ class Entity(h5py.Group): return np.tile([0, 1], (self.positions.shape[0], 1)) return self["orientations"] - @property - def orientations_rad(self): - # If actions_speeds_turns does not exist yet or the poses hash is not correct - if ( - "calculated_orientations_rad" not in self - or "poses_hash" not in self.attrs - or self.poses_hash != self.attrs["poses_hash"] - ): - - ori_rad = utils.limit_angle_range( - np.arctan2(self.orientations[:, 1], self.orientations[:, 0]), - _range=(0, 2 * np.pi), - )[:, np.newaxis] - - try: - - self.attrs["poses_hash"] = self.poses_hash - self["calculated_orientations_rad"] = ori_rad.astype(np.float64) - except RuntimeError as e: - if robofish.io.warn_when_unable_to_store: - print( - "Trying to store calculated orientations_rad in file to reuse it but the file was opened as read-only.\n" - "If you open the file with mode 'r+' (or 'w' if it is still open from creation time) the information can be stored and reused.\n" - "To disable this message execute robofish.io.disable_warning_when_unable_to_store().\n" - ) - else: - ori_rad = self["calculated_orientations_rad"] - - return ori_rad - @property @deprecation.deprecated( deprecated_in="0.2", @@ -220,11 +192,19 @@ class Entity(h5py.Group): @property def poses_hash(self): + # The hash of h5py datasets changes each time the file is reopened. + # Also the hash of casting the array to bytes and calculating the hash changes. + def npsumhash(a): + return hash(np.nansum(a)) + if "orientations" in self: - h = (hash(self["positions"]) + hash(self["orientations"])) // 2 + h = (npsumhash(self["positions"]) + npsumhash(self["orientations"])) // 2 + elif "positions" in self: + print("We found positions") + h = npsumhash(self["positions"]) else: - h = hash(self["positions"]) - return int(h) + h = 0 + return h @property def poses(self): @@ -267,8 +247,59 @@ class Entity(h5py.Group): turn = utils.limit_angle_range(diff[:, 2], _range=(-np.pi, np.pi)) return np.stack([speed, turn], axis=-1) - @property - def actions_speeds_turns(self): + def update_calculated_data(self, verbose=False, force_update=False): + if ( + "poses_hash" not in self.attrs + or self.attrs["poses_hash"] != self.poses_hash + or "calculated_orientations_rad" not in self + or "calculated_actions_speeds_turns" not in self + or "unfinished_calculations" in self.attrs + or force_update + ): + try: + self.attrs["poses_hash"] = self.poses_hash + self.attrs["unfinished_calculations"] = True + if "orientations" in self: + ori_rad = self.calculate_orientations_rad() + if "calculated_orientations_rad" in self: + del self["calculated_orientations_rad"] + self["calculated_orientations_rad"] = ori_rad.astype(np.float64) + + speeds_turns = self.calculate_actions_speeds_turns() + if "calculated_actions_speeds_turns" in self: + del self["calculated_actions_speeds_turns"] + self["calculated_actions_speeds_turns"] = speeds_turns.astype( + np.float64 + ) + del self.attrs["unfinished_calculations"] + + if verbose: + print( + f"Updated calculated data for entity {self.name} with poses_hash {self.poses_hash}" + ) + elif verbose: + print( + "Since there were no orientations in the data, nothing was calculated." + ) + except RuntimeError as e: + print("Trying to update calculated data in a read-only file") + raise e + else: + if verbose: + print( + f"Nothing to be updated in entity {self.name}. Poses_hash was {self.attrs['poses_hash']}" + ) + + assert self.attrs["poses_hash"] == self.poses_hash + + def calculate_orientations_rad(self): + ori_rad = utils.limit_angle_range( + np.arctan2(self.orientations[:, 1], self.orientations[:, 0]), + _range=(0, 2 * np.pi), + )[:, np.newaxis] + return ori_rad + + def calculate_actions_speeds_turns(self): """Calculate the speed, turn and from the recorded positions and orientations. The turn is calculated by the change of orientation between frames. @@ -278,36 +309,32 @@ class Entity(h5py.Group): Returns: An array with shape (number_of_positions -1, 2 (speed in cm/frame, turn in rad/frame). """ + ori = self.orientations + ori_rad = self.orientations_rad + pos = self.positions + turn = utils.limit_angle_range(np.diff(ori_rad, axis=0)[:, 0]) + pos_diff = np.diff(pos, axis=0) + speed = np.array( + [np.dot(pos_diff[i], ori[i + 1]) for i in range(pos_diff.shape[0])] + ) + return np.stack([speed, turn], axis=-1) - # If actions_speeds_turns does not exist yet or the poses hash is not correct - if ( - "calculated_actions_speeds_turns" not in self - or "poses_hash" not in self.attrs - or self.poses_hash != self.attrs["poses_hash"] - ): - ori = self.orientations - ori_rad = self.orientations_rad - pos = self.positions - turn = utils.limit_angle_range(np.diff(ori_rad, axis=0)[:, 0]) - pos_diff = np.diff(pos, axis=0) - speed = np.array( - [np.dot(pos_diff[i], ori[i + 1]) for i in range(pos_diff.shape[0])] - ) - actions_speeds_turns = np.stack([speed, turn], axis=-1) - - try: - self.attrs["poses_hash"] = self.poses_hash - self["calculated_actions_speeds_turns"] = actions_speeds_turns.astype( - np.float64 - ) - except RuntimeError as e: - if robofish.io.warn_when_unable_to_store: - print( - "Trying to store calculated actions_speeds_turns in file to reuse it but the file was opened as read-only.\n" - "If you open the file with mode 'r+' (or 'w' if it is still open from creation time) the information can be stored and reused.\n" - "To disable this message execute robofish.io.disable_warning_when_unable_to_store().\n" - ) + @property + def actions_speeds_turns(self): + if "calculated_actions_speeds_turns" in self: + assert ( + self.attrs["poses_hash"] == self.poses_hash + ), f"The calculated poses_hash was not identical to the stored poses_hash. Please update the calculated data after changing positions or orientations with entity.update_calculated_data(). stored hash: {self.attrs['poses_hash']}, calculated hash: {self.poses_hash}." + return self["calculated_actions_speeds_turns"] else: - actions_speeds_turns = self["calculated_actions_speeds_turns"] + return self.calculate_actions_speeds_turns() - return actions_speeds_turns + @property + def orientations_rad(self): + if "calculated_orientations_rad" in self: + assert ( + self.attrs["poses_hash"] == self.poses_hash + ), f"The calculated poses_hash was not identical to the stored poses_hash. Please update the calculated data after changing positions or orientations with entity.update_calculated_data(). stored hash: {self.attrs['poses_hash']}, calculated hash: {self.poses_hash}." + return self["calculated_orientations_rad"] + else: + return self.calculate_orientations_rad() diff --git a/src/robofish/io/file.py b/src/robofish/io/file.py index 40097b5..d57c7bf 100644 --- a/src/robofish/io/file.py +++ b/src/robofish/io/file.py @@ -75,6 +75,7 @@ class File(h5py.File): monotonic_time_points_us: Iterable = None, calendar_time_points: Iterable = None, open_copy: bool = False, + validate_poses_hash: bool = True, ): """Create a new RoboFish Track Format object. @@ -214,6 +215,38 @@ class File(h5py.File): calendar_time_points=calendar_time_points, default=True, ) + else: + # A quick validation to find h5py files which are not robofish.io files + if any([a not in self.attrs for a in ["world_size_cm", "format_version"]]): + msg = f"The opened file {self.path} does not include world_size_cm or format_version. It seems that the file is not a robofish.io.File." + if strict_validate: + raise KeyError(msg) + else: + warnings.warn(msg) + return + + # Validate that the stored poses hash still fits. + if validate_poses_hash: + for entity in self.entities: + if "poses_hash" in entity.attrs: + if entity.attrs["poses_hash"] != entity.poses_hash: + warnings.warn( + f"The stored hash is not identical with the newly calculated hash. In entity {entity.name} in {self.path}. f.entity_actions_turns_speeds and f.entity_orientation_rad will return wrong results.\n" + f"stored: {entity.attrs['poses_hash']}, calculated: {entity.poses_hash}" + ) + assert ( + "unfinished_calculations" not in entity.attrs + ), f"The calculated data of file {self.path} is uncomplete and was probably aborted during calculation. please recalculate with `robofish-io-update-calculated-data {self.path}`." + + else: + warnings.warn( + f"The file did not include pre-calculated data so the actions_speeds_turns " + f"and orientations_rad will have to be be recalculated everytime.\n" + f"Please use `robofish-io-update-calculated-data {self.path}` in the " + f"commandline or\nopen and close the file with robofish.io.File(f, 'r+') " + f"in python.\nIf the data should be recalculated every time open the file " + "with the bool option validate_poses_hash=False." + ) if validate: self.validate(strict_validate) @@ -225,8 +258,14 @@ class File(h5py.File): if (type, value, traceback) == (None, None, None): if self.mode != "r": # No need to validate read only files (performance). self.validate() + super().__exit__(type, value, traceback) + def close(self): + if self.mode != "r": + self.update_calculated_data() + super().close() + def save_as( self, path: Union[str, Path], @@ -243,6 +282,7 @@ class File(h5py.File): The file itself, so something like f = robofish.io.File().save_as("file.hdf5") works """ + self.update_calculated_data() self.validate(strict_validate=strict_validate) # Ensure all buffered data has been written to disk @@ -471,6 +511,10 @@ class File(h5py.File): ) return entity_names + def update_calculated_data(self, verbose=False): + for e in self.entities: + e.update_calculated_data(verbose) + def clear_calculated_data(self, verbose=True): """Delete all calculated data from the files.""" txt = "" @@ -1129,7 +1173,7 @@ class File(h5py.File): frames=n_frames, init_func=init, blit=platform.system() != "Darwin", - interval=self.frequency, + interval=1000 / self.frequency, repeat=False, ) diff --git a/tests/resources/nan_test.hdf5 b/tests/resources/nan_test.hdf5 index 3ff017ef600aec8928a7e55c38a68c2821876e22..92c3050e3b5204721b0eb0a0543adf0c4d5f65c0 100644 GIT binary patch delta 1402 zcmeB3xKTMlgXxCNMy(a>6O+U@zhtkM657B30T~dALBnQql@cG5pa6paScJm>Off`E z{;1bDSxR*d4-10_M2O*p;lzz{TpCbbg2BX%a+7zce%ky?jgQlohrxn@g@J*Ak%60m zhk=8kAip@ZI6fn>7;2LM2Ll5KSgQk=WCW884EO*4|F1n+mqm8+FFiFQm?+E=7@wIz zf&s)9RIrDeV#3W}@}GI~FBJtQ4vWb$stU|?nl6*=RSPFCP*D(6hnmj>rPU{2RFULE z)s0)B0IEWWHC!H+5W@<XCfDdoz^voXD~>NNNKH*CjxQ-K$}66{pUrdg0glHk^$GgY zN9-9G7#j4YuYhQU;~Musbc4S16A+#7Tk$1`b~vu_4n#Mcoc<9^>q~zD(Fs%MeFM=9 z3=BW&85kHG5|{o4@fD71`~%SqVqpxR&}=w4oe@me+++gN`qC^83=H)Q4Ub}2K>`U= z=dpolj|c2v`nMt{m|mC61*Q|1a)W3Gk>@;M`nU!kn6Ap=2hj_}!URCH!iR5Qdcn!* zf*?M_VhbTK4Rs(xSd}n{-=Hro;=llMGUpx<u)w1jQ4r0r=P#IUm^x1k#D9=yFAky; zJRX4Q2a1gnApU`1zfT|^!b0$YlO{Ms7C=LEfpqIvkh}ww@8BB=4yglBe!{#3-|Rv5 zH$Vko3SfMg0Sr)im<0?5kSK#W045KM0vLT@;mlVc2f+9Z)4IV?3RABj^y79tSOL_6 zhM>SRU_R6WNJ?U0fLREV-wzUm%7Y9*=7TH%^Yx$(TsZR;m=9GCa|p~l1_p+DaH4?; zfP9Fo0TcpYKGZ-^2!Z*y4MZ^yt{<u%-TVW=^?skg2I@du40j<cv7pg#A3_bp2!Wu$ zGhp+e@}R&1V<;aKg2*APpd<9-Hdp}a0FVV>K2!rd4nh2h8!IMvC@I({C*~xV<|LM+ hro`tLWv1qpB$j06=M~2nC8mHaLy}KShDf1jegMPULqY%m delta 60 zcmcan*^w|ogNegnqt*)c$z1GWn_sXuNKM|rB*4_5w7E)&k7=?2i`3*_dKHtUROc`< RO=eg7Jn=xl=3i=joB(sX6vqGn diff --git a/tests/resources/valid_1.hdf5 b/tests/resources/valid_1.hdf5 index de8a1dabe0f28ecb0b1600aa01918328858e0f8f..664e40a39b908364ac70cf99dc11715a6ecd2abc 100644 GIT binary patch delta 5549 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 V0Ng;R0dNDM@*o$3YCmuY006#ENQwXe delta 198 zcmaE{m9e3LWrBzpBLf2bhXMwNjan<%Cv&ljZGOT2LTd5`CIO}wN}C@k@i9#{aB!Gx zp!$xHX>z{W=ZOabHYceIa5A!QE;jIG0xP^>H2INI+++je3dRMK8C9DmYn#kwWST5) j`f;*?LC|I{vuB*l3=ABb9Xa>&Z4ThvKrXTI03Ra&IpalS diff --git a/tests/resources/valid_2.hdf5 b/tests/resources/valid_2.hdf5 index 10caedaafa4d0d13c4be720f2c8d48dec184a092..eb8c69e1a01f1704c29c94b7c85590c5443275dd 100644 GIT binary patch delta 5581 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 delta 111 zcmcbyo^eOj1Pvw&+l^W)*e7$bi*0_v-XJx31Csz#gVN+Iqqs>D0-IkbEnu8%z#=u- z%eZo~wCZd|rpfGTKPMgt*rd+I3D%@wV8H~|RA4kY%jg(L@n$b$5k^L)$?Hu%P7bgu K*nGgWf*k-!<0V!A diff --git a/tests/robofish/io/test_app_io.py b/tests/robofish/io/test_app_io.py index ea4933f..6c3a16f 100644 --- a/tests/robofish/io/test_app_io.py +++ b/tests/robofish/io/test_app_io.py @@ -2,7 +2,6 @@ import robofish.io.app as app from robofish.io import utils import pytest import logging -from pathlib import Path logging.getLogger().setLevel(logging.INFO) @@ -19,11 +18,16 @@ def test_app_validate(): self.path = path self.output_format = output_format - raw_output = app.validate(DummyArgs([resources_path], "raw")) + # invalid.hdf5 should pass a warning + with pytest.warns(UserWarning): + raw_output = app.validate(DummyArgs([resources_path], "raw")) # The three files valid.hdf5, almost_valid.hdf5, and invalid.hdf5 should be found. assert len(raw_output) == 4 - app.validate(DummyArgs([resources_path], "human")) + + # invalid.hdf5 should pass a warning + with pytest.warns(UserWarning): + app.validate(DummyArgs([resources_path], "human")) def test_app_print(): -- GitLab