From bf63663bf180de084526fa1f3d025993f7a0e651 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Tue, 18 Nov 2025 09:23:13 -0500 Subject: [PATCH 1/7] add more speed divisions to playback rate, update volume to have dispaly of percentage --- .../components/ControlsContainer.vue | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/client/dive-common/components/ControlsContainer.vue b/client/dive-common/components/ControlsContainer.vue index 186c02233..615ba6a61 100644 --- a/client/dive-common/components/ControlsContainer.vue +++ b/client/dive-common/components/ControlsContainer.vue @@ -58,7 +58,7 @@ export default defineComponent({ setup(_, { emit }) { const handler = useHandler(); const currentView = ref('Detections'); - const ticks = ref([0.25, 0.5, 0.75, 1.0, 2.0, 4.0, 8.0]); + const ticks = ref([0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0, 6.0, 7.0, 8.0]); const cameraStore = useCameraStore(); const multiCam = ref(cameraStore.camMap.value.size > 1); const selectedCamera = useSelectedCamera(); @@ -319,7 +319,6 @@ export default defineComponent({ open-on-hover close-delay="500" open-delay="250" - rounded="pill" > - + + + {{ volume * 100 }}% + + @@ -366,20 +369,22 @@ export default defineComponent({ - + - + + {{ speed }}x + From 39f92ec8261677d1c2af6ead05f8f3c552a51bbb Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Tue, 18 Nov 2025 10:20:33 -0500 Subject: [PATCH 2/7] only convert when there is media to convert --- client/platform/desktop/frontend/components/Recent.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/platform/desktop/frontend/components/Recent.vue b/client/platform/desktop/frontend/components/Recent.vue index 07e747f60..d3aeabf36 100644 --- a/client/platform/desktop/frontend/components/Recent.vue +++ b/client/platform/desktop/frontend/components/Recent.vue @@ -105,7 +105,6 @@ export default defineComponent({ importing.value = true; await request(async () => { const conversionArgs = await api.finalizeImport(args); - api.convert(conversionArgs); pendingImportPayload.value = null; // close dialog if (conversionArgs.mediaList.length === 0) { router.push({ @@ -113,6 +112,8 @@ export default defineComponent({ params: { id: conversionArgs.meta.id }, }); } else { + // Queue conversion job + api.convert(conversionArgs); // Display new data and await transcoding to complete const recentsMeta = await api.loadMetadata(conversionArgs.meta.id); setRecents(recentsMeta); From 9b3b067fd7285be85a4198a71c7822b5ba97ad3e Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Tue, 18 Nov 2025 11:45:42 -0500 Subject: [PATCH 3/7] if image will not decode, convert to large image --- client/src/components/annotators/ImageAnnotator.vue | 8 +++++++- server/dive_tasks/tasks.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/components/annotators/ImageAnnotator.vue b/client/src/components/annotators/ImageAnnotator.vue index 7128c9c75..c81fd3849 100644 --- a/client/src/components/annotators/ImageAnnotator.vue +++ b/client/src/components/annotators/ImageAnnotator.vue @@ -330,7 +330,13 @@ export default defineComponent({ if (local.imgs.length) { const imgInternal = cacheFrame(0); - imgInternal.onloadPromise.then(() => { + imgInternal.onloadPromise.then(async () => { + try { + await imgInternal.image.decode(); + } catch (error) { + emit('large-image-warning', true); + return; + } initializeViewer(imgInternal.image.naturalWidth, imgInternal.image.naturalHeight); const quadFeatureLayer = geoViewer.value.createLayer('feature', { features: ['quad'], diff --git a/server/dive_tasks/tasks.py b/server/dive_tasks/tasks.py index 609ffd1f3..d93202fe5 100644 --- a/server/dive_tasks/tasks.py +++ b/server/dive_tasks/tasks.py @@ -677,7 +677,7 @@ def convert_large_images(self: Task, folderId, user_id: str, user_login: str): ] for item in items_to_convert: # Assumes 1 file per item - gc.post(f'/item/{item["_id"]}/tiles') + gc.post(f'item/{item["_id"]}/tiles') gc.addMetadataToFolder( str(folderId), {"type": constants.LargeImageType}, # mark the parent folder as able to annotate. From 847e0af39221b4011d1b4d5c0801dbb6b6bb6def Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Tue, 18 Nov 2025 14:23:02 -0500 Subject: [PATCH 4/7] fix error with items that are already large images --- server/dive_tasks/tasks.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/server/dive_tasks/tasks.py b/server/dive_tasks/tasks.py index d93202fe5..6e5257065 100644 --- a/server/dive_tasks/tasks.py +++ b/server/dive_tasks/tasks.py @@ -648,7 +648,7 @@ def convert_images(self: Task, folderId, user_id: str, user_login: str): command = ["ffmpeg", "-i", str(item_path), str(new_item_path)] utils.stream_subprocess(self, context, manager, {'args': command}) gc.uploadFileToFolder(folderId, new_item_path) - gc.delete(f"item/{item['_id']}") + gc.delete(f"item/{str(item['_id'])}") gc.addMetadataToFolder( str(folderId), @@ -677,7 +677,12 @@ def convert_large_images(self: Task, folderId, user_id: str, user_login: str): ] for item in items_to_convert: # Assumes 1 file per item - gc.post(f'item/{item["_id"]}/tiles') + try: + gc.get(f'item/{str(item["_id"])}/tiles') + manager.write(f'Skipping {item["name"]}, already a large image\n') + continue + except Exception: + gc.post(f'item/{str(item["_id"])}/tiles') gc.addMetadataToFolder( str(folderId), {"type": constants.LargeImageType}, # mark the parent folder as able to annotate. From 4bd803db40d658ac5053c670b301e6425e06f28b Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 20 Nov 2025 13:45:23 -0500 Subject: [PATCH 5/7] fix loading OSM issues with referencing external URLs --- client/src/components/annotators/LargeImageAnnotator.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/annotators/LargeImageAnnotator.vue b/client/src/components/annotators/LargeImageAnnotator.vue index 73e81062a..ff1d96013 100644 --- a/client/src/components/annotators/LargeImageAnnotator.vue +++ b/client/src/components/annotators/LargeImageAnnotator.vue @@ -370,7 +370,7 @@ export default defineComponent({ resp2.tileWidth, resp2.tileHeight, ); - local.nextLayer = geoViewer.value.createLayer('osm', newParams.layer); + local.nextLayer = geoViewer.value.createLayer('osm', { ...localParams, ...newParams.layer }); local.nextLayer._options.maxLevel = newParams.layer.maxLevel; local.nextLayer._options.tileWidth = newParams.layer.tileWidth; local.nextLayer._options.tileHeight = newParams.layer.tileWidth; From 12262cea969369e372877bfbc60779b2e3c1b84a Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Thu, 20 Nov 2025 13:45:41 -0500 Subject: [PATCH 6/7] add notification so that reloading prompt occurs after conversion --- server/dive_server/crud_rpc.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server/dive_server/crud_rpc.py b/server/dive_server/crud_rpc.py index f02261bb5..1d93be936 100644 --- a/server/dive_server/crud_rpc.py +++ b/server/dive_server/crud_rpc.py @@ -719,3 +719,10 @@ def convert_large_image( newjob.job[constants.JOBCONST_PRIVATE_QUEUE] = job_is_private newjob.job[constants.JOBCONST_DATASET_ID] = dsFolder["_id"] Job().save(newjob.job) + Notification().createNotification( + type='job_status', + data=newjob.job, + user=user, + expires=datetime.now() + timedelta(seconds=30), + ) + From e9d359ed39ebe29164ddd3b8620d6257c3794c15 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Fri, 21 Nov 2025 08:10:16 -0500 Subject: [PATCH 7/7] more precise exception logic for large image conversion --- server/dive_tasks/tasks.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/server/dive_tasks/tasks.py b/server/dive_tasks/tasks.py index 6e5257065..659f88658 100644 --- a/server/dive_tasks/tasks.py +++ b/server/dive_tasks/tasks.py @@ -11,7 +11,7 @@ import zipfile from GPUtil import getGPUs -from girder_client import GirderClient +from girder_client import GirderClient, HttpError from girder_worker.app import app from girder_worker.task import Task from girder_worker.utils import JobManager, JobStatus @@ -678,11 +678,24 @@ def convert_large_images(self: Task, folderId, user_id: str, user_login: str): for item in items_to_convert: # Assumes 1 file per item try: - gc.get(f'item/{str(item["_id"])}/tiles') + # Does it already have tiles? + gc.get(f'item/{item["_id"]}/tiles') manager.write(f'Skipping {item["name"]}, already a large image\n') continue - except Exception: - gc.post(f'item/{str(item["_id"])}/tiles') + except HttpError as e: + # Safely parse JSON if possible + message = "" + try: + message = e.response.json().get("message", "") + except Exception: + pass # non-JSON response, leave message empty + # This is the Girder message when no large image exists + if e.status == 400 and message == "No large image file in this item.": + manager.write(f'Converting {item["name"]} to large image\n') + gc.post(f'item/{item["_id"]}/tiles') + else: + # Re-raise unexpected errors to fail the job + raise gc.addMetadataToFolder( str(folderId), {"type": constants.LargeImageType}, # mark the parent folder as able to annotate.