OpenCV Abhängigkeiten für Agent besser verpacken
This commit is contained in:
@@ -10,4 +10,5 @@ FEDEO_SCAN_MODE=Color
|
|||||||
FEDEO_SCAN_SOURCE=
|
FEDEO_SCAN_SOURCE=
|
||||||
FEDEO_SCAN_POSTPROCESS=false
|
FEDEO_SCAN_POSTPROCESS=false
|
||||||
FEDEO_SCAN_POSTPROCESS_PROFILE=document
|
FEDEO_SCAN_POSTPROCESS_PROFILE=document
|
||||||
FEDEO_SCAN_POSTPROCESS_PYTHON=python3
|
FEDEO_SCAN_POSTPROCESS_PYTHON=
|
||||||
|
FEDEO_SCAN_POSTPROCESS_STRICT=false
|
||||||
|
|||||||
1
agents/fedeo-device-agent/.gitignore
vendored
1
agents/fedeo-device-agent/.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
|
.venv-opencv
|
||||||
.env
|
.env
|
||||||
*.log
|
*.log
|
||||||
*.tmp
|
*.tmp
|
||||||
|
|||||||
@@ -58,9 +58,7 @@ npm run dev
|
|||||||
Für automatischen Zuschnitt, leichte Entzerrung, Rotation und Kontrastkorrektur kann die OpenCV-Pipeline aktiviert werden.
|
Für automatischen Zuschnitt, leichte Entzerrung, Rotation und Kontrastkorrektur kann die OpenCV-Pipeline aktiviert werden.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 -m venv .venv-opencv
|
npm run setup:opencv
|
||||||
. .venv-opencv/bin/activate
|
|
||||||
pip install -r requirements-opencv.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Konfiguration:
|
Konfiguration:
|
||||||
@@ -69,8 +67,11 @@ Konfiguration:
|
|||||||
FEDEO_SCAN_POSTPROCESS=true
|
FEDEO_SCAN_POSTPROCESS=true
|
||||||
FEDEO_SCAN_POSTPROCESS_PROFILE=receipt
|
FEDEO_SCAN_POSTPROCESS_PROFILE=receipt
|
||||||
FEDEO_SCAN_POSTPROCESS_PYTHON=/pfad/zum/agent/.venv-opencv/bin/python
|
FEDEO_SCAN_POSTPROCESS_PYTHON=/pfad/zum/agent/.venv-opencv/bin/python
|
||||||
|
FEDEO_SCAN_POSTPROCESS_STRICT=false
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Wenn `FEDEO_SCAN_POSTPROCESS_PYTHON` leer bleibt, verwendet der Agent automatisch `.venv-opencv/bin/python`, sofern diese Umgebung existiert. Falls OpenCV nicht installiert ist und `FEDEO_SCAN_POSTPROCESS_STRICT=false` gesetzt ist, lädt der Agent den Rohscan hoch, statt den Auftrag komplett fehlschlagen zu lassen.
|
||||||
|
|
||||||
Profile:
|
Profile:
|
||||||
|
|
||||||
- `receipt`: Bons und schmale Belege werden bevorzugt hochkant zugeschnitten und kontrastiert.
|
- `receipt`: Bons und schmale Belege werden bevorzugt hochkant zugeschnitten und kontrastiert.
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"dev": "tsx src/main.ts",
|
"dev": "tsx src/main.ts",
|
||||||
"start": "node dist/main.js"
|
"start": "node dist/main.js",
|
||||||
|
"setup:opencv": "sh scripts/setup-opencv.sh"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
26
agents/fedeo-device-agent/scripts/setup-opencv.sh
Normal file
26
agents/fedeo-device-agent/scripts/setup-opencv.sh
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||||
|
AGENT_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||||
|
VENV_DIR="${FEDEO_SCAN_POSTPROCESS_VENV:-$AGENT_DIR/.venv-opencv}"
|
||||||
|
PYTHON_BIN="${PYTHON:-python3}"
|
||||||
|
|
||||||
|
echo "FEDEO OpenCV-Umgebung wird vorbereitet"
|
||||||
|
echo "Agent: $AGENT_DIR"
|
||||||
|
echo "Venv: $VENV_DIR"
|
||||||
|
|
||||||
|
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
|
||||||
|
echo "Fehler: $PYTHON_BIN wurde nicht gefunden." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$PYTHON_BIN" -m venv "$VENV_DIR"
|
||||||
|
"$VENV_DIR/bin/python" -m pip install --upgrade pip
|
||||||
|
"$VENV_DIR/bin/python" -m pip install -r "$AGENT_DIR/requirements-opencv.txt"
|
||||||
|
"$VENV_DIR/bin/python" -c "import cv2, PIL, numpy; print('OpenCV OK')"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Fertig. Verwende in .env:"
|
||||||
|
echo "FEDEO_SCAN_POSTPROCESS=true"
|
||||||
|
echo "FEDEO_SCAN_POSTPROCESS_PYTHON=$VENV_DIR/bin/python"
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
import os from "node:os"
|
import os from "node:os"
|
||||||
|
import { existsSync } from "node:fs"
|
||||||
|
import { fileURLToPath } from "node:url"
|
||||||
import { AgentConfig } from "./types.js"
|
import { AgentConfig } from "./types.js"
|
||||||
import { loadDotEnv } from "./env.js"
|
import { loadDotEnv } from "./env.js"
|
||||||
|
|
||||||
|
const currentFile = fileURLToPath(import.meta.url)
|
||||||
|
const agentRoot = path.resolve(path.dirname(currentFile), "..")
|
||||||
|
|
||||||
const optional = (value: string | undefined) => {
|
const optional = (value: string | undefined) => {
|
||||||
const trimmed = value?.trim()
|
const trimmed = value?.trim()
|
||||||
return trimmed ? trimmed : undefined
|
return trimmed ? trimmed : undefined
|
||||||
@@ -30,6 +35,11 @@ const postprocessProfileFromEnv = (value: string | undefined): AgentConfig["post
|
|||||||
return "document"
|
return "document"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultPostprocessPython = () => {
|
||||||
|
const localVenvPython = path.join(agentRoot, ".venv-opencv", "bin", "python")
|
||||||
|
return existsSync(localVenvPython) ? localVenvPython : "python3"
|
||||||
|
}
|
||||||
|
|
||||||
export const loadConfig = (): AgentConfig => {
|
export const loadConfig = (): AgentConfig => {
|
||||||
loadDotEnv(process.env.FEDEO_AGENT_ENV || ".env")
|
loadDotEnv(process.env.FEDEO_AGENT_ENV || ".env")
|
||||||
|
|
||||||
@@ -52,6 +62,7 @@ export const loadConfig = (): AgentConfig => {
|
|||||||
scanSource: optional(process.env.FEDEO_SCAN_SOURCE),
|
scanSource: optional(process.env.FEDEO_SCAN_SOURCE),
|
||||||
scanPostprocess: booleanFromEnv(process.env.FEDEO_SCAN_POSTPROCESS, false),
|
scanPostprocess: booleanFromEnv(process.env.FEDEO_SCAN_POSTPROCESS, false),
|
||||||
postprocessProfile: postprocessProfileFromEnv(process.env.FEDEO_SCAN_POSTPROCESS_PROFILE),
|
postprocessProfile: postprocessProfileFromEnv(process.env.FEDEO_SCAN_POSTPROCESS_PROFILE),
|
||||||
postprocessPython: optional(process.env.FEDEO_SCAN_POSTPROCESS_PYTHON) || "python3",
|
postprocessPython: optional(process.env.FEDEO_SCAN_POSTPROCESS_PYTHON) || defaultPostprocessPython(),
|
||||||
|
postprocessStrict: booleanFromEnv(process.env.FEDEO_SCAN_POSTPROCESS_STRICT, false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import path from "node:path"
|
|||||||
import { AgentConfig, ScanJob, ScanResult } from "../types.js"
|
import { AgentConfig, ScanJob, ScanResult } from "../types.js"
|
||||||
import { commandExists, runCommand } from "../commands.js"
|
import { commandExists, runCommand } from "../commands.js"
|
||||||
import { hasOpenCvPostprocessRuntime, postprocessScan } from "./postprocess.js"
|
import { hasOpenCvPostprocessRuntime, postprocessScan } from "./postprocess.js"
|
||||||
|
import { log } from "../logger.js"
|
||||||
|
|
||||||
const mimeTypes = {
|
const mimeTypes = {
|
||||||
pdf: "application/pdf",
|
pdf: "application/pdf",
|
||||||
@@ -51,6 +52,12 @@ const ensureFilenameExtension = (filename: string, format: AgentConfig["scanForm
|
|||||||
return `${filename.slice(0, -ext.length)}${expectedExt}`
|
return `${filename.slice(0, -ext.length)}${expectedExt}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fallbackRawResult = (scanOutputPath: string, jobId: string): ScanResult => ({
|
||||||
|
path: scanOutputPath,
|
||||||
|
filename: `${jobId}.raw.png`,
|
||||||
|
mimeType: "image/png",
|
||||||
|
})
|
||||||
|
|
||||||
export const hasSane = () => commandExists("scanimage")
|
export const hasSane = () => commandExists("scanimage")
|
||||||
|
|
||||||
export const listScanners = async () => {
|
export const listScanners = async () => {
|
||||||
@@ -111,10 +118,27 @@ export const runScan = async (config: AgentConfig, job: ScanJob): Promise<ScanRe
|
|||||||
|
|
||||||
if (shouldPostprocess) {
|
if (shouldPostprocess) {
|
||||||
if (!await hasOpenCvPostprocessRuntime(config)) {
|
if (!await hasOpenCvPostprocessRuntime(config)) {
|
||||||
throw new Error("OpenCV-Nachbearbeitung ist aktiviert, aber python3 mit cv2, Pillow und numpy ist nicht verfügbar")
|
const message = "OpenCV-Nachbearbeitung ist aktiviert, aber python3 mit cv2, Pillow und numpy ist nicht verfügbar"
|
||||||
|
if (config.postprocessStrict) throw new Error(message)
|
||||||
|
|
||||||
|
log.warn(`${message}. Rohscan wird ohne Korrektur hochgeladen.`, {
|
||||||
|
jobId: job.id,
|
||||||
|
python: config.postprocessPython,
|
||||||
|
})
|
||||||
|
return fallbackRawResult(scanOutputPath, job.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
return await postprocessScan(config, scanOutputPath, filename, format, postprocessProfile)
|
return await postprocessScan(config, scanOutputPath, filename, format, postprocessProfile)
|
||||||
|
} catch (error) {
|
||||||
|
if (config.postprocessStrict) throw error
|
||||||
|
|
||||||
|
log.warn("OpenCV-Nachbearbeitung fehlgeschlagen. Rohscan wird ohne Korrektur hochgeladen.", {
|
||||||
|
jobId: job.id,
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
})
|
||||||
|
return fallbackRawResult(scanOutputPath, job.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export type AgentConfig = {
|
|||||||
scanPostprocess: boolean
|
scanPostprocess: boolean
|
||||||
postprocessProfile: "document" | "receipt" | "raw"
|
postprocessProfile: "document" | "receipt" | "raw"
|
||||||
postprocessPython: string
|
postprocessPython: string
|
||||||
|
postprocessStrict: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentHeartbeat = {
|
export type AgentHeartbeat = {
|
||||||
|
|||||||
Reference in New Issue
Block a user