Skip to content

Commit

Permalink
Fix/image procssing status (#386)
Browse files Browse the repository at this point in the history
* fix: add state image processing started to already image uploaded state

* fix: change updated time to be same as created at

* fix: add state for image processing to already present url

* refactor: add funcitonality to start image processing manually

* refactor: remove upload button

* style: add bg color for IMAGE_UPLOADED state

* fix: resolved image processing issues, state management for color, and dashboard listing

---------

Co-authored-by: Bijay Rauniyar <[email protected]>
Co-authored-by: Pradip-p <[email protected]>
  • Loading branch information
3 people authored Dec 13, 2024
1 parent fad365d commit c507709
Show file tree
Hide file tree
Showing 15 changed files with 282 additions and 117 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""change task events state
Revision ID: e23c05f21542
Revises: 8ae4e43a7011
Create Date: 2024-12-06 08:00:16.223517
"""

from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = "e23c05f21542"
down_revision: Union[str, None] = "8ae4e43a7011"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

old_state_enum = sa.Enum(
"REQUEST_FOR_MAPPING",
"UNLOCKED_TO_MAP",
"LOCKED_FOR_MAPPING",
"UNLOCKED_TO_VALIDATE",
"LOCKED_FOR_VALIDATION",
"UNLOCKED_DONE",
"UNFLYABLE_TASK",
"IMAGE_UPLOADED",
"IMAGE_PROCESSED",
"IMAGE_PROCESSING_FAILED",
name="state",
)

new_state_enum = sa.Enum(
"REQUEST_FOR_MAPPING",
"UNLOCKED_TO_MAP",
"LOCKED_FOR_MAPPING",
"UNLOCKED_TO_VALIDATE",
"LOCKED_FOR_VALIDATION",
"UNLOCKED_DONE",
"UNFLYABLE_TASK",
"IMAGE_UPLOADED",
"IMAGE_PROCESSING_FAILED",
"IMAGE_PROCESSING_STARTED",
"IMAGE_PROCESSING_FINISHED",
name="state_new",
)


def upgrade():
# Step 1: Create the new enum type
new_state_enum.create(op.get_bind())

# Step 2: Add a new column with the new enum type
op.add_column("task_events", sa.Column("new_state", new_state_enum, nullable=True))

# Step 3: Populate the new state column with the transformed data
op.execute(
"""
UPDATE task_events
SET new_state =
CASE
WHEN state = 'IMAGE_PROCESSED' THEN 'IMAGE_PROCESSING_FINISHED'
ELSE state::text
END::state_new
"""
)

# Step 4: Drop the old state column
op.drop_column("task_events", "state")

# Step 5: Rename the new_state column to state
op.alter_column("task_events", "new_state", new_column_name="state")

# Step 6: Drop the old enum type
op.execute("DROP TYPE state;")

# Step 7: Rename the new enum type to state
op.execute("ALTER TYPE state_new RENAME TO state;")

## then add the image processing started state to all the image uploaded file
op.execute("""
WITH added_image_processing_started AS (
SELECT gen_random_uuid() AS event_id,
task_id,
project_id,
user_id,
created_at + INTERVAL '10 seconds' AS created_at,
comment,
created_at + INTERVAL '10 seconds' AS updated_at,
'IMAGE_PROCESSING_STARTED'::state AS state
FROM task_events WHERE state = 'IMAGE_UPLOADED'
)
INSERT INTO task_events (event_id, task_id, project_id, user_id, created_at, comment, updated_at, state)
SELECT event_id, task_id, project_id, user_id, created_at, comment, updated_at, state
FROM added_image_processing_started;
""")


def downgrade():
op.execute("DELETE from task_events WHERE state = 'IMAGE_PROCESSING_STARTED';")
# Step 1: Rename the new enum type back to the old name
op.execute("ALTER TYPE state RENAME TO state_new;")

# Step 2: Create the old enum type again (assuming you have the definition of the old enum type)
# You would need to define the old state enum type here, e.g.:
old_state_enum.create(op.get_bind())

# Step 3: Add the old state column with the old enum type
op.add_column("task_events", sa.Column("state_old", old_state_enum, nullable=True))

# Step 4: Populate the old state column with the transformed data
op.execute(
"""
UPDATE task_events
SET state_old =
CASE
WHEN state = 'IMAGE_PROCESSING_FINISHED' THEN 'IMAGE_PROCESSED'
ELSE state::text
END::state
"""
)

# Step 5: Drop the new_state column
op.drop_column("task_events", "state")
op.alter_column("task_events", "state_old", new_column_name="state")

# Step 6: Drop the new enum type
op.execute("DROP TYPE state_new;")
7 changes: 5 additions & 2 deletions src/backend/app/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,9 @@ class State(int, Enum):
UNLOCKED_DONE = 4
UNFLYABLE_TASK = 5
IMAGE_UPLOADED = 6
IMAGE_PROCESSED = 7
IMAGE_PROCESSING_FAILED = 8
IMAGE_PROCESSING_FAILED = 7
IMAGE_PROCESSING_STARTED = 8
IMAGE_PROCESSING_FINISHED = 9


class EventType(str, Enum):
Expand All @@ -178,6 +179,7 @@ class EventType(str, Enum):
- ``comment`` -- Keep the state the same, but simply add a comment.
- ``unlock`` -- Unlock a task state by unlocking it if it's locked.
- ``image_upload`` -- Set the state to *image uploaded* when the task image is uploaded.
- ``image_processing_start`` -- Set the state to *image processing started* when the image processing is started by user.
Note that ``task_id`` must be specified in the endpoint too.
"""
Expand All @@ -194,3 +196,4 @@ class EventType(str, Enum):
COMMENT = "comment"
UNLOCK = "unlock"
IMAGE_UPLOAD = "image_upload"
IMAGE_PROCESSING_START = "image_processing_start"
6 changes: 3 additions & 3 deletions src/backend/app/projects/image_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def process_images_from_s3(self, bucket_name, name=None, options=[], webhook=Non
self.user_id,
"Task completed.",
State.IMAGE_UPLOADED,
State.IMAGE_PROCESSED,
State.IMAGE_PROCESSING_FINISHED,
timestamp(),
)
return task
Expand Down Expand Up @@ -315,11 +315,11 @@ async def download_and_upload_assets_from_odm_to_s3(
user_id=user_id,
comment=comment,
initial_state=current_state,
final_state=State.IMAGE_PROCESSED,
final_state=State.IMAGE_PROCESSING_FINISHED,
updated_at=timestamp(),
)
log.info(
f"Task {dtm_task_id} state updated to IMAGE_PROCESSED in the database."
f"Task {dtm_task_id} state updated to IMAGE_PROCESSING_FINISHED in the database."
)

except Exception as e:
Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/projects/project_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def get_centroids(db: Connection):
ST_AsGeoJSON(p.centroid)::jsonb AS centroid,
COUNT(t.id) AS total_task_count,
COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) AS ongoing_task_count,
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSED' THEN 1 END) AS completed_task_count
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) AS completed_task_count
FROM
projects p
LEFT JOIN
Expand Down
8 changes: 4 additions & 4 deletions src/backend/app/projects/project_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ async def odm_webhook(
current_state = await task_logic.get_task_state(db, dtm_project_id, dtm_task_id)
current_state_value = State[current_state.get("state")]
match current_state_value:
case State.IMAGE_UPLOADED:
case State.IMAGE_PROCESSING_STARTED:
log.info(
f"Task ID: {task_id}, Status: already IMAGE_UPLOADED - no update needed."
)
Expand All @@ -537,7 +537,7 @@ async def odm_webhook(
dtm_project_id,
dtm_task_id,
dtm_user_id,
State.IMAGE_UPLOADED,
State.IMAGE_PROCESSING_STARTED,
"Task completed.",
)

Expand All @@ -553,7 +553,7 @@ async def odm_webhook(
dtm_project_id,
dtm_task_id,
dtm_user_id,
State.IMAGE_UPLOADED,
State.IMAGE_PROCESSING_FAILED,
"Task completed.",
)

Expand All @@ -572,7 +572,7 @@ async def odm_webhook(
dtm_task_id,
dtm_user_id,
"Image processing failed.",
State.IMAGE_UPLOADED,
State.IMAGE_PROCESSING_STARTED,
State.IMAGE_PROCESSING_FAILED,
timestamp(),
)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/app/projects/project_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ async def all(
COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'REQUEST_FOR_MAPPING', 'IMAGE_UPLOADED', 'UNFLYABLE_TASK') THEN 1 END) AS ongoing_task_count,
-- Count based on the latest state of tasks
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSED' THEN 1 END) AS completed_task_count
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) AS completed_task_count
FROM projects p
LEFT JOIN tasks t ON t.project_id = p.id
Expand Down
40 changes: 38 additions & 2 deletions src/backend/app/tasks/task_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ async def get_task_stats(db: Connection, user_data: AuthUser):
raw_sql = """
SELECT
COUNT(CASE WHEN te.state = 'REQUEST_FOR_MAPPING' THEN 1 END) AS request_logs,
COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'IMAGE_UPLOADED', 'IMAGE_PROCESSING_FAILED') THEN 1 END) AS ongoing_tasks,
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSED' THEN 1 END) AS completed_tasks,
COUNT(CASE WHEN te.state IN ('LOCKED_FOR_MAPPING', 'IMAGE_UPLOADED', 'IMAGE_PROCESSING_STARTED','IMAGE_PROCESSING_FAILED') THEN 1 END) AS ongoing_tasks,
COUNT(CASE WHEN te.state = 'IMAGE_PROCESSING_FINISHED' THEN 1 END) AS completed_tasks,
COUNT(CASE WHEN te.state = 'UNFLYABLE_TASK' THEN 1 END) AS unflyable_tasks
FROM (
Expand Down Expand Up @@ -611,4 +611,40 @@ async def handle_event(
detail.updated_at,
)

case EventType.IMAGE_PROCESSING_START:
current_task_state = await get_task_state(db, project_id, task_id)
if not current_task_state:
raise HTTPException(
status_code=400, detail="Task is not ready for image upload."
)
state = current_task_state.get("state")
locked_user_id = current_task_state.get("user_id")

# Determine error conditions: Current State must be IMAGE_UPLOADED or IMAGE_PROCESSING_FAILED.
if state not in (
State.IMAGE_UPLOADED.name,
State.IMAGE_PROCESSING_FAILED.name,
):
raise HTTPException(
status_code=400,
detail="Task state does not match expected state for image processing to start.",
)

if user_id != locked_user_id:
raise HTTPException(
status_code=403,
detail="You cannot upload an image for this task as it is locked by another user.",
)

return await update_task_state(
db,
project_id,
task_id,
user_id,
f"Task image processing started by user {user_data.name}.",
State.IMAGE_UPLOADED,
State.IMAGE_PROCESSING_STARTED,
detail.updated_at,
)

return True
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,20 @@ const DescriptionBox = () => {
dispatch(resetFilesExifData());
}, [dispatch]);

const { mutate: reStartImageryProcess, isLoading: imageProcessingStarting } =
const { mutate: startImageryProcess, isLoading: imageProcessingStarting } =
useMutation({
mutationFn: () =>
postProcessImagery(projectId as string, taskId as string),
onSuccess: () => {
updateStatus({
projectId,
taskId,
data: { event: 'image_upload', updated_at: new Date().toISOString() },
data: {
event: 'image_processing_start',
updated_at: new Date().toISOString(),
},
});
toast.success('Image processing re-started');
toast.success('Image processing started');
},
});

Expand Down Expand Up @@ -184,28 +187,6 @@ const DescriptionBox = () => {
const handleDownloadResult = () => {
if (!taskAssetsInformation?.assets_url) return;

// fetch(`${taskAssetsInformation?.assets_url}`, { method: 'GET' })
// .then(response => {
// if (!response.ok) {
// throw new Error(`Network response was ${response.statusText}`);
// }
// return response.blob();
// })
// .then(blob => {
// const url = window.URL.createObjectURL(blob);
// const link = document.createElement('a');
// link.href = url;
// link.download = 'assets.zip';
// document.body.appendChild(link);
// link.click();
// link.remove();
// window.URL.revokeObjectURL(url);
// })
// .catch(error =>
// toast.error(`There wan an error while downloading file
// ${error}`),
// );

try {
const link = document.createElement('a');
link.href = taskAssetsInformation?.assets_url;
Expand Down Expand Up @@ -282,14 +263,28 @@ const DescriptionBox = () => {
</Button>
</div>
)}
{taskAssetsInformation?.state === 'IMAGE_UPLOADED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="replay"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Start Processing
</Button>
</div>
)}
{taskAssetsInformation?.state === 'IMAGE_PROCESSING_FAILED' && (
<div className="">
<Button
variant="ghost"
className="naxatw-bg-red naxatw-text-white disabled:!naxatw-cursor-not-allowed disabled:naxatw-bg-gray-500 disabled:naxatw-text-white"
leftIcon="replay"
iconClassname="naxatw-text-[1.125rem]"
onClick={() => reStartImageryProcess()}
onClick={() => startImageryProcess()}
disabled={imageProcessingStarting || statusUpdating}
>
Re-run processing
Expand Down
Loading

0 comments on commit c507709

Please sign in to comment.