+
+
+ {playbackError && (
+
+ {playbackError}
+
+ )}
+
{children}
-
);
diff --git a/src/services/AnnotationService.js b/src/services/AnnotationService.js
index 450f8a4..dce6ed5 100644
--- a/src/services/AnnotationService.js
+++ b/src/services/AnnotationService.js
@@ -13,48 +13,76 @@ export const isMouseOverDetection = (x, y, detection, containerRef) => {
return relativeX >= detection.x1 && relativeX <= detection.x2 && relativeY >= detection.y1 && relativeY <= detection.y2;
};
-// Function to create an annotation image
export const createAnnotationImage = (videoRef, detections, containerRef) => {
+ if (!videoRef?.current || !containerRef?.current) {
+ console.warn("Missing video or container reference");
+ return null;
+ }
+
const canvas = document.createElement('canvas');
- if (!containerRef.current) return null;
const container = containerRef.current;
- canvas.width = container.offsetWidth;
- canvas.height = container.offsetHeight;
+ canvas.width = container.offsetWidth || 640;
+ canvas.height = container.offsetHeight || 480;
+
const ctx = canvas.getContext('2d');
+ if (!ctx) return null;
- ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
+ try {
+ ctx.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
+ } catch (e) {
+ console.error("Error drawing video to canvas:", e);
+ return null;
+ }
- detections.forEach(detection => {
+ if (detections && detections.length > 0) {
+ detections.forEach(detection => {
+ if (!detection?.class) return;
- ctx.fillRect(detection.x1, detection.y1, detection.x2 - detection.x1, detection.y2 - detection.y1);
- ctx.lineWidth = 2;
- ctx.strokeRect(detection.x1, detection.y1, detection.x2 - detection.x1, detection.y2 - detection.y1);
+ // Ensure proper opacity for background - consistently using 0.4 opacity
+ const bgColor = detection.class.Color?.startsWith('rgba')
+ ? detection.class.Color.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/, 'rgba($1, $2, $3, 0.4)')
+ : detection.class.Color?.replace(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/, 'rgba($1, $2, $3, 0.4)') || 'rgba(255, 0, 0, 0.4)';
- ctx.fillStyle = 'white';
- ctx.font = '12px Arial';
- ctx.fillText(detection.class.Name, detection.x1, detection.y1 - 5);
- });
+ // Ensure full opacity for border
+ const borderColor = detection.class.Color?.startsWith('rgba')
+ ? detection.class.Color.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[\d.]+\)/, 'rgba($1, $2, $3, 1)')
+ : detection.class.Color || 'rgba(255, 0, 0, 1)';
+
+ ctx.fillStyle = bgColor;
+ ctx.strokeStyle = borderColor;
+
+ const x = Math.max(0, detection.x1 || 0);
+ const y = Math.max(0, detection.y1 || 0);
+ const width = Math.max(1, (detection.x2 || 0) - (detection.x1 || 0));
+ const height = Math.max(1, (detection.y2 || 0) - (detection.y1 || 0));
+
+ ctx.fillRect(x, y, width, height);
+ ctx.lineWidth = 2;
+ ctx.strokeRect(x, y, width, height);
+
+ ctx.fillStyle = 'white';
+ ctx.font = '12px Arial';
+ ctx.fillText(detection.class.Name || 'Unknown', x, y - 5);
+ });
+ }
return canvas.toDataURL('image/png');
};
-
-export const calculateNewPosition = (mouseX, mouseY, dragOffset, detection, containerRef) => {
-
+export const calculateNewPosition = (mouseX, mouseY, dragOffset, detection, containerRef) => {
let newX1 = mouseX - dragOffset.x;
let newY1 = mouseY - dragOffset.y;
let newX2 = newX1 + (detection.x2 - detection.x1);
let newY2 = newY1 + (detection.y2 - detection.y1);
if (!containerRef.current) {
- return { newX1, newY1, newX2, newY2 }; // Return early with unchanged values
+ return { newX1, newY1, newX2, newY2 };
}
let containerWidth = containerRef.current.offsetWidth;
let containerHeight = containerRef.current.offsetHeight;
-
if (newX1 < 0) {
newX1 = 0;
newX2 = detection.x2 - detection.x1;
@@ -72,13 +100,13 @@ export const calculateNewPosition = (mouseX, mouseY, dragOffset, detection, con
newY1 = newY2 - (detection.y2 - detection.y1);
}
return { newX1, newY1, newX2, newY2 };
-
};
export const calculateResizedPosition = (mouseX, mouseY, position, detection, containerRef) => {
let { x1, y1, x2, y2 } = detection;
- const containerWidth = containerRef.current.offsetWidth;
- const containerHeight = containerRef.current.offsetHeight;
+ const containerWidth = containerRef.current?.offsetWidth || 640;
+ const containerHeight = containerRef.current?.offsetHeight || 480;
+
switch (position) {
case 'top-left':
x1 = Math.min(mouseX, detection.x2 - 5);
@@ -111,11 +139,11 @@ export const calculateResizedPosition = (mouseX, mouseY, position, detection, co
default:
break;
}
- // Boundary checks
+
x1 = Math.max(0, x1);
y1 = Math.max(0, y1);
x2 = Math.min(containerWidth, x2);
y2 = Math.min(containerHeight, y2);
- return { x1, y1, x2, y2 };
+ return { ...detection, x1, y1, x2, y2 };
};
\ No newline at end of file
diff --git a/src/services/DataHandler.js b/src/services/DataHandler.js
new file mode 100644
index 0000000..e283b92
--- /dev/null
+++ b/src/services/DataHandler.js
@@ -0,0 +1,58 @@
+const convertToYoloFormat = (detection, imageWidth, imageHeight) => {
+ const width = (detection.x2 - detection.x1) / imageWidth;
+ const height = (detection.y2 - detection.y1) / imageHeight;
+ const centerX = (detection.x1 / imageWidth) + (width / 2);
+ const centerY = (detection.y1 / imageHeight) + (height / 2);
+
+ return {
+ classId: detection.class.Id,
+ centerX,
+ centerY,
+ width,
+ height
+ };
+};
+
+const saveAnnotation = (time, detections, imageData) => {
+ if (!detections || !detections.length || !imageData) {
+ console.warn("Nothing to save: missing detections or image data");
+ return null;
+ }
+
+ try {
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+ const imageFilename = `annotation_${timestamp}.png`;
+ const annotationFilename = `annotation_${timestamp}.txt`;
+
+ const imageWidth = detections[0]?.x2 > 0 ?
+ Math.max(...detections.map(d => d.x2)) : 640;
+ const imageHeight = detections[0]?.y2 > 0 ?
+ Math.max(...detections.map(d => d.y2)) : 480;
+
+ const yoloAnnotations = detections.map(detection => {
+ const yolo = convertToYoloFormat(detection, imageWidth, imageHeight);
+ return `${yolo.classId} ${yolo.centerX.toFixed(6)} ${yolo.centerY.toFixed(6)} ${yolo.width.toFixed(6)} ${yolo.height.toFixed(6)}`;
+ }).join('\n');
+
+ console.log(`Saving image to: ${imageFilename}`);
+ console.log(`Saving annotations to: ${annotationFilename}`);
+
+ const imageLink = document.createElement('a');
+ imageLink.href = imageData;
+ imageLink.download = imageFilename;
+ imageLink.click();
+
+ const annotationBlob = new Blob([yoloAnnotations], { type: 'text/plain' });
+ const annotationLink = document.createElement('a');
+ annotationLink.href = URL.createObjectURL(annotationBlob);
+ annotationLink.download = annotationFilename;
+ annotationLink.click();
+
+ return { imageFilename, annotationFilename };
+ } catch (error) {
+ console.error("Error saving annotation:", error);
+ return null;
+ }
+};
+
+export default saveAnnotation;
\ No newline at end of file