From c6db93d39a65b8b9998a4976772de9117a641f39 Mon Sep 17 00:00:00 2001 From: nhi-nguyen-csis Date: Mon, 5 May 2025 20:19:53 -0700 Subject: [PATCH 01/12] added sway buttons --- package-lock.json | 6 ++ web/src/App.js | 38 +++++++++++ web/src/components/Canvas/Canvas.jsx | 54 ++++++++++++++- .../SwayPointLabel/SwayPointLabel.jsx | 44 ++++++++++++ .../SwayPointLabel/SwayPointLabel.module.css | 67 +++++++++++++++++++ web/src/components/SwayPointLabel/index.js | 2 + .../VideoController/VideoController.jsx | 11 +++ web/src/utils/constant.js | 41 +++++++----- 8 files changed, 246 insertions(+), 17 deletions(-) create mode 100644 package-lock.json create mode 100644 web/src/components/SwayPointLabel/SwayPointLabel.jsx create mode 100644 web/src/components/SwayPointLabel/SwayPointLabel.module.css create mode 100644 web/src/components/SwayPointLabel/index.js diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bf3ed9d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Pro", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/web/src/App.js b/web/src/App.js index 17accb0..8956e0c 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -8,6 +8,7 @@ import Selection from "./components/Selection"; import { VERSION } from "./version"; import KeypointList from "./components/KeypointLabel"; import { keypointsIndex } from "./utils/constant"; +import { SwayPointLabel } from "./components/SwayPointLabel/SwayPointLabel"; const colorLength = 400; @@ -365,6 +366,24 @@ function App() { setMarkedKeypoints(newMarkedKeypoints); }; + // Sway Point + const [swayPoints, setSwayPoints] = useState([]); + const [selectedSwayPoint, setSelectedSwayPoint] = useState(); + const [markedSwayPoints, setMarkedSwayPoints] = useState([]); + // TODO: NEED TO IMPLEMENT + const handleCompleteMarkSwayPoint = () => { + + }; + + const handleSaveSwayPoint = () => { + + }; + + const handleMarkedSwayPoint = (key) => { + setSelectedSwayPoint(undefined) + setMarkedSwayPoints([...markedSwayPoints, key]); + }; + return (
@@ -456,6 +475,12 @@ function App() { onErrorMarkedKeypoint={() => setErrorChooseKeypoint(true)} isRemoveKeypoint={isRemoveKeypoint} onRemoveKeypoint={handleRemoveKeypoint} + + //Swaypoint handler + swayPoints={swayPoints} + setSwayPoints={setSwayPoints} + selectedSwayPoint={selectedSwayPoint} + onMarkSwayPoint={handleMarkedSwayPoint} />
@@ -516,6 +544,16 @@ function App() {
Rotation is under construction...
Don't refresh midway, no cache yet
+ + {/* Sway Boundaries Labeling */} +
+

Sway Boundaries

+ +
); diff --git a/web/src/components/Canvas/Canvas.jsx b/web/src/components/Canvas/Canvas.jsx index e2560f4..6ea2e39 100644 --- a/web/src/components/Canvas/Canvas.jsx +++ b/web/src/components/Canvas/Canvas.jsx @@ -15,6 +15,9 @@ import { keypointsText, labelKeypointWidth, skeletonPair, + swayPointsText, + swaySkeletonPair, + colorSwayPointByIndexInSkeletonPair, } from "../../utils/constant"; const CIRCLE_RAD = 4; @@ -27,6 +30,12 @@ export const Canvas = ({ onMarkKeypoint, onRemoveKeypoint, onErrorMarkedKeypoint, + + //sway + swayPoints, + setSwayPoints, + currentSwayLabel, + onMarkSwayPoint, }) => { const isInsideCircle = ( circle_x, @@ -70,8 +79,19 @@ export const Canvas = ({ } else { onErrorMarkedKeypoint(); } + + if (currentSwayLabel != null) { + if (event.evt.button !== 2 && event.target.getStage()) { + setSwayPoints((prevArray) => [ + ...prevArray, + { x: x, y: y, label: currentSwayLabel }, + ]); + onMarkSwayPoint(currentSwayLabel); + } + } } }; + const renderSkeleton = () => { const lineArray = []; skeletonPair.forEach((pair, index) => { @@ -85,6 +105,22 @@ export const Canvas = ({ }); } }); + + //----------- SWAY ------------- + swaySkeletonPair.forEach((pair, index) => { + const line = swayPoints.filter( + (swayPoints) => swayPoints.label === pair[0] || swayPoints.label === pair[1] + ); + if (line.length === 2) { + lineArray.push({ + swayPoints: [line[0].x, line[0].y, line[1].x, line[1].y], + color: colorSwayPointByIndexInSkeletonPair(index), + dash: [5, 5], // Make sway point connections dashed + }); + } + }); + //----------- SWAY ------------- + return lineArray.map((linePoints) => ( )); }; + //----------- SWAY ------------- + const getLabelText = (label) => { + return keypointsText[label] || swayPointsText[label] || "Unknown"; + }; + + const getLabelColor = (label) => { + return swayPointsText[label] ? "#FFA500" : "red"; // Orange for sway points, red for keypoints + }; + //----------- SWAY ------------- + + return ( @@ -113,8 +161,10 @@ export const Canvas = ({ diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.jsx b/web/src/components/SwayPointLabel/SwayPointLabel.jsx new file mode 100644 index 0000000..9e06f0d --- /dev/null +++ b/web/src/components/SwayPointLabel/SwayPointLabel.jsx @@ -0,0 +1,44 @@ +import styles from "./SwayPointLabel.module.css"; +import clsx from "clsx"; +import tickGreenIcon from "../../assets/tick-green-icon.svg"; +import eraserIcon from "../../assets/cards_school_eraser.svg"; + +export function SwayPointLabel({ + onComplete, + isRemoveSwayPpoint, + setIsRemoveSwayPpoint, +}){ + return( +
+
+ + +
+ +
+ + +
+
+ ); +} \ No newline at end of file diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.module.css b/web/src/components/SwayPointLabel/SwayPointLabel.module.css new file mode 100644 index 0000000..d56e716 --- /dev/null +++ b/web/src/components/SwayPointLabel/SwayPointLabel.module.css @@ -0,0 +1,67 @@ +.swayPointContainer { + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + padding-left: 20px; + padding-right: 10px; +} + +.timeContainer{ + margin-top: 10px; + display: flex; + flex-direction: column; /* Stack buttons vertically */ + gap: 10px; /* Adds spacing between buttons */ + width: 100%; + height: 70px; + align-items: center; /* Align buttons to the left (optional) */ +} + + +.swayPointButton, .completeButton { + border-radius: 5px; + padding: 0; + width: 168px; + font-size: 14px; + border: none; + height: inherit; + text-align: center; +} + +.swayPointButton:hover, .completeButton:hover{ + filter: brightness(80%); + cursor: pointer; + } + +.completeButton { + padding: 8px; + width: 80%; + background-color: #6497dd; +} + +.swayPointController{ + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + margin-top: 10px; +} + +.eraserIcon { + width: 20px; + height: 20px; +} + +.eraserButton { +padding: 3px; +background-color: transparent; +border-radius: 5px; +border: none; +cursor: pointer; +border: 2px solid transparent; +} +.isEraserActive { +border: 2px solid grey; +background-color: #6497dd;; +} \ No newline at end of file diff --git a/web/src/components/SwayPointLabel/index.js b/web/src/components/SwayPointLabel/index.js new file mode 100644 index 0000000..dafff95 --- /dev/null +++ b/web/src/components/SwayPointLabel/index.js @@ -0,0 +1,2 @@ +import { SwayPointLabel } from "./SwayPointLabel"; +export default SwayPointLabel; diff --git a/web/src/components/VideoController/VideoController.jsx b/web/src/components/VideoController/VideoController.jsx index 08472c0..968ab95 100644 --- a/web/src/components/VideoController/VideoController.jsx +++ b/web/src/components/VideoController/VideoController.jsx @@ -26,6 +26,12 @@ export function VideoController({ onErrorMarkedKeypoint, isRemoveKeypoint, onRemoveKeypoint, + + // Sway point + swayPoints, + setSwayPoints, + selectedSwayPoint, + onMarkSwayPoint, }) { const [volume, setVolume] = useState(0.7); const [timeStart] = useState(0); @@ -93,6 +99,11 @@ export function VideoController({ onErrorMarkedKeypoint={onErrorMarkedKeypoint} isRemove={isRemoveKeypoint} onRemoveKeypoint={onRemoveKeypoint} + //sway + swayPoints={swayPoints} + setSwayPoints={setSwayPoints} + currentSwayLabel={selectedSwayPoint} + onMarkSwayPoint={onMarkSwayPoint} /> )} diff --git a/web/src/utils/constant.js b/web/src/utils/constant.js index 3615b43..8884cdb 100644 --- a/web/src/utils/constant.js +++ b/web/src/utils/constant.js @@ -1,5 +1,6 @@ export const keypointsIndex = [0, 5, 6, 7, 8, 9, 10, 11, 12]; + export const labelKeypointWidth = (text) => { const len = text.length; if (len < 5) { @@ -16,6 +17,7 @@ export const labelKeypointWidth = (text) => { return 75; } }; + export const skeletonPair = [ [0, 5], [0, 6], @@ -45,6 +47,7 @@ export const keypointsText = { 12: "right hip", }; + export const colorKeypointByIndexInSkeletonPair = (indexInSkeletonPair) => { switch (indexInSkeletonPair) { case 0: @@ -65,18 +68,26 @@ export const colorKeypointByIndexInSkeletonPair = (indexInSkeletonPair) => { } }; -// export const keypointsText = { -// nose: 0, -// "left eye": 1, -// "right eye": 2, -// "left ear": 3, -// "right ear": 4, -// "left shoulder": 5, -// "right shoulder": 6, -// "left elbow": 7, -// "right elbow": 8, -// "left wrist": 9, -// "right wrist": 10, -// "left hip": 11, -// "right hip": 12, -// }; +// ====================== SWAY POINTS ====================== // +// left sternum, left umbilicus, right sternum, right umbilicus +export const swayPointsIndex = [13, 14, 15, 16]; +export const swayPointsText = { + 13: "left sternum", + 14: "left umbilicus", + 15: "right sternum", + 16: "right umbilicus", +}; + +export const swaySkeletonPair = [ + [13, 14], // left sternum → left umbilicus + [15, 16], // right sternum → right umbilicus +]; + +// maps colors to sway skeleton pairs +export const colorSwayPointByIndexInSkeletonPair = (pairIndex) => { + switch (pairIndex) { + case 0: case 1: return "#FFA500"; // Orange + case 2: case 3: return "#000000"; // Black + default: return null; + } +}; From fe06b30f70b0e3b781f15a2bce74d842669aefd8 Mon Sep 17 00:00:00 2001 From: nhi-nguyen-csis Date: Mon, 5 May 2025 22:27:42 -0700 Subject: [PATCH 02/12] added sway points --- web/src/App.js | 20 ++++- web/src/components/Canvas/Canvas.jsx | 78 +++++++++++++---- .../SwayPointLabel/SwayPointLabel.jsx | 71 ++++++++++++---- .../SwayPointLabel/SwayPointLabel.module.css | 85 +++++++++++-------- .../VideoController/VideoController.jsx | 2 + web/src/utils/constant.js | 19 +++-- 6 files changed, 196 insertions(+), 79 deletions(-) diff --git a/web/src/App.js b/web/src/App.js index 8956e0c..d791250 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -144,6 +144,12 @@ function App() { setMarkedKeypoints([]); setErrorChooseKeypoint(false); setIsRemoveKeypoint(false); + + //sway + setSwayPoints([]); + setSelectedSwayPoint(null); + setMarkedSwayPoints([]); + setIsRemoveSwayPoint(false); }; const updateLabels = (start, end, label) => { @@ -370,6 +376,7 @@ function App() { const [swayPoints, setSwayPoints] = useState([]); const [selectedSwayPoint, setSelectedSwayPoint] = useState(); const [markedSwayPoints, setMarkedSwayPoints] = useState([]); + const [isRemoveSwayPoint, setIsRemoveSwayPoint] = useState(false); // TODO: NEED TO IMPLEMENT const handleCompleteMarkSwayPoint = () => { @@ -481,6 +488,8 @@ function App() { setSwayPoints={setSwayPoints} selectedSwayPoint={selectedSwayPoint} onMarkSwayPoint={handleMarkedSwayPoint} + isRemoveSwayPoint={isRemoveSwayPoint} + />
diff --git a/web/src/components/Canvas/Canvas.jsx b/web/src/components/Canvas/Canvas.jsx index 6ea2e39..4eafb10 100644 --- a/web/src/components/Canvas/Canvas.jsx +++ b/web/src/components/Canvas/Canvas.jsx @@ -22,6 +22,7 @@ import { const CIRCLE_RAD = 4; const ERROR_TEXT_THRESHOLD = 5; + export const Canvas = ({ points, setPoints, @@ -31,11 +32,13 @@ export const Canvas = ({ onRemoveKeypoint, onErrorMarkedKeypoint, - //sway + // sway swayPoints, setSwayPoints, currentSwayLabel, onMarkSwayPoint, + isRemoveSwayPoint = false, + onRemoveSwayPoint, }) => { const isInsideCircle = ( circle_x, @@ -51,8 +54,11 @@ export const Canvas = ({ return true; else return false; }; + const handleMouseDown = (event) => { const { x, y } = event.target?.getStage()?.getPointerPosition(); + + // Handle keypoints if (isRemove) { let deletedPoint = null; const newPoints = points.filter((point) => { @@ -79,7 +85,24 @@ export const Canvas = ({ } else { onErrorMarkedKeypoint(); } + } + // Handle sway points + if (isRemoveSwayPoint) { + let deletedPoint = null; + const newPoints = swayPoints.filter((point) => { + const { x: x_circle, y: y_circle } = point; + if (isInsideCircle(x_circle, y_circle, x, y)) { + deletedPoint = point; + return false; + } + return true; + }); + if (deletedPoint) { + onRemoveSwayPoint(deletedPoint.label); + } + setSwayPoints(newPoints); + } else { if (currentSwayLabel != null) { if (event.evt.button !== 2 && event.target.getStage()) { setSwayPoints((prevArray) => [ @@ -88,12 +111,14 @@ export const Canvas = ({ ]); onMarkSwayPoint(currentSwayLabel); } - } + } } }; const renderSkeleton = () => { const lineArray = []; + + // Render keypoint skeleton skeletonPair.forEach((pair, index) => { const line = points.filter( (point) => point.label === pair[0] || point.label === pair[1] @@ -106,23 +131,23 @@ export const Canvas = ({ } }); - //----------- SWAY ------------- + // Render sway skeleton swaySkeletonPair.forEach((pair, index) => { const line = swayPoints.filter( - (swayPoints) => swayPoints.label === pair[0] || swayPoints.label === pair[1] + (point) => point.label === pair[0] || point.label === pair[1] ); if (line.length === 2) { lineArray.push({ - swayPoints: [line[0].x, line[0].y, line[1].x, line[1].y], + points: [line[0].x, line[0].y, line[1].x, line[1].y], color: colorSwayPointByIndexInSkeletonPair(index), - dash: [5, 5], // Make sway point connections dashed + dash: [5, 5], }); } }); - //----------- SWAY ------------- - return lineArray.map((linePoints) => ( + return lineArray.map((linePoints, i) => ( { return keypointsText[label] || swayPointsText[label] || "Unknown"; }; const getLabelColor = (label) => { - return swayPointsText[label] ? "#FFA500" : "red"; // Orange for sway points, red for keypoints + return swayPointsText[label] ? "green" : "red"; // green for sway points, red for keypoints }; - //----------- SWAY ------------- - return ( {renderSkeleton()} - {points.map((e) => ( - <> + + {/* Render keypoints */} + {points.map((e, i) => ( + - + + ))} + + {/* Render sway points */} + {swayPoints.map((e, i) => ( + + + + + ))} diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.jsx b/web/src/components/SwayPointLabel/SwayPointLabel.jsx index 9e06f0d..33556d2 100644 --- a/web/src/components/SwayPointLabel/SwayPointLabel.jsx +++ b/web/src/components/SwayPointLabel/SwayPointLabel.jsx @@ -1,26 +1,67 @@ import styles from "./SwayPointLabel.module.css"; +import { swayPointsText, swayPointsIndex } from "../../utils/constant"; import clsx from "clsx"; import tickGreenIcon from "../../assets/tick-green-icon.svg"; import eraserIcon from "../../assets/cards_school_eraser.svg"; export function SwayPointLabel({ + marked, + selected, + swayPoints = swayPointsIndex, + onSwayPoint, onComplete, - isRemoveSwayPpoint, - setIsRemoveSwayPpoint, + isRemoveSwayPoint, + setIsRemoveSwayPoint, }){ - return( + return (
-
- - + {/* Set time Buttons */} +
+
+ +
+
+ +
- + {/* Set left and right boundary buttons */} + {swayPoints.map((value, index) => { + const isMarked = marked.includes(value); + return ( +
+ + {isMarked && ( + green-tick + )} +
+ ); + })} + {/* Set save current sway boundary button */}
)} diff --git a/web/src/utils/constant.js b/web/src/utils/constant.js index 8884cdb..a1553e3 100644 --- a/web/src/utils/constant.js +++ b/web/src/utils/constant.js @@ -13,8 +13,10 @@ export const labelKeypointWidth = (text) => { return 60; } else if (len < 14) { return 65; - } else { + } else if (len < 16){ return 75; + } else{ + return 115; } }; @@ -72,10 +74,10 @@ export const colorKeypointByIndexInSkeletonPair = (indexInSkeletonPair) => { // left sternum, left umbilicus, right sternum, right umbilicus export const swayPointsIndex = [13, 14, 15, 16]; export const swayPointsText = { - 13: "left sternum", - 14: "left umbilicus", - 15: "right sternum", - 16: "right umbilicus", + 13: "Left Sway - Sternum", + 14: "Left Sway - Umbilicus", + 15: "Right Sway - Sternum", + 16: "Right Sway - umbilicus", }; export const swaySkeletonPair = [ @@ -85,9 +87,8 @@ export const swaySkeletonPair = [ // maps colors to sway skeleton pairs export const colorSwayPointByIndexInSkeletonPair = (pairIndex) => { - switch (pairIndex) { - case 0: case 1: return "#FFA500"; // Orange - case 2: case 3: return "#000000"; // Black - default: return null; + if(pairIndex === 0){ + return "red"; } + return "#000000"; }; From e786c7f7cbf8ff62590f6fa7de8960f5bc9b85e0 Mon Sep 17 00:00:00 2001 From: nhi-nguyen-csis Date: Mon, 5 May 2025 22:53:07 -0700 Subject: [PATCH 03/12] fix the eraser button --- web/src/App.js | 6 ++++++ web/src/components/VideoController/VideoController.jsx | 2 ++ web/src/utils/constant.js | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/App.js b/web/src/App.js index d791250..185ec8e 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -391,6 +391,11 @@ function App() { setMarkedSwayPoints([...markedSwayPoints, key]); }; + const handleRemoveSwayPoint = (key) => { + const newMarkedSwayPoints = markedSwayPoints.filter((k) => k !== key); + setMarkedSwayPoints(newMarkedSwayPoints); + }; + return (
@@ -489,6 +494,7 @@ function App() { selectedSwayPoint={selectedSwayPoint} onMarkSwayPoint={handleMarkedSwayPoint} isRemoveSwayPoint={isRemoveSwayPoint} + onRemoveSwayPoint={handleRemoveSwayPoint} />
diff --git a/web/src/components/VideoController/VideoController.jsx b/web/src/components/VideoController/VideoController.jsx index 9cb1e58..f0f3047 100644 --- a/web/src/components/VideoController/VideoController.jsx +++ b/web/src/components/VideoController/VideoController.jsx @@ -33,6 +33,7 @@ export function VideoController({ selectedSwayPoint, onMarkSwayPoint, isRemoveSwayPoint, + onRemoveSwayPoint, }) { const [volume, setVolume] = useState(0.7); const [timeStart] = useState(0); @@ -106,6 +107,7 @@ export function VideoController({ currentSwayLabel={selectedSwayPoint} onMarkSwayPoint={onMarkSwayPoint} isRemoveSwayPoint={isRemoveSwayPoint} + onRemoveSwayPoint={onRemoveSwayPoint} />
)} diff --git a/web/src/utils/constant.js b/web/src/utils/constant.js index a1553e3..126bdcc 100644 --- a/web/src/utils/constant.js +++ b/web/src/utils/constant.js @@ -90,5 +90,5 @@ export const colorSwayPointByIndexInSkeletonPair = (pairIndex) => { if(pairIndex === 0){ return "red"; } - return "#000000"; + return "green"; }; From 74d2984e7b3c29be35a676672206b48989641fd0 Mon Sep 17 00:00:00 2001 From: nhi-nguyen-csis Date: Tue, 6 May 2025 00:57:55 -0700 Subject: [PATCH 04/12] added the function for sway label --- web/src/App.js | 109 +++++++++++++++++- .../KeypointLabel/KeypointLabel.module.css | 1 + .../SwayPointLabel/SwayPointLabel.jsx | 26 ++++- .../SwayPointLabel/SwayPointLabel.module.css | 4 +- 4 files changed, 131 insertions(+), 9 deletions(-) diff --git a/web/src/App.js b/web/src/App.js index 185ec8e..6f65d49 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -146,6 +146,7 @@ function App() { setIsRemoveKeypoint(false); //sway + setSwayPointData([]); setSwayPoints([]); setSelectedSwayPoint(null); setMarkedSwayPoints([]); @@ -372,20 +373,113 @@ function App() { setMarkedKeypoints(newMarkedKeypoints); }; - // Sway Point + // Sway point handling const [swayPoints, setSwayPoints] = useState([]); const [selectedSwayPoint, setSelectedSwayPoint] = useState(); const [markedSwayPoints, setMarkedSwayPoints] = useState([]); const [isRemoveSwayPoint, setIsRemoveSwayPoint] = useState(false); + const [swayPointData, setSwayPointData] = useState([]); + const [startTime, setStartTime] = useState([]); + const [endTime, setEndTime] = useState([]); + const [endTimeSet, setEndTimeSet] = useState(false); + // TODO: NEED TO IMPLEMENT const handleCompleteMarkSwayPoint = () => { + if (swayPoints.length !== 4) { + setMessage("Please mark all 4 sway points before saving"); + return; + } + + // Sort points by label + const sortedPoints = [...swayPoints].sort((a, b) => a.label - b.label); + + // Store the data + setSwayPointData([ + ...swayPointData, + { + startTime: startTime, + endTime: endTime, + points: sortedPoints + } + ]); + + setSwayPoints([]); + setSelectedSwayPoint(undefined); + setMarkedSwayPoints([]); + setEndTimeSet(false); + console.log("The current sway boundary has been saved!!!!"); }; - const handleSaveSwayPoint = () => { + const handleSaveSwayBoundaries = () => { + if (!swayPointData || swayPointData.length === 0 || !swayPointData[0]?.points) { + setMessage("No sway points data available"); + return; + } + + // Create CSV header + const csvHeader = [ + "StartTime", + "EndTime", + "Left_Sternum_X", + "Left_Sternum_Y", + "Right_Sternum_X", + "Right_Sternum_Y", + "Left_Umbilicus_X", + "Left_Umbilicus_Y", + "Right_Umbilicus_X", + "Right_Umbilicus_Y" + ]; + + // Sort sway points by label + // const sortedPoints = [...swayPoints].sort((a, b) => a.label - b.label); + + // // Extract coordinates in order: left sternum, right sternum, left umbilicus, right umbilicus + // const coordinates = []; + // for (const point of sortedPoints) { + // coordinates.push(point.x, point.y); + // } + + // // Create data row with start time, end time, and all coordinates + // const dataRow = [ + // startTime / fps, // Convert frame number to seconds + // endTime / fps, // Convert frame number to seconds + // ...coordinates + // ]; + + // // Download CSV + // downloadCSV([csvHeader, dataRow], `${video}-sway-boundaries.csv`); + // Prepare all data rows + const dataRows = swayPointData.map((boundary) => { + // Sort points by label to ensure consistent order (13-16) + const sortedPoints = boundary.points.sort((a, b) => a.label - b.label); + + // Extract coordinates in order: left sternum, right sternum, left umbilicus, right umbilicus + const coordinates = sortedPoints.flatMap(point => [point.x, point.y]); + + return [ + boundary.startTime, + boundary.endTime, + ...coordinates + ]; + }); + + // Download CSV with header and all data rows + downloadCSV([csvHeader, ...dataRows], `${video}-sway-boundaries.csv`); + }; + const handleSetStart = () => { + setStartTime(getIndex(time.current, fpsRef.current)); }; + const handleSetEnd = () => { + setEndTime(getIndex(time.current, fpsRef.current)); + setEndTimeSet(true); + }; + + console.log("start time: "); + console.log(startTime); + const handleMarkedSwayPoint = (key) => { setSelectedSwayPoint(undefined) setMarkedSwayPoints([...markedSwayPoints, key]); @@ -396,6 +490,10 @@ function App() { setMarkedSwayPoints(newMarkedSwayPoints); }; + console.log(swayPointData); + console.log("..."); + console.log(swayPoints); + return (
@@ -495,7 +593,6 @@ function App() { onMarkSwayPoint={handleMarkedSwayPoint} isRemoveSwayPoint={isRemoveSwayPoint} onRemoveSwayPoint={handleRemoveSwayPoint} - />
diff --git a/web/src/components/KeypointLabel/KeypointLabel.module.css b/web/src/components/KeypointLabel/KeypointLabel.module.css index bc0113e..b369cdb 100644 --- a/web/src/components/KeypointLabel/KeypointLabel.module.css +++ b/web/src/components/KeypointLabel/KeypointLabel.module.css @@ -15,6 +15,7 @@ justify-content: space-between; height: 30px; } + .keypointButton, .completeButton { border-radius: 5px; padding: 0; diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.jsx b/web/src/components/SwayPointLabel/SwayPointLabel.jsx index 33556d2..6c0c64c 100644 --- a/web/src/components/SwayPointLabel/SwayPointLabel.jsx +++ b/web/src/components/SwayPointLabel/SwayPointLabel.jsx @@ -12,18 +12,34 @@ export function SwayPointLabel({ onComplete, isRemoveSwayPoint, setIsRemoveSwayPoint, + onSetStart, + onSetEnd, + endTimeSet, }){ + // Disable save current sway boundary button + // if all points are not marked OR end time isn't set + const isSaveDisabled = marked.length < 4 || !endTimeSet; + return (
{/* Set time Buttons */}
-
-
@@ -63,7 +79,11 @@ export function SwayPointLabel({ })} {/* Set save current sway boundary button */}
- to begin +

+

+ Step 2: All 6 points and timing must be set before saving +

+

+ Step 3: Export data with "Save Sway Boundaries" +

+
{ setSelectedSwayPoint(k); @@ -654,7 +679,7 @@ function App() { setIsRemoveSwayPoint={setIsRemoveSwayPoint} onSetStart={handleSetStart} onSetEnd={handleSetEnd} - endTimeSet={endTimeSet} + timeButtonsClicked={timeButtonsClicked} />
@@ -663,3 +688,4 @@ function App() { } export default App; + diff --git a/web/src/assets/tick-blue-icon.png b/web/src/assets/tick-blue-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..652a8a4c40c4eeb78b12027f344b63ece91e0ebe GIT binary patch literal 9651 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 literal 0 HcmV?d00001 diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.jsx b/web/src/components/SwayPointLabel/SwayPointLabel.jsx index 6c0c64c..de01c6e 100644 --- a/web/src/components/SwayPointLabel/SwayPointLabel.jsx +++ b/web/src/components/SwayPointLabel/SwayPointLabel.jsx @@ -1,7 +1,7 @@ import styles from "./SwayPointLabel.module.css"; import { swayPointsText, swayPointsIndex } from "../../utils/constant"; import clsx from "clsx"; -import tickGreenIcon from "../../assets/tick-green-icon.svg"; +import tickGreenIcon from "../../assets/tick-blue-icon.png"; import eraserIcon from "../../assets/cards_school_eraser.svg"; export function SwayPointLabel({ @@ -14,11 +14,12 @@ export function SwayPointLabel({ setIsRemoveSwayPoint, onSetStart, onSetEnd, - endTimeSet, + timeButtonsClicked, }){ // Disable save current sway boundary button // if all points are not marked OR end time isn't set - const isSaveDisabled = marked.length < 4 || !endTimeSet; + // const isSaveDisabled = marked.length < 4 || !timeButtonsClicked.end; + const isSaveDisabled = !timeButtonsClicked.end; return (
@@ -27,21 +28,41 @@ export function SwayPointLabel({
+ {timeButtonsClicked.start && ( + green-tick + )}
+ {timeButtonsClicked.end && ( + green-tick + )}
{/* Set left and right boundary buttons */} @@ -63,7 +84,7 @@ export function SwayPointLabel({ onSwayPoint(value); setIsRemoveSwayPoint(false); }} - disabled={isMarked} + disabled={isMarked || !timeButtonsClicked.start} > {swayPointsText[value]} @@ -102,4 +123,5 @@ export function SwayPointLabel({
); -} \ No newline at end of file +} + diff --git a/web/src/components/VideoController/VideoController.jsx b/web/src/components/VideoController/VideoController.jsx index f0f3047..1edd7b2 100644 --- a/web/src/components/VideoController/VideoController.jsx +++ b/web/src/components/VideoController/VideoController.jsx @@ -116,3 +116,4 @@ export function VideoController({ ) ); } + diff --git a/web/src/index.css b/web/src/index.css index 27e3be5..2eb011a 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -334,7 +334,7 @@ select:hover { flex-direction: column; border-radius: 5px; background-color: #80abdc; - padding: 10px; + padding: 8px; margin: 10px; } .selections-container h3 { @@ -346,7 +346,7 @@ select:hover { flex-direction: row; justify-content: space-evenly; align-items: center; - margin-top: 10px; + margin-top: 5px; } .selection-item div { @@ -363,4 +363,19 @@ select:hover { color: red; padding: 0 10px; font-size: 12px; -} \ No newline at end of file +} + +.sway-hint { + padding: 0px 8px; + user-select: none; + font-size: 13px; +} + +.demo-btn { + background-color: #D3FDCC; + border: 1px solid #ced4da; + padding: 2px 2px; + margin: 5px; + border-radius: 4px; + font-size: 12px; +} From f98a3e087d7ce56fc228604f489fdf070c4fc162 Mon Sep 17 00:00:00 2001 From: nhi-nguyen-csis Date: Wed, 7 May 2025 17:57:59 -0700 Subject: [PATCH 07/12] added left and right center buttons and drop down export button --- web/src/App.js | 59 ++++++++------ .../SwayPointLabel/SwayPointLabel.jsx | 6 +- web/src/index.css | 79 ++++++++++++++++++- web/src/utils/constant.js | 14 ++-- 4 files changed, 126 insertions(+), 32 deletions(-) diff --git a/web/src/App.js b/web/src/App.js index a476845..1015a80 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -393,8 +393,8 @@ function App() { // TODO: NEED TO IMPLEMENT const handleCompleteMarkSwayPoint = () => { - if (swayPoints.length !== 4) { - setMessage("Please mark all 4 sway points before saving"); + if (swayPoints.length !== 6) { + setMessage("Please mark all 6 sway points before saving"); return; } @@ -423,7 +423,7 @@ function App() { const handleSaveSwayBoundaries = () => { if (!swayPointData || swayPointData.length === 0 || !swayPointData[0]?.points) { - setMessage("No sway points data available"); + setMessage("No sway boundary data available"); return; } @@ -433,12 +433,16 @@ function App() { "EndTime", "Left_Sternum_X", "Left_Sternum_Y", - "Right_Sternum_X", - "Right_Sternum_Y", "Left_Umbilicus_X", "Left_Umbilicus_Y", + "Left_Center_X", + "Left_Center_Y", + "Right_Sternum_X", + "Right_Sternum_Y", "Right_Umbilicus_X", - "Right_Umbilicus_Y" + "Right_Umbilicus_Y", + "Right_Center_X", + "Right_Center_Y" ]; @@ -481,6 +485,7 @@ function App() { const handleRemoveSwayPoint = (key) => { const newMarkedSwayPoints = markedSwayPoints.filter((k) => k !== key); setMarkedSwayPoints(newMarkedSwayPoints); + setTimeButtonsClicked(prev => ({...prev, end: false})); }; console.log(swayPointData); @@ -600,18 +605,32 @@ function App() { />

{video}

{source !== "" && ( - <> - +
+ - - - - )} +
+
+ )}
@@ -656,16 +675,10 @@ function App() { {/*
Note: points and timing must be set before saving
*/}

- Step 1: Click + Tooltip: Click to begin -

-

- Step 2: All 6 points and timing must be set before saving -

-

- Step 3: Export data with "Save Sway Boundaries" + to begin. All 6 points and timing must be set before saving

@@ -52,7 +52,7 @@ export function SwayPointLabel({ timeButtonsClicked.end && styles.marked )} onClick={onSetEnd} - disabled={marked.length < 4} + disabled={marked.length < 6} > Set End Time @@ -105,7 +105,7 @@ export function SwayPointLabel({ onClick={onComplete} disabled={isSaveDisabled} > - Save Current Sway Boundary + Save Current Sway Data
diff --git a/web/src/components/SwayPointLabel/SwayPointLabel.jsx b/web/src/components/SwayPointLabel/SwayPointLabel.jsx index d30e81f..5088f40 100644 --- a/web/src/components/SwayPointLabel/SwayPointLabel.jsx +++ b/web/src/components/SwayPointLabel/SwayPointLabel.jsx @@ -105,7 +105,7 @@ export function SwayPointLabel({ onClick={onComplete} disabled={isSaveDisabled} > - Save Current Sway Data + Save Current Sway Boundary