Skip to content

Commit

Permalink
storing intermediate volume for dice score computation (#270)
Browse files Browse the repository at this point in the history
* storing intermediate volume for dice score computation for training users

* Use intermediate masks for dice score

---------

Co-authored-by: David Borland <[email protected]>
  • Loading branch information
hyi and davidborland authored Dec 20, 2023
1 parent 61c0cae commit 216721f
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 9 deletions.
7 changes: 4 additions & 3 deletions client/src/utils/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ const getAssignment = async (subvolumeId, itemId) => {
};
};

const getVolumeMask = async volume => {
const getIntermediateVolumeMask = async volume => {
const volumeFiles = await axios.get(`/item/${ volume.id }/files`);
const maskFile = volumeFiles.data.find(({ name }) => name.includes('_masks.tif'));
const maskFile = volumeFiles.data.find(({ name }) => name.includes('_masks_user_intermediate.tif')) ??
volumeFiles.data.find(({ name }) => name.includes('_masks.tif'));

const response = await axios.get(fileUrl(maskFile._id), { responseType: 'arraybuffer' });

Expand All @@ -120,7 +121,7 @@ const getGoldStandard = async volume => {

const getTrainingInfo = async volume => {
const [maskData, goldData] = await Promise.all([
getVolumeMask(volume),
getIntermediateVolumeMask(volume),
getGoldStandard(volume)
]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,10 @@ def save_user_annotation_as_item(user, item_id, done, reject, comment, color, cu
# the assignment is already reviewed and sent back from reviewers for reannotation
selected_for_review = True

if 'training_user' in whole_item['meta']:
# training volume, need to store training user's intermediate work for computing dice score
update_assignment_in_whole_item(whole_item, item_id, mask_file_name=annot_file_name,
intermediate=True)
return {
'annotation_file_name': annot_file_name,
'selected_for_review': selected_for_review,
Expand Down
23 changes: 17 additions & 6 deletions girder/plugins/ninjato_api/girder_ninjato_api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
REVIEW_APPROVE_KEY = 'review_approved'
ASSIGN_COUNT_FOR_REVIEW = 10
TRAINING_KEY = 'training_info'
INTERMEDIATE_SUFFIX = '_user_intermediate.tif'


def flatten(iterable):
Expand Down Expand Up @@ -256,13 +257,16 @@ def _create_region(region_key, whole_item, extent_dict):
return region_item


def update_assignment_in_whole_item(whole_item, assign_item_id, mask_file_name=None):
def update_assignment_in_whole_item(whole_item, assign_item_id, mask_file_name=None,
intermediate=False):
"""
update subvolume whole item mask with updated verified assignment mask
:param whole_item: subvolume whole item to update
:param assign_item_id: assignment item id to update whole subvolume mask with
:param mask_file_name: annotation mask file name to update assignment. If it is None,
annotation mask file name is checked automatically
:param intermediate: whether to store updated assignment mask in whole item as an annotation
mask file with suffix _user_intermediate.tif for training user dice score computation
:return: True if update action succeeds; otherwise, return False
"""
assign_item = Item().findOne({'_id': ObjectId(assign_item_id)})
Expand All @@ -283,6 +287,8 @@ def update_assignment_in_whole_item(whole_item, assign_item_id, mask_file_name=N
for item_file in item_files:
if '_masks' not in item_file['name']:
continue
if not intermediate and item_file['name'].endswith(INTERMEDIATE_SUFFIX):
continue
file_res_path = path_util.getResourcePath('file', item_file, force=True)
file_name = os.path.basename(file_res_path)
whole_tif, whole_path = _get_tif_file_content_and_path(item_file)
Expand All @@ -299,8 +305,13 @@ def update_assignment_in_whole_item(whole_item, assign_item_id, mask_file_name=N
whole_out_tif.write_image(np.copy(image))

assetstore_id = item_file['assetstoreId']
# remove the original file and create new file using updated TIFF mask
File().remove(item_file)
if intermediate and not item_file['name'].endswith(INTERMEDIATE_SUFFIX):
# intermediate user file does not exist yet, set the file name to be stored with
# intermediate file suffix pattern
file_name = f'{os.path.splitext(file_name)[0]}{INTERMEDIATE_SUFFIX}'
else:
# remove the original file and create new file using updated TIFF mask
File().remove(item_file)
save_file(assetstore_id, whole_item, output_path, User().getAdmins()[0], file_name)
return

Expand Down Expand Up @@ -639,7 +650,7 @@ def _update_user_mask(item_id, old_content, old_extent, new_extent=None):
mask_file_name = ''
assetstore_id = ''
for item_file in item_files:
if '_masks' not in item_file['name']:
if '_masks' not in item_file['name'] or item_file['name'].endswith(INTERMEDIATE_SUFFIX):
continue
if item_file['name'].endswith('_user.tif'):
# remove the user mask before writing a new mask to it
Expand Down Expand Up @@ -758,7 +769,7 @@ def remove_region_from_active_assignment(whole_item, assign_item_id, region_id,
item_files = File().find({'itemId': whole_item['_id']})
# compute updated range after removing the region from the user's assignment
for item_file in item_files:
if '_masks' not in item_file['name']:
if '_masks' not in item_file['name'] or item_file['name'].endswith(INTERMEDIATE_SUFFIX):
continue
tif, _ = _get_tif_file_content_and_path(item_file)
images = _get_tif_image_array(tif)
Expand Down Expand Up @@ -1006,7 +1017,7 @@ def save_content_data(user, item_id, content_data, review=False):
annot_file_name = file['name']
assetstore_id = file['assetstoreId']
break
elif '_masks' in file['name']:
elif '_masks' in file['name'] and not file['name'].endswith(INTERMEDIATE_SUFFIX):
mask_file_name = file['name']
if mask_file_name.endswith('_user.tif'):
annot_file_name = mask_file_name
Expand Down

0 comments on commit 216721f

Please sign in to comment.