From ed7f534b3fe658ed5dd6e141ac7d45590093021d Mon Sep 17 00:00:00 2001 From: yasin_yari Date: Fri, 9 Sep 2022 14:31:25 +0200 Subject: [PATCH 1/5] add instance segmentation support --- exporters/spline_segmentation_exporter.py | 86 +++++++++++++++++++---- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/exporters/spline_segmentation_exporter.py b/exporters/spline_segmentation_exporter.py index 200e190..eee0b5b 100644 --- a/exporters/spline_segmentation_exporter.py +++ b/exporters/spline_segmentation_exporter.py @@ -17,6 +17,8 @@ class SplineSegmentationExporterForm(forms.Form): path = forms.CharField(label='Storage path', max_length=1000) delete_existing_data = forms.BooleanField(label='Delete any existing data at storage path', initial=False, required=False) + json_annotations = forms.BooleanField(label='Export JSON annotations for instance segmentation', initial=False, + required=False) def __init__(self, task, data=None): super().__init__(data) @@ -24,6 +26,39 @@ def __init__(self, task, data=None): queryset=Subject.objects.filter(dataset__task=task)) +def create_json(coord, image_size, filename): + """This function creates a JSON dictionary according to labelme format""" + + data = [] + coordinates = coord + + for i in range(len(coordinates)): + if i % 2 == 0: + data.append( + { + "label": str(coordinates[i + 1]), + "points": coordinates[i], + "group_id": 'null', + "shape_type": "polygon", + "flags": {}, + } + ) + + json_dict = { + "version": "4.5.6", + "flags": {}, + "shapes": data, + "imagePath": os.path.basename(os.path.normpath(filename[:-7])) + '.png', + "imageData": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAF/AVEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0f", + "imageHeight": image_size[0], + "imageWidth": image_size[1] + + } + + return json_dict + + + class SplineSegmentationExporter(Exporter): """ asdads @@ -37,6 +72,7 @@ def get_form(self, data=None): def export(self, form): delete_existing_data = form.cleaned_data['delete_existing_data'] + json_annotations = form.cleaned_data['json_annotations'] # Create dir, delete old if it exists path = form.cleaned_data['path'] if delete_existing_data: @@ -51,11 +87,11 @@ def export(self, form): # Create folder if it doesn't exist create_folder(path) - self.add_subjects_to_path(path, form.cleaned_data['subjects']) + self.add_subjects_to_path(path, form.cleaned_data['subjects'], json_annotations) return True, path - def add_subjects_to_path(self, path, data): + def add_subjects_to_path(self, path, data, json_annotations): # For each subject for subject in data: @@ -89,14 +125,14 @@ def add_subjects_to_path(self, path, data): image_pil = PIL.Image.open(new_filename) image_size = image_pil.size spacing = [1, 1] - self.save_segmentation(frame, image_size, join(subject_subfolder, target_gt_name), spacing) + self.save_segmentation(frame, image_size, join(subject_subfolder, target_gt_name), spacing, json_annotations) return True, path def get_object_segmentation(self, image_size, frame): segmentation = np.zeros(image_size, dtype=np.uint8) tension = 0.5 - + xy_unique = [] labels = Label.objects.filter(task=frame.image_annotation.task).order_by('id') counter = 1 for label in labels: @@ -104,6 +140,7 @@ def get_object_segmentation(self, image_size, frame): for object in objects: previous_x = None previous_y = None + xy = [] control_points = ControlPoint.objects.filter(label=label, image=frame, object=object.object).order_by('index') max_index = len(control_points) for i in range(max_index): @@ -152,26 +189,45 @@ def get_object_segmentation(self, image_size, frame): previous_x = x previous_y = y + xy.append(previous_x) + xy.append(previous_y) + segmentation[y, x] = counter + xy = [xy[j:j + 2] for j in range(0, len(xy), 2)] + xy_unique.append(xy) + xy_unique.append(a.label) + + coordinates = [] + [coordinates.append(x) for x in xy_unique if x not in coordinates] + # Fill the hole segmentation[binary_fill_holes(segmentation == counter)] = counter counter += 1 - return segmentation + return segmentation, coordinates + - def save_segmentation(self, frame, image_size, filename, spacing): + def save_segmentation(self, frame, image_size, filename, spacing, json_annotations): image_size = [image_size[1], image_size[0]] # Create compounded segmentation object - segmentation = self.get_object_segmentation(image_size, frame) - - segmentation_mhd = MetaImage(data=segmentation) - segmentation_mhd.set_attribute('ImageQuality', frame.image_annotation.image_quality) - segmentation_mhd.set_spacing(spacing) - metadata = ImageMetadata.objects.filter(image=frame.image_annotation.image) - for item in metadata: - segmentation_mhd.set_attribute(item.name, item.value) - segmentation_mhd.write(filename) + segmentation, coords = self.get_object_segmentation(image_size, frame) + + if json_annotations: + json_dict = create_json(coords, image_size, filename) + with open(filename[:-7] + '.json', "w") as f: + print("The json file is created") + jason_str = json.dumps(json_dict) + f.write(jason_str) + + else: + segmentation_mhd = MetaImage(data=segmentation) + segmentation_mhd.set_attribute('ImageQuality', frame.image_annotation.image_quality) + segmentation_mhd.set_spacing(spacing) + metadata = ImageMetadata.objects.filter(image=frame.image_annotation.image) + for item in metadata: + segmentation_mhd.set_attribute(item.name, item.value) + segmentation_mhd.write(filename) From d2d38162638a4b6ba761cb3b00c8a08204124b28 Mon Sep 17 00:00:00 2001 From: yasin_yari Date: Thu, 29 Sep 2022 11:05:35 +0200 Subject: [PATCH 2/5] Dynamic image data support and bug fix --- exporters/spline_segmentation_exporter.py | 46 ++++++++++++++++++----- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/exporters/spline_segmentation_exporter.py b/exporters/spline_segmentation_exporter.py index eee0b5b..7934fde 100644 --- a/exporters/spline_segmentation_exporter.py +++ b/exporters/spline_segmentation_exporter.py @@ -12,7 +12,10 @@ import json from scipy.ndimage.morphology import binary_fill_holes import PIL - +import io +import codecs +from PIL import Image +import base64 class SplineSegmentationExporterForm(forms.Form): path = forms.CharField(label='Storage path', max_length=1000) @@ -26,7 +29,27 @@ def __init__(self, task, data=None): queryset=Subject.objects.filter(dataset__task=task)) -def create_json(coord, image_size, filename): +def img_b64_to_arr(img_b64): + """convert image data to image array""" + + f = io.BytesIO() + f.write(base64.b64decode(img_b64)) + img_arr = np.array(PIL.Image.open(f)) + return img_arr + +def img_arr_to_b64(img_arr): + """convert image array to image data (base 64) according to labelme format""" + + img_pil = Image.open(img_arr) + f = io.BytesIO() + img_pil.save(f, format='PNG') + data = f.getvalue() + encData = codecs.encode(data, 'base64').decode() + encData = encData.replace('\n', '') + return encData + + +def create_json(coord, image_size, filename, image_data): """This function creates a JSON dictionary according to labelme format""" data = [] @@ -49,7 +72,7 @@ def create_json(coord, image_size, filename): "flags": {}, "shapes": data, "imagePath": os.path.basename(os.path.normpath(filename[:-7])) + '.png', - "imageData": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAF/AVEDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0f", + "imageData": image_data, "imageHeight": image_size[0], "imageWidth": image_size[1] @@ -132,9 +155,11 @@ def add_subjects_to_path(self, path, data, json_annotations): def get_object_segmentation(self, image_size, frame): segmentation = np.zeros(image_size, dtype=np.uint8) tension = 0.5 - xy_unique = [] + coordinates = [] labels = Label.objects.filter(task=frame.image_annotation.task).order_by('id') counter = 1 + xy_new_temp =0 + for label in labels: objects = ControlPoint.objects.filter(label=label, image=frame).only('object').distinct() for object in objects: @@ -194,12 +219,12 @@ def get_object_segmentation(self, image_size, frame): segmentation[y, x] = counter - xy = [xy[j:j + 2] for j in range(0, len(xy), 2)] - xy_unique.append(xy) - xy_unique.append(a.label) + xy_new = [xy[j:j + 2] for j in range(0, len(xy), 2)] - coordinates = [] - [coordinates.append(x) for x in xy_unique if x not in coordinates] + if i == max_index-1 and xy_new_temp != xy_new: + coordinates.append(xy_new) + coordinates.append(a.label) + xy_new_temp = xy_new # Fill the hole segmentation[binary_fill_holes(segmentation == counter)] = counter @@ -216,7 +241,8 @@ def save_segmentation(self, frame, image_size, filename, spacing, json_annotatio segmentation, coords = self.get_object_segmentation(image_size, frame) if json_annotations: - json_dict = create_json(coords, image_size, filename) + image_data = img_arr_to_b64(frame.image_annotation.image.format.replace('#', str(frame.frame_nr))) + json_dict = create_json(coords, image_size, filename, image_data) with open(filename[:-7] + '.json', "w") as f: print("The json file is created") jason_str = json.dumps(json_dict) From 894f30955c4b2ad271aedf277f4ecd2b95d17a60 Mon Sep 17 00:00:00 2001 From: Erik Smistad Date: Thu, 29 Sep 2022 12:32:09 +0200 Subject: [PATCH 3/5] Fixed bug with json export not working with mhd data --- exporters/spline_segmentation_exporter.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/exporters/spline_segmentation_exporter.py b/exporters/spline_segmentation_exporter.py index 7934fde..86fcb32 100644 --- a/exporters/spline_segmentation_exporter.py +++ b/exporters/spline_segmentation_exporter.py @@ -40,7 +40,7 @@ def img_b64_to_arr(img_b64): def img_arr_to_b64(img_arr): """convert image array to image data (base 64) according to labelme format""" - img_pil = Image.open(img_arr) + img_pil = Image.fromarray(img_arr) f = io.BytesIO() img_pil.save(f, format='PNG') data = f.getvalue() @@ -241,7 +241,14 @@ def save_segmentation(self, frame, image_size, filename, spacing, json_annotatio segmentation, coords = self.get_object_segmentation(image_size, frame) if json_annotations: - image_data = img_arr_to_b64(frame.image_annotation.image.format.replace('#', str(frame.frame_nr))) + image_filename = frame.image_annotation.image.format.replace('#', str(frame.frame_nr)) + if image_filename.endswith('.mhd'): + image_mhd = MetaImage(filename=image_filename) + image_array = image_mhd.get_pixel_data() + else: + image_pil = PIL.Image.open(image_filename) + image_array = np.asarray(image_pil) + image_data = img_arr_to_b64(image_array) json_dict = create_json(coords, image_size, filename, image_data) with open(filename[:-7] + '.json', "w") as f: print("The json file is created") From cd84794b88dc47773a7842c9c723b4ea13c00845 Mon Sep 17 00:00:00 2001 From: Erik Smistad Date: Thu, 29 Sep 2022 12:32:51 +0200 Subject: [PATCH 4/5] Changed text in json boolean field --- exporters/spline_segmentation_exporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/spline_segmentation_exporter.py b/exporters/spline_segmentation_exporter.py index 86fcb32..f7e4865 100644 --- a/exporters/spline_segmentation_exporter.py +++ b/exporters/spline_segmentation_exporter.py @@ -20,7 +20,7 @@ class SplineSegmentationExporterForm(forms.Form): path = forms.CharField(label='Storage path', max_length=1000) delete_existing_data = forms.BooleanField(label='Delete any existing data at storage path', initial=False, required=False) - json_annotations = forms.BooleanField(label='Export JSON annotations for instance segmentation', initial=False, + json_annotations = forms.BooleanField(label='Export annotations as geoJSON files for instance segmentation', initial=False, required=False) def __init__(self, task, data=None): From 0e82e47a330efb26f8b6efb7d9e56dd3cc04c47e Mon Sep 17 00:00:00 2001 From: yasin_yari Date: Fri, 30 Sep 2022 07:40:25 +0200 Subject: [PATCH 5/5] bug fix to support image_path for mhd files as well --- exporters/spline_segmentation_exporter.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/exporters/spline_segmentation_exporter.py b/exporters/spline_segmentation_exporter.py index f7e4865..9da80dc 100644 --- a/exporters/spline_segmentation_exporter.py +++ b/exporters/spline_segmentation_exporter.py @@ -49,7 +49,7 @@ def img_arr_to_b64(img_arr): return encData -def create_json(coord, image_size, filename, image_data): +def create_json(coord, image_size, image_data, image_path): """This function creates a JSON dictionary according to labelme format""" data = [] @@ -71,7 +71,7 @@ def create_json(coord, image_size, filename, image_data): "version": "4.5.6", "flags": {}, "shapes": data, - "imagePath": os.path.basename(os.path.normpath(filename[:-7])) + '.png', + "imagePath": image_path, "imageData": image_data, "imageHeight": image_size[0], "imageWidth": image_size[1] @@ -242,14 +242,17 @@ def save_segmentation(self, frame, image_size, filename, spacing, json_annotatio if json_annotations: image_filename = frame.image_annotation.image.format.replace('#', str(frame.frame_nr)) + image_path = os.path.basename(os.path.normpath(image_filename)) if image_filename.endswith('.mhd'): image_mhd = MetaImage(filename=image_filename) image_array = image_mhd.get_pixel_data() + else: image_pil = PIL.Image.open(image_filename) image_array = np.asarray(image_pil) + image_data = img_arr_to_b64(image_array) - json_dict = create_json(coords, image_size, filename, image_data) + json_dict = create_json(coords, image_size, image_data, image_path) with open(filename[:-7] + '.json', "w") as f: print("The json file is created") jason_str = json.dumps(json_dict)