Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backend/app/database/face_clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,8 @@ def db_get_images_by_cluster_id(
rows = cursor.fetchall()
conn.close()

from app.utils.images import image_util_parse_metadata

images = []
for row in rows:
(
Expand All @@ -299,7 +301,7 @@ def db_get_images_by_cluster_id(
"image_id": image_id,
"image_path": image_path,
"thumbnail_path": thumbnail_path,
"metadata": metadata,
"metadata": image_util_parse_metadata(metadata),
"face_id": face_id,
"confidence": confidence,
"bbox": bbox,
Expand Down
4 changes: 3 additions & 1 deletion backend/app/database/faces.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ def get_all_face_embeddings():
)
results = cursor.fetchall()

from app.utils.images import image_util_parse_metadata

images_dict = {}
for (
embeddings,
Expand All @@ -184,7 +186,7 @@ def get_all_face_embeddings():
"path": path,
"folder_id": folder_id,
"thumbnailPath": thumbnail_path,
"metadata": metadata,
"metadata": image_util_parse_metadata(metadata),
"isTagged": bool(is_tagged),
"tags": [],
}
Expand Down
71 changes: 52 additions & 19 deletions backend/app/database/images.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Standard library imports
import sqlite3
from typing import List, Tuple, TypedDict
from typing import Any, List, Mapping, Tuple, TypedDict, Union

# App-specific imports
from app.config.settings import (
Expand All @@ -21,15 +21,32 @@ class ImageRecord(TypedDict):
path: ImagePath
folder_id: FolderId
thumbnailPath: str
metadata: str
metadata: Union[Mapping[str, Any], str]
isTagged: bool


class UntaggedImageRecord(TypedDict):
"""Represents an image record returned for tagging."""

id: ImageId
path: ImagePath
folder_id: FolderId
thumbnailPath: str
metadata: Mapping[str, Any]


ImageClassPair = Tuple[ImageId, ClassId]


def db_create_images_table() -> None:
def _connect() -> sqlite3.Connection:
conn = sqlite3.connect(DATABASE_PATH)
# Ensure ON DELETE CASCADE and other FKs are enforced
conn.execute("PRAGMA foreign_keys = ON")
return conn


def db_create_images_table() -> None:
conn = _connect()
cursor = conn.cursor()

# Create new images table with merged fields
Expand Down Expand Up @@ -69,15 +86,23 @@ def db_bulk_insert_images(image_records: List[ImageRecord]) -> bool:
if not image_records:
return True

conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
cursor.executemany(
"""
INSERT OR IGNORE INTO images (id, path, folder_id, thumbnailPath, metadata, isTagged)
INSERT INTO images (id, path, folder_id, thumbnailPath, metadata, isTagged)
VALUES (:id, :path, :folder_id, :thumbnailPath, :metadata, :isTagged)
""",
ON CONFLICT(path) DO UPDATE SET
folder_id=excluded.folder_id,
thumbnailPath=excluded.thumbnailPath,
metadata=excluded.metadata,
isTagged=CASE
WHEN excluded.isTagged THEN 1
ELSE images.isTagged
END
""",
image_records,
)
conn.commit()
Expand All @@ -97,7 +122,7 @@ def db_get_all_images() -> List[dict]:
Returns:
List of dictionaries containing all image data including tags
"""
conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand Down Expand Up @@ -132,18 +157,23 @@ def db_get_all_images() -> List[dict]:
tag_name,
) in results:
if image_id not in images_dict:
# Safely parse metadata JSON -> dict
from app.utils.images import image_util_parse_metadata

metadata_dict = image_util_parse_metadata(metadata)

Comment thread
rohan-pandeyy marked this conversation as resolved.
images_dict[image_id] = {
"id": image_id,
"path": path,
"folder_id": folder_id,
"folder_id": str(folder_id),
"thumbnailPath": thumbnail_path,
"metadata": metadata,
"metadata": metadata_dict,
"isTagged": bool(is_tagged),
"tags": [],
}

# Add tag if it exists
if tag_name:
# Add tag if it exists (avoid duplicates)
if tag_name and tag_name not in images_dict[image_id]["tags"]:
images_dict[image_id]["tags"].append(tag_name)

# Convert to list and set tags to None if empty
Expand All @@ -165,7 +195,7 @@ def db_get_all_images() -> List[dict]:
conn.close()


def db_get_untagged_images() -> List[ImageRecord]:
def db_get_untagged_images() -> List[UntaggedImageRecord]:
"""
Find all images that need AI tagging.
Returns images where:
Expand All @@ -175,7 +205,7 @@ def db_get_untagged_images() -> List[ImageRecord]:
Returns:
List of dictionaries containing image data: id, path, folder_id, thumbnailPath, metadata
"""
conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand All @@ -193,13 +223,16 @@ def db_get_untagged_images() -> List[ImageRecord]:

untagged_images = []
for image_id, path, folder_id, thumbnail_path, metadata in results:
from app.utils.images import image_util_parse_metadata

md = image_util_parse_metadata(metadata)
untagged_images.append(
{
"id": image_id,
"path": path,
"folder_id": folder_id,
"folder_id": str(folder_id) if folder_id is not None else None,
"thumbnailPath": thumbnail_path,
"metadata": metadata,
"metadata": md,
}
)

Expand All @@ -220,7 +253,7 @@ def db_update_image_tagged_status(image_id: ImageId, is_tagged: bool = True) ->
Returns:
True if update was successful, False otherwise
"""
conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand Down Expand Up @@ -251,7 +284,7 @@ def db_insert_image_classes_batch(image_class_pairs: List[ImageClassPair]) -> bo
if not image_class_pairs:
return True

conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand Down Expand Up @@ -287,7 +320,7 @@ def db_get_images_by_folder_ids(
if not folder_ids:
return []

conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand Down Expand Up @@ -323,7 +356,7 @@ def db_delete_images_by_ids(image_ids: List[ImageId]) -> bool:
if not image_ids:
return True

conn = sqlite3.connect(DATABASE_PATH)
conn = _connect()
cursor = conn.cursor()

try:
Expand Down
4 changes: 2 additions & 2 deletions backend/app/routes/face_clusters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import uuid
import os
from typing import Optional, List
from typing import Optional, List, Dict, Any
from pydantic import BaseModel
from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL
from fastapi import APIRouter, HTTPException, status
Expand Down Expand Up @@ -42,7 +42,7 @@ class ImageData(BaseModel):
path: str
folder_id: str
thumbnailPath: str
metadata: str
metadata: Dict[str, Any]
isTagged: bool
tags: Optional[List[str]] = None
bboxes: BoundingBox
Expand Down
18 changes: 16 additions & 2 deletions backend/app/routes/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,32 @@
from typing import List, Optional
from app.database.images import db_get_all_images
from app.schemas.images import ErrorResponse
from app.utils.images import image_util_parse_metadata
from pydantic import BaseModel

router = APIRouter()


# Response Models
class MetadataModel(BaseModel):
name: str
date_created: Optional[str]
width: int
height: int
file_location: str
file_size: int
item_type: str
latitude: Optional[float] = None
longitude: Optional[float] = None
location: Optional[str] = None


class ImageData(BaseModel):
id: str
path: str
folder_id: str
thumbnailPath: str
metadata: str
metadata: MetadataModel
isTagged: bool
tags: Optional[List[str]] = None

Expand Down Expand Up @@ -42,7 +56,7 @@ def get_all_images():
path=image["path"],
folder_id=image["folder_id"],
thumbnailPath=image["thumbnailPath"],
metadata=image["metadata"],
metadata=image_util_parse_metadata(image["metadata"]),
isTagged=image["isTagged"],
tags=image["tags"],
)
Expand Down
4 changes: 2 additions & 2 deletions backend/app/schemas/face_clusters.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pydantic import BaseModel
from typing import List, Optional, Dict, Union
from typing import List, Optional, Dict, Union, Any


# Request Models
Expand Down Expand Up @@ -50,7 +50,7 @@ class ImageInCluster(BaseModel):
id: str
path: str
thumbnailPath: Optional[str] = None
metadata: Optional[str] = None
metadata: Optional[Dict[str, Any]] = None
face_id: int
confidence: Optional[float] = None
bbox: Optional[Dict[str, Union[int, float]]] = None
Expand Down
Loading
Loading