Skip to content

Commit

Permalink
Save poses in local collection
Browse files Browse the repository at this point in the history
We can now upload a video, stop it at a desired timestamp,
extract a skeleton from the video frame, edit the name of the
pose and adjust the weights for each limb, and finally, store it
as a pose in a collection that's stored on the user's local storage.
  • Loading branch information
jakmeier committed Oct 15, 2024
1 parent 5583c30 commit 36def38
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 69 deletions.
5 changes: 5 additions & 0 deletions bouncy_frontend/src/lib/components/editor/PoseEditForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
onPoseUpdated(pose);
}
/** @returns {PoseWrapper} newPose */
export function getPose() {
return pose;
}
/** @param {PoseWrapper} newPose */
function onPoseUpdated(newPose) {
skeleton = newPose.skeleton();
Expand Down
18 changes: 17 additions & 1 deletion bouncy_frontend/src/lib/components/editor/VideoToStep.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
import PoseEditForm from '$lib/components/editor/PoseEditForm.svelte';
import { fileToUrl, waitForVideoMetaLoaded } from '$lib/promise_util';
import { PoseDetection } from '$lib/pose';
import Button from '../ui/Button.svelte';
const poseCtx = getContext('pose');
const localCollectionCtx = getContext('localCollection');
let tracker = new Tracker();
registerTracker(tracker);
Expand All @@ -23,6 +25,8 @@
let poseFromForm;
/** @type {(skeleton: import("$lib/instructor/bouncy_instructor").PoseWrapper)=>void} */
let loadPose;
/** @type {()=>import("$lib/instructor/bouncy_instructor").PoseWrapper} */
let getPose;
/** @type {import("$lib/instructor/bouncy_instructor").SkeletonV2 | undefined} */
let liveSkeleton;
Expand Down Expand Up @@ -99,6 +103,11 @@
loadPose(pose);
}
}
function savePose() {
let pose = getPose();
localCollectionCtx.addPose(pose);
}
</script>

<p>
Expand Down Expand Up @@ -138,4 +147,11 @@

<button class="light full-width short" on:click={copyPose}> ↓ </button>

<PoseEditForm bind:loadPose></PoseEditForm>
<PoseEditForm bind:loadPose bind:getPose></PoseEditForm>

