Skip to content

Commit a89e45f

Browse files
authored
Import mask merge (#317)
* restructure folder output for maskGenerate for ease of creating masks.zip * logic paramemters naming and remove additive * front-end support for mask logic options * front-end support for mask logic options * increment version
1 parent d363d56 commit a89e45f

11 files changed

Lines changed: 79 additions & 47 deletions

File tree

client/dive-common/apispec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ interface Api {
170170
openFromDisk(datasetType: DatasetType | 'calibration' | 'annotation' | 'text' | 'zip', directory?: boolean):
171171
Promise<{canceled?: boolean; filePaths: string[]; fileList?: File[]; root?: string}>;
172172
importAnnotationFile(id: string, path: string, file?: File,
173-
additive?: boolean, additivePrepend?: string): Promise<boolean>;
173+
additive?: boolean, additivePrepend?: string, maskLogic?: 'replace' | 'merge'): Promise<boolean>;
174174
}
175175
const ApiSymbol = Symbol('api');
176176

client/dive-common/components/ImportAnnotations.vue

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { defineComponent, ref } from 'vue';
2+
import { defineComponent, Ref, ref } from 'vue';
33
import { useApi } from 'dive-common/apispec';
44
import { usePrompt } from 'dive-common/vue-utilities/prompt-service';
55
import { useHandler } from 'vue-media-annotator/provides';
@@ -37,6 +37,8 @@ export default defineComponent({
3737
const menuOpen = ref(false);
3838
const additive = ref(false);
3939
const additivePrepend = ref('');
40+
const maskActions = ref(['replace', 'merge']);
41+
const maskActionSelection: Ref<'replace' | 'merge'> = ref('merge');
4042
const openUpload = async () => {
4143
try {
4244
const ret = await openFromDisk('annotation');
@@ -52,6 +54,7 @@ export default defineComponent({
5254
ret.fileList[0],
5355
additive.value,
5456
additivePrepend.value,
57+
maskActionSelection.value,
5558
);
5659
} else {
5760
importFile = await importAnnotationFile(
@@ -60,6 +63,7 @@ export default defineComponent({
6063
undefined,
6164
additive.value,
6265
additivePrepend.value,
66+
maskActionSelection.value,
6367
);
6468
}
6569
@@ -91,6 +95,8 @@ export default defineComponent({
9195
menuOpen,
9296
additive,
9397
additivePrepend,
98+
maskActionSelection,
99+
maskActions,
94100
95101
};
96102
},
@@ -183,6 +189,14 @@ export default defineComponent({
183189
clearable
184190
/>
185191
</div>
192+
<v-row>
193+
<v-select
194+
v-model="maskActionSelection"
195+
:items="maskActions"
196+
label="Mask Import Action"
197+
hide-details
198+
/>
199+
</v-row>
186200
</v-col>
187201
</v-container>
188202
</v-card>

client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dive-dsa",
3-
"version": "1.11.16",
3+
"version": "1.11.17",
44
"author": {
55
"name": "Kitware, Inc.",
66
"email": "Bryon.Lewis@kitware.com"

client/platform/web-girder/api/dataset.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function makeViameFolder({
112112
);
113113
}
114114

115-
async function importAnnotationFile(parentId: string, path: string, file?: HTMLFile, additive = false, additivePrepend = '') {
115+
async function importAnnotationFile(parentId: string, path: string, file?: HTMLFile, additive = false, additivePrepend = '', maskLogic: 'replace' | 'merge' = 'merge') {
116116
if (file === undefined) {
117117
return false;
118118
}
@@ -138,7 +138,7 @@ async function importAnnotationFile(parentId: string, path: string, file?: HTMLF
138138
if (path.endsWith('.json') || path.endsWith('.csv')) {
139139
skipJobs = true;
140140
}
141-
const final = await postProcess(parentId, skipJobs, false, additive, additivePrepend);
141+
const final = await postProcess(parentId, skipJobs, false, additive, additivePrepend, maskLogic);
142142
return final.status === 200;
143143
}
144144
}

client/platform/web-girder/api/rpc.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import girderRest from 'platform/web-girder/plugins/girder';
22
import type { GirderJob } from '@girder/components/src';
33

4-
function postProcess(folderId: string, skipJobs = false, skipTranscoding = false, additive = false, additivePrepend = '') {
4+
function postProcess(folderId: string, skipJobs = false, skipTranscoding = false, additive = false, additivePrepend = '', maskLogic: 'replace' | 'merge' = 'merge') {
55
return girderRest.post(`dive_rpc/postprocess/${folderId}`, null, {
66
params: {
7-
skipJobs, skipTranscoding, additive, additivePrepend,
7+
skipJobs, skipTranscoding, additive, additivePrepend, maskLogic,
88
},
99
});
1010
}

dive-dsa-slicer/example-docker-containers/SAM2Demo/SAM2Demo.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,11 @@ def process_masks_folder(
359359
folderId: str,
360360
masks_path: Path,
361361
subfolder_name='masks',
362-
logic: Literal['replace', 'additive', 'merge'] = 'merge'):
362+
maskLogic: Literal['replace', 'merge'] = 'merge'):
363363
"""
364364
Upload mask images and RLE_MASKS.json file (if available or generate an empty one).
365365
"""
366-
if logic == 'replace':
366+
if maskLogic == 'replace':
367367
folders = list(gc.listFolder(folderId, 'folder', name=subfolder_name))
368368
if len(folders) > 0:
369369
for folder in folders:
@@ -400,7 +400,7 @@ def process_masks_folder(
400400
track_id = track_dir.name
401401
if not has_rle_mask:
402402
rle_masks_json[str(track_id)] = {}
403-
if logic == 'replace':
403+
if maskLogic == 'replace':
404404
# Check if the track already exists
405405
track_folders = list(gc.listFolder(masks_folder['_id'], 'folder', name=track_id))
406406
if len(track_folders) > 0:
@@ -412,7 +412,7 @@ def process_masks_folder(
412412

413413
for image_path in track_dir.glob("*.png"):
414414
frame_number = image_path.stem
415-
if logic == 'merge':
415+
if maskLogic == 'merge':
416416
# Check if the image already exists
417417
existing_items = list(gc.listItem(track_folder['_id'], name=image_path.name))
418418
if len(existing_items) > 0:
@@ -469,15 +469,15 @@ def process_masks_folder(
469469

470470
track_json_path = masks_path / 'TrackJSON.json'
471471
if track_json_path.exists():
472-
# Process the TrackJSON.json file based on the logic parameter
473-
if logic == 'replace':
472+
# Process the TrackJSON.json file based on the maskLogic parameter
473+
if maskLogic == 'replace':
474474
# Replace the existing TrackJSON.json file
475475
gc.uploadFileToFolder(
476476
folderId,
477477
str(track_json_path),
478478
)
479479
gc.post(f'dive_rpc/postprocess/{folderId}', data={"skipJobs": True})
480-
if logic in ['additive', 'merge']:
480+
if maskLogic == 'merge':
481481
with open(track_json_path, 'r') as f:
482482
json_data = json.load(f)
483483
# Get the existing Tracks in the system

scripts/masks/maskTrackGenerator.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# /// script
2+
# requires-python = ">=3.13"
3+
# dependencies = [
4+
# "click",
5+
# "girder-client",
6+
# "numpy",
7+
# "pillow",
8+
# "pycocotools",
9+
# "scikit-image",
10+
# "setuptools"
11+
# ]
12+
# ///
113
import json
214
import random
315
import numpy as np
@@ -19,9 +31,9 @@
1931
SHAPE_SIZE = (100, 100) # width, height of shape
2032

2133
CONFIG = [
22-
{'track_number': 0, 'num_frames': 500, 'mask_type': 'pentagon', 'motion_type': 'circle'},
23-
{'track_number': 1, 'num_frames': 300, 'mask_type': 'circle', 'motion_type': 'bounce'},
24-
{'track_number': 2, 'num_frames': 200, 'mask_type': 'rectangle', 'motion_type': 'circle'},
34+
{'track_number': 2, 'num_frames': 500, 'mask_type': 'pentagon', 'motion_type': 'circle'},
35+
{'track_number': 3, 'num_frames': 300, 'mask_type': 'circle', 'motion_type': 'bounce'},
36+
{'track_number': 4, 'num_frames': 200, 'mask_type': 'rectangle', 'motion_type': 'circle'},
2537
]
2638

2739

@@ -99,10 +111,10 @@ def bouncing_motion(num_frames, img_size, shape_size):
99111
@click.option('--upload', is_flag=True, help='Upload generated masks to Girder.')
100112
def generate_tracks(upload):
101113
output_masks_exist = os.path.exists('outputMasks')
102-
rle_json_exist = os.path.exists('RLEMask.json')
114+
rle_json_exist = os.path.exists('RLE_MASKS.json')
103115

104116
if output_masks_exist and rle_json_exist:
105-
print("Found existing outputMasks folder and RLEMask.json. Skipping generation.")
117+
print("Found existing outputMasks folder and RLE_MASKS.json. Skipping generation.")
106118
else:
107119
print("Generating new masks and JSON files...")
108120

@@ -173,7 +185,7 @@ def generate_tracks(upload):
173185
}
174186

175187
if OUTPUT_MASKS:
176-
output_dir = os.path.join('outputMasks', f'{track_id}')
188+
output_dir = os.path.join('outputMasks/masks', f'{track_id}')
177189
os.makedirs(output_dir, exist_ok=True)
178190

179191
alpha_channel = (mask > 0).astype(np.uint8) * 255
@@ -187,18 +199,22 @@ def generate_tracks(upload):
187199

188200
track_data['tracks'][str(track_id)] = track
189201

190-
with open('trackJSON.json', 'w') as f:
202+
with open('TrackJSON.json', 'w') as f:
191203
json.dump(track_data, f, indent=2)
192204

193-
with open('RLEMask.json', 'w') as f:
194-
json.dump(mask_data, f, indent=2)
195205

196206
with open('RLE_MASKS.json', 'w') as f:
197207
json.dump(rle_masks_json, f, indent=2)
198208

199-
print('Generated trackJSON.json, RLEMask.json, RLE_MASKS.json', end='')
209+
print('Generated TrackJSON.json, RLE_MASKS.json', end='')
200210
if OUTPUT_MASKS:
201211
print(', and PNG masks in outputMasks/')
212+
with open('outputMasks/masks/TrackJSON.json', 'w') as f:
213+
json.dump(track_data, f, indent=2)
214+
215+
with open('outputMasks/masks/RLE_MASKS.json', 'w') as f:
216+
json.dump(rle_masks_json, f, indent=2)
217+
202218
else:
203219
print('.')
204220

server/dive_server/crud_rpc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def postprocess(
201201
skipTranscoding=False,
202202
additive=False,
203203
additivePrepend='',
204-
logic='replace',
204+
maskLogic='merge',
205205
) -> dict:
206206
"""
207207
Post-processing to be run after media/annotation import
@@ -251,7 +251,7 @@ def postprocess(
251251
itemId=str(item["_id"]),
252252
user_id=str(user["_id"]),
253253
user_login=str(user["login"]),
254-
logic=logic,
254+
maskLogic=maskLogic,
255255
girder_job_title=f"Extracting {item['_id']} to folder {str(dsFolder['_id'])}",
256256
girder_client_token=str(token["_id"]),
257257
girder_job_type="private" if job_is_private else "convert",

server/dive_server/views_rpc.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,26 +84,25 @@ def __init__(self, resourceName):
8484
required=False,
8585
)
8686
.param(
87-
"logic",
87+
"maskLogic",
8888
"Logic to use when combining annotations using Zip files (specifically masks.zip). \
8989
'replace' will replace existing annotations with new ones. \
90-
'merge' will combine existing and new annotations. \
91-
'additive' will add new annotations to existing ones.",
90+
'merge' will combine existing and new annotations.",
9291
paramType="formData",
9392
dataType="string",
9493
default='merge',
9594
required=False,
9695
)
9796
)
98-
def postprocess(self, folder, skipJobs, skipTranscoding, additive, additivePrepend, logic):
97+
def postprocess(self, folder, skipJobs, skipTranscoding, additive, additivePrepend, maskLogic):
9998
result = crud_rpc.postprocess(
10099
self.getCurrentUser(),
101100
folder,
102101
skipJobs,
103102
skipTranscoding,
104103
additive,
105104
additivePrepend,
106-
logic,
105+
maskLogic,
107106
)
108107
# Return the folder for backward compatibility, but also include job_ids
109108
return result

server/dive_tasks/sam_tasks.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,12 @@ def process_masks_folder(
391391
folderId: str,
392392
masks_path: Path,
393393
subfolder_name='masks',
394-
logic: Literal['replace', 'additive', 'merge'] = 'merge',
394+
maskLogic: Literal['replace', 'merge'] = 'merge',
395395
):
396396
"""
397397
Upload mask images and RLE_MASKS.json file (if available or generate an empty one).
398398
"""
399-
if logic == 'replace':
399+
if maskLogic == 'replace':
400400
folders = list(gc.listFolder(folderId, 'folder', name=subfolder_name))
401401
if len(folders) > 0:
402402
for folder in folders:
@@ -433,7 +433,7 @@ def process_masks_folder(
433433
track_id = track_dir.name
434434
if not has_rle_mask:
435435
rle_masks_json[str(track_id)] = {}
436-
if logic == 'replace':
436+
if maskLogic == 'replace':
437437
# Check if the track already exists
438438
track_folders = list(gc.listFolder(masks_folder['_id'], 'folder', name=track_id))
439439
if len(track_folders) > 0:
@@ -445,7 +445,7 @@ def process_masks_folder(
445445

446446
for image_path in track_dir.glob("*.png"):
447447
frame_number = image_path.stem
448-
if logic == 'merge':
448+
if maskLogic == 'merge':
449449
# Check if the image already exists
450450
existing_items = list(gc.listItem(track_folder['_id'], name=image_path.name))
451451
if len(existing_items) > 0:
@@ -506,14 +506,14 @@ def process_masks_folder(
506506
track_json_path = masks_path / 'TrackJSON.json'
507507
if track_json_path.exists():
508508
# Process the TrackJSON.json file based on the logic parameter
509-
if logic == 'replace':
509+
if maskLogic == 'replace':
510510
# Replace the existing TrackJSON.json file
511511
gc.uploadFileToFolder(
512512
folderId,
513513
str(track_json_path),
514514
)
515515
gc.post(f'dive_rpc/postprocess/{folderId}', data={"skipJobs": True})
516-
if logic in ['additive', 'merge']:
516+
if maskLogic == 'merge':
517517
with open(track_json_path, 'r') as f:
518518
json_data = json.load(f)
519519
# Get the existing Tracks in the system

0 commit comments

Comments
 (0)