feat(v2): add multiple extra features from the pdf slides

This commit is contained in:
2026-07-09 01:22:02 +03:30
parent ae240e7ac1
commit 20a43d5c9a
16 changed files with 1003 additions and 239 deletions

View File

@@ -1,10 +1,12 @@
import time
import numpy as np
from django.conf import settings
from django.utils import timezone
from .algorithms import ProcessingError, decode_image, histogram, process_image
from .models import ImageSession, ProcessingJob
from .algorithms import ProcessingError, average_images, decode_image, histogram, histogram_payload, normalize_to_uint8, process_image, verify_registration
from .models import ImageSession, ImageState, ProcessingJob
from .registry import apply_registered_operation
from .storage import load_image_array, payload_for_image, save_image_array
from .tasks import run_batch_job
@@ -25,19 +27,145 @@ def image_session_create(*, uploaded_file=None, image_base64=None):
original_histogram=hist,
expires_at=timezone.now() + timezone.timedelta(hours=settings.IMAGE_SESSION_TTL_HOURS),
)
state = image_state_create(
session=session,
parent=None,
image=image,
operation="upload",
params={},
label="S0 Upload",
prefix="state-upload",
)
payload = {
"session_id": str(session.id),
"active_state_id": str(state.id),
"width": session.width,
"height": session.height,
"channels": session.channels,
"color_mode": session.color_mode,
"original_histogram": hist,
"histogram": state.histogram,
"expires_at": session.expires_at.isoformat(),
"states": [image_state_payload(state=state, include_image=True)],
}
payload.update(payload_for_image(image, relative_path))
return payload
def image_state_create(*, session, parent, image, operation, params, label=None, prefix="state"):
relative_path = save_image_array(image, prefix)
sequence = session.states.count()
state = ImageState.objects.create(
session=session,
parent=parent,
sequence=sequence,
label=label or f"S{sequence} {operation}",
operation=operation,
params=params or {},
image=relative_path,
width=image.shape[1],
height=image.shape[0],
channels=image.shape[2] if image.ndim == 3 else 1,
color_mode="RGB" if image.ndim == 3 else "L",
histogram=histogram_payload(image),
)
return state
def image_state_payload(*, state, include_image=True):
payload = {
"state_id": str(state.id),
"session_id": str(state.session_id),
"parent_state_id": str(state.parent_id) if state.parent_id else None,
"sequence": state.sequence,
"label": state.label,
"operation": state.operation,
"params": state.params,
"width": state.width,
"height": state.height,
"channels": state.channels,
"color_mode": state.color_mode,
"histogram": state.histogram,
"created_at": state.created_at.isoformat(),
}
if include_image:
image = load_image_array(state.image)
payload.update(payload_for_image(image, state.image))
else:
payload["image_path"] = state.image
payload["image_url"] = f"{settings.MEDIA_URL}{state.image}"
return payload
def image_states_payload(*, states):
return [image_state_payload(state=state, include_image=True) for state in states]
def image_state_apply_operation(*, state, operation, params):
if state.session.expired:
raise ProcessingError("Image session has expired.")
source = load_image_array(state.image)
result = apply_registered_operation(source, operation, params or {})
new_state = image_state_create(
session=state.session,
parent=state,
image=result,
operation=operation,
params=params or {},
label=None,
prefix=f"state-{operation}",
)
return image_state_payload(state=new_state, include_image=True)
def combine_states(*, states, operation, params=None):
params = params or {}
if len(states) < 2:
raise ProcessingError("At least two states are required.")
session = states[0].session
if any(state.session_id != session.id for state in states):
raise ProcessingError("All states must belong to the same session.")
images = [load_image_array(state.image) for state in states]
verify_registration(images)
if operation == "average":
result = average_images(images)
elif operation == "add":
result = np_clip_sum(images)
elif operation == "subtract":
result = normalize_to_uint8(images[0].astype("float32") - images[1].astype("float32"))
elif operation == "dot_product":
result = normalize_to_uint8(np.prod([image.astype("float32") / 255.0 for image in images], axis=0))
elif operation == "and":
result = images[0].copy()
for image in images[1:]:
result = result & image
elif operation == "or":
result = images[0].copy()
for image in images[1:]:
result = result | image
else:
raise ProcessingError(f"Unsupported combine operation '{operation}'.")
new_state = image_state_create(
session=session,
parent=states[0],
image=result,
operation=f"combine_{operation}",
params={**params, "state_ids": [str(state.id) for state in states]},
label=None,
prefix=f"state-combine-{operation}",
)
return image_state_payload(state=new_state, include_image=True)
def np_clip_sum(images):
total = np.zeros_like(images[0], dtype="float32")
for image in images:
total += image.astype("float32")
return np.clip(total, 0, 255).astype("uint8")
def image_session_process(*, session, operation, params):
if session.expired:
raise ProcessingError("Image session has expired.")