215 lines
5.7 KiB
Python
215 lines
5.7 KiB
Python
"""Demonstrate common dataframe operations with a catalog server."""
|
|
|
|
# region: setup
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import datafusion as dfn
|
|
import numpy as np
|
|
import pyarrow as pa
|
|
from datafusion import col, lit
|
|
from datafusion import functions as F
|
|
|
|
import rerun as rr
|
|
|
|
sample_5_path = (
|
|
Path(__file__).parents[4] / "tests" / "assets" / "rrd" / "sample_5"
|
|
)
|
|
|
|
server = rr.server.Server(datasets={"sample_dataset": sample_5_path})
|
|
CATALOG_URL = server.url()
|
|
client = rr.catalog.CatalogClient(CATALOG_URL)
|
|
dataset = client.get_dataset(name="sample_dataset")
|
|
observations = dataset.filter_contents(["/observation/**"]).reader(
|
|
index="real_time"
|
|
)
|
|
# endregion: setup
|
|
|
|
# region: group_by
|
|
first_last = observations.aggregate(
|
|
col("rerun_segment_id"),
|
|
[
|
|
F.first_value(col("real_time")).alias("start"),
|
|
F.last_value(col("real_time")).alias("end"),
|
|
],
|
|
)
|
|
|
|
# Sort for consistency here
|
|
first_last = first_last.sort("start")
|
|
pa.table(first_last)["start"][0]
|
|
# endregion: group_by
|
|
|
|
# region: join_query
|
|
joints = dataset.filter_contents(["/observation/joint_positions"])
|
|
|
|
# Find the earliest joint position in each episode (cast to unix epoch
|
|
# nanoseconds for easier math later)
|
|
joint_min_t = (
|
|
joints
|
|
.reader(index="real_time")
|
|
.with_column("joint_epoch_ns", col("real_time").cast(pa.int64()))
|
|
.select("rerun_segment_id", "joint_epoch_ns")
|
|
.aggregate(
|
|
col("rerun_segment_id"),
|
|
F.min(col("joint_epoch_ns")).alias("joint_min_t"),
|
|
)
|
|
)
|
|
|
|
cameras = dataset.filter_contents(["/camera/**"])
|
|
|
|
# Find the earliest camera frame in each episode (cast to unix epoch
|
|
# nanoseconds for easier math later)
|
|
camera_min_t = (
|
|
cameras
|
|
.reader(index="real_time")
|
|
.with_column("camera_epoch_ns", col("real_time").cast(pa.int64()))
|
|
.select(
|
|
"rerun_segment_id",
|
|
col("real_time").cast(pa.int64()).alias("camera_epoch_ns"),
|
|
)
|
|
.aggregate(
|
|
col("rerun_segment_id"),
|
|
F.min(col("camera_epoch_ns")).alias("camera_min_t"),
|
|
)
|
|
)
|
|
|
|
# Join the two dataframes
|
|
min_t = camera_min_t.join(
|
|
joint_min_t.with_column_renamed("rerun_segment_id", "segment_id"),
|
|
left_on="rerun_segment_id",
|
|
right_on="segment_id",
|
|
how="left",
|
|
)
|
|
delta_t = min_t.select(
|
|
col("rerun_segment_id"),
|
|
(col("camera_min_t") - col("joint_min_t")).alias("start_delta_t"),
|
|
)
|
|
THRESHOLD_S = 1
|
|
NANO_S = 1_000_000_000
|
|
outliers = delta_t.filter(
|
|
dfn.Expr.between(
|
|
col("start_delta_t"),
|
|
-THRESHOLD_S * NANO_S,
|
|
THRESHOLD_S * NANO_S,
|
|
negated=True,
|
|
),
|
|
)
|
|
outliers = outliers.with_column(
|
|
"start_delta_t_s", col("start_delta_t") / 1_000_000_000.0
|
|
)
|
|
|
|
print(
|
|
f"{outliers.count()=}\n",
|
|
f"{joint_min_t.count()=}\n",
|
|
f"{camera_min_t.count()=}",
|
|
sep="",
|
|
)
|
|
# endregion: join_query
|
|
|
|
# region: sub_episodes
|
|
# Grab a dataframe
|
|
all_data = (
|
|
dataset
|
|
.filter_contents(["/action/**", "/observation/**"])
|
|
.reader(index="real_time", fill_latest_at=True)
|
|
.filter(
|
|
col(
|
|
"/observation/joint_positions:Scalars:scalars"
|
|
).is_not_null() # filter out rows where there is no observation
|
|
)
|
|
)
|
|
|
|
# Drop heavy columns for performance
|
|
light_slice = all_data.select(
|
|
"rerun_segment_id",
|
|
"real_time",
|
|
"/observation/gripper_position:Scalars:scalars",
|
|
)
|
|
|
|
# Define criteria for sub-episode start/end
|
|
THRESHOLD = 0.1
|
|
light_slice = light_slice.with_column(
|
|
"gripper_open",
|
|
col("/observation/gripper_position:Scalars:scalars") > [THRESHOLD],
|
|
)
|
|
|
|
# Find start and end
|
|
light_slice = light_slice.with_column(
|
|
"prev_gripper_open",
|
|
F.lag(
|
|
col("gripper_open"),
|
|
default_value=False,
|
|
partition_by=[col("rerun_segment_id")],
|
|
order_by=[col("real_time")],
|
|
),
|
|
)
|
|
light_slice = light_slice.with_column(
|
|
"gripper_change",
|
|
col("gripper_open").cast(pa.int8())
|
|
- col("prev_gripper_open").cast(pa.int8()),
|
|
)
|
|
|
|
slice_times = light_slice.with_column(
|
|
"start",
|
|
F
|
|
.case(col("gripper_change"))
|
|
.when(lit(1), col("real_time"))
|
|
.otherwise(lit(None)),
|
|
).with_column(
|
|
"end",
|
|
F
|
|
.case(col("gripper_change"))
|
|
.when(lit(-1), col("real_time"))
|
|
.otherwise(lit(None)),
|
|
)
|
|
|
|
# Helper because pyarrow timestamps didn't have a nice min/max utility
|
|
max_ts = pa.scalar(np.iinfo(np.int64).max, type=pa.timestamp("ns"))
|
|
min_ts = pa.scalar(
|
|
np.iinfo(np.int64).min + 1_000_000_000, type=pa.timestamp("ns")
|
|
)
|
|
|
|
# This generates the column for the last observed start time
|
|
slice_dense_times = (
|
|
slice_times
|
|
.select("rerun_segment_id", "real_time", "start", "end")
|
|
.with_column(
|
|
"dense_start",
|
|
F.last_value(col("start")).over(
|
|
dfn.expr.Window(
|
|
window_frame=dfn.expr.WindowFrame("rows", None, 0),
|
|
order_by=col("real_time"),
|
|
partition_by=col("rerun_segment_id"),
|
|
null_treatment=dfn.common.NullTreatment.IGNORE_NULLS,
|
|
)
|
|
),
|
|
)
|
|
.fill_null(value=max_ts, subset=["dense_start"])
|
|
)
|
|
|
|
# This generates the column for the next observed end time (by finding the
|
|
# last_value in reversed order)
|
|
slice_dense_times = slice_dense_times.with_column(
|
|
"dense_end",
|
|
F.last_value(col("end")).over(
|
|
dfn.expr.Window(
|
|
window_frame=dfn.expr.WindowFrame("rows", None, 0),
|
|
order_by=col("real_time").sort(ascending=False),
|
|
partition_by=col("rerun_segment_id"),
|
|
null_treatment=dfn.common.NullTreatment.IGNORE_NULLS,
|
|
)
|
|
),
|
|
).fill_null(value=min_ts, subset=["dense_end"])
|
|
|
|
slice_dense_times = slice_dense_times.select(
|
|
"rerun_segment_id", "real_time", "dense_start", "dense_end"
|
|
)
|
|
|
|
sub_episodes = slice_dense_times.filter(
|
|
dfn.Expr.between(col("real_time"), col("dense_start"), col("dense_end")),
|
|
)
|
|
|
|
print(f"{sub_episodes.count()=}")
|
|
# endregion: sub_episodes
|