[New Feature] Support SAM 2.1 (#59)
* support sam 2.1 * refine config path and ckpt path * update README
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import useMessagesSnackbar from '@/common/components/snackbar/useDemoMessagesSnackbar';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import {activeTrackletObjectIdAtom, labelTypeAtom} from '@/demo/atoms';
|
||||
import {Add} from '@carbon/icons-react';
|
||||
import {useSetAtom} from 'jotai';
|
||||
|
||||
export default function AddObjectButton() {
|
||||
const video = useVideo();
|
||||
const setActiveTrackletId = useSetAtom(activeTrackletObjectIdAtom);
|
||||
const setLabelType = useSetAtom(labelTypeAtom);
|
||||
const {enqueueMessage} = useMessagesSnackbar();
|
||||
|
||||
async function addObject() {
|
||||
enqueueMessage('addObjectClick');
|
||||
const tracklet = await video?.createTracklet();
|
||||
if (tracklet != null) {
|
||||
setActiveTrackletId(tracklet.id);
|
||||
setLabelType('positive');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={addObject}
|
||||
className="group flex justify-start mx-4 px-4 bg-transparent text-white !rounded-xl border-none cursor-pointer">
|
||||
<div className="flex gap-6 items-center">
|
||||
<div className=" group-hover:bg-graydark-700 border border-white relative h-12 w-12 md:w-20 md:h-20 shrink-0 rounded-lg flex items-center justify-center">
|
||||
<Add size={36} className="group-hover:text-white text-gray-300" />
|
||||
</div>
|
||||
<div className="font-medium text-base">Add another object</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import useRestartSession from '@/common/components/session/useRestartSession';
|
||||
import useMessagesSnackbar from '@/common/components/snackbar/useDemoMessagesSnackbar';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import {isPlayingAtom, isStreamingAtom, labelTypeAtom} from '@/demo/atoms';
|
||||
import {Reset} from '@carbon/icons-react';
|
||||
import stylex from '@stylexjs/stylex';
|
||||
import {useAtomValue, useSetAtom} from 'jotai';
|
||||
import {useState} from 'react';
|
||||
import {Button, Loading} from 'react-daisyui';
|
||||
|
||||
const styles = stylex.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
onRestart: () => void;
|
||||
};
|
||||
|
||||
export default function ClearAllPointsInVideoButton({onRestart}: Props) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const isPlaying = useAtomValue(isPlayingAtom);
|
||||
const isStreaming = useAtomValue(isStreamingAtom);
|
||||
const setLabelType = useSetAtom(labelTypeAtom);
|
||||
const {clearMessage} = useMessagesSnackbar();
|
||||
const {restartSession} = useRestartSession();
|
||||
|
||||
const video = useVideo();
|
||||
|
||||
async function handleRestart() {
|
||||
if (video === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
if (isPlaying) {
|
||||
video.pause();
|
||||
}
|
||||
if (isStreaming) {
|
||||
await video.abortStreamMasks();
|
||||
}
|
||||
const isSuccessful = await video.clearPointsInVideo();
|
||||
if (!isSuccessful) {
|
||||
await restartSession();
|
||||
}
|
||||
video.frame = 0;
|
||||
setLabelType('positive');
|
||||
onRestart();
|
||||
clearMessage();
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...stylex.props(styles.container)}>
|
||||
<Button
|
||||
color="ghost"
|
||||
onClick={handleRestart}
|
||||
className="!px-4 !rounded-full font-medium text-white hover:bg-black"
|
||||
startIcon={isLoading ? <Loading size="sm" /> : <Reset size={20} />}>
|
||||
Start over
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import PrimaryCTAButton from '@/common/components/button/PrimaryCTAButton';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import {ChevronRight} from '@carbon/icons-react';
|
||||
|
||||
type Props = {
|
||||
onSessionClose: () => void;
|
||||
};
|
||||
|
||||
export default function CloseSessionButton({onSessionClose}: Props) {
|
||||
const video = useVideo();
|
||||
|
||||
function handleCloseSession() {
|
||||
video?.closeSession();
|
||||
video?.logAnnotations();
|
||||
onSessionClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<PrimaryCTAButton onClick={handleCloseSession} endIcon={<ChevronRight />}>
|
||||
Good to go
|
||||
</PrimaryCTAButton>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ChangeVideo from '@/common/components/gallery/ChangeVideoModal';
|
||||
import useMessagesSnackbar from '@/common/components/snackbar/useDemoMessagesSnackbar';
|
||||
import {DEMO_SHORT_NAME} from '@/demo/DemoConfig';
|
||||
import {useEffect, useRef} from 'react';
|
||||
|
||||
export default function FirstClickView() {
|
||||
const isFirstClickMessageShown = useRef(false);
|
||||
const {enqueueMessage} = useMessagesSnackbar();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFirstClickMessageShown.current) {
|
||||
isFirstClickMessageShown.current = true;
|
||||
enqueueMessage('firstClick');
|
||||
}
|
||||
}, [enqueueMessage]);
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col p-8">
|
||||
<div className="grow flex flex-col gap-6">
|
||||
<h2 className="text-2xl">Click an object in the video to start</h2>
|
||||
<p className="!text-gray-60">
|
||||
You'll be able to use {DEMO_SHORT_NAME} to make fun edits to any
|
||||
video by tracking objects and applying visual effects.
|
||||
</p>
|
||||
<p className="!text-gray-60">
|
||||
To start, click any object in the video.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<ChangeVideo />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {InformationFilled} from '@carbon/icons-react';
|
||||
|
||||
export default function LimitNotice() {
|
||||
return (
|
||||
<div className="mt-6 gap-3 mx-6 flex items-center text-gray-400">
|
||||
<div>
|
||||
<InformationFilled size={32} />
|
||||
</div>
|
||||
<div className="text-sm leading-snug">
|
||||
In this demo, you can track up to 3 objects, even though the SAM 2 model
|
||||
does not have a limit.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ClearAllPointsInVideoButton from '@/common/components/annotations/ClearAllPointsInVideoButton';
|
||||
import ObjectThumbnail from '@/common/components/annotations/ObjectThumbnail';
|
||||
import {OBJECT_TOOLBAR_INDEX} from '@/common/components/toolbar/ToolbarConfig';
|
||||
import {BaseTracklet} from '@/common/tracker/Tracker';
|
||||
import {activeTrackletObjectIdAtom, trackletObjectsAtom} from '@/demo/atoms';
|
||||
import {spacing} from '@/theme/tokens.stylex';
|
||||
import stylex from '@stylexjs/stylex';
|
||||
import {useAtomValue, useSetAtom} from 'jotai';
|
||||
|
||||
const styles = stylex.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
padding: spacing[5],
|
||||
borderTop: '1px solid #DEE3E9',
|
||||
},
|
||||
trackletsContainer: {
|
||||
flexGrow: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: spacing[5],
|
||||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
showActiveObject: () => void;
|
||||
onTabChange: (newIndex: number) => void;
|
||||
};
|
||||
|
||||
export default function MobileObjectsList({
|
||||
showActiveObject,
|
||||
onTabChange,
|
||||
}: Props) {
|
||||
const tracklets = useAtomValue(trackletObjectsAtom);
|
||||
const setActiveTrackletId = useSetAtom(activeTrackletObjectIdAtom);
|
||||
|
||||
function handleSelectObject(tracklet: BaseTracklet) {
|
||||
setActiveTrackletId(tracklet.id);
|
||||
showActiveObject();
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...stylex.props(styles.container)}>
|
||||
<div {...stylex.props(styles.trackletsContainer)}>
|
||||
{tracklets.map(tracklet => {
|
||||
const {id, color, thumbnail} = tracklet;
|
||||
return (
|
||||
<ObjectThumbnail
|
||||
key={id}
|
||||
color={color}
|
||||
thumbnail={thumbnail}
|
||||
onClick={() => {
|
||||
handleSelectObject(tracklet);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<ClearAllPointsInVideoButton
|
||||
onRestart={() => onTabChange(OBJECT_TOOLBAR_INDEX)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import MobileObjectsToolbarHeader from '@/common/components/annotations/MobileObjectsToolbarHeader';
|
||||
import ObjectsToolbarBottomActions from '@/common/components/annotations/ObjectsToolbarBottomActions';
|
||||
import {getObjectLabel} from '@/common/components/annotations/ObjectUtils';
|
||||
import ToolbarObject from '@/common/components/annotations/ToolbarObject';
|
||||
import MobileFirstClickBanner from '@/common/components/MobileFirstClickBanner';
|
||||
import {activeTrackletObjectAtom, isFirstClickMadeAtom} from '@/demo/atoms';
|
||||
import {useAtomValue} from 'jotai';
|
||||
|
||||
type Props = {
|
||||
onTabChange: (newIndex: number) => void;
|
||||
};
|
||||
|
||||
export default function MobileObjectsToolbar({onTabChange}: Props) {
|
||||
const activeTracklet = useAtomValue(activeTrackletObjectAtom);
|
||||
const isFirstClickMade = useAtomValue(isFirstClickMadeAtom);
|
||||
|
||||
if (!isFirstClickMade) {
|
||||
return <MobileFirstClickBanner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<MobileObjectsToolbarHeader />
|
||||
{activeTracklet != null && (
|
||||
<ToolbarObject
|
||||
label={getObjectLabel(activeTracklet)}
|
||||
tracklet={activeTracklet}
|
||||
isActive={true}
|
||||
isMobile={true}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ObjectsToolbarBottomActions onTabChange={onTabChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ToolbarProgressChip from '@/common/components/toolbar/ToolbarProgressChip';
|
||||
import {isStreamingAtom, streamingStateAtom} from '@/demo/atoms';
|
||||
import {useAtomValue} from 'jotai';
|
||||
|
||||
export default function MobileObjectsToolbarHeader() {
|
||||
const isStreaming = useAtomValue(isStreamingAtom);
|
||||
const streamingState = useAtomValue(streamingStateAtom);
|
||||
|
||||
return (
|
||||
<div className="w-full flex gap-4 items-center px-5 py-5">
|
||||
<div className="grow text-sm text-white">
|
||||
<ToolbarProgressChip />
|
||||
{streamingState === 'full'
|
||||
? 'Review your selected objects across the video, and continue to edit if needed. Once everything looks good, press “Next” to continue.'
|
||||
: isStreaming
|
||||
? 'Watch the video closely for any places where your objects aren’t tracked correctly. You can also stop tracking to make additional edits.'
|
||||
: 'Edit your object selection with a few more clicks if needed. Press “Track objects” to track your objects throughout the video.'}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import PointsToggle from '@/common/components/annotations/PointsToggle';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import useReportError from '@/common/error/useReportError';
|
||||
import {
|
||||
activeTrackletObjectIdAtom,
|
||||
isPlayingAtom,
|
||||
isStreamingAtom,
|
||||
} from '@/demo/atoms';
|
||||
import {
|
||||
AddFilled,
|
||||
Select_02,
|
||||
SubtractFilled,
|
||||
TrashCan,
|
||||
} from '@carbon/icons-react';
|
||||
import {useAtom, useAtomValue} from 'jotai';
|
||||
import {useState} from 'react';
|
||||
import type {ButtonProps} from 'react-daisyui';
|
||||
import {Button} from 'react-daisyui';
|
||||
|
||||
type Props = {
|
||||
objectId: number;
|
||||
active: boolean;
|
||||
};
|
||||
|
||||
function CustomButton({className, ...props}: ButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
size="sm"
|
||||
color="ghost"
|
||||
className={`font-medium border-none hover:bg-black px-2 h-10 ${className}`}
|
||||
{...props}>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ObjectActions({objectId, active}: Props) {
|
||||
const [isRemovingObject, setIsRemovingObject] = useState<boolean>(false);
|
||||
const [activeTrackId, setActiveTrackletId] = useAtom(
|
||||
activeTrackletObjectIdAtom,
|
||||
);
|
||||
const isStreaming = useAtomValue(isStreamingAtom);
|
||||
const isPlaying = useAtom(isPlayingAtom);
|
||||
|
||||
const video = useVideo();
|
||||
const reportError = useReportError();
|
||||
|
||||
async function handleRemoveObject(
|
||||
event: React.MouseEvent<HTMLButtonElement>,
|
||||
) {
|
||||
try {
|
||||
event.stopPropagation();
|
||||
setIsRemovingObject(true);
|
||||
if (isStreaming) {
|
||||
await video?.abortStreamMasks();
|
||||
}
|
||||
if (isPlaying) {
|
||||
video?.pause();
|
||||
}
|
||||
await video?.deleteTracklet(objectId);
|
||||
} catch (error) {
|
||||
reportError(error);
|
||||
} finally {
|
||||
setIsRemovingObject(false);
|
||||
if (activeTrackId === objectId) {
|
||||
setActiveTrackletId(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{active && (
|
||||
<div className="text-sm mt-1 leading-snug text-gray-400 hidden md:block ml-2 md:mb-4">
|
||||
Select <AddFilled size={14} className="inline" /> to add areas to the
|
||||
object and <SubtractFilled size={14} className="inline" /> to remove
|
||||
areas from the object in the video. Click on an existing point to
|
||||
delete it.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between items-center md:mt-2 mt-0">
|
||||
{active ? (
|
||||
<PointsToggle />
|
||||
) : (
|
||||
<>
|
||||
<CustomButton startIcon={<Select_02 size={24} />}>
|
||||
Edit selection
|
||||
</CustomButton>
|
||||
<CustomButton
|
||||
loading={isRemovingObject}
|
||||
onClick={handleRemoveObject}
|
||||
startIcon={!isRemovingObject && <TrashCan size={24} />}>
|
||||
<span className="hidden md:inline">Clear</span>
|
||||
</CustomButton>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {BLUE_PINK_FILL_BR} from '@/theme/gradientStyle';
|
||||
|
||||
type Props = {
|
||||
showPlus?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export default function ObjectPlaceholder({showPlus = true, onClick}: Props) {
|
||||
return (
|
||||
<div
|
||||
className={`relative ${BLUE_PINK_FILL_BR} h-12 w-12 md:h-20 md:w-20 shrink-0 rounded-lg`}
|
||||
onClick={onClick}>
|
||||
{showPlus && (
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none">
|
||||
<path
|
||||
d="M16 7H9V0H7V7H0V9H7V16H9V9H16V7Z"
|
||||
fill="#667788"
|
||||
fillOpacity={1}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
type Props = {
|
||||
thumbnail: string | null;
|
||||
color: string;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export default function ObjectThumbnail({thumbnail, color, onClick}: Props) {
|
||||
return (
|
||||
<div
|
||||
className="relative h-12 w-12 md:w-20 md:h-20 shrink-0 p-2 rounded-lg bg-contain bg-no-repeat bg-center"
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
}}
|
||||
onClick={onClick}>
|
||||
<div
|
||||
className="w-full h-full bg-contain bg-no-repeat bg-center"
|
||||
style={{
|
||||
backgroundImage: thumbnail == null ? 'none' : `url(${thumbnail})`,
|
||||
}}></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {BaseTracklet} from '@/common/tracker/Tracker';
|
||||
|
||||
export function getObjectLabel(tracklet: BaseTracklet) {
|
||||
return `Object ${tracklet.id + 1}`;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import AddObjectButton from '@/common/components/annotations/AddObjectButton';
|
||||
import FirstClickView from '@/common/components/annotations/FirstClickView';
|
||||
import LimitNotice from '@/common/components/annotations/LimitNotice';
|
||||
import ObjectsToolbarBottomActions from '@/common/components/annotations/ObjectsToolbarBottomActions';
|
||||
import ObjectsToolbarHeader from '@/common/components/annotations/ObjectsToolbarHeader';
|
||||
import {getObjectLabel} from '@/common/components/annotations/ObjectUtils';
|
||||
import ToolbarObject from '@/common/components/annotations/ToolbarObject';
|
||||
import {
|
||||
activeTrackletObjectAtom,
|
||||
activeTrackletObjectIdAtom,
|
||||
isAddObjectEnabledAtom,
|
||||
isFirstClickMadeAtom,
|
||||
isTrackletObjectLimitReachedAtom,
|
||||
trackletObjectsAtom,
|
||||
} from '@/demo/atoms';
|
||||
import {useAtomValue, useSetAtom} from 'jotai';
|
||||
|
||||
type Props = {
|
||||
onTabChange: (newIndex: number) => void;
|
||||
};
|
||||
|
||||
export default function ObjectsToolbar({onTabChange}: Props) {
|
||||
const tracklets = useAtomValue(trackletObjectsAtom);
|
||||
const activeTracklet = useAtomValue(activeTrackletObjectAtom);
|
||||
const setActiveTrackletId = useSetAtom(activeTrackletObjectIdAtom);
|
||||
const isFirstClickMade = useAtomValue(isFirstClickMadeAtom);
|
||||
const isObjectLimitReached = useAtomValue(isTrackletObjectLimitReachedAtom);
|
||||
const isAddObjectEnabled = useAtomValue(isAddObjectEnabledAtom);
|
||||
|
||||
if (!isFirstClickMade) {
|
||||
return <FirstClickView />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<ObjectsToolbarHeader />
|
||||
<div className="grow w-full overflow-y-auto">
|
||||
{tracklets.map(tracklet => {
|
||||
return (
|
||||
<ToolbarObject
|
||||
key={tracklet.id}
|
||||
label={getObjectLabel(tracklet)}
|
||||
tracklet={tracklet}
|
||||
isActive={activeTracklet?.id === tracklet.id}
|
||||
onClick={() => {
|
||||
setActiveTrackletId(tracklet.id);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{isAddObjectEnabled && <AddObjectButton />}
|
||||
{isObjectLimitReached && <LimitNotice />}
|
||||
</div>
|
||||
<ObjectsToolbarBottomActions onTabChange={onTabChange} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ClearAllPointsInVideoButton from '@/common/components/annotations/ClearAllPointsInVideoButton';
|
||||
import CloseSessionButton from '@/common/components/annotations/CloseSessionButton';
|
||||
import TrackAndPlayButton from '@/common/components/button/TrackAndPlayButton';
|
||||
import ToolbarBottomActionsWrapper from '@/common/components/toolbar/ToolbarBottomActionsWrapper';
|
||||
import {
|
||||
EFFECT_TOOLBAR_INDEX,
|
||||
OBJECT_TOOLBAR_INDEX,
|
||||
} from '@/common/components/toolbar/ToolbarConfig';
|
||||
import {streamingStateAtom} from '@/demo/atoms';
|
||||
import {useAtomValue} from 'jotai';
|
||||
|
||||
type Props = {
|
||||
onTabChange: (newIndex: number) => void;
|
||||
};
|
||||
|
||||
export default function ObjectsToolbarBottomActions({onTabChange}: Props) {
|
||||
const streamingState = useAtomValue(streamingStateAtom);
|
||||
|
||||
const isTrackingEnabled =
|
||||
streamingState !== 'none' && streamingState !== 'full';
|
||||
|
||||
function handleSwitchToEffectsTab() {
|
||||
onTabChange(EFFECT_TOOLBAR_INDEX);
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarBottomActionsWrapper>
|
||||
<ClearAllPointsInVideoButton
|
||||
onRestart={() => onTabChange(OBJECT_TOOLBAR_INDEX)}
|
||||
/>
|
||||
{isTrackingEnabled && <TrackAndPlayButton />}
|
||||
{streamingState === 'full' && (
|
||||
<CloseSessionButton onSessionClose={handleSwitchToEffectsTab} />
|
||||
)}
|
||||
</ToolbarBottomActionsWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ToolbarHeaderWrapper from '@/common/components/toolbar/ToolbarHeaderWrapper';
|
||||
import {isStreamingAtom, streamingStateAtom} from '@/demo/atoms';
|
||||
import {useAtomValue} from 'jotai';
|
||||
|
||||
export default function ObjectsToolbarHeader() {
|
||||
const isStreaming = useAtomValue(isStreamingAtom);
|
||||
const streamingState = useAtomValue(streamingStateAtom);
|
||||
|
||||
return (
|
||||
<ToolbarHeaderWrapper
|
||||
title={
|
||||
streamingState === 'full'
|
||||
? 'Review tracked objects'
|
||||
: isStreaming
|
||||
? 'Tracking objects'
|
||||
: 'Select objects'
|
||||
}
|
||||
description={
|
||||
streamingState === 'full'
|
||||
? 'Review your selected objects across the video, and continue to edit if needed. Once everything looks good, press “Next” to continue.'
|
||||
: isStreaming
|
||||
? 'Watch the video closely for any places where your objects aren’t tracked correctly. You can also stop tracking to make additional edits.'
|
||||
: 'Adjust the selection of your object, or add additional objects. Press “Track objects” to track your objects throughout the video.'
|
||||
}
|
||||
className="mb-8"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {labelTypeAtom} from '@/demo/atoms';
|
||||
import {AddFilled, SubtractFilled} from '@carbon/icons-react';
|
||||
import {useAtom} from 'jotai';
|
||||
|
||||
export default function PointsToggle() {
|
||||
const [labelType, setLabelType] = useAtom(labelTypeAtom);
|
||||
const isPositive = labelType === 'positive';
|
||||
|
||||
const buttonStyle = (selected: boolean) =>
|
||||
`btn-md bg-graydark-800 !text-white md:px-2 lg:px-4 py-0.5 ${selected ? `border border-white hover:bg-graydark-800` : `border-graydark-700 hover:bg-graydark-700`}`;
|
||||
|
||||
return (
|
||||
<div className="flex items-center w-full md:ml-2">
|
||||
<div className="join group grow gap-[1px]">
|
||||
<button
|
||||
className={`w-1/2 btn join-item text-white ${buttonStyle(isPositive)}`}
|
||||
onClick={() => setLabelType('positive')}>
|
||||
<AddFilled size={24} className="text-blue-500" /> Add
|
||||
</button>
|
||||
<button
|
||||
className={`w-1/2 btn join-item text-red-700 ${buttonStyle(!isPositive)}`}
|
||||
onClick={() => setLabelType('negative')}>
|
||||
<SubtractFilled size={24} className="text-red-400" />
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import GradientBorder from '@/common/components/button/GradientBorder';
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
endIcon?: ReactNode;
|
||||
} & React.DOMAttributes<HTMLButtonElement>;
|
||||
|
||||
export default function PrimaryCTAButton({
|
||||
children,
|
||||
disabled,
|
||||
endIcon,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<GradientBorder disabled={disabled}>
|
||||
<button
|
||||
className={`btn ${disabled && 'btn-disabled'} !rounded-full !bg-black !text-white !border-none`}
|
||||
{...props}>
|
||||
{children}
|
||||
{endIcon != null && endIcon}
|
||||
</button>
|
||||
</GradientBorder>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import ObjectActions from '@/common/components/annotations/ObjectActions';
|
||||
import ObjectPlaceholder from '@/common/components/annotations/ObjectPlaceholder';
|
||||
import ObjectThumbnail from '@/common/components/annotations/ObjectThumbnail';
|
||||
import ToolbarObjectContainer from '@/common/components/annotations/ToolbarObjectContainer';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import {BaseTracklet} from '@/common/tracker/Tracker';
|
||||
import emptyFunction from '@/common/utils/emptyFunction';
|
||||
import {activeTrackletObjectIdAtom} from '@/demo/atoms';
|
||||
import {useSetAtom} from 'jotai';
|
||||
|
||||
type Props = {
|
||||
label: string;
|
||||
tracklet: BaseTracklet;
|
||||
isActive: boolean;
|
||||
isMobile?: boolean;
|
||||
onClick?: () => void;
|
||||
onThumbnailClick?: () => void;
|
||||
};
|
||||
|
||||
export default function ToolbarObject({
|
||||
label,
|
||||
tracklet,
|
||||
isActive,
|
||||
isMobile = false,
|
||||
onClick,
|
||||
onThumbnailClick = emptyFunction,
|
||||
}: Props) {
|
||||
const video = useVideo();
|
||||
const setActiveTrackletId = useSetAtom(activeTrackletObjectIdAtom);
|
||||
|
||||
async function handleCancelNewObject() {
|
||||
try {
|
||||
await video?.deleteTracklet(tracklet.id);
|
||||
} catch (error) {
|
||||
reportError(error);
|
||||
} finally {
|
||||
setActiveTrackletId(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (!tracklet.isInitialized) {
|
||||
return (
|
||||
<ToolbarObjectContainer
|
||||
alignItems="center"
|
||||
isActive={isActive}
|
||||
title="New object"
|
||||
subtitle="No object is currently selected. Click an object in the video."
|
||||
thumbnail={<ObjectPlaceholder showPlus={false} />}
|
||||
isMobile={isMobile}
|
||||
onClick={onClick}
|
||||
onCancel={handleCancelNewObject}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ToolbarObjectContainer
|
||||
isActive={isActive}
|
||||
onClick={onClick}
|
||||
title={label}
|
||||
subtitle=""
|
||||
thumbnail={
|
||||
<ObjectThumbnail
|
||||
thumbnail={tracklet.thumbnail}
|
||||
color={tracklet.color}
|
||||
onClick={onThumbnailClick}
|
||||
/>
|
||||
}
|
||||
isMobile={isMobile}>
|
||||
<ObjectActions objectId={tracklet.id} active={isActive} />
|
||||
</ToolbarObjectContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {spacing} from '@/theme/tokens.stylex';
|
||||
import {Close} from '@carbon/icons-react';
|
||||
import stylex from '@stylexjs/stylex';
|
||||
import {PropsWithChildren, ReactNode} from 'react';
|
||||
|
||||
const sharedStyles = stylex.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
overflow: 'hidden',
|
||||
cursor: 'pointer',
|
||||
flexShrink: 0,
|
||||
borderTop: 'none',
|
||||
backgroundColor: {
|
||||
'@media screen and (max-width: 768px)': '#000',
|
||||
},
|
||||
paddingHorizontal: {
|
||||
default: spacing[8],
|
||||
'@media screen and (max-width: 768px)': spacing[5],
|
||||
},
|
||||
paddingBottom: {
|
||||
default: spacing[8],
|
||||
'@media screen and (max-width: 768px)': 10,
|
||||
},
|
||||
},
|
||||
activeContainer: {
|
||||
background: '#000',
|
||||
borderRadius: 16,
|
||||
marginHorizontal: 16,
|
||||
padding: {
|
||||
default: spacing[4],
|
||||
'@media screen and (max-width: 768px)': spacing[5],
|
||||
},
|
||||
marginBottom: {
|
||||
default: spacing[8],
|
||||
'@media screen and (max-width: 768px)': 0,
|
||||
},
|
||||
},
|
||||
itemsCenter: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
rightColumn: {
|
||||
marginStart: {
|
||||
default: spacing[4],
|
||||
'@media screen and (max-width: 768px)': 0,
|
||||
},
|
||||
flexGrow: 1,
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
type ToolbarObjectContainerProps = PropsWithChildren<{
|
||||
alignItems?: 'top' | 'center';
|
||||
isActive: boolean;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
thumbnail: ReactNode;
|
||||
isMobile: boolean;
|
||||
onCancel?: () => void;
|
||||
onClick?: () => void;
|
||||
}>;
|
||||
|
||||
export default function ToolbarObjectContainer({
|
||||
alignItems = 'top',
|
||||
children,
|
||||
isActive,
|
||||
title,
|
||||
subtitle,
|
||||
thumbnail,
|
||||
isMobile,
|
||||
onClick,
|
||||
onCancel,
|
||||
}: ToolbarObjectContainerProps) {
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
{...stylex.props(sharedStyles.container, sharedStyles.itemsCenter)}>
|
||||
<div {...stylex.props(sharedStyles.rightColumn)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={onClick}
|
||||
{...stylex.props(
|
||||
sharedStyles.container,
|
||||
isActive && sharedStyles.activeContainer,
|
||||
alignItems === 'center' && sharedStyles.itemsCenter,
|
||||
)}>
|
||||
{thumbnail}
|
||||
<div {...stylex.props(sharedStyles.rightColumn)}>
|
||||
<div className="text-md font-semibold ml-2">{title}</div>
|
||||
{subtitle.length > 0 && (
|
||||
<div className="text-sm text-gray-400 leading-5 mt-2 ml-2">
|
||||
{subtitle}
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
{onCancel != null && (
|
||||
<div className="items-start self-stretch" onClick={onCancel}>
|
||||
<Close size={32} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import useSelectedFrameHelper from '@/common/components/video/filmstrip/useSelectedFrameHelper';
|
||||
import {BaseTracklet, DatalessMask} from '@/common/tracker/Tracker';
|
||||
import {spacing, w} from '@/theme/tokens.stylex';
|
||||
import stylex from '@stylexjs/stylex';
|
||||
import {useMemo} from 'react';
|
||||
|
||||
const styles = stylex.create({
|
||||
container: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: spacing[4],
|
||||
width: '100%',
|
||||
},
|
||||
trackletNameContainer: {
|
||||
width: w[12],
|
||||
textAlign: 'center',
|
||||
fontSize: '10px',
|
||||
color: 'white',
|
||||
},
|
||||
swimlaneContainer: {
|
||||
flexGrow: 1,
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
height: 12,
|
||||
marginVertical: '0.25rem' /* 4px */,
|
||||
'@media screen and (max-width: 768px)': {
|
||||
marginVertical: 0,
|
||||
},
|
||||
},
|
||||
swimlane: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: '50%',
|
||||
width: '100%',
|
||||
height: 1,
|
||||
transform: 'translate3d(0, -50%, 0)',
|
||||
opacity: 0.4,
|
||||
},
|
||||
segment: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
height: 1,
|
||||
transform: 'translate3d(0, -50%, 0)',
|
||||
},
|
||||
segmentationPoint: {
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
transform: 'translate3d(0, -50%, 0)',
|
||||
borderRadius: '50%',
|
||||
cursor: 'pointer',
|
||||
width: 12,
|
||||
height: 12,
|
||||
'@media screen and (max-width: 768px)': {
|
||||
width: 8,
|
||||
height: 8,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
type SwimlineSegment = {
|
||||
left: number;
|
||||
width: number;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tracklet: BaseTracklet;
|
||||
onSelectFrame: (tracklet: BaseTracklet, index: number) => void;
|
||||
};
|
||||
|
||||
function getSwimlaneSegments(masks: DatalessMask[]): SwimlineSegment[] {
|
||||
if (masks.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const swimlineSegments: SwimlineSegment[] = [];
|
||||
let left = -1;
|
||||
|
||||
for (let frameIndex = 0; frameIndex < masks.length; ++frameIndex) {
|
||||
const isEmpty = masks?.[frameIndex]?.isEmpty ?? true;
|
||||
if (left === -1 && !isEmpty) {
|
||||
left = frameIndex;
|
||||
} else if (left !== -1 && (isEmpty || frameIndex == masks.length - 1)) {
|
||||
swimlineSegments.push({
|
||||
left,
|
||||
width: frameIndex - left + 1,
|
||||
});
|
||||
left = -1;
|
||||
}
|
||||
}
|
||||
|
||||
return swimlineSegments;
|
||||
}
|
||||
|
||||
export default function TrackletSwimlane({tracklet, onSelectFrame}: Props) {
|
||||
const selection = useSelectedFrameHelper();
|
||||
|
||||
const segments = useMemo(() => {
|
||||
return getSwimlaneSegments(tracklet.masks);
|
||||
}, [tracklet.masks]);
|
||||
|
||||
const framesWithPoints = tracklet.points.reduce<number[]>(
|
||||
(frames, pts, frameIndex) => {
|
||||
if (pts != null && pts.length > 0) {
|
||||
frames.push(frameIndex);
|
||||
}
|
||||
return frames;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
if (selection === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...stylex.props(styles.container)}>
|
||||
<div {...stylex.props(styles.trackletNameContainer)}>
|
||||
Object {tracklet.id + 1}
|
||||
</div>
|
||||
<div {...stylex.props(styles.swimlaneContainer)}>
|
||||
<div
|
||||
{...stylex.props(styles.swimlane)}
|
||||
style={{
|
||||
backgroundColor: tracklet.color,
|
||||
}}
|
||||
/>
|
||||
{segments.map(segment => {
|
||||
return (
|
||||
<div
|
||||
key={segment.left}
|
||||
{...stylex.props(styles.segment)}
|
||||
style={{
|
||||
backgroundColor: tracklet.color,
|
||||
left: selection.toPosition(segment.left),
|
||||
width: selection.toPosition(segment.width),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{framesWithPoints.map(index => {
|
||||
return (
|
||||
<div
|
||||
key={`frame${index}`}
|
||||
onClick={() => {
|
||||
onSelectFrame?.(tracklet, index);
|
||||
}}
|
||||
{...stylex.props(styles.segmentationPoint)}
|
||||
style={{
|
||||
left: Math.floor(selection.toPosition(index) - 4),
|
||||
backgroundColor: tracklet.color,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import TrackletSwimlane from '@/common/components/annotations/TrackletSwimlane';
|
||||
import useTracklets from '@/common/components/annotations/useTracklets';
|
||||
import useVideo from '@/common/components/video/editor/useVideo';
|
||||
import {BaseTracklet} from '@/common/tracker/Tracker';
|
||||
import {m, spacing} from '@/theme/tokens.stylex';
|
||||
import stylex from '@stylexjs/stylex';
|
||||
|
||||
const styles = stylex.create({
|
||||
container: {
|
||||
marginTop: m[3],
|
||||
height: 75,
|
||||
paddingHorizontal: spacing[4],
|
||||
'@media screen and (max-width: 768px)': {
|
||||
height: 25,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default function TrackletsAnnotation() {
|
||||
const video = useVideo();
|
||||
const tracklets = useTracklets();
|
||||
|
||||
function handleSelectFrame(_tracklet: BaseTracklet, index: number) {
|
||||
if (video !== null) {
|
||||
video.frame = index;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div {...stylex.props(styles.container)}>
|
||||
{tracklets.map(tracklet => (
|
||||
<TrackletSwimlane
|
||||
key={tracklet.id}
|
||||
tracklet={tracklet}
|
||||
onSelectFrame={handleSelectFrame}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {trackletObjectsAtom} from '@/demo/atoms';
|
||||
import {useAtomValue} from 'jotai';
|
||||
|
||||
export default function useTracklets() {
|
||||
return useAtomValue(trackletObjectsAtom);
|
||||
}
|
||||
Reference in New Issue
Block a user