bbox/polygon durch selections ersetzen
- bbox und polygon Felder in Directus versteckt (Daten bleiben) - Alle Auswahlen laufen nur noch über das selections-Feld - CanvasObject, DirectusObject, API und Zeichenlogik umgestellt - Objekte mit mehreren Auswahlen werden korrekt auf Canvas gezeichnet Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
8
app.py
8
app.py
@@ -79,7 +79,7 @@ def directus_objects():
|
|||||||
token = request.headers.get("Authorization", "")
|
token = request.headers.get("Authorization", "")
|
||||||
if request.method == "GET":
|
if request.method == "GET":
|
||||||
picture_id = request.args.get("picture_id", "")
|
picture_id = request.args.get("picture_id", "")
|
||||||
fields = "id,bbox,polygon,selections,user_notes,parent,status,picture"
|
fields = "id,selections,user_notes,parent,status,picture"
|
||||||
path = f"/items/objects?filter[picture][_eq]={picture_id}&fields={fields}&sort=date_created"
|
path = f"/items/objects?filter[picture][_eq]={picture_id}&fields={fields}&sort=date_created"
|
||||||
data, status = _directus("GET", path, token)
|
data, status = _directus("GET", path, token)
|
||||||
return jsonify(data), status
|
return jsonify(data), status
|
||||||
@@ -771,8 +771,6 @@ def crop_image():
|
|||||||
"id": obj_id,
|
"id": obj_id,
|
||||||
"created_at": timestamp,
|
"created_at": timestamp,
|
||||||
"source_filename": data["filename"],
|
"source_filename": data["filename"],
|
||||||
"mode": processed_selections[0]["mode"], # Legacy-Feld
|
|
||||||
"bbox": processed_selections[0]["bbox"], # Legacy-Feld
|
|
||||||
"image_file": out_image_name,
|
"image_file": out_image_name,
|
||||||
"hierarchy": 1,
|
"hierarchy": 1,
|
||||||
"parent_id": None,
|
"parent_id": None,
|
||||||
@@ -780,10 +778,8 @@ def crop_image():
|
|||||||
"position_de": data.get("position_de", ""),
|
"position_de": data.get("position_de", ""),
|
||||||
"action_de": data.get("action_de", ""),
|
"action_de": data.get("action_de", ""),
|
||||||
"condition_de": data.get("condition_de", ""),
|
"condition_de": data.get("condition_de", ""),
|
||||||
"selections": processed_selections, # Neue Feld: alle Auswahlen nummeriert
|
"selections": processed_selections,
|
||||||
}
|
}
|
||||||
if processed_selections[0]["mode"] == "polygon":
|
|
||||||
meta["polygon"] = processed_selections[0]["polygon"] # Legacy-Feld
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Legacy-Format: einzelne Auswahl (für Rückwärtskompatibilität)
|
# Legacy-Format: einzelne Auswahl (für Rückwärtskompatibilität)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function directusAssetUrl(mediaId: string, token: string): string {
|
|||||||
return `${DIRECTUS_URL}/assets/${mediaId}?access_token=${token}`
|
return `${DIRECTUS_URL}/assets/${mediaId}?access_token=${token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
import type { DirectusObject, BBox, Point } from './types'
|
import type { DirectusObject, Selection } from './types'
|
||||||
|
|
||||||
export async function getDirectusObjects(pictureId: string, token: string): Promise<DirectusObject[]> {
|
export async function getDirectusObjects(pictureId: string, token: string): Promise<DirectusObject[]> {
|
||||||
const res = await fetch(`/api/directus/objects?picture_id=${pictureId}`, {
|
const res = await fetch(`/api/directus/objects?picture_id=${pictureId}`, {
|
||||||
@@ -45,9 +45,7 @@ export async function getDirectusObjects(pictureId: string, token: string): Prom
|
|||||||
|
|
||||||
export async function createDirectusObject(payload: {
|
export async function createDirectusObject(payload: {
|
||||||
picture: string
|
picture: string
|
||||||
bbox: BBox | null
|
selections: Selection[] | null
|
||||||
polygon: Point[] | null
|
|
||||||
selections: import('./types').Selection[] | null
|
|
||||||
user_notes: string | null
|
user_notes: string | null
|
||||||
parent: string | null
|
parent: string | null
|
||||||
}, token: string): Promise<DirectusObject> {
|
}, token: string): Promise<DirectusObject> {
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ export default forwardRef<DrawCanvasHandle, Props>(function DrawCanvas(
|
|||||||
ctx.lineWidth = isSelected ? 3 : 2
|
ctx.lineWidth = isSelected ? 3 : 2
|
||||||
ctx.setLineDash(isSelected ? [2, 2] : [4, 3])
|
ctx.setLineDash(isSelected ? [2, 2] : [4, 3])
|
||||||
|
|
||||||
const { polygon, bbox } = obj
|
const drawShape = (sel: { mode: string; bbox?: { x: number; y: number; width: number; height: number } | null; polygon?: { x: number; y: number }[] | null }) => {
|
||||||
|
const { polygon, bbox } = sel as { polygon?: { x: number; y: number }[] | null; bbox?: { x: number; y: number; width: number; height: number } | null }
|
||||||
if (polygon && polygon.length >= 3) {
|
if (polygon && polygon.length >= 3) {
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
|
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
|
||||||
@@ -93,33 +94,29 @@ export default forwardRef<DrawCanvasHandle, Props>(function DrawCanvas(
|
|||||||
ctx.fillRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
|
ctx.fillRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
|
||||||
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
|
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sel of obj.selections ?? []) drawShape(sel)
|
||||||
|
|
||||||
// White highlight ring for selected object
|
// White highlight ring for selected object
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
ctx.strokeStyle = '#ffffff'
|
ctx.strokeStyle = '#ffffff'
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2
|
||||||
ctx.setLineDash([])
|
ctx.setLineDash([])
|
||||||
if (polygon && polygon.length >= 3) {
|
for (const sel of obj.selections ?? []) drawShape(sel)
|
||||||
ctx.beginPath()
|
|
||||||
ctx.moveTo(polygon[0].x * scale, polygon[0].y * scale)
|
|
||||||
for (let i = 1; i < polygon.length; i++) ctx.lineTo(polygon[i].x * scale, polygon[i].y * scale)
|
|
||||||
ctx.closePath()
|
|
||||||
ctx.stroke()
|
|
||||||
} else if (bbox) {
|
|
||||||
ctx.strokeRect(bbox.x * scale, bbox.y * scale, bbox.width * scale, bbox.height * scale)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index number in object center
|
// Index number: center of first selection
|
||||||
const indexLabel = typeof obj.index === 'number' ? String(obj.index) : ''
|
const indexLabel = typeof obj.index === 'number' ? String(obj.index) : ''
|
||||||
if (indexLabel) {
|
if (indexLabel) {
|
||||||
|
const first = obj.selections?.[0] as { polygon?: { x: number; y: number }[] | null; bbox?: { x: number; y: number; width: number; height: number } | null } | undefined
|
||||||
let cx = 0, cy = 0
|
let cx = 0, cy = 0
|
||||||
if (bbox) {
|
if (first?.bbox) {
|
||||||
cx = (bbox.x + bbox.width / 2) * scale
|
cx = (first.bbox.x + first.bbox.width / 2) * scale
|
||||||
cy = (bbox.y + bbox.height / 2) * scale
|
cy = (first.bbox.y + first.bbox.height / 2) * scale
|
||||||
} else if (polygon && polygon.length > 0) {
|
} else if (first?.polygon && first.polygon.length > 0) {
|
||||||
const xs = polygon.map(p => p.x)
|
const xs = first.polygon.map((p: { x: number; y: number }) => p.x)
|
||||||
const ys = polygon.map(p => p.y)
|
const ys = first.polygon.map((p: { x: number; y: number }) => p.y)
|
||||||
cx = ((Math.min(...xs) + Math.max(...xs)) / 2) * scale
|
cx = ((Math.min(...xs) + Math.max(...xs)) / 2) * scale
|
||||||
cy = ((Math.min(...ys) + Math.max(...ys)) / 2) * scale
|
cy = ((Math.min(...ys) + Math.max(...ys)) / 2) * scale
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ export default function DrawIt() {
|
|||||||
const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({
|
const canvasObjects: CanvasObject[] = objects.map((obj, i) => ({
|
||||||
id: obj.id,
|
id: obj.id,
|
||||||
visible: obj.visible !== false,
|
visible: obj.visible !== false,
|
||||||
bbox: obj.bbox,
|
selections: obj.selections,
|
||||||
polygon: obj.polygon,
|
|
||||||
index: i + 1,
|
index: i + 1,
|
||||||
hierarchy: 1,
|
hierarchy: 1,
|
||||||
}))
|
}))
|
||||||
@@ -88,11 +87,8 @@ export default function DrawIt() {
|
|||||||
if (!currentPicture || !token || currentSelections.length === 0) return
|
if (!currentPicture || !token || currentSelections.length === 0) return
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
const first = currentSelections[0]
|
|
||||||
const obj = await createDirectusObject({
|
const obj = await createDirectusObject({
|
||||||
picture: currentPicture.id,
|
picture: currentPicture.id,
|
||||||
bbox: first.mode === 'rect' ? (first.bbox ?? null) : null,
|
|
||||||
polygon: first.mode === 'polygon' ? (first.polygon ?? null) : null,
|
|
||||||
selections: currentSelections,
|
selections: currentSelections,
|
||||||
user_notes: userNotes.trim() || null,
|
user_notes: userNotes.trim() || null,
|
||||||
parent: parentId,
|
parent: parentId,
|
||||||
@@ -182,7 +178,7 @@ export default function DrawIt() {
|
|||||||
/>
|
/>
|
||||||
<div className="object-item-text">
|
<div className="object-item-text">
|
||||||
<strong>Objekt {i + 1}</strong>
|
<strong>Objekt {i + 1}</strong>
|
||||||
<span>{obj.bbox ? `Rect ${Math.round(obj.bbox.width)}×${Math.round(obj.bbox.height)}` : obj.polygon ? `Poly ${obj.polygon.length} Pkt.` : '–'}</span>
|
<span>{obj.selections?.length ? `${obj.selections.length} Auswahl(en)` : '–'}</span>
|
||||||
{obj.parent && <span style={{ color: 'var(--primary-muted-fg)', fontSize: 11 }}>↳ Kind von #{objects.findIndex(o => o.id === obj.parent) + 1}</span>}
|
{obj.parent && <span style={{ color: 'var(--primary-muted-fg)', fontSize: 11 }}>↳ Kind von #{objects.findIndex(o => o.id === obj.parent) + 1}</span>}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ export interface Sentence {
|
|||||||
export interface CanvasObject {
|
export interface CanvasObject {
|
||||||
id: string
|
id: string
|
||||||
visible?: boolean
|
visible?: boolean
|
||||||
bbox?: BBox | null
|
selections?: Selection[] | null
|
||||||
polygon?: Point[] | null
|
|
||||||
hierarchy?: number
|
hierarchy?: number
|
||||||
index?: number
|
index?: number
|
||||||
}
|
}
|
||||||
@@ -40,8 +39,6 @@ export interface DirectusObject {
|
|||||||
id: string
|
id: string
|
||||||
status: string
|
status: string
|
||||||
picture: string
|
picture: string
|
||||||
bbox: BBox | null
|
|
||||||
polygon: Point[] | null
|
|
||||||
selections: Selection[] | null
|
selections: Selection[] | null
|
||||||
user_notes: string | null
|
user_notes: string | null
|
||||||
parent: string | null
|
parent: string | null
|
||||||
|
|||||||
Reference in New Issue
Block a user