From ea71d21b3b8f31267fef4e703131a53fa7645620 Mon Sep 17 00:00:00 2001 From: Birm Date: Mon, 6 May 2024 15:19:48 -0400 Subject: [PATCH 1/9] Group sequence starts at 1 not 0 --- DicomAnnotUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DicomAnnotUtils.py b/DicomAnnotUtils.py index 6cd337f..0ed2e3f 100644 --- a/DicomAnnotUtils.py +++ b/DicomAnnotUtils.py @@ -134,7 +134,7 @@ def create_annotation_dicom(annot_arrays, slide_file, geojson): # add the annotation data ds.AnnotationGroupSequence = [] - i = 0 + i = 1 idx = 1 point_indices = [] # make the array first? From 4443686fd90ad4089153ff0aedb69958c587bf3b Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 15 May 2024 12:52:42 -0400 Subject: [PATCH 2/9] split out polyline --- DicomAnnotUtils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DicomAnnotUtils.py b/DicomAnnotUtils.py index 0ed2e3f..25d599b 100644 --- a/DicomAnnotUtils.py +++ b/DicomAnnotUtils.py @@ -382,6 +382,8 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli #print('len(points)', len(points)) if len(points) > 0: newFeature = deepcopy(featureTemplate) + if x.GraphicType == "POLYLINE": + newFeature['geometry']['type'] = "Polyline" newFeature['geometry']['coordinates'].append(points.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] @@ -404,6 +406,8 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli points = np.concatenate((points, [points[0]])) if len(points) > 0: newFeature = deepcopy(featureTemplate) + if x.GraphicType == "POLYLINE": + newFeature['geometry']['type'] = "Polyline" newFeature['geometry']['coordinates'].append(points.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] @@ -424,6 +428,8 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli points = coordinates_array points = np.concatenate((points, [points[0]])) newFeature = deepcopy(featureTemplate) + if x.GraphicType == "POLYLINE": + newFeature['geometry']['type'] = "Polyline" newFeature['geometry']['coordinates'].append(points.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] From c1d167f2b2db076562025794ed764f37befef476 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 15 May 2024 13:40:37 -0400 Subject: [PATCH 3/9] radius tuple typo! --- DicomAnnotUtils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DicomAnnotUtils.py b/DicomAnnotUtils.py index 25d599b..ee109ff 100644 --- a/DicomAnnotUtils.py +++ b/DicomAnnotUtils.py @@ -298,7 +298,7 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli newFeature = deepcopy(featureTemplate) newFeature['geometry']['type'] = "Ellipse" newFeature['geometry']['coordinates'] = [center_x, center_y] - newFeature['geometry']["radius"] = [major_axis_length,minor_axis_length], + newFeature['geometry']["radius"] = [major_axis_length,minor_axis_length] newFeature['geometry']["rotation"] = rotation newFeature['bound']['type'] = "Point" newFeature['bound']['coordinates'] = [center_x, center_y] From dee0fb72395a65d8a3b342a7c0bd0490b9d4acf4 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 15 May 2024 17:53:45 -0400 Subject: [PATCH 4/9] try aiohttp multipart --- SlideServer.py | 36 +++++++++++++++++++++++++++++++++--- requirements.txt | 3 ++- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/SlideServer.py b/SlideServer.py index c321a5a..2ede737 100644 --- a/SlideServer.py +++ b/SlideServer.py @@ -31,6 +31,8 @@ import hashlib from urllib.parse import urlparse from dicomweb_client.api import DICOMwebClient +import aiohttp +import asyncio from DicomAnnotUtils import dicomToCamic import pydicom @@ -775,7 +777,7 @@ def find_referenced_image_by_files(source_url, study, ds): # If the instance is not found in the study return None, None -def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): +def OLDdownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): instance_url = source_url + f"/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}" response = requests.get(instance_url, stream=True) if response.status_code == 200: @@ -788,9 +790,9 @@ def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn) if chunk: if not dicom_started: # Check if the chunk contains "\r\n\r\n"" - if b"\r\n\r\n" in chunk: + start_idx = chunk.find(b"\r\n\r\n") + if start_idx != -1: # Find the position of "\r\n\r\n"" in the chunk - start_idx = chunk.find(b"\r\n\r\n") file.write(chunk[start_idx + 4:]) # Set dicom_started to True dicom_started = True @@ -805,6 +807,34 @@ def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn) else: print(f"Failed to retrieve DICOM instance. Status code: {response.status_code}") +def doDownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): + asyncio.run(downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn)) + +async def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): + instance_url = source_url + f"/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}" + async with aiohttp.ClientSession(trust_env=True) as session: + async with session.get(instance_url) as resp: + reader = aiohttp.MultipartReader.from_response(resp) + metadata = None + filedata = None + while True: + part = await reader.next() + if part is None: + break + else: + #print(dir(part)) + #filedata = await part.read(decode=False) + app.logger.info("Working on file: " + output_fn) + with open(output_fn, 'wb') as fd: + while True: + part_chunk = await part.read_chunk(1024*1024) + if part_chunk is None or len(part_chunk)==0: + break + else: + fd.write(part_chunk) + + + # dicom web based routes def doDicomSlideDownloads(source_url, study, series, instance_list, camic_slide_id): diff --git a/requirements.txt b/requirements.txt index f05d286..761666b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ google-auth-oauthlib pycurl pydicom dicomweb-client -pymongo \ No newline at end of file +pymongo +aiohttp \ No newline at end of file From 50935b3cb333f22201c6d06b73886c7b894ec4fb Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 15 May 2024 17:59:27 -0400 Subject: [PATCH 5/9] runner fcn --- SlideServer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SlideServer.py b/SlideServer.py index 2ede737..59c4929 100644 --- a/SlideServer.py +++ b/SlideServer.py @@ -807,10 +807,10 @@ def OLDdownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_ else: print(f"Failed to retrieve DICOM instance. Status code: {response.status_code}") -def doDownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): +def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): asyncio.run(downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn)) -async def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): +async def doDownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): instance_url = source_url + f"/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}" async with aiohttp.ClientSession(trust_env=True) as session: async with session.get(instance_url) as resp: From eeb96e7f2722ec99caa3bf6e02e2fd780ea47b60 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 15 May 2024 18:00:55 -0400 Subject: [PATCH 6/9] wrong fcn --- SlideServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SlideServer.py b/SlideServer.py index 59c4929..d006cf2 100644 --- a/SlideServer.py +++ b/SlideServer.py @@ -808,7 +808,7 @@ def OLDdownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_ print(f"Failed to retrieve DICOM instance. Status code: {response.status_code}") def downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): - asyncio.run(downloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn)) + asyncio.run(doDownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn)) async def doDownloadRawDicom(source_url, study_uid, series_uid, instance_uid, output_fn): instance_url = source_url + f"/studies/{study_uid}/series/{series_uid}/instances/{instance_uid}" From 134c75baea47d351b4449fa4e8e34ab0b355a016 Mon Sep 17 00:00:00 2001 From: nanli-emory Date: Thu, 30 May 2024 17:18:20 -0400 Subject: [PATCH 7/9] fixed the issue that polyline is closed --- DicomAnnotUtils.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/DicomAnnotUtils.py b/DicomAnnotUtils.py index ee109ff..ee052a0 100644 --- a/DicomAnnotUtils.py +++ b/DicomAnnotUtils.py @@ -378,13 +378,15 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli #print("prev", prevIndex, "idx", idx) # make a thing points = coordinates_array[prevIndex:end_idx, :] - points = np.concatenate((points, [points[0]])) + polygon = np.concatenate((points, [points[0]])) #print('len(points)', len(points)) if len(points) > 0: newFeature = deepcopy(featureTemplate) if x.GraphicType == "POLYLINE": newFeature['geometry']['type'] = "Polyline" - newFeature['geometry']['coordinates'].append(points.tolist()) + newFeature['geometry']['coordinates'].append(points.tolist()) + else: + newFeature['geometry']['coordinates'].append(polygon.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] newFeature['bound']['coordinates'].append(bounding_box) @@ -403,12 +405,14 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli # and the bound # then add the last one points = coordinates_array[prevIndex:, :] - points = np.concatenate((points, [points[0]])) + polygon = np.concatenate((points, [points[0]])) if len(points) > 0: newFeature = deepcopy(featureTemplate) if x.GraphicType == "POLYLINE": newFeature['geometry']['type'] = "Polyline" - newFeature['geometry']['coordinates'].append(points.tolist()) + newFeature['geometry']['coordinates'].append(points.tolist()) + else: + newFeature['geometry']['coordinates'].append(polygon.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] newFeature['bound']['coordinates'].append(bounding_box) @@ -426,11 +430,13 @@ def dicomToCamic(annot_path, image_dimensions, output_file, source_url=None, sli else: # whole thing at once. Only do area and circumference here. points = coordinates_array - points = np.concatenate((points, [points[0]])) + polygon = np.concatenate((points, [points[0]])) newFeature = deepcopy(featureTemplate) if x.GraphicType == "POLYLINE": newFeature['geometry']['type'] = "Polyline" - newFeature['geometry']['coordinates'].append(points.tolist()) + newFeature['geometry']['coordinates'].append(points.tolist()) + else: + newFeature['geometry']['coordinates'].append(polygon.tolist()) bounding_box = _makeBound(points) # [[min_x, min_y], [min_x, max_y], [max_x, max_y], [max_x, min_y],[min_x, min_y]] newFeature['bound']['coordinates'].append(bounding_box) From c06890de79b435bbfc2687f3230e7673ecc9a2b4 Mon Sep 17 00:00:00 2001 From: Birm Date: Tue, 19 Nov 2024 20:11:52 -0500 Subject: [PATCH 8/9] Allow set different download folder --- SlideServer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SlideServer.py b/SlideServer.py index d006cf2..a4c6577 100644 --- a/SlideServer.py +++ b/SlideServer.py @@ -61,6 +61,9 @@ app.config['SECRET_KEY'] = os.urandom(24) app.config['ROI_FOLDER'] = "/images/roiDownload" +download_folder = os.getenv('DOWNLOAD_FOLDER', app.config['UPLOAD_FOLDER']) +app.config['DOWNLOAD_FOLDER'] = download_folder + #creating a uploading folder if it doesn't exist if not os.path.exists(app.config['TEMP_FOLDER']): os.mkdir(app.config['TEMP_FOLDER']) @@ -313,7 +316,7 @@ def getSlide(image_name): image_name = secure_relative_path(image_name) if not verify_extension(image_name): return flask.Response(json.dumps({"error": "Bad image type requested"}), status=400, mimetype='text/json') - folder = app.config['UPLOAD_FOLDER'] + folder = app.config['DOWNLOAD_FOLDER'] if os.sep in image_name: folder_and_file = image_name.rsplit(os.sep, 1) folder = os.path.join(folder, folder_and_file[0]) From 33008a50bc71a9045ecf7b6aa60da084e233e102 Mon Sep 17 00:00:00 2001 From: Birm Date: Wed, 20 Nov 2024 00:08:34 -0500 Subject: [PATCH 9/9] allow dl zip --- SlideServer.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SlideServer.py b/SlideServer.py index a4c6577..540396d 100644 --- a/SlideServer.py +++ b/SlideServer.py @@ -64,6 +64,9 @@ download_folder = os.getenv('DOWNLOAD_FOLDER', app.config['UPLOAD_FOLDER']) app.config['DOWNLOAD_FOLDER'] = download_folder +if os.getenv("ALLOW_DOWNLOAD_ZIP") == "True": + ALLOWED_EXTENSIONS.add("zip") + #creating a uploading folder if it doesn't exist if not os.path.exists(app.config['TEMP_FOLDER']): os.mkdir(app.config['TEMP_FOLDER'])