|
|
@@ -1,9 +1,8 @@
|
|
|
import { Component, OnInit, ElementRef, ViewChild } from '@angular/core';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { ImageProcessorService } from '../../services/image-processor.service';
|
|
|
-import { LocalInferenceService } from '../../services/local-inference.service';
|
|
|
import { LocalHistoryService } from '../../services/local-history.service';
|
|
|
-import { InferenceService } from '../../core/services/inference.service';
|
|
|
+import { InferenceService, InferenceMode, LocalEngine } from '../../core/services/inference.service';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
|
|
|
@Component({
|
|
|
@@ -19,7 +18,6 @@ export class AnalyzerComponent implements OnInit {
|
|
|
previewUrl: string | null = null;
|
|
|
results: any = null;
|
|
|
loading = false;
|
|
|
- modelType = 'onnx';
|
|
|
confidence = 0.25;
|
|
|
isDragging = false;
|
|
|
|
|
|
@@ -30,7 +28,6 @@ export class AnalyzerComponent implements OnInit {
|
|
|
) {}
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
- // Confidence is now managed locally for the PoC
|
|
|
this.confidence = 0.25;
|
|
|
}
|
|
|
|
|
|
@@ -76,6 +73,14 @@ export class AnalyzerComponent implements OnInit {
|
|
|
reader.readAsDataURL(file);
|
|
|
}
|
|
|
|
|
|
+ onModeChange(newMode: any): void {
|
|
|
+ this.inferenceService.mode.set(newMode as InferenceMode);
|
|
|
+ }
|
|
|
+
|
|
|
+ onEngineChange(newEngine: any): void {
|
|
|
+ this.inferenceService.localEngine.set(newEngine as LocalEngine);
|
|
|
+ }
|
|
|
+
|
|
|
async analyze(): Promise<void> {
|
|
|
if (!this.selectedFile || !this.previewUrl) return;
|
|
|
|
|
|
@@ -83,30 +88,17 @@ export class AnalyzerComponent implements OnInit {
|
|
|
const start = performance.now();
|
|
|
|
|
|
try {
|
|
|
- // Get image dimensions
|
|
|
+ // Get image dimensions for scaling help
|
|
|
const img = await this.loadImageDimensions(this.selectedFile);
|
|
|
|
|
|
// Use the Master Inference Service Hub
|
|
|
this.inferenceService.analyze(this.previewUrl, img.width, img.height).subscribe({
|
|
|
next: (detections) => {
|
|
|
- // Calculate Industrial Summary
|
|
|
- const summary: any = {
|
|
|
- 'Empty_Bunch': 0,
|
|
|
- 'Underripe': 0,
|
|
|
- 'Abnormal': 0,
|
|
|
- 'Ripe': 0,
|
|
|
- 'Unripe': 0,
|
|
|
- 'Overripe': 0
|
|
|
- };
|
|
|
-
|
|
|
- detections.forEach((d: any) => {
|
|
|
- if (summary[d.class] !== undefined) summary[d.class]++;
|
|
|
- });
|
|
|
-
|
|
|
this.results = {
|
|
|
- industrial_summary: summary,
|
|
|
+ industrial_summary: this.inferenceService.summary(),
|
|
|
inference_ms: performance.now() - start,
|
|
|
- detections: detections
|
|
|
+ detections: detections,
|
|
|
+ original_dimensions: img
|
|
|
};
|
|
|
|
|
|
// Persist to local vault
|
|
|
@@ -132,7 +124,6 @@ export class AnalyzerComponent implements OnInit {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Helper to get raw file dimensions
|
|
|
private loadImageDimensions(file: File): Promise<{width: number, height: number}> {
|
|
|
return new Promise((resolve) => {
|
|
|
const img = new Image();
|
|
|
@@ -146,43 +137,60 @@ export class AnalyzerComponent implements OnInit {
|
|
|
}
|
|
|
|
|
|
drawDetections(): void {
|
|
|
- if (!this.results || !this.results.detections || !this.canvas) return;
|
|
|
+ const detections = this.inferenceService.detections();
|
|
|
+ if (!detections || !this.canvas || !this.previewUrl) return;
|
|
|
|
|
|
const ctx = this.canvas.nativeElement.getContext('2d');
|
|
|
if (!ctx) return;
|
|
|
|
|
|
const img = new Image();
|
|
|
- img.src = this.previewUrl!;
|
|
|
+ img.src = this.previewUrl;
|
|
|
img.onload = () => {
|
|
|
const canvas = this.canvas.nativeElement;
|
|
|
const containerWidth = canvas.parentElement!.clientWidth;
|
|
|
- const scale = containerWidth / img.width;
|
|
|
+
|
|
|
+ // Calculate display scale factor
|
|
|
+ const displayScale = containerWidth / img.width;
|
|
|
|
|
|
canvas.width = containerWidth;
|
|
|
- canvas.height = img.height * scale;
|
|
|
+ canvas.height = img.height * displayScale;
|
|
|
|
|
|
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
|
|
|
|
- this.results.detections.forEach((det: any) => {
|
|
|
- const [x1, y1, x2, y2] = det.box.map((v: number) => v * scale);
|
|
|
+ detections.forEach((det: any) => {
|
|
|
+ let x1: number, y1: number, x2: number, y2: number;
|
|
|
+
|
|
|
+ if (this.inferenceService.mode() === 'api') {
|
|
|
+ // API returns absolute pixels (e.g. 450 on a 640px base, but relative to original)
|
|
|
+ // We need to scale them to the current canvas display size
|
|
|
+ [x1, y1, x2, y2] = det.box.map((v: number) => v * displayScale);
|
|
|
+ } else {
|
|
|
+ // Local mode often uses normalized coordinates or absolute pixels depending on parser
|
|
|
+ // Assuming the InferenceService already normalized or scaled these for us
|
|
|
+ [x1, y1, x2, y2] = det.box.map((v: number) => v * displayScale);
|
|
|
+ }
|
|
|
|
|
|
- ctx.strokeStyle = det.color;
|
|
|
+ ctx.strokeStyle = det.color || '#00A651';
|
|
|
ctx.lineWidth = 3;
|
|
|
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
|
|
|
|
|
|
- ctx.fillStyle = det.color;
|
|
|
- ctx.font = '14px Outfit';
|
|
|
+ // Drawing Label
|
|
|
+ ctx.fillStyle = det.color || '#00A651';
|
|
|
+ ctx.font = 'bold 14px Outfit';
|
|
|
const label = `${det.class} ${Math.round(det.confidence * 100)}%`;
|
|
|
const textWidth = ctx.measureText(label).width;
|
|
|
- ctx.fillRect(x1, y1 - 20, textWidth + 10, 20);
|
|
|
+
|
|
|
+ // Background for text
|
|
|
+ ctx.fillRect(x1, y1 - 25, textWidth + 10, 25);
|
|
|
|
|
|
ctx.fillStyle = '#FFFFFF';
|
|
|
- ctx.fillText(label, x1 + 5, y1 - 5);
|
|
|
+ ctx.fillText(label, x1 + 5, y1 - 7);
|
|
|
});
|
|
|
};
|
|
|
}
|
|
|
|
|
|
getSummaryKeys(): string[] {
|
|
|
- return this.results ? Object.keys(this.results.industrial_summary) : [];
|
|
|
+ const summary = this.inferenceService.summary();
|
|
|
+ return summary ? Object.keys(summary) : [];
|
|
|
}
|
|
|
}
|