mirror of
https://github.com/john-okeefe/foliate-js.git
synced 2026-09-09 11:29:14 -04:00
- Prioritize metadata-based panel detection as highest accuracy method - Remove COCO-SSD ML detection to reduce dependencies - Add document parameter to detection pipeline for metadata access - Improve diagnostic logging throughout detection pipeline
192 lines
6.2 KiB
JavaScript
192 lines
6.2 KiB
JavaScript
// panel-detection/detector.js
|
|
// Main panel detector with lazy-loaded fallback chain
|
|
import { loadMLLibraries, getLibrariesStatus } from "./load-scripts.js";
|
|
export class PanelDetector {
|
|
#cache = new Map();
|
|
#scriptsLoaded = false;
|
|
#model = null;
|
|
|
|
async detectPanels(doc, index, force = false) {
|
|
const cacheKey = `${doc.location?.pathname || ""}-${index}`;
|
|
|
|
if (!force && this.#cache.has(cacheKey)) {
|
|
console.log("[Panel Detection] Using cached result");
|
|
return this.#cache.get(cacheKey);
|
|
}
|
|
|
|
const imageData = this.#extractImageData(doc);
|
|
if (!imageData) {
|
|
return { panels: [], method: "no-image", confidence: 0 };
|
|
}
|
|
console.log(
|
|
"[Panel Detection] Starting detection for image:",
|
|
`${imageData.width}x${imageData.height}px`,
|
|
);
|
|
// Load ML libraries on first use
|
|
if (!this.#scriptsLoaded) {
|
|
const result = await loadMLLibraries();
|
|
if (!result.loaded) {
|
|
console.warn(
|
|
"ML libraries not available, using grid detection:",
|
|
result.reason,
|
|
);
|
|
// Fall back to grid immediately
|
|
const { detectPanelsGrid } = await import("./grid.js");
|
|
const panels = detectPanelsGrid(imageData);
|
|
this.#cache.set(cacheKey, {
|
|
panels,
|
|
method: "grid",
|
|
confidence: 0.4,
|
|
reason: result.reason,
|
|
});
|
|
return {
|
|
panels,
|
|
method: "grid",
|
|
confidence: 0.4,
|
|
reason: result.reason,
|
|
};
|
|
}
|
|
this.#scriptsLoaded = true;
|
|
}
|
|
|
|
const result = await this.#runDetectionPipeline(imageData, doc);
|
|
this.#cache.set(cacheKey, result);
|
|
return result;
|
|
}
|
|
|
|
#extractImageData(doc) {
|
|
const img = doc.querySelector("img") || doc.querySelector("canvas");
|
|
if (!img) return null;
|
|
|
|
const canvas = document.createElement("canvas");
|
|
canvas.width = img.naturalWidth || img.width;
|
|
canvas.height = img.naturalHeight || img.height;
|
|
const ctx = canvas.getContext("2d");
|
|
ctx.drawImage(img, 0, 0);
|
|
return ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
}
|
|
|
|
async #runDetectionPipeline(imageData, doc) {
|
|
const { detectPanelsOpenCV } = await import("./opencv.js");
|
|
const { detectPanelsGrid } = await import("./grid.js");
|
|
// Try metadata extraction (NEW - highest priority)
|
|
console.log("[Panel Detection] Checking for metadata panels...");
|
|
try {
|
|
const { extractPanelMetadata, hasMetadata } =
|
|
await import("./metadata-extractor.js");
|
|
|
|
console.log("[DEBUG] doc type:", typeof doc);
|
|
console.log("[DEBUG] doc exists:", !!doc);
|
|
console.log("[DEBUG] hasMetadata result:", hasMetadata(doc));
|
|
console.log(
|
|
"[DEBUG] Amazon links:",
|
|
doc?.querySelectorAll("a[data-app-amzn-mzn-magnify]")?.length,
|
|
);
|
|
console.log(
|
|
"[DEBUG] Linkhotspots:",
|
|
doc?.querySelectorAll('.linkhotspot[style*="top"]')?.length,
|
|
);
|
|
console.log("[DEBUG] doc URL:", doc.location?.href);
|
|
console.log("[DEBUG] doc readyState:", doc.readyState);
|
|
console.log("[DEBUG] doc has body:", !!doc.body);
|
|
console.log(
|
|
"[DEBUG] doc.body.innerHTML length:",
|
|
doc.body?.innerHTML.length,
|
|
);
|
|
console.log("[DEBUG] All links:", doc.querySelectorAll("a").length);
|
|
console.log("[DEBUG] All divs:", doc.querySelectorAll("div").length);
|
|
if (hasMetadata(doc)) {
|
|
const metadataResult = await extractPanelMetadata(
|
|
doc,
|
|
doc.location?.pathname,
|
|
);
|
|
|
|
if (metadataResult.panels.length > 0) {
|
|
console.log(
|
|
`[Panel Detection] ✓ Using metadata panels (${metadataResult.panels.length} panels)`,
|
|
);
|
|
|
|
// Store panels for potential augmentation
|
|
const metadataPanels = metadataResult.panels;
|
|
|
|
// Check if metadata is complete or needs augmentation
|
|
// For now, use metadata as-is (detection augmentation comes in Phase 2)
|
|
return {
|
|
panels: metadataPanels,
|
|
method: "metadata",
|
|
confidence: metadataResult.confidence,
|
|
};
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("Metadata extraction failed:", e);
|
|
}
|
|
console.log("[Panel Detection] Attempting OpenCV detection...");
|
|
// Try OpenCV (uses global cv)
|
|
try {
|
|
const cv = globalThis.cv;
|
|
if (cv && cv.Mat) {
|
|
// Wait for OpenCV to be ready
|
|
await new Promise((resolve, reject) => {
|
|
const check = () => {
|
|
if (cv && cv.Mat) resolve();
|
|
else if (cv && cv.readyState === "complete")
|
|
reject(new Error("OpenCV failed to load"));
|
|
else setTimeout(check, 50);
|
|
};
|
|
check();
|
|
});
|
|
|
|
const panels = await detectPanelsOpenCV(imageData, cv);
|
|
console.log("[Panel Detection] OpenCV found", panels.length, "panels");
|
|
if (this.#validatePanels(panels, imageData)) {
|
|
console.log("[Panel Detection] ✓ Using OpenCV detection");
|
|
return { panels, method: "opencv", confidence: 0.85 };
|
|
} else {
|
|
console.log("[Panel Detection] ✗ OpenCV panels failed validation");
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("OpenCV detection failed:", e);
|
|
}
|
|
|
|
// Grid fallback (always works)
|
|
console.log("[Panel Detection] Falling back to grid detection");
|
|
const panels = detectPanelsGrid(imageData);
|
|
console.log(
|
|
"[Panel Detection] ✓ Using grid detection, found",
|
|
panels.length,
|
|
"panels",
|
|
);
|
|
return { panels, method: "grid", confidence: 0.4 };
|
|
}
|
|
|
|
#validatePanels(panels, imageData) {
|
|
if (!panels || panels.length === 0) return false;
|
|
if (panels.length > 30) return false;
|
|
|
|
const imgArea = imageData.width * imageData.height;
|
|
let totalPanelArea = 0;
|
|
for (const panel of panels) {
|
|
const panelArea = ((panel.width * panel.height) / 10000) * imgArea;
|
|
totalPanelArea += panelArea;
|
|
}
|
|
|
|
const coverage = totalPanelArea / imgArea;
|
|
return coverage > 0.1 && coverage < 0.95;
|
|
}
|
|
|
|
clear() {
|
|
this.#cache.clear();
|
|
}
|
|
|
|
// Expose library status for debugging
|
|
getStatus() {
|
|
return {
|
|
...getLibrariesStatus(),
|
|
scriptsLoaded: this.#scriptsLoaded,
|
|
cacheSize: this.#cache.size,
|
|
};
|
|
}
|
|
}
|