import { WebTeleoperator } from "./teleoperators/base-teleoperator"; import { MotorConfig } from "./types/teleoperation"; interface LeRobotAction { [key: number]: number; } export declare class LeRobotEpisode { frames: NonIndexedLeRobotDatasetRow[]; /** * Optional start time of the episode * If not set, defaults to the timestamp of the first frame */ private _startTime?; /** * Optional end time of the episode * If not set, defaults to the timestamp of the last frame */ private _endTime?; /** * Creates a new LeRobotEpisode * * @param frames Optional array of frames to initialize the episode with * @param startTime Optional explicit start time for the episode * @param endTime Optional explicit end time for the episode */ constructor(frames?: NonIndexedLeRobotDatasetRow[], startTime?: number, endTime?: number); /** * Adds a new frame to the episode * Ensures frames are always in chronological order * * @param frame The frame to add * @throws Error if the frame's timestamp is before the last frame's timestamp */ add(frame: NonIndexedLeRobotDatasetRow): void; /** * Gets the start time of the episode * If not explicitly set, returns the timestamp of the first frame * If no frames exist, throws an error */ get startTime(): number; /** * Sets an explicit start time for the episode */ set startTime(value: number); /** * Gets the end time of the episode * If not explicitly set, returns the timestamp of the last frame * If no frames exist, throws an error */ get endTime(): number; /** * Sets an explicit end time for the episode */ set endTime(value: number); /** * The time difference between the start and end time of the episode, in seconds */ get timespan(): number; /** * The number of frames in the episode */ get length(): number; /** * Creates a new LeRobotEpisode with frames interpolated at regular intervals * * @param fps The desired frames per second for the interpolated episode * @param startIndex The desired starting index for the episode frames, useful when storing multiple episodes * @returns A new LeRobotEpisode with interpolated frames */ getInterpolatedRegularEpisode(fps: number, startIndex?: number): LeRobotEpisode; /** * Interpolates between two frames to create a new frame at the specified timestamp * * @param frame1 The first frame * @param frame2 The second frame * @param targetTimestamp The timestamp at which to interpolate * @returns A new interpolated frame */ static interpolateFrames(frame1: NonIndexedLeRobotDatasetRow, frame2: NonIndexedLeRobotDatasetRow, targetTimestamp: number): NonIndexedLeRobotDatasetRow; /** * Helper method to interpolate between two arrays * * @param array1 First array of values * @param array2 Second array of values * @param factor Interpolation factor (0-1) * @returns Interpolated array */ private static interpolateArrays; } /** * Base interface for LeRobot dataset rows with common fields */ export interface NonIndexedLeRobotDatasetRow { timestamp: number; action: LeRobotAction; "observation.state": LeRobotAction; episode_index: number; task_index: number; frame_index?: number; index?: number; } /** * Represents a complete row in the LeRobot dataset format after indexing * Used in the final exported dataset */ export interface LeRobotDatasetRow extends NonIndexedLeRobotDatasetRow { frame_index: number; index: number; } /** * A mechanism to store and record, the video of all associated cameras * as well as the teleoperator data * * follows the lerobot dataset format https://github.com/huggingface/lerobot/blob/cf86b9300dc83fdad408cfe4787b7b09b55f12cf/README.md#the-lerobotdataset-format */ export declare class LeRobotDatasetRecorder { teleoperators: WebTeleoperator[]; videoStreams: { [key: string]: MediaStream; }; mediaRecorders: { [key: string]: MediaRecorder; }; videoChunks: { [key: string]: Blob[]; }; videoBlobs: { [key: string]: Blob; }; private videoBlobsByEpisode; private videoMimeByKey; teleoperatorData: LeRobotEpisode[]; private _isRecording; private episodeIndex; private taskIndex; private currentVideoSegmentEpisodeIndex; fps: number; taskDescription: string; private robotLabel?; /** * Ensures BlobPart compatibility across environments by converting Uint8Array * to an ArrayBuffer with correct bounds and ArrayBuffer typing. */ private static toArrayBuffer; constructor(teleoperators: WebTeleoperator[], videoStreams: { [key: string]: MediaStream; }, fps: number, taskDescription?: string); setRobotLabel(label: string): void; private static getSupportedRecorderType; get isRecording(): boolean; get currentEpisode(): LeRobotEpisode | undefined; /** * Adds a new video stream to be recorded * @param key The key to identify this video stream * @param stream The media stream to record from */ addVideoStream(key: string, stream: MediaStream): void; /** * Add a new teleoperator and set up state update callbacks * for recording joint position data in the LeRobot dataset format * * @param teleoperator The teleoperator to add callbacks to */ addTeleoperator(teleoperator: WebTeleoperator): void; /** * Starts recording for all teleoperators and video streams */ startRecording(): void; setEpisodeIndex(index: number): void; setTaskIndex(index: number): void; /** * teleoperatorData by default only contains data * for the episodes in a non-regularized manner * * this function returns episodes in a regularized manner, wherein * the frames in each are interpolated through so that all frames are spaced * equally through each other */ get episodes(): LeRobotEpisode[]; /** * Stops recording for all teleoperators and video streams * @returns An object containing teleoperator data and video blobs */ stopRecording(): Promise<{ teleoperatorData: LeRobotEpisode[]; videoBlobs: { [key: string]: Blob; }; }>; /** * Finalizes the current video segment and immediately starts a new one * while continuing the recording session. Also advances to the next * episode and begins collecting frames under the new episode index. * * @returns The new episode index */ nextEpisodeSegment(): Promise; /** * Clears the teleoperator data and video blobs */ clearRecording(): void; /** * Action is a dictionary of motor positions, timestamp1 and timestamp2 are when the actions occurred * reqTimestamp must be between timestamp1 and timestamp2 * * the keys in action1 and action2 must match, this will loop through the dictionary * and interpolate each action to the required timestamp * * @param action1 Motor positions at timestamp1 * @param action2 Motor positions at timestamp2 * @param timestamp1 The timestamp of action1 * @param timestamp2 The timestamp of action2 * @param reqTimestamp The timestamp at which to interpolate * @returns The interpolated action */ _actionInterpolatate(action1: any, action2: any, timestamp1: number, timestamp2: number, reqTimestamp: number): any; /** * Converts an action object to an array of numbers * follows the same pattern as https://huggingface.co/datasets/lerobot/svla_so100_pickplace * I am not really sure if the array can be in a different order * but I am not going to risk it tbh 😛 * * @param action The action object to convert * @returns An array of numbers */ convertActionToArray(action: any): number[]; /** * Converts an array of MotorConfig objects to an action object * following the same joint order as convertActionToArray * * @param motorConfigs Array of MotorConfig objects * @returns An action object with joint positions */ convertMotorConfigToArray(motorConfigs: MotorConfig[]): number[]; /** * Finds the closest timestamp to the target timestamp * * the data must have timestamps in ascending order * uses binary search to get the closest timestamp * * @param data The data to search through * @param targetTimestamp The target timestamp * @returns The closest timestamp in the data's index */ _findClosestTimestampBefore(data: any[], targetTimestamp: number): number; /** * Takes non-regularly spaced lerobot-ish data and interpolates it to a regularly spaced dataset * also adds additional * - frame_index * - episode_index * - index columns * * to match lerobot dataset requirements */ _interpolateAndCompleteLerobotData(fps: number, frameData: NonIndexedLeRobotDatasetRow[], lastFrameIndex: number): LeRobotDatasetRow[]; /** * converts all the frames of a recording into lerobot dataset frame style * * NOTE : This does not interpolate the data, you are only working with raw data * that is called by the teleop when things are actively "changing" * @param episodeRough */ _convertToLeRobotDataFormatFrames(episodeRough: any[]): NonIndexedLeRobotDatasetRow[]; /** * Converts teleoperator data to a parquet blob * @private * @returns Array of objects containing parquet file content and path */ private _exportEpisodesToBlob; /** * Exports the teleoperator data in lerobot format * @param format The format to return the data in ('json' or 'blob') * @returns Either an array of data objects or a Uint8Array blob depending on format */ exportEpisodes(format?: "json" | "blob"): LeRobotEpisode[] | Promise<{ content: Blob; path: string; }[]>; /** * Exports the media (video) data as blobs * @returns A dictionary of video blobs with the same keys as videoStreams */ exportMediaData(): Promise<{ [key: string]: Blob; }>; /** * Generates metadata for the dataset * @returns Metadata object for the LeRobot dataset */ generateMetadata(_data: any[]): Promise; /** * Generates statistics for the dataset * @returns Statistics object for the LeRobot dataset */ getStatistics(_data: any[]): Promise; /** * Creates a tasks.parquet file containing task description * @returns A Uint8Array blob containing the parquet data */ createTasksParquet(): Promise; /** * Creates the episodes statistics parquet file * @returns A Uint8Array blob containing the parquet data */ getEpisodeStatistics(_data: any[]): Promise; generateREADME(metaInfo: string): string; /** * Creates an array of path and blob content objects for the LeRobot dataset * * @returns An array of {path, content} objects representing the dataset files * @private */ _exportForLeRobotBlobs(): Promise<{ path: string; content: Blob; }[]>; /** * Creates a ZIP file from the dataset blobs * * @returns A Blob containing the ZIP file * @private */ _exportForLeRobotZip(): Promise; /** * Exports the LeRobot dataset in various formats * * @param format The export format - 'blobs', 'zip', or 'zip-download' * @param options Additional options (currently unused) * @returns The exported data in the requested format */ exportForLeRobot(format?: "blobs" | "zip" | "zip-download"): Promise; } import type { RecordConfig, RecordProcess } from "./types/recording.js"; /** * Simple recording function that follows LeRobot.js conventions * * Records robot motor positions and teleoperation data using a clean function API * that matches the patterns established by calibrate() and teleoperate(). * * @param config Recording configuration with explicit teleoperator dependency * @returns RecordProcess with start(), stop(), getState(), and result * * @example * ```typescript * // 1. Create teleoperation * const teleoperationProcess = await teleoperate({ * robot: connectedRobot, * teleop: { type: "keyboard" }, * calibrationData: calibrationData, * }); * * // 2. Create recording with explicit teleoperator dependency * const recordProcess = await record({ * teleoperator: teleoperationProcess.teleoperator, * options: { * fps: 30, * taskDescription: "Pick and place task", * onDataUpdate: (data) => console.log(`Recorded ${data.frameCount} frames`), * } * }); * * // 3. Start both processes * teleoperationProcess.start(); * recordProcess.start(); * * // 4. Stop recording * const robotData = await recordProcess.stop(); * ``` */ export declare function record(config: RecordConfig): Promise; export {}; //# sourceMappingURL=record.d.ts.map