diff --git a/VVTerminal.py b/VVTerminal.py index 686e5bb..1c888c0 100755 --- a/VVTerminal.py +++ b/VVTerminal.py @@ -158,10 +158,14 @@ def process_volume( # region volume, image_shape = ImProc.load_volume(volume_file, verbose=verbose) - if not ImProc.volume_check(volume, loading=True, verbose=verbose): + if volume is None: # make sure the image was loaded. if verbose: print("Error loading volume.") break + elif not ImProc.binary_check(volume): + if verbose: + print("Error: Non-binary image loaded.") + break # If there as an ROI, segment the ROI from the volume. if roi_name: @@ -194,7 +198,7 @@ def process_volume( ) # Make sure the ROI is in the volume. - if not roi_volume or not ImProc.volume_check(volume, verbose=verbose): + if not roi_volume or not ImProc.segmentation_check(volume): ResExp.cache_result( [filename, roi_name, "ROI not in dataset."] ) # Cache results diff --git a/library/image_processing.py b/library/image_processing.py index f89356b..ec13530 100644 --- a/library/image_processing.py +++ b/library/image_processing.py @@ -80,15 +80,45 @@ def reshape_2D(points, volume, verbose=False): return points, volume, image_shape -# Confirm that the volume was either loaded or segmented properly -def volume_check(volume, loading=False, verbose=False): +def binary_check(volume: np.ndarray) -> bool: + """Return a bool indicating if the loaded volume is binary or not. + + Takes a slice from the volume and checks to confirm that only two unique + values are present. + + Parameters: + volume : np.ndarray + + Returns: + bool + True if the spot check of the volume only return two unique values, + False if more than two unique values were identified. + """ + middle = int(volume.shape[0] / 2) + unique = np.unique(volume[middle]) + + return unique.shape[0] < 3 + + +def segmentation_check(volume: np.ndarray) -> bool: + """Return a bool indicating if volume has vessels after the segmentation. + + Some regions of interest may be present in the annotation, but there may + be no corresponding vasculature in the datasets. This function checks to see + if vessels are present. + + Parameters: + volume : np.ndarray + + Returns: + bool + True if vessels are present, False if not. + """ if volume is None: return False - - elif not loading and not np.any(volume): + elif not np.any(volume): return False - else: - return True + return True # Returns file size in bytes diff --git a/library/qt_threading.py b/library/qt_threading.py index 233ff5e..6e0a743 100644 --- a/library/qt_threading.py +++ b/library/qt_threading.py @@ -95,11 +95,17 @@ def run(self): ## Volume processing volume, image_shape = ImProc.load_volume(volume_file) - if not ImProc.volume_check(volume, loading=True): + + if volume is None: file_size = helpers.get_file_size(volume_file, GB=True) self.analysis_status.emit([i, "Error: Unable to read image."]) file_analyzed = False break + elif not ImProc.binary_check(volume): + file_size = helpers.get_file_size(volume_file, GB=True) + self.analysis_status.emit([i, "Error: Non-binary image loaded."]) + file_analyzed = False + break if roi_name: roi_id = j % 255 @@ -138,7 +144,7 @@ def run(self): # point_minima += 1 # Make sure the volume is still present after ROI segmentation - if not roi_volume or not ImProc.volume_check(volume): + if not roi_volume or not ImProc.segmentation_check(volume): self.analysis_status.emit([i, "ROI not in dataset..."]) # Cache results ResExp.cache_result([filename, roi_name, "ROI not in dataset."]) @@ -428,6 +434,8 @@ def run(self): progress = 0 step_weight = (70 / roi_count) / 8 + error_present = False + # Iterate through the ROI's or single file and generate graphs for i, roi_name in enumerate(annotation_data.keys()): ## File initialization @@ -439,9 +447,16 @@ def run(self): ## Volume processing volume, image_shape = ImProc.load_volume(volume_file) - if not ImProc.volume_check(volume, loading=True): + if volume is None: self.analysis_status.emit(["Error: Unable to read image.", 0]) + error_present = True break + elif not ImProc.binary_check(volume): + file_size = helpers.get_file_size(volume_file, GB=True) + self.analysis_status.emit(["Error: Non-binary image loaded.", 0]) + error_present = True + break + progress += step_weight if roi_name: @@ -460,6 +475,7 @@ def run(self): self.failure_emit.emit(1) self.running = False self.complete = True + error_present = True return self.analysis_status.emit(["Labeling volume...", progress]) @@ -469,6 +485,7 @@ def run(self): ) if roi_volumes is None: self.analysis_status.emit(["Error labeling volume...", 0]) + error_present = True break roi_volume = roi_volumes[roi_id] @@ -479,7 +496,7 @@ def run(self): point_minima, point_maxima, roi_id + 1 ) - if not roi_volume or not ImProc.volume_check(volume): + if not roi_volume or not ImProc.segmentation_check(volume): progress += step_weight * 7 self.analysis_status.emit(["ROI not in dataset...", progress]) continue @@ -598,9 +615,10 @@ def run(self): status_updater=self.analysis_status, ) else: - self.analysis_status.emit( - ["Visualization cancelled: Volume has no vessels.", 0] - ) + if not error_present: + self.analysis_status.emit( + ["Visualization cancelled: Volume has no vessels.", 0] + ) self.failure_emit.emit(1) self.running = False diff --git a/tests/test_files/non_binary_volume.tiff b/tests/test_files/non_binary_volume.tiff new file mode 100644 index 0000000..f7732c3 Binary files /dev/null and b/tests/test_files/non_binary_volume.tiff differ diff --git a/tests/test_image_processing.py b/tests/test_image_processing.py new file mode 100644 index 0000000..cc9fbc1 --- /dev/null +++ b/tests/test_image_processing.py @@ -0,0 +1,41 @@ +import os +import sys + +import pytest + +sys.path.insert(1, "/Users/jacobbumgarner/Documents/GitHub/VesselVio") + + +import numpy as np +from library import image_processing + +from skimage.io import imread + + +THIS_PATH = os.path.realpath(__file__) +TEST_FILES = os.path.join(os.path.dirname(THIS_PATH), "test_files") + + +@pytest.mark.datafiles(TEST_FILES) +def test_binary_check(datafiles): + volume = imread(os.path.join(datafiles, "non_binary_volume.tiff")) + + # test 3D + assert image_processing.binary_check(volume) is False + assert image_processing.binary_check(volume > 0) is True + + # test 2D + assert image_processing.binary_check(volume[0]) is False + assert image_processing.binary_check(volume[0] > 0) is True + + +@pytest.mark.datafiles(TEST_FILES) +def test_segmentation_check(datafiles): + # test None + assert image_processing.segmentation_check(None) is False + + # test full + assert image_processing.segmentation_check(np.ones((5, 5, 5))) is True + + # test empty + assert image_processing.segmentation_check(np.zeros((5, 5, 5))) is False