From a2c663296f5fa56ce98b57ef782fd5b2f65da8c2 Mon Sep 17 00:00:00 2001 From: Steven Meyer <108885656+meyertst-aws@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:15:12 -0400 Subject: [PATCH] Python: HealthImaging Actions step 2 (#5405) --- .../metadata/medical-imaging_metadata.yaml | 256 ++++++++++-- python/example_code/medical-imaging/README.md | 22 +- .../medical-imaging/medical_imaging_basics.py | 364 +++++++++++++++++- .../medical-imaging/requirements.txt | 4 +- .../test/test_medical_imaging_basics.py | 342 +++++++++++++++- python/test_tools/medical_imaging_stubber.py | 210 +++++++++- 6 files changed, 1158 insertions(+), 40 deletions(-) diff --git a/.doc_gen/metadata/medical-imaging_metadata.yaml b/.doc_gen/metadata/medical-imaging_metadata.yaml index 5d1fbeca580..1a973c606a5 100644 --- a/.doc_gen/metadata/medical-imaging_metadata.yaml +++ b/.doc_gen/metadata/medical-imaging_metadata.yaml @@ -1,15 +1,14 @@ # zexi 0.4.0 medical-imaging_CreateDatastore: - title: Create a data store using the &AHI; SDK + title: Create a &AHI; data store using an &AWS; SDK title_abbrev: Create a data store - synopsis: create a data store. + synopsis: create a &AHI; data store. category: languages: Bash: versions: - sdk_version: 2 github: aws-cli/bash-linux/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -19,7 +18,6 @@ medical-imaging_CreateDatastore: versions: - sdk_version: 3 github: python/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -28,7 +26,6 @@ medical-imaging_CreateDatastore: versions: - sdk_version: 2 github: javav2/example_code/medicalimaging - sdkguide: excerpts: - snippet_tags: - medicalimaging.java2.create_datastore.main @@ -36,7 +33,6 @@ medical-imaging_CreateDatastore: versions: - sdk_version: 3 github: javascriptv3/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -44,16 +40,15 @@ medical-imaging_CreateDatastore: services: medical-imaging: {CreateDatastore} medical-imaging_DeleteDatastore: - title: Delete a data store using the &AHI; SDK + title: Delete a &AHI; data store using an &AWS; SDK title_abbrev: Delete a data store - synopsis: delete a data store. + synopsis: delete a &AHI; data store. category: languages: Bash: versions: - sdk_version: 2 github: aws-cli/bash-linux/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -63,7 +58,6 @@ medical-imaging_DeleteDatastore: versions: - sdk_version: 3 github: python/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -72,7 +66,6 @@ medical-imaging_DeleteDatastore: versions: - sdk_version: 2 github: javav2/example_code/medicalimaging - sdkguide: excerpts: - snippet_tags: - medicalimaging.java2.delete_datastore.main @@ -80,7 +73,6 @@ medical-imaging_DeleteDatastore: versions: - sdk_version: 3 github: javascriptv3/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -88,16 +80,15 @@ medical-imaging_DeleteDatastore: services: medical-imaging: {DeleteDatastore} medical-imaging_ListDatastores: - title: List a data stores using the &AHI; SDK + title: List &AHI; data stores using an &AWS; SDK title_abbrev: List data stores - synopsis: list a data stores. + synopsis: list &AHI; data stores. category: languages: Bash: versions: - sdk_version: 2 github: aws-cli/bash-linux/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -107,7 +98,6 @@ medical-imaging_ListDatastores: versions: - sdk_version: 3 github: python/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -116,7 +106,6 @@ medical-imaging_ListDatastores: versions: - sdk_version: 2 github: javav2/example_code/medicalimaging - sdkguide: excerpts: - snippet_tags: - medicalimaging.java2.list_datastores.main @@ -124,7 +113,6 @@ medical-imaging_ListDatastores: versions: - sdk_version: 3 github: javascriptv3/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -132,16 +120,15 @@ medical-imaging_ListDatastores: services: medical-imaging: {ListDatastores} medical-imaging_GetDatastore: - title: Get data store properties using the &AHI; SDK + title: Get &AHI; data store properties using an &AWS; SDK title_abbrev: Get data store properties - synopsis: get data store properties. + synopsis: get &AHI; data store properties. category: languages: Bash: versions: - sdk_version: 2 github: aws-cli/bash-linux/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -151,7 +138,6 @@ medical-imaging_GetDatastore: versions: - sdk_version: 3 github: python/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: @@ -160,7 +146,6 @@ medical-imaging_GetDatastore: versions: - sdk_version: 2 github: javav2/example_code/medicalimaging - sdkguide: excerpts: - snippet_tags: - medicalimaging.java2.get_datastore.main @@ -168,10 +153,233 @@ medical-imaging_GetDatastore: versions: - sdk_version: 3 github: javascriptv3/example_code/medical-imaging - sdkguide: excerpts: - description: snippet_tags: - medical-imaging.JavaScript.datastore.getDatastoreV3 services: medical-imaging: {GetDatastore} +medical-imaging_StartDICOMImportJob: + title: Import bulk data into a &AHI; data store using an &AWS; SDK + title_abbrev: Import bulk data into a data store + synopsis: import bulk data into a &AHI; data store. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.StartDICOMImportJob + services: + medical-imaging: {StartDICOMImportJob} +medical-imaging_GetDICOMImportJob: + title: Get import job properties using an &AWS; SDK + title_abbrev: Get import job properties + synopsis: get the import job properties. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.GetDICOMImportJob + services: + medical-imaging: {GetDICOMImportJob} +medical-imaging_ListDICOMImportJobs: + title: List import jobs for a &AHI; data store using an &AWS; SDK + title_abbrev: List import jobs for a data store + synopsis: list import jobs for a &AHI; data store. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.ListDICOMImportJobs + services: + medical-imaging: {ListDICOMImportJobs} +medical-imaging_SearchImageSets: + title: Search &AHI; image sets using an &AWS; SDK + title_abbrev: Search image sets + synopsis: search &AHI; image sets. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.SearchImageSets + services: + medical-imaging: {SearchImageSets} +medical-imaging_GetImageSet: + title: Get &AHI; image set properties using an &AWS; SDK + title_abbrev: Get image set properties + synopsis: get &AHI; image set properties. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.GetImageSet + services: + medical-imaging: {GetImageSet} +medical-imaging_GetImageSetMetadata: + title: Get metadata for a &AHI; image set using an &AWS; SDK + title_abbrev: Get metadata for an image set + synopsis: get metadata for a &AHI; image set. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.GetImageSetMetadata + services: + medical-imaging: {GetImageSetMetadata} +medical-imaging_GetImageFrame: + title: Get a &AHI; image frame using an &AWS; SDK + title_abbrev: Get an image frame + synopsis: get an image frame. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.GetImageFrame + services: + medical-imaging: {GetImageFrame} +medical-imaging_ListImageSetVersions: + title: List &AHI; image set versions using an &AWS; SDK + title_abbrev: List image set versions + synopsis: list &AHI; image set versions. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.ListImageSetVersions + services: + medical-imaging: {ListImageSetVersions} +medical-imaging_UpdateImageSetMetadata: + title: Update &AHI; image set metadata using an &AWS; SDK + title_abbrev: Update image set metadata + synopsis: update &AHI; image set metadata. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.UpdateImageSetMetadata + services: + medical-imaging: {UpdateImageSetMetadata} +medical-imaging_CopyImageSet: + title: Copy a &AHI; image set using an &AWS; SDK + title_abbrev: Copy an image set + synopsis: copy a &AHI; image set. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.CopyImageSet + services: + medical-imaging: {CopyImageSet} +medical-imaging_DeleteImageSet: + title: Delete a &AHI; image set using an &AWS; SDK + title_abbrev: Delete an image set + synopsis: delete a &AHI; image set. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.DeleteImageSet + services: + medical-imaging: {DeleteImageSet} +medical-imaging_TagResource: + title: Add a tag to a &AHI; resource using an &AWS; SDK + title_abbrev: Add a tag to a resource + synopsis: add a tag to a &AHI; resource. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.TagResource + services: + medical-imaging: {TagResource} +medical-imaging_UntagResource: + title: Remove a tag from a &AHI; resource using an &AWS; SDK + title_abbrev: Remove a tag from a resource + synopsis: remove a tag from a &AHI; resource. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.UntagResource + services: + medical-imaging: {UntagResource} +medical-imaging_ListTagsForResource: + title: List tags for a &AHI; resource using an &AWS; SDK + title_abbrev: List tags for a resource + synopsis: list tags for a &AHI; resource. + category: + languages: + Python: + versions: + - sdk_version: 3 + github: python/example_code/medical-imaging + excerpts: + - description: + snippet_tags: + - python.example_code.medical-imaging.ListTagsForResource + services: + medical-imaging: {ListTagsForResource} diff --git a/python/example_code/medical-imaging/README.md b/python/example_code/medical-imaging/README.md index 44ee86176a9..9cf1070487d 100644 --- a/python/example_code/medical-imaging/README.md +++ b/python/example_code/medical-imaging/README.md @@ -1,4 +1,4 @@ - + # HealthImaging code examples for the SDK for Python ## Overview @@ -39,10 +39,24 @@ python -m pip install -r requirements.txt Code excerpts that show you how to call individual service functions. +* [Add a tag to a resource](medical_imaging_basics.py#L388) (`TagResource`) +* [Copy an image set](medical_imaging_basics.py#L334) (`CopyImageSet`) * [Create a data store](medical_imaging_basics.py#L22) (`CreateDatastore`) -* [Delete a data store](medical_imaging_basics.py#L78) (`DeleteDatastore`) -* [Get data store properties](medical_imaging_basics.py#L41) (`GetDatastore`) -* [List data stores](medical_imaging_basics.py#L60) (`ListDatastores`) +* [Delete a data store](medical_imaging_basics.py#L85) (`DeleteDatastore`) +* [Delete an image set](medical_imaging_basics.py#L366) (`DeleteImageSet`) +* [Get an image frame](medical_imaging_basics.py#L258) (`GetImageFrame`) +* [Get data store properties](medical_imaging_basics.py#L42) (`GetDatastore`) +* [Get image set properties](medical_imaging_basics.py#L203) (`GetImageSet`) +* [Get metadata for an image set](medical_imaging_basics.py#L226) (`GetImageSetMetadata`) +* [Get the import job properties](medical_imaging_basics.py#L132) (`GetDICOMImportJob`) +* [List data stores](medical_imaging_basics.py#L62) (`ListDatastores`) +* [List image set versions](medical_imaging_basics.py#L285) (`ListImageSetVersions`) +* [List import jobs for a data store](medical_imaging_basics.py#L153) (`ListDICOMImportJobs`) +* [Lists all tags for a resource](medical_imaging_basics.py#L424) (`ListTagsForResource`) +* [Remove a tag from a resource](medical_imaging_basics.py#L406) (`UntagResource`) +* [Search image sets](medical_imaging_basics.py#L177) (`SearchImageSets`) +* [Start importing bulk data into a data store](medical_imaging_basics.py#L102) (`StartDICOMImportJob`) +* [Update image set metadata](medical_imaging_basics.py#L307) (`UpdateImageSetMetadata`) ## Run the examples diff --git a/python/example_code/medical-imaging/medical_imaging_basics.py b/python/example_code/medical-imaging/medical_imaging_basics.py index a18b9932faf..3c403412abd 100644 --- a/python/example_code/medical-imaging/medical_imaging_basics.py +++ b/python/example_code/medical-imaging/medical_imaging_basics.py @@ -9,7 +9,6 @@ """ import logging - from botocore.exceptions import ClientError logger = logging.getLogger(__name__) @@ -36,6 +35,7 @@ def create_datastore(self, name): raise else: return data_store['datastoreId'] + # snippet-end:[python.example_code.medical-imaging.CreateDatastore] # snippet-start:[python.example_code.medical-imaging.GetDatastore] @@ -43,7 +43,7 @@ def get_datastore_properties(self, datastore_id): """ Get the properties of a data store. - :param datastore_id: The ID of the data store to get. + :param datastore_id: The ID of the data store. :return: The data store properties. """ try: @@ -55,6 +55,7 @@ def get_datastore_properties(self, datastore_id): raise else: return data_store['datastoreProperties'] + # snippet-end:[python.example_code.medical-imaging.GetDatastore] # snippet-start:[python.example_code.medical-imaging.ListDatastores] @@ -65,14 +66,19 @@ def list_datastores(self): :return: The list of data stores. """ try: - data_stores = self.health_imaging_client.list_datastores() + paginator = self.health_imaging_client.get_paginator('list_datastores') + page_iterator = paginator.paginate() + datastore_summaries = [] + for page in page_iterator: + datastore_summaries.extend(page['datastoreSummaries']) except ClientError as err: logger.error( "Couldn't list data stores. Here's why: %s: %s", err.response['Error']['Code'], err.response['Error']['Message']) raise else: - return data_stores['datastoreSummaries'] + return datastore_summaries + # snippet-end:[python.example_code.medical-imaging.ListDatastores] # snippet-start:[python.example_code.medical-imaging.DeleteDatastore] @@ -80,7 +86,7 @@ def delete_datastore(self, datastore_id): """ Delete a data store. - :param datastore_id: The ID of the data store to delete. + :param datastore_id: The ID of the data store. """ try: self.health_imaging_client.delete_datastore(datastoreId=datastore_id) @@ -89,4 +95,350 @@ def delete_datastore(self, datastore_id): "Couldn't delete data store %s. Here's why: %s: %s", datastore_id, err.response['Error']['Code'], err.response['Error']['Message']) raise - # snippet-end:[python.example_code.medical-imaging.DeleteDatastore] \ No newline at end of file + + # snippet-end:[python.example_code.medical-imaging.DeleteDatastore] + + # snippet-start:[python.example_code.medical-imaging.StartDICOMImportJob] + def start_dicom_import_job(self, job_name, datastore_id, role_arn, input_s3_uri, output_s3_uri): + """ + Start a DICOM import job. + + :param job_name: The name of the job. + :param datastore_id: The ID of the data store. + :param role_arn: The Amazon Resource Name (ARN) of the role to use for the job. + :param input_s3_uri: The S3 bucket input prefix path containing the DICOM files. + :param output_s3_uri: The S3 bucket output prefix path for the result. + :return: The job ID. + """ + try: + job = self.health_imaging_client.start_dicom_import_job( + jobName=job_name, + datastoreId=datastore_id, + dataAccessRoleArn=role_arn, + inputS3Uri=input_s3_uri, + outputS3Uri=output_s3_uri, + ) + except ClientError as err: + logger.error( + "Couldn't start DICOM import job. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return job['jobId'] + + # snippet-end:[python.example_code.medical-imaging.StartDICOMImportJob] + + # snippet-start:[python.example_code.medical-imaging.GetDICOMImportJob] + def get_dicom_import_job(self, datastore_id, job_id): + """ + Get the properties of a DICOM import job. + + :param datastore_id: The ID of the data store. + :param job_id: The ID of the job. + :return: The job properties. + """ + try: + job = self.health_imaging_client.get_dicom_import_job(jobId=job_id, datastoreId=datastore_id) + except ClientError as err: + logger.error( + "Couldn't get DICOM import job. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return job['jobProperties'] + + # snippet-end:[python.example_code.medical-imaging.GetDICOMImportJob] + + # snippet-start:[python.example_code.medical-imaging.ListDICOMImportJobs] + def list_dicom_import_jobs(self, datastore_id): + """ + List the DICOM import jobs. + + :param datastore_id: The ID of the data store. + :return: The list of jobs. + """ + try: + paginator = self.health_imaging_client.get_paginator('list_dicom_import_jobs') + page_iterator = paginator.paginate(datastoreId=datastore_id) + job_summaries = [] + for page in page_iterator: + job_summaries.extend(page['jobSummaries']) + except ClientError as err: + logger.error( + "Couldn't list DICOM import jobs. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return job_summaries + + # snippet-end:[python.example_code.medical-imaging.ListDICOMImportJobs] + + # snippet-start:[python.example_code.medical-imaging.SearchImageSets] + def search_image_sets(self, datastore_id, search_filter): + """ + Search for image sets. + + :param datastore_id: The ID of the data store. + :param search_filter: The search filter. + For example: {"filters" : [{ "operator": "EQUAL", "values": [{"DICOMPatientId": "3524578"}]}]}. + :return: The list of image sets. + """ + try: + paginator = self.health_imaging_client.get_paginator('search_image_sets') + page_iterator = paginator.paginate(datastoreId=datastore_id, searchCriteria=search_filter) + metadata_summaries = [] + for page in page_iterator: + metadata_summaries.extend(page['imageSetsMetadataSummaries']) + except ClientError as err: + logger.error( + "Couldn't search image sets. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return metadata_summaries + + # snippet-end:[python.example_code.medical-imaging.SearchImageSets] + + # snippet-start:[python.example_code.medical-imaging.GetImageSet] + def get_image_set(self, datastore_id, image_set_id, version_id): + """ + Get the properties of an image set. + + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :param version_id: The version of the image set. + :return: The image set properties. + """ + try: + image_set = self.health_imaging_client.get_image_set(imageSetId=image_set_id, datastoreId=datastore_id, + versionId=version_id) + except ClientError as err: + logger.error( + "Couldn't get image set. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return image_set + + # snippet-end:[python.example_code.medical-imaging.GetImageSet] + + # snippet-start:[python.example_code.medical-imaging.GetImageSetMetadata] + def get_image_set_metadata(self, metadata_file, datastore_id, image_set_id, version_id=None): + """ + Get the metadata of an image set. + + :param metadata_file: The file to store the JSON gzipped metadata. + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :param version_id: The version of the image set. + """ + try: + if version_id: + image_set_metadata = self.health_imaging_client.get_image_set_metadata(imageSetId=image_set_id, + datastoreId=datastore_id, + versionId=version_id) + else: + image_set_metadata = self.health_imaging_client.get_image_set_metadata(imageSetId=image_set_id, + datastoreId=datastore_id) + print(image_set_metadata) + with open(metadata_file, 'wb') as f: + for chunk in image_set_metadata['imageSetMetadataBlob'].iter_chunks(): + if chunk: + f.write(chunk) + + except ClientError as err: + logger.error( + "Couldn't get image metadata. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + + # snippet-end:[python.example_code.medical-imaging.GetImageSetMetadata] + + # snippet-start:[python.example_code.medical-imaging.GetImageFrame] + def get_pixel_data(self, file_path_to_write, datastore_id, image_set_id, image_frame_id): + """ + Get an image frame's pixel data. + + :param file_path_to_write: The path to write the image frame's HTJ2K encoded pixel data. + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :param image_frame_id: The ID of the image frame. + """ + try: + image_frame = self.health_imaging_client.get_image_frame(datastoreId=datastore_id, + imageSetId=image_set_id, + imageFrameInformation={ + "imageFrameId": image_frame_id}) + with open(file_path_to_write, 'wb') as f: + for chunk in image_frame['imageFrameBlob'].iter_chunks(): + if chunk: + f.write(chunk) + except ClientError as err: + logger.error( + "Couldn't get image frame. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + + # snippet-end:[python.example_code.medical-imaging.GetImageFrame] + + # snippet-start:[python.example_code.medical-imaging.ListImageSetVersions] + def list_image_set_versions(self, datastore_id, image_set_id): + """ + List the image set versions. + + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :return: The list of image set versions. + """ + try: + paginator = self.health_imaging_client.get_paginator('list_image_set_versions') + page_iterator = paginator.paginate(imageSetId=image_set_id, + datastoreId=datastore_id) + image_set_properties_list = [] + for page in page_iterator: + image_set_properties_list.extend(page['imageSetPropertiesList']) + except ClientError as err: + logger.error( + "Couldn't list image set versions. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return image_set_properties_list + + # snippet-end:[python.example_code.medical-imaging.ListImageSetVersions] + + # snippet-start:[python.example_code.medical-imaging.UpdateImageSetMetadata] + def update_image_set_metadata(self, datastore_id, image_set_id, version_id, metadata): + """ + Update the metadata of an image set. + + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :param version_id: The ID of the image set version. + :param metadata: The image set metadata as a dictionary. + For example {"DICOMUpdates": {"updatableAttributes": + "{\"SchemaVersion\":1.1,\"Patient\":{\"DICOM\":{\"PatientName\":\"Garcia^Gloria\"}}}"}} + :return: The updated image set metadata. + """ + try: + updated_metadata = self.health_imaging_client.update_image_set_metadata( + imageSetId=image_set_id, datastoreId=datastore_id, latestVersionId=version_id, + updateImageSetMetadataUpdates=metadata) + except ClientError as err: + logger.error( + "Couldn't update image set metadata. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return updated_metadata + + # snippet-end:[python.example_code.medical-imaging.UpdateImageSetMetadata] + + # snippet-start:[python.example_code.medical-imaging.CopyImageSet] + def copy_image_set(self, datastore_id, image_set_id, version_id, destination_image_set_id=None, + destination_version_id=None): + """ + Copy an image set. + + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :param version_id: The ID of the image set version. + :param destination_image_set_id: The ID of the optional destination image set. + :param destination_version_id: The ID of the optional destination image set version. + :return: The copied image set ID. + """ + try: + copy_image_set_information = {'sourceImageSet': {'latestVersionId': version_id}} + if destination_image_set_id and destination_version_id: + copy_image_set_information["destinationImageSet"] = {"imageSetId": destination_image_set_id, + "latestVersionId": destination_version_id} + copy_results = self.health_imaging_client.copy_image_set( + datastoreId=datastore_id, + sourceImageSetId=image_set_id, + copyImageSetInformation=copy_image_set_information) + except ClientError as err: + logger.error( + "Couldn't copy image set. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return copy_results['destinationImageSetProperties']['imageSetId'] + + # snippet-end:[python.example_code.medical-imaging.CopyImageSet] + + # snippet-start:[python.example_code.medical-imaging.DeleteImageSet] + def delete_image_set(self, datastore_id, image_set_id): + """ + Delete an image set. + + :param datastore_id: The ID of the data store. + :param image_set_id: The ID of the image set. + :return: The delete results. + """ + try: + delete_results = self.health_imaging_client.delete_image_set( + imageSetId=image_set_id, datastoreId=datastore_id) + except ClientError as err: + logger.error( + "Couldn't delete image set. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return delete_results + + # snippet-end:[python.example_code.medical-imaging.DeleteImageSet] + + # snippet-start:[python.example_code.medical-imaging.TagResource] + def tag_resource(self, resource_arn, tags): + """ + Tag a resource. + + :param resource_arn: The ARN of the resource. + :param tags: The tags to apply. + """ + try: + self.health_imaging_client.tag_resource(resourceArn=resource_arn, tags=tags) + except ClientError as err: + logger.error( + "Couldn't tag resource. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + + # snippet-end:[python.example_code.medical-imaging.TagResource] + + # snippet-start:[python.example_code.medical-imaging.UntagResource] + def untag_resource(self, resource_arn, tag_keys): + """ + Untag a resource. + + :param resource_arn: The ARN of the resource. + :param tag_keys: The tag keys to remove. + """ + try: + self.health_imaging_client.untag_resource(resourceArn=resource_arn, tagKeys=tag_keys) + except ClientError as err: + logger.error( + "Couldn't untag resource. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + + # snippet-end:[python.example_code.medical-imaging.UntagResource] + + # snippet-start:[python.example_code.medical-imaging.ListTagsForResource] + def list_tags_for_resource(self, resource_arn): + """ + List the tags for a resource. + + :param resource_arn: The ARN of the resource. + :return: The list of tags. + """ + try: + tags = self.health_imaging_client.list_tags_for_resource(resourceArn=resource_arn) + except ClientError as err: + logger.error( + "Couldn't list tags for resource. Here's why: %s: %s", err.response['Error']['Code'], + err.response['Error']['Message']) + raise + else: + return tags['tags'] + # snippet-end:[python.example_code.medical-imaging.ListTagsForResource] diff --git a/python/example_code/medical-imaging/requirements.txt b/python/example_code/medical-imaging/requirements.txt index 393cf0d1181..6a14b87b4d1 100644 --- a/python/example_code/medical-imaging/requirements.txt +++ b/python/example_code/medical-imaging/requirements.txt @@ -1,3 +1,5 @@ boto3>=1.26.79 pytest>=7.2.1 -requests>=2.28.2 \ No newline at end of file +requests>=2.28.2 +openjphpy>=0.1.0 +botocore~=1.31.30 \ No newline at end of file diff --git a/python/example_code/medical-imaging/test/test_medical_imaging_basics.py b/python/example_code/medical-imaging/test/test_medical_imaging_basics.py index 7e798547241..2144ddf35bd 100644 --- a/python/example_code/medical-imaging/test/test_medical_imaging_basics.py +++ b/python/example_code/medical-imaging/test/test_medical_imaging_basics.py @@ -8,12 +8,13 @@ import boto3 from botocore.exceptions import ClientError import pytest +import os from medical_imaging_basics import MedicalImagingWrapper @pytest.mark.parametrize('error_code', [None, 'TestException']) -def test_create_function(make_stubber, error_code): +def test_create_datastore(make_stubber, error_code): medical_imaging_client = boto3.client('medical-imaging') medical_imaging_stubber = make_stubber(medical_imaging_client) wrapper = MedicalImagingWrapper(medical_imaging_client) @@ -35,7 +36,7 @@ def test_create_function(make_stubber, error_code): @pytest.mark.parametrize('error_code', [None, 'TestException']) -def test_get_properties_function(make_stubber, error_code): +def test_get_datastore_properties(make_stubber, error_code): medical_imaging_client = boto3.client('medical-imaging') medical_imaging_stubber = make_stubber(medical_imaging_client) wrapper = MedicalImagingWrapper(medical_imaging_client) @@ -55,7 +56,7 @@ def test_get_properties_function(make_stubber, error_code): @pytest.mark.parametrize('error_code', [None, 'TestException']) -def test_list_function(make_stubber, error_code): +def test_list_datastores(make_stubber, error_code): medical_imaging_client = boto3.client('medical-imaging') medical_imaging_stubber = make_stubber(medical_imaging_client) wrapper = MedicalImagingWrapper(medical_imaging_client) @@ -74,7 +75,7 @@ def test_list_function(make_stubber, error_code): @pytest.mark.parametrize('error_code', [None, 'TestException']) -def test_delete_function(make_stubber, error_code): +def test_delete_datastore(make_stubber, error_code): medical_imaging_client = boto3.client('medical-imaging') medical_imaging_stubber = make_stubber(medical_imaging_client) wrapper = MedicalImagingWrapper(medical_imaging_client) @@ -90,3 +91,336 @@ def test_delete_function(make_stubber, error_code): with pytest.raises(ClientError) as exc_info: wrapper.delete_datastore(datastore_id) assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_start_dicom_import_job(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + job_id = 'cccccc1234567890abcdef123456789' + job_name = 'job_name' + datastore_id = 'abcdedf1234567890abcdef123456789' + role_arn = 'arn:aws:iam::111111111111:role/dicom_import' + input_s3_uri = 's3://healthimaging-source/CRStudy/' + output_s3_uri = 's3://healthimaging-destination/CRStudy/' + + medical_imaging_stubber.stub_start_dicom_import_job( + job_name, datastore_id, role_arn, input_s3_uri, output_s3_uri, job_id, + error_code=error_code) + + if error_code is None: + result = wrapper.start_dicom_import_job(job_name, datastore_id, + role_arn, + input_s3_uri, + output_s3_uri) + assert result == job_id + else: + with pytest.raises(ClientError) as exc_info: + wrapper.start_dicom_import_job(job_name, datastore_id, + role_arn, + input_s3_uri, + output_s3_uri) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_get_dicom_import_job(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + job_id = 'cccccc1234567890abcdef123456789' + job_status = 'TESTING' + + medical_imaging_stubber.stub_get_dicom_import_job( + job_id, datastore_id, job_status, error_code=error_code) + + if error_code is None: + result = wrapper.get_dicom_import_job(datastore_id, job_id) + assert result['jobStatus'] == job_status + else: + with pytest.raises(ClientError) as exc_info: + wrapper.get_dicom_import_job(datastore_id, job_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_list_dicom_import_jobs(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + + medical_imaging_stubber.stub_list_dicom_import_jobs( + datastore_id, error_code=error_code) + + if error_code is None: + wrapper.list_dicom_import_jobs(datastore_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.list_dicom_import_jobs(datastore_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_search_mage_sets(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + search_filter = { + "filters": [{ + "values": [{"createdAt": '2023-09-13T14:13:39.302000-04:00'}, + {"createdAt": '2023-09-13T14:13:39.302000-04:00'}], + "operator": "BETWEEN" + }] + } + medical_imaging_stubber.stub_search_image_sets( + datastore_id, search_filter, error_code=error_code) + + if error_code is None: + wrapper.search_image_sets(datastore_id, search_filter) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.search_image_sets(datastore_id, search_filter) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_get_image_set(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + version_id = '1' + + medical_imaging_stubber.stub_get_image_set( + datastore_id, image_set_id, version_id, error_code=error_code) + + if error_code is None: + wrapper.get_image_set(datastore_id, image_set_id, version_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.get_image_set(datastore_id, image_set_id, version_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_get_image_set_metadata(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + test_file = 'med-imag-test_file_1234.gzip' + medical_imaging_stubber.stub_get_image_set_metadata( + datastore_id, image_set_id, error_code=error_code) + + if error_code is None: + wrapper.get_image_set_metadata(test_file, datastore_id, image_set_id) + assert os.path.exists(test_file) + os.remove(test_file) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.get_image_set_metadata(test_file, datastore_id, image_set_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_get_pixel_data(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + image_frame_id = 'cccccc1234567890abcdef123456789' + test_file = 'med-imag-test_file_789654.jph' + medical_imaging_stubber.stub_get_pixel_data( + datastore_id, image_set_id, image_frame_id, error_code=error_code) + + if error_code is None: + wrapper.get_pixel_data(test_file, datastore_id, image_set_id, image_frame_id) + assert os.path.exists(test_file) + os.remove(test_file) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.get_pixel_data(test_file, datastore_id, image_set_id, image_frame_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_list_image_set_versions(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + + medical_imaging_stubber.stub_list_image_set_versions( + datastore_id, image_set_id, error_code=error_code) + + if error_code is None: + wrapper.list_image_set_versions(datastore_id, image_set_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.list_image_set_versions(datastore_id, image_set_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_update_image_set_metadata(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + version_id = '1' + metadata = {"DICOMUpdates": {"updatableAttributes": + "{\"SchemaVersion\":1.1,\"Patient\":{\"DICOM\":{\"PatientName\":\"Garcia^Gloria\"}}}"}} + + medical_imaging_stubber.stub_update_image_set_metadata( + datastore_id, image_set_id, version_id, metadata, error_code=error_code) + + if error_code is None: + wrapper.update_image_set_metadata(datastore_id, image_set_id, version_id, metadata) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.update_image_set_metadata(datastore_id, image_set_id, version_id, metadata) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_copy_image_set_without_destination(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + version_id = '1' + new_image_set_id = 'cccccc1234567890abcdef123456789' + + medical_imaging_stubber.stub_copy_image_set_without_destination( + datastore_id, image_set_id, version_id, new_image_set_id, error_code=error_code) + + if error_code is None: + wrapper.copy_image_set(datastore_id, image_set_id, version_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.copy_image_set(datastore_id, image_set_id, version_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_copy_image_set_with_destination(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + version_id = '1' + destination_image_set_id = 'cccccc1234567890abcdef123456789' + destination_version_id = '1' + + medical_imaging_stubber.stub_copy_image_set_with_destination( + datastore_id, image_set_id, version_id, destination_image_set_id, destination_version_id, error_code=error_code) + + if error_code is None: + wrapper.copy_image_set(datastore_id, image_set_id, version_id, destination_image_set_id, destination_version_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.copy_image_set(datastore_id, image_set_id, version_id, destination_image_set_id, + destination_version_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_delete_image_set(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + datastore_id = 'abcdedf1234567890abcdef123456789' + image_set_id = 'cccccc1234567890abcdef123456789' + + medical_imaging_stubber.stub_delete_image_set( + datastore_id, image_set_id, error_code=error_code) + + if error_code is None: + wrapper.delete_image_set(datastore_id, image_set_id) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.delete_image_set(datastore_id, image_set_id) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_tag_resource(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + resource_arn = 'arn:aws:medical-imaging:us-east-1:123456789012:datastore/abcdedf1234567890abcdef123456789/image' \ + '-set/cccccc1234567890abcdef123456789 ' + tags = {'test-key': 'test-value'} + + medical_imaging_stubber.stub_tag_resource( + resource_arn, tags, error_code=error_code) + + if error_code is None: + wrapper.tag_resource(resource_arn, tags) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.tag_resource(resource_arn, tags) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_untag_resource(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + resource_arn = 'arn:aws:medical-imaging:us-east-1:123456789012:datastore/abcdedf1234567890abcdef123456789/image' \ + '-set/cccccc1234567890abcdef123456789 ' + tag_keys = ['test-key'] + + medical_imaging_stubber.stub_untag_resource( + resource_arn, tag_keys, error_code=error_code) + + if error_code is None: + wrapper.untag_resource(resource_arn, tag_keys) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.untag_resource(resource_arn, tag_keys) + assert exc_info.value.response['Error']['Code'] == error_code + + +@pytest.mark.parametrize('error_code', [None, 'TestException']) +def test_list_tags_for_resource(make_stubber, error_code): + medical_imaging_client = boto3.client('medical-imaging') + medical_imaging_stubber = make_stubber(medical_imaging_client) + wrapper = MedicalImagingWrapper(medical_imaging_client) + resource_arn = 'arn:aws:medical-imaging:us-east-1:123456789012:datastore/abcdedf1234567890abcdef123456789/image' \ + '-set/cccccc1234567890abcdef123456789 ' + + medical_imaging_stubber.stub_list_tags_for_resource( + resource_arn, error_code=error_code) + + if error_code is None: + wrapper.list_tags_for_resource(resource_arn) + + else: + with pytest.raises(ClientError) as exc_info: + wrapper.list_tags_for_resource(resource_arn) + assert exc_info.value.response['Error']['Code'] == error_code diff --git a/python/test_tools/medical_imaging_stubber.py b/python/test_tools/medical_imaging_stubber.py index 7a7d6511b22..72290d5a9de 100644 --- a/python/test_tools/medical_imaging_stubber.py +++ b/python/test_tools/medical_imaging_stubber.py @@ -6,6 +6,8 @@ """ from test_tools.example_stubber import ExampleStubber +import botocore +import io class MedicalImagingStubber(ExampleStubber): @@ -17,6 +19,7 @@ class MedicalImagingStubber(ExampleStubber): part of the tests, and will raise errors when the actual parameters differ from the expected. """ + def __init__(self, client, use_stubs=True): """ Initializes the object with a specific client and configures it for @@ -65,7 +68,7 @@ def stub_list_datastores(self, datastore_id, error_code=None): self._stub_bifurcator( 'list_datastores', expected_params, response, error_code=error_code) - def stub_delete_data_store(self, datastore_id, error_code=None): + def stub_delete_data_store(self, datastore_id, error_code=None): expected_params = {'datastoreId': datastore_id} response = { 'datastoreId': datastore_id, @@ -73,3 +76,208 @@ def stub_delete_data_store(self, datastore_id, error_code=None): } self._stub_bifurcator( 'delete_datastore', expected_params, response, error_code=error_code) + + def stub_start_dicom_import_job(self, job_name, datastore_id, + role_arn, + input_s3_uri, output_s3_uri, job_id, + error_code=None): + expected_params = {'jobName': job_name, + 'datastoreId': datastore_id, + 'dataAccessRoleArn': role_arn, + 'inputS3Uri': input_s3_uri, + 'outputS3Uri': output_s3_uri} + response = {'jobId': job_id, + 'datastoreId': datastore_id, + 'jobStatus': 'CREATING', + 'submittedAt': '2019-01-01T00:00:00.000Z'} + + self._stub_bifurcator( + 'start_dicom_import_job', expected_params, response, error_code=error_code) + + def stub_get_dicom_import_job(self, job_id, datastore_id, job_status, error_code=None): + expected_params = {'jobId': job_id, + 'datastoreId': datastore_id} + response = {'jobProperties': { + 'jobId': job_id, + 'jobStatus': job_status, + 'jobName': 'test_job', + 'datastoreId': 'cccccc1234567890abcdef123456789', + 'dataAccessRoleArn': 'arn:aws:iam::111111111111:role/dicom_import', + 'inputS3Uri': 's3://healthimaging-source/CRStudy/', + 'outputS3Uri': 's3://healthimaging-destination/CRStudy/' + } + } + self._stub_bifurcator( + 'get_dicom_import_job', expected_params, response, error_code=error_code) + + def stub_list_dicom_import_jobs(self, datastore_id, error_code=None): + expected_params = {'datastoreId': datastore_id} + response = {'jobSummaries': [{ + 'jobId': 'cccccc1234567890abcdef123456789', + 'jobStatus': 'TESTING', + 'jobName': 'test_job', + 'datastoreId': 'cccccc1234567890abcdef123456789', + 'dataAccessRoleArn': 'arn:aws:iam::111111111111:role/dicom_import', + }] + } + self._stub_bifurcator( + 'list_dicom_import_jobs', expected_params, response, error_code=error_code) + + def stub_search_image_sets(self, datastore_id, search_criteria, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'searchCriteria': search_criteria} + response = {'imageSetsMetadataSummaries': [{ + 'imageSetId': 'cccccc1234567890abcdef123456789', + 'version': 1, + 'createdAt': '2023-09-13T14:13:39.302000-04:00', + 'updatedAt': '2023-09-13T14:13:39.302000-04:00', + }] + } + self._stub_bifurcator( + 'search_image_sets', expected_params, response, error_code=error_code) + + def stub_get_image_set(self, datastore_id, image_set_id, version_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id, + 'versionId': version_id} + response = { + 'datastoreId': '12345678901234567890123456789012', + 'imageSetId': image_set_id, + 'versionId': '1', + 'imageSetState': 'ACTIVE', + } + + self._stub_bifurcator( + 'get_image_set', expected_params, response, error_code=error_code) + + def stub_get_image_set_metadata(self, datastore_id, image_set_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id} + + data_string = b'akdelfaldkflakdflkajs' + stream = botocore.response.StreamingBody(io.BytesIO(data_string), len(data_string)) + response = {'contentType': ' text/plain', + 'contentEncoding': 'gzip', + 'imageSetMetadataBlob': stream} + + self._stub_bifurcator( + 'get_image_set_metadata', expected_params, response, error_code=error_code) + + def stub_get_pixel_data(self, datastore_id, image_set_id, image_frame_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id, + 'imageFrameInformation': {'imageFrameId': image_frame_id}} + + data_string = b'akdelfaldkflakdflkajs' + stream = botocore.response.StreamingBody(io.BytesIO(data_string), len(data_string)) + response = { + 'contentType': 'text/plain', + 'imageFrameBlob': stream + } + + self._stub_bifurcator( + 'get_image_frame', expected_params, response, error_code=error_code) + + def stub_list_image_set_versions(self, datastore_id, image_set_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id} + + response = {'imageSetPropertiesList': [{ + 'imageSetId': 'cccccc1234567890abcdef123456789', + 'versionId': '1', + 'imageSetState': 'TESTING', + 'createdAt': '2023-09-13T14:13:39.302000-04:00', + 'updatedAt': '2023-09-13T14:13:39.302000-04:00', + }] + } + self._stub_bifurcator( + 'list_image_set_versions', expected_params, response, error_code=error_code) + + def stub_update_image_set_metadata(self, datastore_id, image_set_id, version_id, metadata, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id, + 'latestVersionId': version_id, + 'updateImageSetMetadataUpdates': metadata} + + response = { + 'imageSetId': 'cccccc1234567890abcdef123456789', + 'latestVersionId': '1', + 'datastoreId': '12345678901234567890123456789012', + 'imageSetState': 'ACTIVE'} + + self._stub_bifurcator( + 'update_image_set_metadata', expected_params, response, error_code=error_code) + + def stub_copy_image_set_without_destination(self, datastore_id, image_set_id, version_id, copied_image_set_id, + error_code=None): + expected_params = {'datastoreId': datastore_id, + 'sourceImageSetId': image_set_id, + 'copyImageSetInformation': {"sourceImageSet": {"latestVersionId": version_id}}} + + response = { + 'datastoreId': datastore_id, + 'sourceImageSetProperties': {'imageSetId': image_set_id, + 'latestVersionId': version_id}, + 'destinationImageSetProperties': {'imageSetId': copied_image_set_id, + 'latestVersionId': '1'}} + + self._stub_bifurcator( + 'copy_image_set', expected_params, response, error_code=error_code) + + def stub_copy_image_set_with_destination(self, datastore_id, image_set_id, version_id, + destination_image_set_id, destination_version_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'sourceImageSetId': image_set_id, + 'copyImageSetInformation': + {'destinationImageSet': {'imageSetId': destination_image_set_id, + 'latestVersionId': destination_version_id}, + 'sourceImageSet': {'latestVersionId': version_id}}} + + response = { + 'datastoreId': datastore_id, + 'sourceImageSetProperties': {'imageSetId': image_set_id, + 'latestVersionId': version_id}, + 'destinationImageSetProperties': {'imageSetId': destination_image_set_id, + 'latestVersionId': destination_version_id}} + + self._stub_bifurcator( + 'copy_image_set', expected_params, response, error_code=error_code) + + def stub_delete_image_set(self, datastore_id, image_set_id, error_code=None): + expected_params = {'datastoreId': datastore_id, + 'imageSetId': image_set_id} + + response = { + 'datastoreId': datastore_id, + 'imageSetId': image_set_id, + 'imageSetWorkflowStatus': 'DELETED', + 'imageSetState': 'DELETED'} + + self._stub_bifurcator( + 'delete_image_set', expected_params, response, error_code=error_code) + + def stub_tag_resource(self, resource_arn, tags, error_code=None): + expected_params = {'resourceArn': resource_arn, + 'tags': tags} + + response = {} + + self._stub_bifurcator( + 'tag_resource', expected_params, response, error_code=error_code) + + def stub_untag_resource(self, resource_arn, tag_keys, error_code=None): + expected_params = {'resourceArn': resource_arn, + 'tagKeys': tag_keys} + + response = {} + + self._stub_bifurcator( + 'untag_resource', expected_params, response, error_code=error_code) + + def stub_list_tags_for_resource(self, resource_arn, error_code=None): + expected_params = {'resourceArn': resource_arn} + + response = {'tags': {'test-key': 'test-value'}} + + self._stub_bifurcator( + 'list_tags_for_resource', expected_params, response, error_code=error_code) \ No newline at end of file