<Button
symbol="save"
symbolSize={29}
class="light full-width short"
on:click={savePose}
></Button>
131 changes: 80 additions & 51 deletions bouncy_frontend/src/lib/instructor/bouncy_instructor.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,31 @@ export function dances(): (DanceWrapper)[];
export function danceBuilderFromDance(dance_id: string): DanceBuilder;
/**
*/
export enum DetectionState {
/**
* Neutral state, not detecting anything.
*/
Init = 1,
/**
* Dance is positioning themselves, detecting the idle position.
*/
Positioning = 2,
/**
* About to go over to live tracking, playing a countdown audio.
*/
CountDown = 3,
/**
* Tracking current movements.
*/
LiveTracking = 4,
/**
* No longer tracking but the results of the previous tracking are
* available.
*/
TrackingDone = 5,
}
/**
*/
export enum DetectionFailureReason {
/**
* The last match was too recent to have another match.
Expand Down Expand Up @@ -102,29 +127,13 @@ export enum DetectionFailureReason {
NoNewData = 6,
}
/**
* Best guess for what the dancer needs to change to fit the pose.
*/
export enum DetectionState {
/**
* Neutral state, not detecting anything.
*/
Init = 1,
/**
* Dance is positioning themselves, detecting the idle position.
*/
Positioning = 2,
/**
* About to go over to live tracking, playing a countdown audio.
*/
CountDown = 3,
/**
* Tracking current movements.
*/
LiveTracking = 4,
/**
* No longer tracking but the results of the previous tracking are
* available.
*/
TrackingDone = 5,
export enum PoseHint {
DontKnow = 0,
LeftRight = 1,
ZOrder = 2,
WrongDirection = 3,
}
/**
*/
Expand All @@ -140,15 +149,6 @@ export enum SkeletonField {
RightForearm = 8,
RightFoot = 9,
}
/**
* Best guess for what the dancer needs to change to fit the pose.
*/
export enum PoseHint {
DontKnow = 0,
LeftRight = 1,
ZOrder = 2,
WrongDirection = 3,
}

import type { Readable } from "svelte/store";

Expand Down Expand Up @@ -606,6 +606,35 @@ export class PoseApproximation {
}
/**
*/
export class PoseFileWrapper {
free(): void;
/**
*/
constructor();
/**
* @param {string} text
* @returns {PoseFileWrapper}
*/
static fromRon(text: string): PoseFileWrapper;
/**
* @returns {(PoseWrapper)[]}
*/
poses(): (PoseWrapper)[];
/**
* @param {PoseWrapper} new_pose
*/
addPose(new_pose: PoseWrapper): void;
/**
* @param {string} id
*/
removePose(id: string): void;
/**
* @returns {string}
*/
buildRon(): string;
}
/**
*/
export class PoseWrapper {
free(): void;
/**
Expand Down Expand Up @@ -712,6 +741,17 @@ export class Segment {
export class Skeleton {
free(): void;
/**
* Compute 2d coordinates for the skeleton for rendering.
*
* The skeleton will be rendered assuming hard-coded values for body part
* proportional lengths, multiplied with the size parameter. The hip
* segment will have its center at the given position.
* @param {Cartesian2d} hip_center
* @param {number} size
* @returns {SkeletonV2}
*/
render(hip_center: Cartesian2d, size: number): SkeletonV2;
/**
* @param {boolean} sideway
* @returns {Skeleton}
*/
Expand All @@ -725,17 +765,6 @@ export class Skeleton {
*/
debugString(): string;
/**
* Compute 2d coordinates for the skeleton for rendering.
*
* The skeleton will be rendered assuming hard-coded values for body part
* proportional lengths, multiplied with the size parameter. The hip
* segment will have its center at the given position.
* @param {Cartesian2d} hip_center
* @param {number} size
* @returns {SkeletonV2}
*/
render(hip_center: Cartesian2d, size: number): SkeletonV2;
/**
* Does the dancer face away more than they face the camera?
*/
backwards: boolean;
Expand Down Expand Up @@ -917,6 +946,15 @@ export class StepWrapper {
export class Tracker {
free(): void;
/**
* @param {number} timestamp
* @returns {ExportedFrame}
*/
exportFrame(timestamp: number): ExportedFrame;
/**
* @returns {string}
*/
exportKeypoints(): string;
/**
* Create a tracker for all known steps.
*/
constructor();
Expand Down Expand Up @@ -1075,15 +1113,6 @@ export class Tracker {
*/
renderedKeypointsAt(timestamp: number, width: number, height: number): SkeletonV2 | undefined;
/**
* @param {number} timestamp
* @returns {ExportedFrame}
*/
exportFrame(timestamp: number): ExportedFrame;
/**
* @returns {string}
*/
exportKeypoints(): string;
/**
*/
readonly detectionState: ReadableDetectionState;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export function poseapproximation_name(a: number, b: number): void;
export function poseapproximation_worstLimbs(a: number, b: number, c: number): void;
export function poseapproximation_zErrors(a: number, b: number): void;
export function poseapproximation_zOrderErrors(a: number, b: number): void;
export function posefilewrapper_addPose(a: number, b: number, c: number): void;
export function posefilewrapper_buildRon(a: number, b: number): void;
export function posefilewrapper_fromRon(a: number, b: number, c: number): void;
export function posefilewrapper_new_empty(): number;
export function posefilewrapper_poses(a: number, b: number): void;
export function posefilewrapper_removePose(a: number, b: number, c: number, d: number): void;
export function poses(a: number): void;
export function posewrapper_getAngle(a: number, b: number): number;
export function posewrapper_getWeight(a: number, b: number): number;
Expand Down Expand Up @@ -215,6 +221,7 @@ export function __wbg_lesson_free(a: number, b: number): void;
export function __wbg_lessonpart_free(a: number, b: number): void;
export function __wbg_limberror_free(a: number, b: number): void;
export function __wbg_poseapproximation_free(a: number, b: number): void;
export function __wbg_posefilewrapper_free(a: number, b: number): void;
export function __wbg_posewrapper_free(a: number, b: number): void;
export function __wbg_renderablesegment_free(a: number, b: number): void;
export function __wbg_segment_free(a: number, b: number): void;
Expand Down
60 changes: 47 additions & 13 deletions bouncy_frontend/src/routes/LocalCollectionContext.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,45 @@
import {
DanceFileBuilder,
DanceBuilder,
PoseFileWrapper,
PoseWrapper,
} from '$lib/instructor/bouncy_instructor';
import { setContext } from 'svelte';
import { derived, writable } from 'svelte/store';
const ron = browser ? localStorage.dances : null;
const fileBuilder = ron
? DanceFileBuilder.fromRon(ron)
const danceRon = browser ? localStorage.dances : null;
const danceFileBuilder = danceRon
? DanceFileBuilder.fromRon(danceRon)
: new DanceFileBuilder();
const danceBuilderStore = writable(danceFileBuilder);
const poseRon = browser ? localStorage.poses : null;
const poseFileBuilder = poseRon
? PoseFileWrapper.fromRon(poseRon)
: new PoseFileWrapper();
const poseBuilderStore = writable(poseFileBuilder);
const builderStore = writable(fileBuilder);
const ctx = {
builder: builderStore,
dances: derived(builderStore, ($b) => $b.dances()),
danceBuilder: danceBuilderStore,
dances: derived(danceBuilderStore, ($b) => $b.dances()),
poseBuilder: poseBuilderStore,
poses: derived(poseBuilderStore, ($b) => $b.poses()),
addDanceBuilder,
overwriteDanceBuilder,
removeDance,
addPose,
removePose,
};
if (browser) {
ctx.builder.subscribe(
ctx.danceBuilder.subscribe(
(/** @type {DanceFileBuilder} */ builder) =>
(localStorage.dances = builder.buildRon())
);
ctx.poseBuilder.subscribe(
(/** @type {PoseFileWrapper} */ builder) =>
(localStorage.poses = builder.buildRon())
);
}
setContext('localCollection', ctx);
Expand All @@ -37,27 +53,45 @@
* @param {DanceBuilder} danceBuilder
*/
function addDanceBuilder(danceBuilder) {
$builderStore.addDance(danceBuilder);
$danceBuilderStore.addDance(danceBuilder);
// trigger update (can I do better?)
$builderStore = $builderStore;
$danceBuilderStore = $danceBuilderStore;
}
/**
* @param {DanceBuilder} danceBuilder
*/
function overwriteDanceBuilder(danceBuilder) {
$builderStore.overwriteDance(danceBuilder);
$danceBuilderStore.overwriteDance(danceBuilder);
// trigger update (can I do better?)
$builderStore = $builderStore;
$danceBuilderStore = $danceBuilderStore;
}
/**
* @param {String} id
*/
function removeDance(id) {
$builderStore.removeDance(id);
$danceBuilderStore.removeDance(id);
// trigger update (can I do better?)
$danceBuilderStore = $danceBuilderStore;
}
/**
* @param {PoseWrapper} pose
*/
function addPose(pose) {
$poseBuilderStore.addPose(pose);
// trigger update (can I do better?)
$poseBuilderStore = $poseBuilderStore;
}
/**
* @param {String} id
*/
function removePose(id) {
$poseBuilderStore.removePose(id);
// trigger update (can I do better?)
$builderStore = $builderStore;
$poseBuilderStore = $poseBuilderStore;
}
</script>

Expand Down
9 changes: 6 additions & 3 deletions bouncy_frontend/src/routes/editor/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
import SvgAvatar from '$lib/components/avatar/SvgAvatar.svelte';
import VideoToStep from '$lib/components/editor/VideoToStep.svelte';
import { LEFT_RIGHT_COLORING_LIGHT } from '$lib/constants';
import { getContext } from 'svelte';
/** @type {import('./$types').PageData} */
export let data;
const poses = data.lookupPoses();
// const poses = data.lookupPoses();
const localCollectionCtx = getContext('localCollection');
const poses = localCollectionCtx.poses;
</script>

<div class="poses">
{#each poses as pose}
{#each $poses as pose}
<div class="pose">
<p>{pose.name('en')}</p>
<div class="avatar">
Expand All @@ -37,4 +40,4 @@
.pose {
max-width: 200px;
}
</style>
</style>
Loading

0 comments on commit 36def38

Please sign in to comment.