From 23a89af2d74430910d0a21037df478c488761a89 Mon Sep 17 00:00:00 2001 From: Nile Wilson <55208683+niwilso@users.noreply.github.com> Date: Thu, 6 Jul 2023 09:18:50 -0400 Subject: [PATCH] DICOM redactor improvement: Adding exceptions for when DICOM file does not have pixel data (#1104) * Adding exceptions for when DICOM file does not have pixel data * Updating unit tests to accomodate new exception * Fixing linting errors * Adding in test image * Fixing f-string --- .../dicom_image_pii_verify_engine.py | 5 ++++ .../dicom_image_redactor_engine.py | 12 ++++++++- .../tests/test_data/0_ORIGINAL_no_pixels.dcm | Bin 0 -> 2136 bytes .../test_dicom_image_pii_verify_engine.py | 23 ++++++++++++++++++ .../tests/test_dicom_image_redactor_engine.py | 7 ++++-- 5 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 presidio-image-redactor/tests/test_data/0_ORIGINAL_no_pixels.dcm diff --git a/presidio-image-redactor/presidio_image_redactor/dicom_image_pii_verify_engine.py b/presidio-image-redactor/presidio_image_redactor/dicom_image_pii_verify_engine.py index ad95cb3f1..f811319c3 100644 --- a/presidio-image-redactor/presidio_image_redactor/dicom_image_pii_verify_engine.py +++ b/presidio-image-redactor/presidio_image_redactor/dicom_image_pii_verify_engine.py @@ -66,6 +66,11 @@ def verify_dicom_instance( """ instance_copy = deepcopy(instance) + try: + instance_copy.PixelData + except AttributeError: + raise AttributeError("Provided DICOM instance lacks pixel data.") + # Load image for processing with tempfile.TemporaryDirectory() as tmpdirname: # Convert DICOM to PNG and add padding for OCR (during analysis) diff --git a/presidio-image-redactor/presidio_image_redactor/dicom_image_redactor_engine.py b/presidio-image-redactor/presidio_image_redactor/dicom_image_redactor_engine.py index 57cdb7dbb..c262313b1 100644 --- a/presidio-image-redactor/presidio_image_redactor/dicom_image_redactor_engine.py +++ b/presidio-image-redactor/presidio_image_redactor/dicom_image_redactor_engine.py @@ -50,8 +50,13 @@ def redact( :return: DICOM instance with redacted pixel data. """ # Check input - if type(image) != pydicom.dataset.FileDataset: + if type(image) not in [pydicom.dataset.FileDataset, pydicom.dataset.Dataset]: raise TypeError("The provided image must be a loaded DICOM instance.") + try: + image.PixelData + except AttributeError: + raise AttributeError("Provided DICOM instance lacks pixel data.") + instance = deepcopy(image) # Load image for processing @@ -771,6 +776,11 @@ def _redact_single_dicom_image( # Load instance instance = pydicom.dcmread(dst_path) + try: + instance.PixelData + except AttributeError: + raise AttributeError("Provided DICOM file lacks pixel data.") + # Load image for processing with tempfile.TemporaryDirectory() as tmpdirname: # Convert DICOM to PNG and add padding for OCR (during analysis) diff --git a/presidio-image-redactor/tests/test_data/0_ORIGINAL_no_pixels.dcm b/presidio-image-redactor/tests/test_data/0_ORIGINAL_no_pixels.dcm new file mode 100644 index 0000000000000000000000000000000000000000..a1ae4d0a537b229946c4cef1a8d96b59742ca022 GIT binary patch literal 2136 zcmc&#+inv_82)XdXh8*S5_4+ zRjFd%@7y8jC@1pRk<1)9z9StEUwk0H0*x(Fjsjb%F!FqMWf-yFRFeHL3O$xy=t)o8 zdLA^YWW#2C$tz;O+d3pQNL(lQJ&ICFx;mEULcT#|dxhBXMU`$-oUT&dFOyhUWYHE< zPST?DC`}IXqLanN(*sBc-{eKVm6j#g#o$0MT?exT4|ibm&(&E}GhY8obruQaLZn!_ z97@-s&873*rSrX|Gt(;UIr5u+Xpy9A`qX4r?3k?8#~hC%i`+jrXp*D6rbRh={AUhc z8U>#3`of|^^pL1kSTv)Xpmz7~cXm77Othx+*>pZ!jHeTezDM0e<@4!c0l}+RXOr>b zt!PhAXXC~2#G)VQ3&>s96WRFr%i-umWW&jHF3RD2`1195^kz%svG4;wV8r~<vk46*GnVgIk;{NFPcs_c2GMpT3iFQW>0SA~B+g~B( zlR~uMW+38RGRspw7`^El7r z+@fklce+elgCe$QrJ_6XcM(3EUw;n~eK`Ns?C7uk3C=r3|8ehGo(|Fi@zyp!18+|{ zJh~^HfWKx(@m7`;*yD_yKwL#9G&XjVtQ63*an^H6;zymN?Dw7}{r$aeT-v~p&Q zq@>S*u!)VOY=&ru3~bsqlguEo?G@;<8$Mmf-1XXkSIuh;ask@D4LUy^ogGaZdArlV zrSE5rWVjeYx?$^#qL8^61X0c8Lzw?9gTjku*GSlRp_LSkP9j|9Ru=>4igJ3qcr%=j z#G?=|0<#QjbhM^3*QFom9|0uY)VJ)?*u;~@<4&21y#oGfRJmy?P|P)2Gqfw4;IA7x z3Ios^hHiL{S0n3|*6;OuDQq8tBt$vXJXc20U7cxq_xT+3wb_Nd^6oMkW~RQ2+1IH0 z?grFwXB8`j$4Z4tZqw}N%Qj^2BDpCU_qtlqQIb}M*E zS%_uqET2C`$3Mn|pz9yKIUIO_Z2DdpxvD7xPqG&&+!*CzSWQ3hv^WxU<1ZVf+|b21 zRphyDh*OipAoRkJgC+;C^=pYTT=&C9r(VCo4Wb`A?Vhrf`1}{ z9DWUT71R|&k*2Qb{$>3HFIk$@aW{3|zSNN(8d|b)>r~j<3xiu4O3Md^+AopQrjhcO zOSNp%VRab=x}x1yfftz@K)uvh(D>Os`*tkK1zxV<{K%l<-;uwJ1zXsrTje0`C2^4) m$Sr9LHgiBYw#ByTQWxT~73Zn2#RVpJx^e3PPTQs{z5Z_qr+2de literal 0 HcmV?d00001 diff --git a/presidio-image-redactor/tests/test_dicom_image_pii_verify_engine.py b/presidio-image-redactor/tests/test_dicom_image_pii_verify_engine.py index 8a2eb64ad..ccd5b16d6 100644 --- a/presidio-image-redactor/tests/test_dicom_image_pii_verify_engine.py +++ b/presidio-image-redactor/tests/test_dicom_image_pii_verify_engine.py @@ -1,5 +1,6 @@ """Unit tests for dicom_image_pii_verify_engine """ +from copy import deepcopy import pydicom from presidio_image_redactor import ( @@ -121,6 +122,28 @@ def test_verify_dicom_instance_happy_path( assert mock_analyze.call_count == 1 assert mock_verify.call_count == 1 +def test_verify_dicom_instance_exception( + mock_engine: DicomImagePiiVerifyEngine, + get_mock_dicom_instance: pydicom.dataset.FileDataset, +): + """Test exceptions for DicomImagePiiVerifyEngine.verify_dicom_instance + Args: + mock_engine (DicomImagePiiVerifyEngine): Instantiated engine. + get_mock_dicom_instance (pydicom.dataset.FileDataset): Loaded DICOM. + """ + with pytest.raises(Exception) as exc_info: + # Arrange + padding_width = 25 + test_instance = deepcopy(get_mock_dicom_instance) + del test_instance.PixelData + expected_error_type = AttributeError + + # Act + _, _, _ = mock_engine.verify_dicom_instance(test_instance, padding_width) + + # Assert + assert expected_error_type == exc_info.typename + # ------------------------------------------------------ # DicomImagePiiVerifyEngine.eval_dicom_instance() diff --git a/presidio-image-redactor/tests/test_dicom_image_redactor_engine.py b/presidio-image-redactor/tests/test_dicom_image_redactor_engine.py index 7bea8fcb6..3dc7acd47 100644 --- a/presidio-image-redactor/tests/test_dicom_image_redactor_engine.py +++ b/presidio-image-redactor/tests/test_dicom_image_redactor_engine.py @@ -41,6 +41,7 @@ def mock_engine(): Path(TEST_DICOM_PARENT_DIR), [ Path(TEST_DICOM_PARENT_DIR, "0_ORIGINAL.dcm"), + Path(TEST_DICOM_PARENT_DIR, "0_ORIGINAL_no_pixels.dcm"), Path(TEST_DICOM_PARENT_DIR, "RGB_ORIGINAL.dcm"), Path(TEST_DICOM_DIR_2, "1_ORIGINAL.DCM"), Path(TEST_DICOM_DIR_2, "2_ORIGINAL.dicom"), @@ -537,7 +538,7 @@ def test_add_padding_exceptions( "src_path, expected_num_of_files", [ (Path(TEST_DICOM_PARENT_DIR, "0_ORIGINAL.dcm"), 1), - (Path(TEST_DICOM_PARENT_DIR), 15), + (Path(TEST_DICOM_PARENT_DIR), 16), (Path(TEST_DICOM_DIR_1), 3), (Path(TEST_DICOM_DIR_2), 2), (Path(TEST_DICOM_DIR_3), 1), @@ -1120,7 +1121,8 @@ def test_DicomImageRedactorEngine_redact_happy_path( (Path(TEST_DICOM_PARENT_DIR), "TypeError"), ("path_here", "TypeError"), (np.random.randint(255, size=(64, 64)), "TypeError"), - (Image.fromarray(np.random.randint(255, size=(400, 400),dtype=np.uint8)), "TypeError") + (Image.fromarray(np.random.randint(255, size=(400, 400),dtype=np.uint8)), "TypeError"), + (pydicom.dcmread(Path(TEST_DICOM_PARENT_DIR, "0_ORIGINAL_no_pixels.dcm")), "AttributeError") ], ) def test_DicomImageRedactorEngine_redact_exceptions( @@ -1255,6 +1257,7 @@ def save_as(self, dst_path: str): [ (Path(TEST_DICOM_PARENT_DIR), "FileNotFoundError"), (Path("nonexistentfile.extension"), "FileNotFoundError"), + (Path(TEST_DICOM_PARENT_DIR, "0_ORIGINAL_no_pixels.dcm"), "AttributeError") ], ) def test_DicomImageRedactorEngine_redact_single_dicom_image_exceptions(