diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b876338..c7bdeda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,8 +39,29 @@ jobs: path: ${{github.workspace}}/dist/raidionicsseg-*.whl if-no-files-found: error + setup-test-data: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Download test resources + working-directory: tests + run: | + pip install requests + python -c "from download_resources import download_resources; download_resources('../test_data')" + + - name: Upload test resources + uses: actions/upload-artifact@v4 + with: + name: test-resources + path: ./test_data test: - needs: build + needs: [build, setup-test-data] runs-on: ${{ matrix.os }} strategy: matrix: @@ -127,6 +148,12 @@ jobs: - name: Clone repo uses: actions/checkout@v4 + - name: Download test resources + uses: actions/download-artifact@v4 + with: + name: test-resources + path: ./tests/unit_tests_results_dir + - name: Integration tests run: | pip install pytest pytest-cov pytest-timeout requests diff --git a/.github/workflows/build_macos_arm_11.yml b/.github/workflows/build_macos_arm_11.yml index 34c6847..1aa6634 100644 --- a/.github/workflows/build_macos_arm_11.yml +++ b/.github/workflows/build_macos_arm_11.yml @@ -76,7 +76,7 @@ jobs: run: cd ${{github.workspace}}/tests && python3 test_inference_segmentation_mediastinum.py - name: Inference unit test with test-time augmentation - run: cd ${{github.workspace}}/tests && python3 test_inference_segmentation_test_time_augmentation.py + run: cd ${{github.workspace}}/tests && python3 test_inference_segmentation_advanced.py # - name: Test with pytest # run: | diff --git a/pyproject.toml b/pyproject.toml index 24e3022..1460841 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "nibabel", "h5py", "pandas", - "SimpleITK", + "SimpleITK<=2.4.1", "aenum", "scikit-image", "tqdm", diff --git a/raidionicsseg/PreProcessing/brain_clipping.py b/raidionicsseg/PreProcessing/brain_clipping.py index 96d196e..cb0fb43 100644 --- a/raidionicsseg/PreProcessing/brain_clipping.py +++ b/raidionicsseg/PreProcessing/brain_clipping.py @@ -1,11 +1,10 @@ -import configparser import logging import os -import subprocess from copy import deepcopy from pathlib import PurePath from typing import List from typing import Tuple +from typing import Union import numpy as np from nibabel.processing import resample_to_output @@ -18,12 +17,10 @@ def crop_MR_background( - filepath: str, volume: np.ndarray, new_spacing: Tuple[float], - storage_path: str, parameters: ConfigResources, - crop_bbox=None, + crop_bbox: List[int] = None, ) -> Tuple[np.ndarray, List[int]]: """ Performs different background cropping inside an MRI volume, as defined by the 'crop_background' stored in a model @@ -53,12 +50,14 @@ def crop_MR_background( The bounding region is expressed as: [minx, miny, minz, maxx, maxy, maxz]. """ if parameters.crop_background == "minimum": - return crop_MR(volume, parameters, crop_bbox) + return crop_background_minimum(volume=volume, crop_bbox=crop_bbox) elif parameters.crop_background == "brain_clip" or parameters.crop_background == "brain_mask": - return skull_stripping_tf(filepath, volume, new_spacing, storage_path, parameters) + return skull_stripping(volume=volume, new_spacing=new_spacing, parameters=parameters) -def crop_MR(volume: np.ndarray, parameters, crop_bbox=None) -> Tuple[np.ndarray, List[int]]: +def crop_background_minimum( + volume: np.ndarray, crop_bbox: Union[None, List[int]] = None +) -> Tuple[np.ndarray, List[int]]: """ Performs background cropping inside an MRI volume in 'minimum' mode whereby the black space around the head is removed. @@ -67,8 +66,7 @@ def crop_MR(volume: np.ndarray, parameters, crop_bbox=None) -> Tuple[np.ndarray, ---------- volume : np.ndarray Patient MRI volume to crop. - parameters : :obj:`ConfigResources` - UNUSED, to remove. + crop_bbox: Returns ------- np.ndarray @@ -107,11 +105,11 @@ def crop_MR(volume: np.ndarray, parameters, crop_bbox=None) -> Tuple[np.ndarray, return cropped_volume, crop_bbox -def skull_stripping_tf( - filepath, volume: np.ndarray, new_spacing: Tuple[float], storage_path: str, parameters: ConfigResources +def skull_stripping( + volume: np.ndarray, new_spacing: Tuple[float], parameters: ConfigResources ) -> Tuple[np.ndarray, List[int]]: """ - Generates a brain segmentation mask by running model inference and performs skull stripping. + Performs skull stripping over the provided input volume using the brain mask manually provided. Parameters ---------- @@ -119,8 +117,6 @@ def skull_stripping_tf( Patient MRI volume to skull strip. new_spacing : Tuple[float] . - storage_path : str - Destination folder where the results will be stored. parameters : :obj:`ConfigResources` Loaded configuration specifying runtime parameters. Returns @@ -132,23 +128,7 @@ def skull_stripping_tf( The bounding region is expressed as: [minx, miny, minz, maxx, maxy, maxz]. """ if not os.path.exists(parameters.runtime_brain_mask_filepath): - brain_config_filename = os.path.join(os.path.dirname(parameters.config_filename), "brain_main_config.ini") - new_parameters = configparser.ConfigParser() - new_parameters.read(parameters.config_filename) - new_parameters.set( - "System", "model_folder", os.path.join(os.path.dirname(parameters.model_folder), "MRI_Brain") - ) - new_parameters.set("Runtime", "reconstruction_method", "thresholding") - new_parameters.set("Runtime", "reconstruction_order", "resample_first") - with open(brain_config_filename, "w") as cf: - new_parameters.write(cf) - old_parameters = deepcopy(parameters) - from raidionicsseg.fit import run_model - - run_model(brain_config_filename) - brain_mask_filename = os.path.join(storage_path, "labels_Brain.nii.gz") - os.remove(brain_config_filename) - parameters = old_parameters + raise ValueError("A brain segmentation mask must be provided inside ['Neuro']['brain_segmentation_filename']") else: brain_mask_filename = parameters.runtime_brain_mask_filepath @@ -176,7 +156,7 @@ def advanced_crop_exclude_background( volume: np.ndarray, crop_mode: str, brain_mask: np.ndarray ) -> Tuple[np.ndarray, List[int]]: """ - Perfoms skull stripping either in mode 'brain_clip' or mode 'brain_mask'. + Performs skull stripping either in mode 'brain_clip' or mode 'brain_mask'. Parameters ---------- diff --git a/raidionicsseg/PreProcessing/mediastinum_clipping.py b/raidionicsseg/PreProcessing/mediastinum_clipping.py index abb0878..58b2b0a 100644 --- a/raidionicsseg/PreProcessing/mediastinum_clipping.py +++ b/raidionicsseg/PreProcessing/mediastinum_clipping.py @@ -1,4 +1,4 @@ -import configparser +import logging import os from copy import deepcopy from typing import List @@ -15,7 +15,7 @@ def crop_mediastinum_volume( - filepath: str, volume: np.ndarray, new_spacing: Tuple[float], storage_path: str, parameters: ConfigResources + volume: np.ndarray, new_spacing: Tuple[float], parameters: ConfigResources, crop_bbox: List[int] = None ) -> Tuple[np.ndarray, List[int]]: """ Performs different background cropping inside a mediastinal CT volume, as defined by the 'crop_background' stored @@ -25,14 +25,10 @@ def crop_mediastinum_volume( Parameters ---------- - filepath : str - Filepath of the input volume (CT or MRI) to use. volume : np.ndarray . new_spacing : Tuple[float] . - storage_path : str - Destination folder where the results will be stored. parameters : :obj:`ConfigResources` Loaded configuration specifying runtime parameters. Returns @@ -45,90 +41,110 @@ def crop_mediastinum_volume( The bounding region is expressed as: [minx, miny, minz, maxx, maxy, maxz]. """ if parameters.crop_background == "minimum": - return mediastinum_clipping(volume, parameters) - elif parameters.crop_background == "brain_clip" or parameters.crop_background == "brain_mask": - return mediastinum_clipping_DL(filepath, volume, new_spacing, storage_path, parameters) - - -def mediastinum_clipping(volume, parameters): - intensity_threshold = -250 - airmetal_mask = deepcopy(volume) - airmetal_mask[airmetal_mask > intensity_threshold] = 0 - airmetal_mask[airmetal_mask <= intensity_threshold] = 1 - - airmetal_mask = smo.binary_closing(airmetal_mask, iterations=5) - - labels, nb_components = smeas.label(airmetal_mask) - airmetal_pieces = smeas.find_objects(labels, min(nb_components, 1000)) - - nums = [] - for p in enumerate(airmetal_pieces): - bb = p[1] - z = bb[2].stop - bb[2].start - y = bb[1].stop - bb[1].start - x = bb[0].stop - bb[0].start - nums.append(x * y * z) - - # Should check if the first two or three elements are "as big". If two the following code is correct, if three - # something should be changed so that the lungs is the third (normally?) and background the first two. - ind_bg = nums.index(np.max(nums)) + 1 - nums.remove(np.max(nums)) - ind_lungs = ( - nums.index(np.max(nums)) + 1 + 1 - ) # +1 because find_objects labels start at 1, and +1 because we remove one value above - # ind_lungs = nums.index(sorted(nums, reverse=True)[0])+1 - # for l in range(nb_components): - # nums.append(np.count_nonzero(np.where(labels == l))) - - background_mask = np.copy(labels) - background_mask[background_mask != ind_bg] = 0 - - lungstrachea_mask = np.copy(labels) - lungstrachea_mask[lungstrachea_mask != ind_lungs] = 0 - lungstrachea_mask[lungstrachea_mask == ind_lungs] = 1 - - lungs_boundingbox = airmetal_pieces[ind_lungs - 1] # Because indexing starts at 0, so have to decrease by one - crop_bbox = [ - lungs_boundingbox[0].start, - lungs_boundingbox[1].start, - lungs_boundingbox[2].start, - lungs_boundingbox[0].stop, - lungs_boundingbox[1].stop, - lungs_boundingbox[2].stop, - ] - - cropped_volume = volume[crop_bbox[0] : crop_bbox[3], crop_bbox[1] : crop_bbox[4], crop_bbox[2] : crop_bbox[5]] - - print("Cropped mediastinum values: {}".format(lungs_boundingbox)) + return mediastinum_clipping(volume=volume, parameters=parameters, crop_bbox=crop_bbox) + elif parameters.crop_background == "lungs_clip" or parameters.crop_background == "lungs_mask": + return mediastinum_clipping_advanced( + volume=volume, new_spacing=new_spacing, parameters=parameters + ) + + +def mediastinum_clipping( + volume: np.ndarray, parameters: ConfigResources, crop_bbox=None +) -> Tuple[np.ndarray, List[int]]: + if crop_bbox is None: + intensity_threshold = -250 + airmetal_mask = deepcopy(volume) + airmetal_mask[airmetal_mask > intensity_threshold] = 0 + airmetal_mask[airmetal_mask <= intensity_threshold] = 1 + + airmetal_mask = smo.binary_closing(airmetal_mask, iterations=5) + + labels, nb_components = smeas.label(airmetal_mask) + airmetal_pieces = smeas.find_objects(labels, min(nb_components, 1000)) + + nums = [] + for p in enumerate(airmetal_pieces): + bb = p[1] + z = bb[2].stop - bb[2].start + y = bb[1].stop - bb[1].start + x = bb[0].stop - bb[0].start + nums.append(x * y * z) + + # Should check if the first two or three elements are "as big". If two the following code is correct, if three + # something should be changed so that the lungs is the third (normally?) and background the first two. + ind_bg = nums.index(np.max(nums)) + 1 + nums.remove(np.max(nums)) + ind_lungs = ( + nums.index(np.max(nums)) + 1 + 1 + ) # +1 because find_objects labels start at 1, and +1 because we remove one value above + # ind_lungs = nums.index(sorted(nums, reverse=True)[0])+1 + # for l in range(nb_components): + # nums.append(np.count_nonzero(np.where(labels == l))) + + background_mask = np.copy(labels) + background_mask[background_mask != ind_bg] = 0 + + lungstrachea_mask = np.copy(labels) + lungstrachea_mask[lungstrachea_mask != ind_lungs] = 0 + lungstrachea_mask[lungstrachea_mask == ind_lungs] = 1 + + lungs_boundingbox = airmetal_pieces[ind_lungs - 1] # Because indexing starts at 0, so have to decrease by one + crop_bbox = [ + lungs_boundingbox[0].start, + lungs_boundingbox[1].start, + lungs_boundingbox[2].start, + lungs_boundingbox[0].stop, + lungs_boundingbox[1].stop, + lungs_boundingbox[2].stop, + ] + + cropped_volume = volume[crop_bbox[0] : crop_bbox[3], crop_bbox[1] : crop_bbox[4], crop_bbox[2] : crop_bbox[5]] + else: + min_row, min_col, min_depth, max_row, max_col, max_depth = ( + crop_bbox[0], + crop_bbox[1], + crop_bbox[2], + crop_bbox[3], + crop_bbox[4], + crop_bbox[5], + ) + cropped_volume = volume[min_row:max_row, min_col:max_col, min_depth:max_depth] + logging.debug( + "Mediastinum background cropping with: [{}, {}, {}, {}, {}, {}].\n".format( + crop_bbox[0], crop_bbox[1], crop_bbox[2], crop_bbox[3], crop_bbox[4], crop_bbox[5] + ) + ) return cropped_volume, crop_bbox -def mediastinum_clipping_DL(filepath, volume, new_spacing, storage_path, parameters): +def mediastinum_clipping_advanced( + volume: np.ndarray, new_spacing: Tuple[float], parameters: ConfigResources +) -> Tuple[np.ndarray, List[int]]: + """ + + Parameters + ---------- + volume + new_spacing + parameters + + Returns + ------- + + """ if not parameters.runtime_lungs_mask_filepath and not os.path.exists(parameters.runtime_lungs_mask_filepath): - lung_config_filename = os.path.join(os.path.dirname(parameters.config_filename), "lungs_main_config.ini") - new_parameters = configparser.ConfigParser() - new_parameters.read(parameters.config_filename) - new_parameters.set("System", "model_folder", os.path.join(os.path.dirname(parameters.model_folder), "CT_Lungs")) - new_parameters.set("Runtime", "reconstruction_method", "thresholding") - new_parameters.set("Runtime", "reconstruction_order", "resample_first") - with open(lung_config_filename, "w") as cf: - new_parameters.write(cf) - old_parameters = deepcopy(parameters) - from raidionicsseg.fit import run_model - - run_model(lung_config_filename) - lungs_mask_filename = os.path.join(storage_path, "labels_Lungs.nii.gz") - os.remove(lung_config_filename) - parameters = old_parameters + raise ValueError( + "A brain segmentation mask must be provided inside ['Mediastinum']['lungs_segmentation_filename']" + ) else: lungs_mask_filename = parameters.runtime_lungs_mask_filepath lungs_mask_ni = load_nifti_volume(lungs_mask_filename) resampled_volume = resample_to_output(lungs_mask_ni, new_spacing, order=0) lungs_mask = resampled_volume.get_fdata().astype("uint8") + # In case the lungs mask has a different label for each lung lungs_mask[lungs_mask != 0] = 1 - lung_region = regionprops(lungs_mask) min_row, min_col, min_depth, max_row, max_col, max_depth = lung_region[0].bbox if parameters.crop_background == "invert": diff --git a/raidionicsseg/PreProcessing/pre_processing.py b/raidionicsseg/PreProcessing/pre_processing.py index fccecf3..e85b830 100644 --- a/raidionicsseg/PreProcessing/pre_processing.py +++ b/raidionicsseg/PreProcessing/pre_processing.py @@ -1,7 +1,6 @@ import logging import os from copy import deepcopy -from typing import Any from typing import List from typing import Tuple @@ -17,8 +16,7 @@ from ..Utils.volume_utilities import intensity_normalization from ..Utils.volume_utilities import resize_volume from .brain_clipping import crop_MR_background -from .mediastinum_clipping import mediastinum_clipping -from .mediastinum_clipping import mediastinum_clipping_DL +from .mediastinum_clipping import crop_mediastinum_volume def prepare_pre_processing( @@ -49,9 +47,6 @@ def prepare_pre_processing( if pre_processing_parameters.preprocessing_inputs_sub_indexes is not None: for ip in pre_processing_parameters.preprocessing_inputs_sub_indexes: - # input0_fn = os.path.join(folder, 'input' + str(ip[0]) + '.nii.gz') - # input1_fn = os.path.join(folder, 'input' + str(ip[1]) + '.nii.gz') - # new_input = abs(nib.load(input0_fn).get_fdata()[:] - nib.load(input1_fn).get_fdata()[:]) new_input = abs(non_norm_inputs[ip[0]] - non_norm_inputs[ip[1]]) data = run_pre_processing_diffloader( new_input, @@ -102,7 +97,7 @@ def run_pre_processing( logging.debug("Preprocessing - Resampling.") new_spacing = pre_processing_parameters.output_spacing - if pre_processing_parameters.output_spacing == None: + if pre_processing_parameters.output_spacing is None: tmp = np.min(nib_volume.header.get_zooms()) new_spacing = [tmp, tmp, tmp] @@ -117,9 +112,8 @@ def run_pre_processing( pre_processing_parameters.crop_background is not None and pre_processing_parameters.crop_background != "false" ): - # data, crop_bbox = mediastinum_clipping(volume=data, parameters=pre_processing_parameters) - data, crop_bbox = mediastinum_clipping_DL( - filename, data, new_spacing, storage_path, pre_processing_parameters + data, crop_bbox = crop_mediastinum_volume( + volume=data, new_spacing=new_spacing, parameters=pre_processing_parameters, crop_bbox=crop_bbox ) else: if ( @@ -127,7 +121,7 @@ def run_pre_processing( and not pre_processing_parameters.predictions_use_preprocessed_data ): data, crop_bbox = crop_MR_background( - filename, data, new_spacing, storage_path, pre_processing_parameters, crop_bbox + volume=data, new_spacing=new_spacing, parameters=pre_processing_parameters, crop_bbox=crop_bbox ) if pre_processing_parameters.new_axial_size: diff --git a/raidionicsseg/Utils/io.py b/raidionicsseg/Utils/io.py index 29a39e8..83b3c39 100644 --- a/raidionicsseg/Utils/io.py +++ b/raidionicsseg/Utils/io.py @@ -68,7 +68,7 @@ def dump_predictions( nib.save(img, predictions_output_path) except Exception as e: logging.error( - "Following error collected during model predictions dump on disk: \n {}".format(traceback.format_exc()) + f"Following error collected during model predictions dump on disk: {e} \n {traceback.format_exc()}" ) raise ValueError("Predictions dump on disk could not fully proceed.") @@ -105,7 +105,8 @@ def dump_classification_predictions(predictions: np.ndarray, parameters: ConfigR elif reconstruction_method == "argmax": prediction_filename = os.path.join(storage_path, "classification-label.csv") with open(prediction_filename, "w") as file: - file.write("Class: {}\n".format(class_names[np.argmax(predictions)])) + file.write("Class\n") + file.write("{}\n".format(class_names[np.argmax(predictions)])) else: raise ValueError( "No classification reconstruction method for {}. \n " diff --git a/raidionicsseg/Utils/volume_utilities.py b/raidionicsseg/Utils/volume_utilities.py index 4f8ed6e..7d43dd1 100644 --- a/raidionicsseg/Utils/volume_utilities.py +++ b/raidionicsseg/Utils/volume_utilities.py @@ -2,10 +2,10 @@ import numpy as np import SimpleITK as sitk -from skimage.measure import regionprops from skimage.transform import resize -from .configuration_parser import * +from .configuration_parser import ConfigResources +from .configuration_parser import ImagingModalityType def input_file_category_disambiguation(input_filename: str) -> str: @@ -58,12 +58,9 @@ def resize_volume(volume, new_slice_size, slicing_plane, order=1): return new_volume -def __intensity_normalization_CT(volume, parameters): +def __intensity_normalization_CT(volume: np.ndarray, parameters: ConfigResources) -> np.ndarray: result = np.copy(volume) - # result[volume < parameters.intensity_clipping_values[0]] = parameters.intensity_clipping_values[0] - # result[volume > parameters.intensity_clipping_values[1]] = parameters.intensity_clipping_values[1] - if parameters.normalization_method == "zeromean": mean_val = np.mean(result) var_val = np.std(result) @@ -78,14 +75,19 @@ def __intensity_normalization_CT(volume, parameters): return result -def __intensity_normalization_MRI(volume, parameters): +def __intensity_normalization_MRI(volume: np.ndarray, parameters: ConfigResources) -> np.ndarray: + """ + + Parameters + ---------- + volume + parameters + + Returns + ------- + + """ result = deepcopy(volume).astype("float32") - # result[result < 0] = 0 # Soft clipping at 0 for MRI - # if parameters.intensity_clipping_range[1] - parameters.intensity_clipping_range[0] != 100: - # limits = np.percentile(volume, q=parameters.intensity_clipping_range) - # result = np.clip(volume, limits[0], limits[1]) - # # result[volume < limits[0]] = limits[0] - # # result[volume > limits[1]] = limits[1] if parameters.normalization_method == "zeromean": mean_val = np.mean(result) @@ -110,14 +112,14 @@ def __intensity_normalization_MRI(volume, parameters): return result.astype("float32") -def intensity_normalization(volume, parameters): +def intensity_normalization(volume: np.ndarray, parameters: ConfigResources) -> np.ndarray: if parameters.imaging_modality == ImagingModalityType.CT: return __intensity_normalization_CT(volume, parameters) elif parameters.imaging_modality == ImagingModalityType.MRI: return __intensity_normalization_MRI(volume, parameters) -def intensity_clipping(volume, parameters): +def intensity_clipping(volume: np.ndarray, parameters: ConfigResources) -> np.ndarray: result = deepcopy(volume) if parameters.imaging_modality == ImagingModalityType.MRI: result[result < 0] = 0 # Soft clipping at 0 for MRI diff --git a/raidionicsseg/__main__.py b/raidionicsseg/__main__.py index 73867c1..9da02a2 100644 --- a/raidionicsseg/__main__.py +++ b/raidionicsseg/__main__.py @@ -46,7 +46,7 @@ def main(): try: run_model(config_filename=config_filename) except Exception as e: - print("{}".format(traceback.format_exc())) + print(f"{e}\n{traceback.format_exc()}") if __name__ == "__main__": diff --git a/raidionicsseg/fit.py b/raidionicsseg/fit.py index 826a002..28f031b 100644 --- a/raidionicsseg/fit.py +++ b/raidionicsseg/fit.py @@ -3,7 +3,6 @@ import multiprocessing as mp import os import platform -import threading import time import traceback @@ -12,7 +11,6 @@ from .Inference.predictions_reconstruction import reconstruct_post_predictions from .PreProcessing.pre_processing import prepare_pre_processing from .Utils.configuration_parser import ConfigResources -from .Utils.configuration_parser import * from .Utils.io import dump_classification_predictions from .Utils.io import dump_predictions @@ -79,7 +77,6 @@ def __segment(pre_processing_parameters: ConfigResources) -> None: os.path.basename(inputs_folder), os.path.basename(base_model_path) ) ) - # models_path = sorted(glob.glob(os.path.join(base_model_path, "**", "*")), key=lambda x: x.split('/')[-2][0]) models_path = sorted( glob.glob(os.path.join(base_model_path, "**", "*")), key=lambda x: os.path.basename(os.path.dirname(x)) ) @@ -146,7 +143,6 @@ def __classify(pre_processing_parameters: ConfigResources): logging.info("LOG: Classification - 3 steps.") overall_start = start = time.time() - # models_path = sorted(glob.glob(os.path.join(base_model_path, "**", "*")), key=lambda x: x.split('/')[-2][0]) models_path = sorted( glob.glob(os.path.join(base_model_path, "**", "*")), key=lambda x: os.path.basename(os.path.dirname(x)) ) diff --git a/tests/conftest.py b/tests/conftest.py index 728b7e5..43526c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,131 +1,17 @@ import os import shutil import pytest -import requests import logging -import traceback -import zipfile +from tests.download_resources import download_resources @pytest.fixture(scope="session") -def temp_dir(): +def test_dir(): test_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'unit_tests_results_dir') - if os.path.exists(test_dir): - shutil.rmtree(test_dir) - os.makedirs(test_dir) + if not os.path.exists(test_dir): + os.makedirs(test_dir) + if not os.listdir(test_dir): + download_resources(test_dir=test_dir) yield test_dir logging.info(f"Removing the temporary directory for tests.") - shutil.rmtree(test_dir) - -@pytest.fixture(scope="session") -def data_test2(temp_dir): - try: - test_image_url = 'https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Samples-RaidionicsSegLib-UnitTest2.zip' - test_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-MRI_Brain-v13.zip' - dest_dir = os.path.join(temp_dir, "Test2") - if os.path.exists(dest_dir): - shutil.rmtree(dest_dir) - os.makedirs(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'inference_volume.zip') - headers = {} - response = requests.get(test_image_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'brain_model.zip') - headers = {} - response = requests.get(test_model_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - except Exception as e: - logging.error("Error during resources download with: \n {}.\n".format(traceback.format_exc())) - shutil.rmtree(dest_dir) - raise ValueError("Error during resources download.\n") - return dest_dir - - -@pytest.fixture(scope="session") -def data_test3(temp_dir): - try: - test_image_url = 'https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Samples-RaidionicsSegLib-UnitTest3.zip' - test_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-CT_Tumor-v13.zip' - dest_dir = os.path.join(temp_dir, "Test3") - if os.path.exists(dest_dir): - shutil.rmtree(dest_dir) - os.makedirs(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'inference_volume.zip') - headers = {} - response = requests.get(test_image_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'tumor_model.zip') - headers = {} - response = requests.get(test_model_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - - except Exception as e: - logging.error("Error during resources download with: \n {}.\n".format(traceback.format_exc())) - shutil.rmtree(dest_dir) - raise ValueError("Error during resources download.\n") - return dest_dir - -@pytest.fixture(scope="session") -def data_test_classification_neuro(temp_dir): - try: - test_image_url = 'https://github.com/raidionics/Raidionics-models/releases/download/1.2.0/Samples-RaidionicsSegLib-UnitTest2.zip' - test_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-MRI_SequenceClassifier-v13.zip' - dest_dir = os.path.join(temp_dir, "Test_classif_neuro") - if os.path.exists(dest_dir): - shutil.rmtree(dest_dir) - os.makedirs(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'inference_volume.zip') - headers = {} - response = requests.get(test_image_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - - archive_dl_dest = os.path.join(dest_dir, 'classif_model.zip') - headers = {} - response = requests.get(test_model_url, headers=headers, stream=True) - response.raise_for_status() - if response.status_code == requests.codes.ok: - with open(archive_dl_dest, "wb") as f: - for chunk in response.iter_content(chunk_size=1048576): - f.write(chunk) - with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: - zip_ref.extractall(dest_dir) - except Exception as e: - logging.error("Error during resources download with: \n {}.\n".format(traceback.format_exc())) - shutil.rmtree(dest_dir) - raise ValueError("Error during resources download.\n") - return dest_dir \ No newline at end of file + shutil.rmtree(test_dir) \ No newline at end of file diff --git a/tests/download_resources.py b/tests/download_resources.py new file mode 100644 index 0000000..06b3957 --- /dev/null +++ b/tests/download_resources.py @@ -0,0 +1,108 @@ +import os +import shutil +import requests +import logging +import traceback +import zipfile + + +def download_resources(test_dir: str): + try: + test_preop_neuro_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Samples-RaidionicsSegLib-UnitTest-PreopNeuro.zip' + test_diffloader_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Samples-RaidionicsSegLib-UnitTest-DiffLoader.zip' + test_medi_url = "https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Samples-RaidionicsSegLib-UnitTest-Mediastinum.zip" + dest_dir = os.path.join(test_dir, "Inputs") + os.makedirs(dest_dir, exist_ok=True) + + archive_dl_dest = os.path.join(dest_dir, 'preop_neuro_usecase.zip') + headers = {} + response = requests.get(test_preop_neuro_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + archive_dl_dest = os.path.join(dest_dir, 'diffloader_usecase.zip') + headers = {} + response = requests.get(test_diffloader_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + archive_dl_dest = os.path.join(dest_dir, 'mediastinum_usecase.zip') + headers = {} + response = requests.get(test_medi_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + except Exception as e: + logging.error(f"Error during input data download with: {e} \n {traceback.format_exc()}\n") + shutil.rmtree(dest_dir) + raise ValueError("Error during input data download.\n") + + try: + brain_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-MRI_Brain-v13.zip' + postop_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-MRI_TumorCE_Postop-v13.zip' + seq_classif_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-MRI_SequenceClassifier-v13.zip' + ct_tumor_model_url = 'https://github.com/raidionics/Raidionics-models/releases/download/v1.3.0-rc/Raidionics-CT_Tumor-v13.zip' + dest_dir = os.path.join(test_dir, "Models") + os.makedirs(dest_dir, exist_ok=True) + + archive_dl_dest = os.path.join(dest_dir, 'brain_model.zip') + headers = {} + response = requests.get(brain_model_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + archive_dl_dest = os.path.join(dest_dir, 'postop_model.zip') + headers = {} + response = requests.get(postop_model_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + archive_dl_dest = os.path.join(dest_dir, 'seq_classif_model.zip') + headers = {} + response = requests.get(seq_classif_model_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + + archive_dl_dest = os.path.join(dest_dir, 'ct_tumor_model.zip') + headers = {} + response = requests.get(ct_tumor_model_url, headers=headers, stream=True) + response.raise_for_status() + if response.status_code == requests.codes.ok: + with open(archive_dl_dest, "wb") as f: + for chunk in response.iter_content(chunk_size=1048576): + f.write(chunk) + with zipfile.ZipFile(archive_dl_dest, 'r') as zip_ref: + zip_ref.extractall(dest_dir) + except Exception as e: + logging.error(f"Error during models download with: {e}\n {traceback.format_exc()}.\n") + shutil.rmtree(dest_dir) + raise ValueError("Error during models download.\n") diff --git a/tests/generic_tests/test_inference_classification_simple.py b/tests/generic_tests/test_inference_classification_simple.py index 016eec6..28c8bfa 100644 --- a/tests/generic_tests/test_inference_classification_simple.py +++ b/tests/generic_tests/test_inference_classification_simple.py @@ -2,28 +2,29 @@ import shutil import configparser import logging -import sys -import subprocess import traceback +import pandas as pd +import numpy as np -def test_inference_cli(data_test_classification_neuro): +def test_inference_classification_prob(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) - logging.info("Running standard inference using the CLI.\n") + logging.info("Running standard inference test as a Python package.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test_classification_neuro, "output_cli") + output_folder = os.path.join(test_dir, "output_package") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) + seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test_classification_neuro, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'DiffLoader', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test_classification_neuro, 'MRI_SequenceClassifier')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_SequenceClassifier')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -33,45 +34,36 @@ def test_inference_cli(data_test_classification_neuro): with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) - logging.info("Inference CLI test started.\n") try: - import platform - if platform.system() == 'Windows': - subprocess.check_call(['raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug'], shell=True) - elif platform.system() == 'Darwin' and platform.processor() == 'arm': - subprocess.check_call(['python3', '-m', 'raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug']) - else: - subprocess.check_call(['raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug']) + logging.info("Running inference.\n") + from raidionicsseg.fit import run_model + run_model(seg_config_filename) + + logging.info("Collecting and comparing results.\n") + classification_results_filename = os.path.join(output_folder, 'classification-results.csv') + assert os.path.exists(classification_results_filename), "No classification results were generated.\n" + results = pd.read_csv(classification_results_filename) + assert np.argmax(results["Prediction"].values) == 0, "The classification results is not T1-CE" except Exception as e: - logging.error(f"Error during inference CLI test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) - raise ValueError("Error during inference CLI test.\n") - - logging.info("Collecting and comparing results.\n") - classification_results_filename = os.path.join(output_folder, 'classification-results.csv') - assert os.path.exists(classification_results_filename), "Inference CLI test failed, no classification results were generated.\n" + raise ValueError("Error during inference Python package test.\n") except Exception as e: - logging.error(f"Error during inference CLI test with: \n {traceback.format_exc()}.\n") - raise ValueError("Error during inference CLI test.\n") + logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") + raise ValueError("Error during inference Python package test.\n") + if os.path.exists(output_folder): shutil.rmtree(output_folder) - -def test_inference_package(data_test_classification_neuro): +def test_inference_classification_labels(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) logging.info("Running standard inference test as a Python package.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test_classification_neuro, "output_package") + output_folder = os.path.join(test_dir, "output_package") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) @@ -79,14 +71,14 @@ def test_inference_package(data_test_classification_neuro): seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test_classification_neuro, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'DiffLoader', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test_classification_neuro, 'MRI_SequenceClassifier')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_SequenceClassifier')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') seg_config.set('Runtime', 'overlapping_ratio', '0.') - seg_config.set('Runtime', 'reconstruction_method', 'probabilities') + seg_config.set('Runtime', 'reconstruction_method', 'argmax') seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) @@ -97,8 +89,10 @@ def test_inference_package(data_test_classification_neuro): run_model(seg_config_filename) logging.info("Collecting and comparing results.\n") - classification_results_filename = os.path.join(output_folder, 'classification-results.csv') - assert os.path.exists(classification_results_filename), "Inference CLI test failed, no classification results were generated.\n" + classification_results_filename = os.path.join(output_folder, 'classification-label.csv') + assert os.path.exists(classification_results_filename), "No classification results were generated.\n" + results = pd.read_csv(classification_results_filename) + assert results["Class"].values[0] == "T1-CE", "The classification results is not T1-CE" except Exception as e: logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") if os.path.exists(output_folder): diff --git a/tests/generic_tests/test_inference_segmentation_test_time_augmentation.py b/tests/generic_tests/test_inference_segmentation_advanced.py similarity index 57% rename from tests/generic_tests/test_inference_segmentation_test_time_augmentation.py rename to tests/generic_tests/test_inference_segmentation_advanced.py index b49591b..bf3e3cd 100644 --- a/tests/generic_tests/test_inference_segmentation_test_time_augmentation.py +++ b/tests/generic_tests/test_inference_segmentation_advanced.py @@ -2,28 +2,29 @@ import shutil import configparser import logging -import sys -import subprocess import traceback +import nibabel as nib +import numpy as np -def test_inference_cli_tta(data_test2): +def test_inference_segmentation_tta_single_input(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) - logging.info("Running segmentation inference with TTA using the CLI.\n") + logging.info("Running inference with test-time augmentation.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test2, "output_cli_tta") + output_folder = os.path.join(test_dir, "output_package_tta") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) + seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test2, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test2, 'MRI_Brain')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_Brain')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -36,45 +37,34 @@ def test_inference_cli_tta(data_test2): with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) - logging.info("Inference with TTA CLI test started.\n") try: - import platform - if platform.system() == 'Windows': - subprocess.check_call(['raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug'], shell=True) - elif platform.system() == 'Darwin' and platform.processor() == 'arm': - subprocess.check_call(['python3', '-m', 'raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug']) - else: - subprocess.check_call(['raidionicsseg', - '{config}'.format(config=seg_config_filename), - '--verbose', 'debug']) + logging.info("Running inference\n") + from raidionicsseg.fit import run_model + run_model(seg_config_filename) + + logging.info("Collecting and comparing results.\n") + brain_segmentation_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') + assert os.path.exists(brain_segmentation_filename), "No brain mask was generated.\n" except Exception as e: - logging.error(f"Error during inference with TTA CLI test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference with TTA Python package test with: {e}\n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) - raise ValueError("Error during inference with TTA CLI test.\n") - - logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') - assert os.path.exists(brain_segmentation_filename), "Inference with TTA CLI test failed, no brain mask was generated.\n" + raise ValueError("Error during inference with TTA Python package test.\n") except Exception as e: - logging.error(f"Error during inference with TTA CLI test with: \n {traceback.format_exc()}.\n") - raise ValueError("Error during inference with TTA CLI test.\n") + logging.error(f"Error during inference with TTA Python package test with: {e} \n {traceback.format_exc()}.\n") + raise ValueError("Error during inference with TTA Python package test.\n") + if os.path.exists(output_folder): shutil.rmtree(output_folder) - -def test_inference_package_tta(data_test2): +def test_inference_segmentation_model_ensembling(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) - logging.info("Running inference with TTA test as a Python package.\n") + logging.info("Running inference with model ensembling.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test2, "output_package_tta") + output_folder = os.path.join(test_dir, "output_package_me") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) @@ -82,37 +72,43 @@ def test_inference_package_tta(data_test2): seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test2, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'DiffLoader', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test2, 'MRI_Brain')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_TumorCE_Postop/t1c_t1w_t1d')) seg_config.add_section('Runtime') - seg_config.set('Runtime', 'folds_ensembling', 'False') + seg_config.set('Runtime', 'folds_ensembling', '3') seg_config.set('Runtime', 'ensembling_strategy', 'average') seg_config.set('Runtime', 'overlapping_ratio', '0.') seg_config.set('Runtime', 'reconstruction_method', 'thresholding') seg_config.set('Runtime', 'reconstruction_order', 'resample_first') - seg_config.set('Runtime', 'test_time_augmentation_iteration', '2') + seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) try: - logging.info("Running inference with TTA.\n") + logging.info("Running inference\n") from raidionicsseg.fit import run_model run_model(seg_config_filename) logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') - assert os.path.exists(brain_segmentation_filename), "Inference with TTA Python test failed, no brain mask was generated.\n" + segmentation_pred_filename = os.path.join(output_folder, 'labels_TumorCE.nii.gz') + assert os.path.exists(segmentation_pred_filename), "No segmentation mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'DiffLoader', 'verif', 'input0_labels_TumorCE_foldensemble.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + # assert np.array_equal(segmentation_pred, segmentation_gt), "Ground truth and prediction arrays are not identical" + assert np.count_nonzero(np.abs( + segmentation_pred - segmentation_gt)) < 600, "Ground truth and prediction arrays are very different" except Exception as e: - logging.error(f"Error during inference with TTA Python package test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during model ensembling inference with: {e}\n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) - raise ValueError("Error during inference with TTA Python package test.\n") + raise ValueError("Error during model ensembling inference.\n") except Exception as e: - logging.error(f"Error during inference with TTA Python package test with: \n {traceback.format_exc()}.\n") - raise ValueError("Error during inference with TTA Python package test.\n") + logging.error(f"Error during model ensembling inference with: {e} \n {traceback.format_exc()}.\n") + raise ValueError("Error during model ensembling inference.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) \ No newline at end of file diff --git a/tests/generic_tests/test_inference_segmentation_diffloader.py b/tests/generic_tests/test_inference_segmentation_diffloader.py new file mode 100644 index 0000000..6d15a4d --- /dev/null +++ b/tests/generic_tests/test_inference_segmentation_diffloader.py @@ -0,0 +1,75 @@ +import os +import shutil +import configparser +import logging +import traceback +import nibabel as nib +import numpy as np + + +def test_inference_diffloader_neuro(test_dir): + """ + Testing the input difference loader between T1-CE and T1w inputs. + + Parameters + ---------- + input_data_dir + input_models_dir + + Returns + ------- + + """ + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + logging.info("Running standard inference test as a Python package.\n") + + logging.info("Preparing configuration file.\n") + try: + output_folder = os.path.join(test_dir, "output_diffloader") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + os.makedirs(output_folder) + + seg_config = configparser.ConfigParser() + seg_config.add_section('System') + seg_config.set('System', 'gpu_id', "-1") + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'DiffLoader', 'inputs')) + seg_config.set('System', 'output_folder', output_folder) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_TumorCE_Postop/t1c_t1w_t1d')) + seg_config.add_section('Runtime') + seg_config.set('Runtime', 'folds_ensembling', 'False') + seg_config.set('Runtime', 'ensembling_strategy', 'average') + seg_config.set('Runtime', 'overlapping_ratio', '0.') + seg_config.set('Runtime', 'reconstruction_method', 'thresholding') + seg_config.set('Runtime', 'reconstruction_order', 'resample_first') + seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') + seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') + seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') + with open(seg_config_filename, 'w') as outfile: + seg_config.write(outfile) + + try: + logging.info("Running inference.\n") + from raidionicsseg.fit import run_model + run_model(seg_config_filename) + + logging.info("Collecting and comparing results.\n") + segmentation_pred_filename = os.path.join(output_folder, 'labels_TumorCE.nii.gz') + assert os.path.exists(segmentation_pred_filename), "No tumorCE mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'DiffLoader', 'verif', 'input0_label-TumorCE.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + # assert np.array_equal(segmentation_pred, segmentation_gt), "Ground truth and prediction arrays are not identical" + assert np.count_nonzero(np.abs(segmentation_pred - segmentation_gt)) < 600, "Ground truth and prediction arrays are very different" + except Exception as e: + logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + raise ValueError("Error during inference Python package test.\n") + except Exception as e: + logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") + raise ValueError("Error during inference Python package test.\n") + + if os.path.exists(output_folder): + shutil.rmtree(output_folder) \ No newline at end of file diff --git a/tests/generic_tests/test_inference_segmentation_mediastinum.py b/tests/generic_tests/test_inference_segmentation_mediastinum.py index c47dbc9..7e7eee2 100644 --- a/tests/generic_tests/test_inference_segmentation_mediastinum.py +++ b/tests/generic_tests/test_inference_segmentation_mediastinum.py @@ -5,25 +5,27 @@ import sys import subprocess import traceback +import nibabel as nib +import numpy as np -def test_inference_cli(data_test3): +def test_inference_cli(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) logging.info("Running standard inference using the CLI for a mediastinum model.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test3, "output_cli") + output_folder = os.path.join(test_dir, "output_medi_cli") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test3, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', "Mediastinum", 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test3, 'CT_Tumor')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'CT_Tumor')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -33,7 +35,8 @@ def test_inference_cli(data_test3): seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') seg_config.add_section('Mediastinum') - seg_config.set('Mediastinum', 'lungs_segmentation_filename', os.path.join(data_test3, 'inputs', + seg_config.set('Mediastinum', 'lungs_segmentation_filename', os.path.join(test_dir, 'Inputs', + "Mediastinum", 'inputs', 'input0_label_lungs.nii.gz')) seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: @@ -47,9 +50,6 @@ def test_inference_cli(data_test3): '{config}'.format(config=seg_config_filename), '--verbose', 'debug'], shell=True) elif platform.system() == 'Darwin' and platform.processor() == 'arm': - # subprocess.check_call(['python3', '-m', 'raidionicsseg', - # '{config}'.format(config=seg_config_filename), - # '--verbose', 'debug']) result = subprocess.run(['python3', '-m', 'raidionicsseg', '{config}'.format(config=seg_config_filename), '--verbose', 'debug'], @@ -73,22 +73,27 @@ def test_inference_cli(data_test3): logging.info("Collecting and comparing results.\n") segmentation_filename = os.path.join(output_folder, 'labels_Tumor.nii.gz') - assert os.path.exists(segmentation_filename), "Inference CLI test failed, no tumor mask was generated.\n" + assert os.path.exists(segmentation_filename), "No tumor mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'Mediastinum', 'verif', 'input0_labels_Tumor.nii.gz') + segmentation_pred = nib.load(segmentation_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, + segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: - logging.error(f"Error during test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during test with: {e} \n {traceback.format_exc()}.\n") raise ValueError("Error during test.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) -def test_inference_package(data_test3): +def test_inference_package(test_dir): logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) logging.info("Running standard inference test as a Python package for a mediastinum model.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test3, "output_package") + output_folder = os.path.join(test_dir, "output_package") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) @@ -96,9 +101,9 @@ def test_inference_package(data_test3): seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test3, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'Mediastinum', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test3, 'CT_Tumor')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'CT_Tumor')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -108,7 +113,8 @@ def test_inference_package(data_test3): seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') seg_config.add_section('Mediastinum') - seg_config.set('Mediastinum', 'lungs_segmentation_filename', os.path.join(data_test3, 'inputs', + seg_config.set('Mediastinum', 'lungs_segmentation_filename', os.path.join(test_dir, 'Inputs', + "Mediastinum", 'inputs', 'input0_label_lungs.nii.gz')) seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: @@ -121,14 +127,19 @@ def test_inference_package(data_test3): logging.info("Collecting and comparing results.\n") segmentation_filename = os.path.join(output_folder, 'labels_Tumor.nii.gz') - assert os.path.exists(segmentation_filename), "Inference CLI test failed, no tumor mask was generated.\n" + assert os.path.exists(segmentation_filename), "No tumor mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'Mediastinum', 'verif', 'input0_labels_Tumor.nii.gz') + segmentation_pred = nib.load(segmentation_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, + segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: - logging.error(f"Error during test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during test with: {e} \n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) raise ValueError("Error during test.\n") except Exception as e: - logging.error(f"Error during test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during test with: {e} \n {traceback.format_exc()}.\n") raise ValueError("Error during test.\n") if os.path.exists(output_folder): diff --git a/tests/generic_tests/test_inference_segmentation_reconstruction.py b/tests/generic_tests/test_inference_segmentation_reconstruction.py new file mode 100644 index 0000000..704becd --- /dev/null +++ b/tests/generic_tests/test_inference_segmentation_reconstruction.py @@ -0,0 +1,139 @@ +import os +import shutil +import configparser +import logging +import sys +import subprocess +import traceback +import nibabel as nib +import numpy as np + + +def test_inference_segmentation_reconstruction_order(test_dir): + """ + Executing the module as a Python package + Parameters + ---------- + test_dir + + Returns + ------- + + """ + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + logging.info("Running inference segmentation reconstruction test.\n") + + logging.info("Preparing configuration file.\n") + try: + output_folder = os.path.join(test_dir, "output_package") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + os.makedirs(output_folder) + + seg_config = configparser.ConfigParser() + seg_config.add_section('System') + seg_config.set('System', 'gpu_id', "-1") + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs')) + seg_config.set('System', 'output_folder', output_folder) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_Brain')) + seg_config.add_section('Runtime') + seg_config.set('Runtime', 'folds_ensembling', 'False') + seg_config.set('Runtime', 'ensembling_strategy', 'average') + seg_config.set('Runtime', 'overlapping_ratio', '0.') + seg_config.set('Runtime', 'reconstruction_method', 'thresholding') + seg_config.set('Runtime', 'reconstruction_order', 'resample_second') + seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') + seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') + seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') + with open(seg_config_filename, 'w') as outfile: + seg_config.write(outfile) + + try: + logging.info("Running inference.\n") + from raidionicsseg.fit import run_model + run_model(seg_config_filename) + + logging.info("Collecting and comparing results.\n") + segmentation_pred_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "Inference CLI test failed, no brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'verif', 'input0_labels_Brain_resample_second.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + # assert np.array_equal(segmentation_pred, segmentation_gt), "Ground truth and prediction arrays are not identical" + except Exception as e: + logging.error(f"Error during inference Python package test with: {e} \n {traceback.format_exc()}.\n") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + raise ValueError("Error during inference Python package test.\n") + except Exception as e: + logging.error(f"Error during inference Python package test with: {e}\n {traceback.format_exc()}.\n") + raise ValueError("Error during inference Python package test.\n") + + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + + +def test_inference_segmentation_reconstruction_method(test_dir): + """ + Executing the module as a Python package + Parameters + ---------- + test_dir + + Returns + ------- + + """ + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + logging.info("Running inference segmentation reconstruction test.\n") + + logging.info("Preparing configuration file.\n") + try: + output_folder = os.path.join(test_dir, "output_package") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + os.makedirs(output_folder) + + seg_config = configparser.ConfigParser() + seg_config.add_section('System') + seg_config.set('System', 'gpu_id', "-1") + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs')) + seg_config.set('System', 'output_folder', output_folder) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_Brain')) + seg_config.add_section('Runtime') + seg_config.set('Runtime', 'folds_ensembling', 'False') + seg_config.set('Runtime', 'ensembling_strategy', 'average') + seg_config.set('Runtime', 'overlapping_ratio', '0.') + seg_config.set('Runtime', 'reconstruction_method', 'probabilities') + seg_config.set('Runtime', 'reconstruction_order', 'resample_first') + seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') + seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') + seg_config_filename = os.path.join(output_folder, 'test_seg_config.ini') + with open(seg_config_filename, 'w') as outfile: + seg_config.write(outfile) + + try: + logging.info("Running inference.\n") + from raidionicsseg.fit import run_model + run_model(seg_config_filename) + + logging.info("Collecting and comparing results.\n") + segmentation_pred_filename = os.path.join(output_folder, 'pred_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "Inference CLI test failed, no brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'verif', 'input0_pred_Brain.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + # assert np.array_equal(segmentation_pred, segmentation_gt), "Ground truth and prediction arrays are not identical" + except Exception as e: + logging.error(f"Error during inference Python package test with: {e} \n {traceback.format_exc()}.\n") + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + raise ValueError("Error during inference Python package test.\n") + except Exception as e: + logging.error(f"Error during inference Python package test with: {e}\n {traceback.format_exc()}.\n") + raise ValueError("Error during inference Python package test.\n") + + if os.path.exists(output_folder): + shutil.rmtree(output_folder) \ No newline at end of file diff --git a/tests/generic_tests/test_inference_segmentation_simple.py b/tests/generic_tests/test_inference_segmentation_simple.py index 97b68e3..6881322 100644 --- a/tests/generic_tests/test_inference_segmentation_simple.py +++ b/tests/generic_tests/test_inference_segmentation_simple.py @@ -5,25 +5,38 @@ import sys import subprocess import traceback +import nibabel as nib +import numpy as np -def test_inference_cli(data_test2): +def test_inference_cli(test_dir): + """ + Executing the module as a command line argument + Parameters + ---------- + input_data_dir + input_models_dir + + Returns + ------- + + """ logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) logging.info("Running standard inference using the CLI.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test2, "output_cli") + output_folder = os.path.join(test_dir, "output_cli") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test2, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test2, 'MRI_Brain')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_Brain')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -52,29 +65,45 @@ def test_inference_cli(data_test2): '{config}'.format(config=seg_config_filename), '--verbose', 'debug']) except Exception as e: - logging.error(f"Error during inference CLI test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference CLI test with: {e}\n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) raise ValueError("Error during inference CLI test.\n") logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') - assert os.path.exists(brain_segmentation_filename), "Inference CLI test failed, no brain mask was generated.\n" + segmentation_pred_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "No brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs', 'input0_label_Brain.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, + segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: - logging.error(f"Error during inference CLI test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference CLI test with: {e}\n {traceback.format_exc()}.\n") raise ValueError("Error during inference CLI test.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) -def test_inference_package(data_test2): +def test_inference_package(test_dir): + """ + Executing the module as a Python package + Parameters + ---------- + input_data_dir + input_models_dir + + Returns + ------- + + """ logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) logging.info("Running standard inference test as a Python package.\n") logging.info("Preparing configuration file.\n") try: - output_folder = os.path.join(data_test2, "output_package") + output_folder = os.path.join(test_dir, "output_package") if os.path.exists(output_folder): shutil.rmtree(output_folder) os.makedirs(output_folder) @@ -82,9 +111,9 @@ def test_inference_package(data_test2): seg_config = configparser.ConfigParser() seg_config.add_section('System') seg_config.set('System', 'gpu_id', "-1") - seg_config.set('System', 'inputs_folder', os.path.join(data_test2, 'inputs')) + seg_config.set('System', 'inputs_folder', os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs')) seg_config.set('System', 'output_folder', output_folder) - seg_config.set('System', 'model_folder', os.path.join(data_test2, 'MRI_Brain')) + seg_config.set('System', 'model_folder', os.path.join(test_dir, 'Models', 'MRI_Brain')) seg_config.add_section('Runtime') seg_config.set('Runtime', 'folds_ensembling', 'False') seg_config.set('Runtime', 'ensembling_strategy', 'average') @@ -103,15 +132,19 @@ def test_inference_package(data_test2): run_model(seg_config_filename) logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') - assert os.path.exists(brain_segmentation_filename), "Inference CLI test failed, no brain mask was generated.\n" + segmentation_pred_filename = os.path.join(output_folder, 'labels_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "Inference CLI test failed, no brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs', 'input0_label_Brain.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: - logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference Python package test with: {e} \n {traceback.format_exc()}.\n") if os.path.exists(output_folder): shutil.rmtree(output_folder) raise ValueError("Error during inference Python package test.\n") except Exception as e: - logging.error(f"Error during inference Python package test with: \n {traceback.format_exc()}.\n") + logging.error(f"Error during inference Python package test with: {e}\n {traceback.format_exc()}.\n") raise ValueError("Error during inference Python package test.\n") if os.path.exists(output_folder): diff --git a/tests/local_docker_tests/test_docker_inference_segmentation_gpu.py b/tests/local_docker_tests/test_docker_inference_segmentation_gpu.py index 2b6725c..cb14fb8 100644 --- a/tests/local_docker_tests/test_docker_inference_segmentation_gpu.py +++ b/tests/local_docker_tests/test_docker_inference_segmentation_gpu.py @@ -6,10 +6,11 @@ import sys import subprocess import traceback -import zipfile +import nibabel as nib +import numpy as np -def test_docker_inference_segmentation_simple(data_test2): +def test_docker_inference_segmentation_simple(test_dir): """ Testing the CLI within a Docker container for a simple segmentation inference unit test, running on GPU. The latest Docker image is being hosted at: dbouget/raidionics-segmenter:v1.4-py39-cuda12.4 @@ -39,14 +40,14 @@ def test_docker_inference_segmentation_simple(data_test2): seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') seg_config.set('Runtime', 'use_preprocessed_data', 'False') - seg_config_filename = os.path.join(data_test2, 'test_seg_config.ini') + seg_config_filename = os.path.join(test_dir, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) logging.info("Running inference unit test in Docker container.\n") try: import platform - cmd_docker = ['docker', 'run', '-v', '{}:/workspace/resources'.format(data_test2), + cmd_docker = ['docker', 'run', '-v', '{}:/workspace/resources'.format(test_dir), '--network=host', '--ipc=host', '--gpus=all', '--user', str(os.geteuid()), 'dbouget/raidionics-segmenter:v1.4-py39-cuda12.4', '-c', '/workspace/resources/test_seg_config.ini', '-v', 'debug'] @@ -59,9 +60,13 @@ def test_docker_inference_segmentation_simple(data_test2): raise ValueError("Error during inference test in Docker container.\n") logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(data_test2, "outputs", 'labels_Brain.nii.gz') - assert os.path.exists( - brain_segmentation_filename), "Inference Docker test failed, no brain mask was generated.\n" + segmentation_pred_filename = os.path.join(test_dir, "outputs", 'labels_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "No brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs', 'input0_label_Brain.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, + segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: logging.error(f"Error during inference Docker test with: \n {traceback.format_exc()}.\n") raise ValueError("Error during inference Docker test.\n") \ No newline at end of file diff --git a/tests/local_docker_tests/test_docker_inference_segmentation_simple.py b/tests/local_docker_tests/test_docker_inference_segmentation_simple.py index 7f4af5a..3253719 100644 --- a/tests/local_docker_tests/test_docker_inference_segmentation_simple.py +++ b/tests/local_docker_tests/test_docker_inference_segmentation_simple.py @@ -6,10 +6,11 @@ import sys import subprocess import traceback -import zipfile +import nibabel as nib +import numpy as np -def test_docker_inference_segmentation_simple(data_test2): +def test_docker_inference_segmentation_simple(test_dir): """ Testing the CLI within a Docker container for a simple segmentation inference unit test, running on CPU. The latest Docker image is being hosted at: dbouget/raidionics-segmenter:v1.4-py39-cpu @@ -39,14 +40,14 @@ def test_docker_inference_segmentation_simple(data_test2): seg_config.set('Runtime', 'test_time_augmentation_iteration', '0') seg_config.set('Runtime', 'test_time_augmentation_fusion_mode', 'average') seg_config.set('Runtime', 'use_preprocessed_data', 'False') - seg_config_filename = os.path.join(data_test2, 'test_seg_config.ini') + seg_config_filename = os.path.join(test_dir, 'test_seg_config.ini') with open(seg_config_filename, 'w') as outfile: seg_config.write(outfile) logging.info("Running inference unit test in Docker container.\n") try: import platform - cmd_docker = ['docker', 'run', '-v', '{}:/workspace/resources'.format(data_test2), + cmd_docker = ['docker', 'run', '-v', '{}:/workspace/resources'.format(test_dir), '--network=host', '--ipc=host', '--user', str(os.geteuid()), 'dbouget/raidionics-segmenter:v1.4-py39-cpu', '-c', '/workspace/resources/test_seg_config.ini', '-v', 'debug'] @@ -59,9 +60,13 @@ def test_docker_inference_segmentation_simple(data_test2): raise ValueError("Error during inference test in Docker container.\n") logging.info("Collecting and comparing results.\n") - brain_segmentation_filename = os.path.join(data_test2, "outputs", 'labels_Brain.nii.gz') - assert os.path.exists( - brain_segmentation_filename), "Inference Docker test failed, no brain mask was generated.\n" + segmentation_pred_filename = os.path.join(test_dir, "outputs", 'labels_Brain.nii.gz') + assert os.path.exists(segmentation_pred_filename), "No brain mask was generated.\n" + segmentation_gt_filename = os.path.join(test_dir, 'Inputs', 'PreopNeuro', 'inputs', 'input0_label_Brain.nii.gz') + segmentation_pred = nib.load(segmentation_pred_filename).get_fdata()[:] + segmentation_gt = nib.load(segmentation_gt_filename).get_fdata()[:] + assert np.array_equal(segmentation_pred, + segmentation_gt), "Ground truth and prediction arrays are not identical" except Exception as e: logging.error(f"Error during inference Docker test with: \n {traceback.format_exc()}.\n") raise ValueError("Error during inference Docker test.\n") \ No newline at end of file