pepijn223 HF Staff commited on
Commit
824eff6
Β·
unverified Β·
1 Parent(s): 83fd2de

Add top jerky episodes

Browse files
README.md CHANGED
@@ -1,6 +1,6 @@
1
  # LeRobot Dataset Visualizer
2
 
3
- LeRobot Dataset Visualizer is a web application for interactive exploration and visualization of robotics datasets, particularly those in the LeRobot format. It enables users to browse, view, and analyze episodes from large-scale robotics datasets, combining synchronized video playback with rich, interactive data graphs.
4
 
5
  ## Project Overview
6
 
 
1
  # LeRobot Dataset Visualizer
2
 
3
+ LeRobot Dataset Tool and Visualizer is a web application for interactive exploration and visualization of robotics datasets, particularly those in the LeRobot format. It enables users to browse, view, and analyze episodes from large-scale robotics datasets, combining synchronized video playback with rich, interactive data graphs.
4
 
5
  ## Project Overview
6
 
src/app/[org]/[dataset]/[episode]/fetch-data.ts CHANGED
@@ -1391,6 +1391,11 @@ export type AggAlignment = {
1391
  numPairs: number;
1392
  };
1393
 
 
 
 
 
 
1394
  export type CrossEpisodeVarianceData = {
1395
  actionNames: string[];
1396
  timeBins: number[];
@@ -1400,6 +1405,7 @@ export type CrossEpisodeVarianceData = {
1400
  aggVelocity: AggVelocityStat[];
1401
  aggAutocorrelation: AggAutocorrelation | null;
1402
  speedDistribution: SpeedDistEntry[];
 
1403
  multimodality: number[][] | null;
1404
  trajectoryClustering: TrajectoryClustering | null;
1405
  aggAlignment: AggAlignment | null;
@@ -1692,6 +1698,18 @@ export async function loadCrossEpisodeActionVariance(
1692
  return { chartData, suggestedChunk, shortKeys };
1693
  })();
1694
 
 
 
 
 
 
 
 
 
 
 
 
 
1695
  // Speed distribution: all episode movement scores (not just lowest 10)
1696
  const speedDistribution: SpeedDistEntry[] = movementScores.map(s => ({
1697
  episodeIndex: s.episodeIndex,
@@ -1967,7 +1985,7 @@ export async function loadCrossEpisodeActionVariance(
1967
  return {
1968
  actionNames, timeBins, variance, numEpisodes: episodeActions.length,
1969
  lowMovementEpisodes, aggVelocity, aggAutocorrelation,
1970
- speedDistribution, multimodality, trajectoryClustering, aggAlignment,
1971
  };
1972
  }
1973
 
 
1391
  numPairs: number;
1392
  };
1393
 
1394
+ export type JerkyEpisode = {
1395
+ episodeIndex: number;
1396
+ meanAbsDelta: number;
1397
+ };
1398
+
1399
  export type CrossEpisodeVarianceData = {
1400
  actionNames: string[];
1401
  timeBins: number[];
 
1405
  aggVelocity: AggVelocityStat[];
1406
  aggAutocorrelation: AggAutocorrelation | null;
1407
  speedDistribution: SpeedDistEntry[];
1408
+ jerkyEpisodes: JerkyEpisode[];
1409
  multimodality: number[][] | null;
1410
  trajectoryClustering: TrajectoryClustering | null;
1411
  aggAlignment: AggAlignment | null;
 
1698
  return { chartData, suggestedChunk, shortKeys };
1699
  })();
1700
 
1701
+ // Per-episode jerkiness: mean |Ξ”a| across all dimensions
1702
+ const jerkyEpisodes: JerkyEpisode[] = episodeActions.map(({ index, actions: ep }) => {
1703
+ let sum = 0, count = 0;
1704
+ for (let t = 1; t < ep.length; t++) {
1705
+ for (let d = 0; d < actionDim; d++) {
1706
+ sum += Math.abs((ep[t][d] ?? 0) - (ep[t - 1][d] ?? 0));
1707
+ count++;
1708
+ }
1709
+ }
1710
+ return { episodeIndex: index, meanAbsDelta: count > 0 ? sum / count : 0 };
1711
+ }).sort((a, b) => b.meanAbsDelta - a.meanAbsDelta);
1712
+
1713
  // Speed distribution: all episode movement scores (not just lowest 10)
1714
  const speedDistribution: SpeedDistEntry[] = movementScores.map(s => ({
1715
  episodeIndex: s.episodeIndex,
 
1985
  return {
1986
  actionNames, timeBins, variance, numEpisodes: episodeActions.length,
1987
  lowMovementEpisodes, aggVelocity, aggAutocorrelation,
1988
+ speedDistribution, jerkyEpisodes, multimodality, trajectoryClustering, aggAlignment,
1989
  };
1990
  }
1991
 
src/app/layout.tsx CHANGED
@@ -5,8 +5,8 @@ import "./globals.css";
5
  const inter = Inter({ subsets: ["latin"] });
6
 
7
  export const metadata: Metadata = {
8
- title: "LeRobot Dataset Visualizer",
9
- description: "Visualization of LeRobot Datasets",
10
  };
11
 
12
  export default function RootLayout({
 
5
  const inter = Inter({ subsets: ["latin"] });
6
 
7
  export const metadata: Metadata = {
8
+ title: "LeRobot Dataset Tool and Visualizer",
9
+ description: "Tool and Visualizer for LeRobot Datasets",
10
  };
11
 
12
  export default function RootLayout({
src/app/page.tsx CHANGED
@@ -128,7 +128,7 @@ function HomeInner() {
128
  {/* Centered Content */}
129
  <div className="relative z-10 h-screen flex flex-col items-center justify-center text-white text-center">
130
  <h1 className="text-4xl md:text-5xl font-bold mb-6 drop-shadow-lg">
131
- LeRobot Dataset Visualizer
132
  </h1>
133
  <form onSubmit={handleGo} className="flex gap-2 justify-center mt-6">
134
  <input
 
128
  {/* Centered Content */}
129
  <div className="relative z-10 h-screen flex flex-col items-center justify-center text-white text-center">
130
  <h1 className="text-4xl md:text-5xl font-bold mb-6 drop-shadow-lg">
131
+ LeRobot Dataset Tool and Visualizer
132
  </h1>
133
  <form onSubmit={handleGo} className="flex gap-2 justify-center mt-6">
134
  <input
src/components/action-insights-panel.tsx CHANGED
@@ -10,7 +10,7 @@ import {
10
  ResponsiveContainer,
11
  Tooltip,
12
  } from "recharts";
13
- import type { CrossEpisodeVarianceData, LowMovementEpisode, AggVelocityStat, AggAutocorrelation, SpeedDistEntry, TrajectoryClustering, AggAlignment } from "@/app/[org]/[dataset]/[episode]/fetch-data";
14
 
15
  const DELIMITER = " | ";
16
  const COLORS = [
@@ -171,7 +171,7 @@ function AutocorrelationSection({ data, fps, agg, numEpisodes }: { data: Record<
171
 
172
  // ─── Action Velocity ─────────────────────────────────────────────
173
 
174
- function ActionVelocitySection({ data, agg, numEpisodes }: { data: Record<string, number>[]; agg?: AggVelocityStat[]; numEpisodes?: number }) {
175
  const actionKeys = useMemo(() => (data.length > 0 ? getActionKeys(data[0]) : []), [data]);
176
 
177
  const fallbackStats = useMemo(() => {
@@ -299,6 +299,46 @@ function ActionVelocitySection({ data, agg, numEpisodes }: { data: Record<string
299
  </ul>
300
  <p className="text-xs text-slate-500 pt-1">{insight.tip}</p>
301
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  </div>
303
  );
304
  }
@@ -1135,7 +1175,7 @@ const ActionInsightsPanel: React.FC<ActionInsightsPanelProps> = ({
1135
  </div>
1136
 
1137
  <AutocorrelationSection data={flatChartData} fps={fps} agg={showAgg ? crossEpisodeData?.aggAutocorrelation : null} numEpisodes={crossEpisodeData?.numEpisodes} />
1138
- <ActionVelocitySection data={flatChartData} agg={showAgg ? crossEpisodeData?.aggVelocity : undefined} numEpisodes={crossEpisodeData?.numEpisodes} />
1139
 
1140
  {crossEpisodeData?.speedDistribution && crossEpisodeData.speedDistribution.length > 2 && (
1141
  <SpeedVarianceSection distribution={crossEpisodeData.speedDistribution} numEpisodes={crossEpisodeData.numEpisodes} />
 
10
  ResponsiveContainer,
11
  Tooltip,
12
  } from "recharts";
13
+ import type { CrossEpisodeVarianceData, LowMovementEpisode, AggVelocityStat, AggAutocorrelation, SpeedDistEntry, JerkyEpisode, TrajectoryClustering, AggAlignment } from "@/app/[org]/[dataset]/[episode]/fetch-data";
14
 
15
  const DELIMITER = " | ";
16
  const COLORS = [
 
171
 
172
  // ─── Action Velocity ─────────────────────────────────────────────
173
 
174
+ function ActionVelocitySection({ data, agg, numEpisodes, jerkyEpisodes }: { data: Record<string, number>[]; agg?: AggVelocityStat[]; numEpisodes?: number; jerkyEpisodes?: JerkyEpisode[] }) {
175
  const actionKeys = useMemo(() => (data.length > 0 ? getActionKeys(data[0]) : []), [data]);
176
 
177
  const fallbackStats = useMemo(() => {
 
299
  </ul>
300
  <p className="text-xs text-slate-500 pt-1">{insight.tip}</p>
301
  </div>
302
+
303
+ {jerkyEpisodes && jerkyEpisodes.length > 0 && <JerkyEpisodesList episodes={jerkyEpisodes} />}
304
+ </div>
305
+ );
306
+ }
307
+
308
+ function JerkyEpisodesList({ episodes }: { episodes: JerkyEpisode[] }) {
309
+ const [showAll, setShowAll] = useState(false);
310
+ const display = showAll ? episodes : episodes.slice(0, 15);
311
+
312
+ return (
313
+ <div className="bg-slate-900/60 rounded-md px-4 py-3 border border-slate-700/60 space-y-2">
314
+ <div className="flex items-center justify-between">
315
+ <p className="text-sm font-medium text-slate-200">
316
+ Most Jerky Episodes <span className="text-xs text-slate-500 font-normal">sorted by mean |Ξ”a|</span>
317
+ </p>
318
+ {episodes.length > 15 && (
319
+ <button onClick={() => setShowAll(v => !v)} className="text-xs text-slate-400 hover:text-slate-200 transition-colors">
320
+ {showAll ? "Show top 15" : `Show all ${episodes.length}`}
321
+ </button>
322
+ )}
323
+ </div>
324
+ <div className="max-h-48 overflow-y-auto">
325
+ <table className="w-full text-xs">
326
+ <thead>
327
+ <tr className="text-slate-500 border-b border-slate-700">
328
+ <th className="text-left py-1 pr-3">Episode</th>
329
+ <th className="text-right py-1">Mean |Ξ”a|</th>
330
+ </tr>
331
+ </thead>
332
+ <tbody>
333
+ {display.map(e => (
334
+ <tr key={e.episodeIndex} className="border-b border-slate-800/40 text-slate-300">
335
+ <td className="py-1 pr-3">ep {e.episodeIndex}</td>
336
+ <td className="py-1 text-right tabular-nums">{e.meanAbsDelta.toFixed(4)}</td>
337
+ </tr>
338
+ ))}
339
+ </tbody>
340
+ </table>
341
+ </div>
342
  </div>
343
  );
344
  }
 
1175
  </div>
1176
 
1177
  <AutocorrelationSection data={flatChartData} fps={fps} agg={showAgg ? crossEpisodeData?.aggAutocorrelation : null} numEpisodes={crossEpisodeData?.numEpisodes} />
1178
+ <ActionVelocitySection data={flatChartData} agg={showAgg ? crossEpisodeData?.aggVelocity : undefined} numEpisodes={crossEpisodeData?.numEpisodes} jerkyEpisodes={showAgg ? crossEpisodeData?.jerkyEpisodes : undefined} />
1179
 
1180
  {crossEpisodeData?.speedDistribution && crossEpisodeData.speedDistribution.length > 2 && (
1181
  <SpeedVarianceSection distribution={crossEpisodeData.speedDistribution} numEpisodes={crossEpisodeData.numEpisodes} />