tbp/monty/frameworks/utils/logging_utils.py: what the loggers write with.
The Python file is the largest in Monty's framework, and most of it is pandas
shape-shifting and plotting (lm_stats_to_dataframe, get_rgba_frames_single_sm,
the buffer-size recipe total_size) that exists to feed a notebook. What is
here is the part a logger cannot do without, ported as it is needed rather than
as it appears:
maybe_rename_existing_file/1andmaybe_rename_existing_dir/1— a run's files belong to that run, so what a previous run left is moved aside, once, with a_oldsuffix. Every handler that writes a file needs this, which is why it is here and not copied into each of them.target_data_to_dict/1— the primary target as the columns a CSV carries: object, position, rotation, scale.compute_pose_error/3— how far a recognition's pose was from the pose the object was placed with, which is therotation_errora CSV column holds.
total_size/1 is deliberately absent: Monty keeps it for the size-based logging
it removed (maybe_log is dead code with a "remove if not used" note), and a
memory-footprint recipe has nothing to do in a language that does not flatten
objects into dicts.
Differences from Monty, on purpose
- The renames return
:okor{:error, reason}instead of raising (Python raises on a directory that is not one). A logger that cannot move a file aside can still report the reason and stop, which the fan-out already handles; a raise from inside a callback loses the rest of the episode. - A target key that is absent is
nilrather than aKeyError: it becomes an empty cell in the CSV, which is the truth — the run did not have a position to record — where a crash would take the whole episode with it.
Summary
Functions
The angular error between a predicted rotation and the target's.
The angular error of every predicted rotation against the target's.
format_columns_for_wandb: the one column a table cannot take as it is.
get_rgba_frames_single_sm: a run of frames as wandb.Video wants them.
get_graph_lm_episode_stats: one episode's stats for one learning module.
lm_stats_to_dataframe: an episode's learning modules as one table.
Moves path to <name>_old if it is a directory, replacing an older _old.
Moves path to <name>_old<ext> if it exists, replacing an older _old.
get_stats_per_lm: every learning module's stats for one episode.
The primary target as the columns a CSV carries.
Functions
@spec compute_pose_error( Monty.Geometry.Rotation.t() | [Monty.Geometry.Rotation.t()], Monty.Geometry.Rotation.t(), keyword() ) :: float()
The angular error between a predicted rotation and the target's.
Monty's compute_pose_error, which is the smallest of compute_pose_errors: the
geodesic distance on SO(3), the angle of the relative rotation. It is what a
recognition's rotation_error is, so a run's pose accuracy is this number and
not a difference of Euler angles — two rotations can differ by nought in every
Euler angle and still be different rotations.
predicted may be a list, in which case the closest one wins, which is Monty's
np.min over the errors: a symmetric object seen at any of its equivalent poses
is not wrong.
@spec compute_pose_errors( Monty.Geometry.Rotation.t() | [Monty.Geometry.Rotation.t()], Monty.Geometry.Rotation.t() ) :: [float()]
The angular error of every predicted rotation against the target's.
Monty's compute_pose_errors, which compute_pose_error/3 is the smallest of:
a hypothesis space's pose errors are the whole distribution, not its best
member, and that is what the pyview dashboard's telemetry draws.
format_columns_for_wandb: the one column a table cannot take as it is.
Monty's words: "various columns break wandb because we are playing fast and loose
with types". A result is a list of object names, and a list in a cell makes a
table column whose type depends on the row; joined with ^ it stays a string in
every row. Everything else is passed through.
Monty deep-copies first, because Python mutates a dict in place. A map here is already a value, so there is nothing to copy and nothing to be careful about — which is the only difference between the two.
get_rgba_frames_single_sm: a run of frames as wandb.Video wants them.
A frame is rows of pixels — (height, width, channels) — and wandb takes
(channels, height, width) per timestep, which is Monty's np.moveaxis. A
frame with no pixels has no channels to move, and is passed through as the empty
frame it is.
get_graph_lm_episode_stats: one episode's stats for one learning module.
The performance is a judgement about an object, so it needs three things the
module has to have been told or to have worked out: the object it was asked
about (primary_target), the object it concluded (detected_object), and which
objects the graph it matched was built from (graph_id_to_target). Without the
last one there is no way to tell a recognition from a coincidence, which is why
a training episode records it.
A module that was never on an object reports patch_off_object and no steps: it
was never in the episode, and calling that a failure would put a wrong number in
a file someone averages.
@spec lm_stats_to_dataframe(%{optional(term()) => map()}, keyword()) :: %{ columns: [String.t()], data: [[term()]] }
lm_stats_to_dataframe: an episode's learning modules as one table.
Monty builds a pandas frame: a row per learning module, a column per statistic,
and lm_id last saying which module the row is. A table is what wandb takes, so
that is what this answers — %{columns: [...], data: [[...], ...]} — with
lm_id appended as pandas appends it. The columns are in name order: pandas
keeps dict insertion order, and an Elixir map has none to keep, so a table whose
columns depended on how a map happened to be built would be a table that changed
for reasons a reader cannot see.
Sensor modules and the motor system are in the same dictionary and are not rows: a table a reader compares is made of the things that are comparable. An episode with no learning module in it is no rows rather than an error.
Options: :format_for_wandb, which runs each module's statistics through
format_columns_for_wandb/1 first.
Moves path to <name>_old if it is a directory, replacing an older _old.
A path that exists and is not a directory is {:error, {:not_a_directory, path}}
— the caller asked for a directory to be moved aside and got a file, which is a
run set up wrong rather than a run with nothing to do.
Moves path to <name>_old<ext> if it exists, replacing an older _old.
One _old, not a pile of them: a run that has been restarted three times is
still one previous run.
get_stats_per_lm: every learning module's stats for one episode.
Keyed LM_0, LM_1 … by the module's position, which is the key Monty's CSV
rows and its lm_id column are written from, and each module's stats carry the
run's step counts, the episode's seed and the target — because a row that cannot
say which object it was about cannot be compared with one that can.
Options: :step and :matching_steps, the run's counters (Monty's
model.episode_steps and model.matching_steps), and :episode_seed.
The primary target as the columns a CSV carries.
Monty reads object, position, euler_rotation, rotation and scale off
the environment's target and names them primary_target_*, because those are
the names its readers filter on. Scale is uniform along every dimension, so only
its first entry is recorded.