feat(v4): add docker-compose and production-ready application
This commit is contained in:
@@ -14,6 +14,11 @@ from .tasks import run_batch_job
|
||||
|
||||
|
||||
def image_session_create(*, uploaded_file=None, image_base64=None):
|
||||
"""Create a new image session and its initial S0 upload state.
|
||||
|
||||
The session groups all later processing states so a user can present the full workflow.
|
||||
"""
|
||||
|
||||
if uploaded_file and uploaded_file.size > settings.MAX_UPLOAD_MB * 1024 * 1024:
|
||||
raise ProcessingError(f"Upload exceeds {settings.MAX_UPLOAD_MB} MB.")
|
||||
|
||||
@@ -55,6 +60,11 @@ def image_session_create(*, uploaded_file=None, image_base64=None):
|
||||
|
||||
|
||||
def compact_workspace_image(image):
|
||||
"""Resize large uploads to the configured maximum dimension for faster processing.
|
||||
|
||||
This keeps classroom-sized experiments responsive even when the uploaded file is very large.
|
||||
"""
|
||||
|
||||
max_dimension = int(getattr(settings, "IMAGE_WORKSPACE_MAX_DIMENSION", 1400))
|
||||
if max_dimension <= 0:
|
||||
return image
|
||||
@@ -68,6 +78,11 @@ def compact_workspace_image(image):
|
||||
|
||||
|
||||
def image_state_create(*, session, parent, image, operation, params, label=None, prefix="state"):
|
||||
"""Persist one processed image state with its metadata, parent, and histogram.
|
||||
|
||||
Saving every result makes it possible to compare steps and combine previous states later.
|
||||
"""
|
||||
|
||||
relative_path = save_image_array(image, prefix)
|
||||
max_sequence = session.states.aggregate(value=Max("sequence"))["value"]
|
||||
sequence = 0 if max_sequence is None else max_sequence + 1
|
||||
@@ -89,6 +104,8 @@ def image_state_create(*, session, parent, image, operation, params, label=None,
|
||||
|
||||
|
||||
def image_state_payload(*, state, include_image=True):
|
||||
"""Serialize an image state for the frontend workspace."""
|
||||
|
||||
payload = {
|
||||
"state_id": str(state.id),
|
||||
"session_id": str(state.session_id),
|
||||
@@ -114,10 +131,17 @@ def image_state_payload(*, state, include_image=True):
|
||||
|
||||
|
||||
def image_states_payload(*, states):
|
||||
"""Serialize a list of image states."""
|
||||
|
||||
return [image_state_payload(state=state, include_image=True) for state in states]
|
||||
|
||||
|
||||
def image_state_delete(*, state):
|
||||
"""Delete a non-S0 state while keeping child states available.
|
||||
|
||||
Use it to remove unhelpful experiments without losing later useful results.
|
||||
"""
|
||||
|
||||
if state.sequence == 0 or state.operation == "upload":
|
||||
raise ProcessingError("The original S0 upload state cannot be deleted.")
|
||||
image_path = state.image
|
||||
@@ -127,6 +151,11 @@ def image_state_delete(*, state):
|
||||
|
||||
|
||||
def image_state_apply_operation(*, state, operation, params):
|
||||
"""Apply one registered algorithm to a selected state and save the result as a new state.
|
||||
|
||||
This is the main workspace action: every filter or transform becomes a reproducible step.
|
||||
"""
|
||||
|
||||
if state.session.expired:
|
||||
raise ProcessingError("Image session has expired.")
|
||||
source = load_image_array(state.image)
|
||||
@@ -144,6 +173,11 @@ def image_state_apply_operation(*, state, operation, params):
|
||||
|
||||
|
||||
def combine_states(*, states, operation, params=None):
|
||||
"""Combine registered states using add, subtract, dot product, average, and/or.
|
||||
|
||||
Use it for MATLAB-like image arithmetic, change detection, masking, and K-image denoising.
|
||||
"""
|
||||
|
||||
params = params or {}
|
||||
if len(states) < 2:
|
||||
raise ProcessingError("At least two states are required.")
|
||||
@@ -185,6 +219,11 @@ def combine_states(*, states, operation, params=None):
|
||||
|
||||
|
||||
def np_clip_sum(images):
|
||||
"""Add several registered images and clip the result to [0, 255].
|
||||
|
||||
Use it to combine brightness/detail contributions while keeping the output displayable.
|
||||
"""
|
||||
|
||||
total = np.zeros_like(images[0], dtype="float32")
|
||||
for image in images:
|
||||
total += image.astype("float32")
|
||||
@@ -192,6 +231,11 @@ def np_clip_sum(images):
|
||||
|
||||
|
||||
def image_session_process(*, session, operation, params):
|
||||
"""Run a legacy single-image operation against the original session image.
|
||||
|
||||
This keeps the older API working while the state workspace handles the main presentation flow.
|
||||
"""
|
||||
|
||||
if session.expired:
|
||||
raise ProcessingError("Image session has expired.")
|
||||
|
||||
@@ -217,12 +261,19 @@ def image_session_process(*, session, operation, params):
|
||||
|
||||
|
||||
def batch_job_create(*, operation, session_ids, params=None):
|
||||
"""Create a Celery-backed processing job for heavier batch operations.
|
||||
|
||||
Use it when an operation may take longer than an interactive request should block.
|
||||
"""
|
||||
|
||||
job = ProcessingJob.objects.create(operation=operation, params=params or {})
|
||||
run_batch_job.delay(str(job.id), operation, [str(session_id) for session_id in session_ids])
|
||||
return job
|
||||
|
||||
|
||||
def processing_job_payload(*, job):
|
||||
"""Serialize a processing job, including result image data when available."""
|
||||
|
||||
payload = {
|
||||
"job_id": str(job.id),
|
||||
"operation": job.operation,
|
||||
|
||||
Reference in New Issue
Block a user