From e6d8d3f9c9a31b46e07c4834eae88962d192aad1 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 26 Mar 2024 14:03:01 +0100 Subject: [PATCH 01/46] Added dependencies --- pyproject.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 06abe4c..b21598e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,8 +38,10 @@ dependencies = [ 'numpy>=1.21', 'scipy!=1.13.0', 'pandas', - 'lapy>=1.0.0', - 'psutil' + 'lapy >= 1.0.0, <2', + 'psutil', + 'nibabel', + 'scikit-image', ] [project.optional-dependencies] From 639e771669189d0f965efed1b21770b5ff13dd66 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 26 Mar 2024 14:03:38 +0100 Subject: [PATCH 02/46] Removed FreeSurfer checks, shell commands --- brainprint/brainprint.py | 11 ------- brainprint/utils/utils.py | 64 --------------------------------------- 2 files changed, 75 deletions(-) diff --git a/brainprint/brainprint.py b/brainprint/brainprint.py index a3a84d3..fd8665a 100644 --- a/brainprint/brainprint.py +++ b/brainprint/brainprint.py @@ -16,8 +16,6 @@ from .utils.utils import ( create_output_paths, export_brainprint_results, - test_freesurfer, - validate_environment, validate_subject_dir, ) @@ -249,8 +247,6 @@ def run_brainprint( - Eigenvectors - Distances """ # noqa: E501 - validate_environment() - test_freesurfer() subject_dir = validate_subject_dir(subjects_dir, subject_id) destination = create_output_paths( subject_dir=subject_dir, @@ -301,8 +297,6 @@ def __init__( asymmetry: bool = False, asymmetry_distance: str = "euc", keep_temp: bool = False, - environment_validation: bool = True, - freesurfer_validation: bool = True, use_cholmod: bool = False, ) -> None: """ @@ -353,11 +347,6 @@ def __init__( self._eigenvectors = None self._distances = None - if environment_validation: - validate_environment() - if freesurfer_validation: - test_freesurfer() - def run(self, subject_id: str, destination: Path = None) -> dict[str, Path]: """ Run Brainprint analysis for a specified subject. diff --git a/brainprint/utils/utils.py b/brainprint/utils/utils.py index 28622a6..53b89d0 100644 --- a/brainprint/utils/utils.py +++ b/brainprint/utils/utils.py @@ -10,70 +10,6 @@ import pandas as pd -def validate_environment() -> None: - """ - Checks whether required environment variables are set. - """ - if not os.getenv("FREESURFER_HOME"): - raise RuntimeError( - "FreeSurfer root directory must be set as the $FREESURFER_HOME " - "environment variable!" - ) - - -def test_freesurfer() -> None: - """ - Tests FreeSurfer binarize are accessible and executable. - - Raises - ------ - RuntimeError - Failed to execute test FreeSurfer command - """ - command = "mri_binarize -version" - try: - run_shell_command(command) - except FileNotFoundError as err: - raise RuntimeError( - "Failed to run FreeSurfer command, please check the required binaries " - "are included in your $PATH." - ) from err - - -def run_shell_command(command: str, verbose: bool = False): - """ - Execute shell command. - - Parameters - ---------- - command : str - Shell command to be executed - - Raises - ------ - RuntimeError - Shell command execution failure - """ - if verbose: - print(f"Executing command:\t{command}", end="\n") - args = shlex.split(command) - try: - return_code = subprocess.call(args) - except Exception as e: - message = ( - f"Failed to execute the following command:\n{command}\n" - f"The following exception was raised:\n{e}" - ) - print(message) - raise - if return_code != 0: - message = ( - f"Execution of the following command:\n{command}\n" - "Returned non-zero exit code!" - ) - raise RuntimeError(message) - - def validate_subject_dir(subjects_dir: Path, subject_id: str) -> Path: """ Checks the input FreeSurfer preprocessing results directory exists. From 6385ed88cb0e01b09db63a41fa6ef841ac109bed Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 26 Mar 2024 15:42:09 +0100 Subject: [PATCH 03/46] Removed mri_binarize, mri_mc, mris_convert --- brainprint/surfaces.py | 56 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 57cadff..3bd8403 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -2,11 +2,15 @@ Utility module holding surface generation related functions. """ import uuid +import nibabel as nb +import numpy as np from pathlib import Path from lapy import TriaMesh -from .utils.utils import run_shell_command +from skimage.measure import marching_cubes + +#from .utils.utils import run_shell_command # currently only needed for mri_pretess def create_aseg_surface( @@ -27,7 +31,7 @@ def create_aseg_surface( Returns ------- Path - Path to the generated surface in VTK format. + Path to the generated surface in VTK format """ aseg_path = subject_dir / "mri/aseg.mgz" norm_path = subject_dir / "mri/norm.mgz" @@ -36,45 +40,41 @@ def create_aseg_surface( # binarize on selected labels (creates temp indices_mask) # always binarize first, otherwise pretess may scale aseg if labels are # larger than 255 (e.g. aseg+aparc, bug in mri_pretess?) - binarize_template = "mri_binarize --i {source} --match {match} --o {destination}" - binarize_command = binarize_template.format( - source=aseg_path, match=" ".join(indices), destination=indices_mask - ) - run_shell_command(binarize_command) + + aseg = nb.load(aseg_path) + indices_num = [ int(x) for x in indices ] + aseg_data_bin = np.isin(aseg.get_fdata(), indices_num).astype(np.float32) + aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) + nb.save(img=aseg_bin, filename=indices_mask) label_value = "1" + # if norm exist, fix label (pretess) - if norm_path.is_file(): - pretess_template = ( - "mri_pretess {source} {label_value} {norm_path} {destination}" - ) - pretess_command = pretess_template.format( - source=indices_mask, - label_value=label_value, - norm_path=norm_path, - destination=indices_mask, - ) - run_shell_command(pretess_command) + #if norm_path.is_file(): + # pretess_template = ( + # "mri_pretess {source} {label_value} {norm_path} {destination}" + # ) + # pretess_command = pretess_template.format( + # source=indices_mask, + # label_value=label_value, + # norm_path=norm_path, + # destination=indices_mask, + # ) + # run_shell_command(pretess_command) # runs marching cube to extract surface surface_name = f"{temp_name}.surf" surface_path = destination / surface_name - extraction_template = "mri_mc {source} {label_value} {destination}" - extraction_command = extraction_template.format( - source=indices_mask, label_value=label_value, destination=surface_path - ) - run_shell_command(extraction_command) + + vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5) # convert to vtk relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) conversion_destination = destination / relative_path - conversion_template = "mris_convert {source} {destination}" - conversion_command = conversion_template.format( - source=surface_path, destination=conversion_destination - ) - run_shell_command(conversion_command) + + TriaMesh(v=vertices, t=trias).write_vtk(filename=conversion_destination) return conversion_destination From d0601f90cf8c0463a772cb46362d506349fda6f8 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 26 Mar 2024 15:45:46 +0100 Subject: [PATCH 04/46] Removed mri_pretess, currently without replacement --- brainprint/surfaces.py | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 3bd8403..bfc6f4a 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -10,9 +10,6 @@ from skimage.measure import marching_cubes -#from .utils.utils import run_shell_command # currently only needed for mri_pretess - - def create_aseg_surface( subject_dir: Path, destination: Path, indices: list[int] ) -> Path: @@ -34,38 +31,17 @@ def create_aseg_surface( Path to the generated surface in VTK format """ aseg_path = subject_dir / "mri/aseg.mgz" - norm_path = subject_dir / "mri/norm.mgz" - temp_name = f"temp/aseg.{uuid.uuid4()}" + temp_name = "temp/aseg.{uid}".format(uid=uuid.uuid4()) indices_mask = destination / f"{temp_name}.mgz" - # binarize on selected labels (creates temp indices_mask) - # always binarize first, otherwise pretess may scale aseg if labels are - # larger than 255 (e.g. aseg+aparc, bug in mri_pretess?) + # binarize on selected labels (creates temp indices_mask) aseg = nb.load(aseg_path) indices_num = [ int(x) for x in indices ] aseg_data_bin = np.isin(aseg.get_fdata(), indices_num).astype(np.float32) aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) nb.save(img=aseg_bin, filename=indices_mask) - label_value = "1" - - # if norm exist, fix label (pretess) - #if norm_path.is_file(): - # pretess_template = ( - # "mri_pretess {source} {label_value} {norm_path} {destination}" - # ) - # pretess_command = pretess_template.format( - # source=indices_mask, - # label_value=label_value, - # norm_path=norm_path, - # destination=indices_mask, - # ) - # run_shell_command(pretess_command) - # runs marching cube to extract surface - surface_name = f"{temp_name}.surf" - surface_path = destination / surface_name - vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5) # convert to vtk @@ -73,7 +49,6 @@ def create_aseg_surface( indices="_".join(indices) ) conversion_destination = destination / relative_path - TriaMesh(v=vertices, t=trias).write_vtk(filename=conversion_destination) return conversion_destination From a87175c3f7fa0e1d07191634b691beede320fbec Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 15:08:36 +0200 Subject: [PATCH 05/46] BrainPrint Pytests --- test_cases/test_asymmetry.py | 54 +++++++ test_cases/test_brainprint.py | 293 ++++++++++++++++++++++++++++++++++ test_cases/test_surfaces.py | 131 +++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 test_cases/test_asymmetry.py create mode 100644 test_cases/test_brainprint.py create mode 100644 test_cases/test_surfaces.py diff --git a/test_cases/test_asymmetry.py b/test_cases/test_asymmetry.py new file mode 100644 index 0000000..9d6229d --- /dev/null +++ b/test_cases/test_asymmetry.py @@ -0,0 +1,54 @@ +from typing import Dict + +import numpy as np +import pytest +from lapy import shapedna + +from brainprint import asymmetry +from brainprint.asymmetry import compute_asymmetry +from brainprint.brainprint import run_brainprint + + +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing, replace this your local subject directory + subjects_dir = "../../brainprint_test_data/subjects" + return subjects_dir + + +def test_run_brainprint(sample_subjects_dir): + """ + Test the run_brainprint function with sample data. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: Assumes the `run_brainprint` function is correctly implemented and checks result types and eigenvalue matrix properties. + """ + subject_id = "bert" + result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + eigenvalues, eigenvectors, distances = result + + # Computed eigen values from run_brainprint + distances = compute_asymmetry(eigenvalues, distance="euc", skip_cortex=False) + + # Assert that distances is a dictionary + assert isinstance( + distances, dict + ), "Distances is not a dictionary with string keys and float values" + assert all( + value >= 0 for value in distances.values() + ), "Negative distance values found" + distances_with_cortex = compute_asymmetry( + eigenvalues, distance="euc", skip_cortex=False + ) + distances_without_cortex = compute_asymmetry( + eigenvalues, distance="euc", skip_cortex=True + ) + # Assert that the results differ when skip_cortex is True and False + assert ( + distances_with_cortex != distances_without_cortex + ), "Distances are the same with and without cortex" diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py new file mode 100644 index 0000000..9a17068 --- /dev/null +++ b/test_cases/test_brainprint.py @@ -0,0 +1,293 @@ +from pathlib import Path + +import numpy as np +import pytest +from lapy import TriaMesh, shapedna + +from brainprint import Brainprint +from brainprint.brainprint import ( + compute_brainprint, + compute_surface_brainprint, + run_brainprint, +) +from brainprint.surfaces import create_surfaces +from brainprint.utils.utils import ( + create_output_paths, + export_brainprint_results, + test_freesurfer, + validate_environment, + validate_subject_dir, +) + +""" +In order to run the tests, please export the directory of freesurfer in your virtual environment +export FREESURFER_HOME=/groups/ag-reuter/software-centos/fs72 +source $FREESURFER_HOME/SetUpFreeSurfer.sh +""" + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing, replace this your local subject directory + subjects_dir = "../../brainprint_test_data/subjects" + return subjects_dir + + +@pytest.fixture +def tria_mesh_fixture(): + """ + Create a triangular mesh fixture with predefined points and triangles. + + Returns: + TriaMesh: An instance of the TriaMesh class with predefined data. + """ + points = np.array( + [ + [0.0, 0.0, 0.0], + [0, 1, 0], + [1, 1, 0], + [1, 0, 0], + [0, 0, 1], + [0, 1, 1], + [1, 1, 1], + [1, 0, 1], + ] + ) + trias = np.array( + [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 6], + [6, 7, 4], + [0, 4, 7], + [7, 3, 0], + [0, 4, 5], + [5, 1, 0], + [1, 5, 6], + [6, 2, 1], + [3, 7, 6], + [6, 2, 3], + ] + ) + return TriaMesh(points, trias) + + +# Test the initialization of the Brainprint class +def test_brainprint_initialization(sample_subjects_dir): + """ + Test the initialization and run of the Brainprint class. + + This test case validates the initialization of the Brainprint class and checks its attribute values. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to incorrect attribute initialization. + + Note: + - Assumes Brainprint class is correctly implemented. + - Checks if Brainprint instance attributes are initialized as expected. + """ + # Create an instance of Brainprint + brainprint = Brainprint(sample_subjects_dir) + + # Check if the attributes are initialized correctly + assert brainprint.subjects_dir == sample_subjects_dir + assert brainprint.num == 50 + assert brainprint.norm == "none" + assert not brainprint.skip_cortex + assert not brainprint.reweight + assert not brainprint.keep_eigenvectors + assert not brainprint.asymmetry + assert brainprint.asymmetry_distance == "euc" + assert not brainprint.keep_temp + assert not brainprint.use_cholmod + + # Change subject id if requried + subject_id = "bert" + result = brainprint.run(subject_id=subject_id) + assert isinstance(result, dict) + + # Check if the values in the dict are of type Dict[str, Path] + for key, value in result.items(): + assert isinstance(value, Path) + + +def test_apply_eigenvalues_options(tria_mesh_fixture): + """ + Test the apply_eigenvalues_options function. + + This test validates the behavior of the apply_eigenvalues_options function by applying eigenvalues normalization. + + Parameters: + tria_mesh_fixture: Fixture providing a triangular mesh for testing. + + Raises: + AssertionError: If the test fails due to unexpected eigenvalues normalization results. + + Note: + - Assumes the `apply_eigenvalues_options` function is correctly implemented. + - The 'norm' variable specifies the eigenvalues normalization method for testing. + - The test checks if the result 'eigenvalues' is None, indicating successful eigenvalues normalization. + """ + norm = "none" + + eigenvalues = shapedna.normalize_ev( + geom=tria_mesh_fixture, + evals=np.array([10, 2, 33]), + method=norm, + ) + assert eigenvalues == None + + +def test_compute_surface_brainprint(): + """ + Test the compute_surface_brainprint function. + + This test validates the behavior of the compute_surface_brainprint function using a sample VTK path. + + Raises: + AssertionError: If the test fails due to unexpected return types. + + Note: + - Assumes the `compute_surface_brainprint` function is correctly implemented. + - Replace 'path' with the actual path to a VTK file for meaningful testing. + - The test checks that the result is a tuple and unpacks it into 'eigenvalues' and 'eigenvectors', then verifies their types. + """ + + path = "/home/ashrafo/LaPy/data/cubeTria.vtk" + # This path must be replace with the actival .vtk path + result = compute_surface_brainprint(path, num=50) + eigenvalues, eigenvectors = result + assert isinstance(result, tuple), "Return value is not a tuple" + assert isinstance(eigenvalues, np.ndarray), "Eigenvalues is not a numpyarray" + assert len(eigenvalues) == 52, "Eigenvalues has an incorrect length" + assert eigenvectors is None or isinstance( + eigenvectors, np.ndarray + ), "Eigenvectors is not None or a NumPy array" + + +def test_run_brainprint(sample_subjects_dir): + """ + Test the run_brainprint function. + + This test validates the behavior of the run_brainprint function by running it with sample data. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected return types or eigenvalue matrix properties. + + Note: + - Assumes the `run_brainprint` function is correctly implemented. + - The test checks: + - The result is a tuple. + - 'eigenvalues' is a dictionary. + - 'eigenvectors' is either None or a dictionary. + - 'distances' is either None or a dictionary. + - If 'eigenvalues' is not None and the subject is found in it, further checks are performed on the eigenvalue matrix. + - It verifies that the matrix contains at least two rows. + - It ensures that the values in the first two rows are non-negative. + """ + subject_id = "bert" + result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + eigenvalues, eigenvectors, distances = result + assert isinstance(result, tuple), "Return value is not a tuple" + assert isinstance(eigenvalues, dict), "Return value is not a dictionary" + assert eigenvectors is None or isinstance( + eigenvectors, dict + ), "Eigenvectors is not None or a NumPy array" + assert distances is None or isinstance( + eigenvectors, dict + ), "Distacnces is not None or a dictionary" + + # Check if "area" and "volume" are the first two rows in the eigenvalue matrix + if eigenvalues is not None and subject_id in eigenvalues: + eigenvalue_matrix = eigenvalues[subject_id] + assert ( + eigenvalue_matrix.shape[0] >= 2 + ), "Eigenvalue matrix has fewer than two rows" + + # Check the values of the first two rows are non-zero + assert np.all( + eigenvalue_matrix[:2] >= 0 + ), "Area and volume values are not non-negative" + + +def test_compute_brainprint(sample_subjects_dir): + """ + Test the compute_brainprint function. + + This test validates the behavior of the compute_brainprint function using sample data. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected return types. + + Note: + - Assumes that the functions `validate_subject_dir`, `create_output_paths`, `create_surfaces`, + and `compute_brainprint` are correctly implemented and available in the test environment. + """ + subject_id = "bert" + subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) + destination = create_output_paths(subject_dir=subject_dir, destination=None) + surfaces = create_surfaces(subject_dir, destination, skip_cortex=False) + result = compute_brainprint(surfaces) + eigenvalues, eigenvectors = result + assert isinstance(result, tuple), "result is not a tuple" + assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" + assert eigenvectors is None or isinstance( + eigenvectors, dict + ), "eigenvectors are not none or dict type" + + +def test_run_brainprint(sample_subjects_dir): + """ + Test the run_brainprint function. + + This test case validates the behavior of the run_brainprint function by running it + with sample data and checking the properties of the results, including the types of + the returned values and the structure of the eigenvalue matrix. + + Parameters: + sample_subjects_dir (str): The path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected return types or eigenvalue matrix properties. + + Note: + - This test assumes that the run_brainprint function is correctly implemented and + available in the test environment. + - The test checks the following: + - The result is a tuple. + - 'eigenvalues' is a dictionary. + - 'eigenvectors' is either None or a dictionary. + - 'distances' is either None or a dictionary. + - If 'eigenvalues' is not None and the subject is found in it, it further checks + that the eigenvalue matrix contains at least two rows and that the values in + the first two rows are assumed to be non-negative. + """ + subject_id = "bert" + result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + eigenvalues, eigenvectors, distances = result + assert isinstance(result, tuple), "result is not tuple" + assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" + assert eigenvectors is None or isinstance( + eigenvectors, dict + ), "eigenvector is not none or dict type" + assert distances is None or isinstance(eigenvectors, dict) + + # Check if "area" and "volume" are the first two rows in the eigenvalue matrix + if eigenvalues is not None and subject_id in eigenvalues: + eigenvalue_matrix = eigenvalues[subject_id] + assert eigenvalue_matrix.shape[0] >= 2 # Ensure there are at least two rows + + # Check the values of the first two rows are non-zero + assert np.all( + eigenvalue_matrix[:2] >= 0 + ) # Assuming "area" and "volume" are positive values diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py new file mode 100644 index 0000000..ad9e00f --- /dev/null +++ b/test_cases/test_surfaces.py @@ -0,0 +1,131 @@ +import uuid +from pathlib import Path +from typing import Dict, List + +import pytest +from lapy import TriaMesh + +# from brainprint.utils.utils import run_shell_command +from brainprint.surfaces import ( + create_aseg_surface, + create_aseg_surfaces, + create_cortical_surfaces, + read_vtk, + surf_to_vtk, +) + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing, replace this your local subject directory + subject_dir = "../../brainprint_test_data/bert" + return subject_dir + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_destination_dir(): + # Use a temporary directory for testing, replace this your local subject directory + destination = "../../brainprint_test_data/destination" + return destination + + +def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): + """ + Test the create_aseg_surfaces function. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + sample_destination_dir (str): Path to the sample destination directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: + - Assumes the `create_aseg_surface` function is correctly implemented. + - Checks the result for non-None, path type, and existence of the result file. + - Verifies that the result file name matches the expected .vtk file name. + """ + + subject_dir = Path(sample_subjects_dir) + destination = Path(sample_destination_dir) + indices = ["label1", "label2"] + result = create_aseg_surface(subject_dir, destination, indices) + + assert result is not None, "The result is not None" + assert isinstance(result, Path), "The result is not a path" + assert result.exists(), "The result file does not exist" + + expected_file_name = "aseg.final.label1_label2.vtk" + assert result.name == expected_file_name, "The result file does not match .vtk file" + + +def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): + """ + Test the create_cortical_surfaces function. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + sample_destination_dir (str): Path to the sample destination directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: + - Assumes the `create_cortical_surfaces` function is correctly implemented. + - Validates the expected dictionary structure with label names as keys and Path objects. + - Verifies specific key-value pairs in the result. + """ + subject_dir = Path(sample_subjects_dir) + destination = Path(sample_destination_dir) + # Call the function + result = create_cortical_surfaces(subject_dir, destination) + + # Assert that the result is a dictionary containing label names as keys and Path objects as values + assert isinstance(result, dict) + assert all(isinstance(value, Path) for value in result.values()) + assert "lh-white-2d" in result + assert result["lh-white-2d"] == destination / "surfaces" / "lh.white.vtk" + assert "rh-white-2d" in result + assert result["rh-white-2d"] == destination / "surfaces" / "rh.white.vtk" + assert "lh-pial-2d" in result + assert result["lh-pial-2d"] == destination / "surfaces" / "lh.pial.vtk" + assert "rh-pial-2d" in result + assert result["rh-pial-2d"] == destination / "surfaces" / "rh.pial.vtk" + + +def test_read_vtk(): + """ + Test the read_vtk function with a sample VTK file. + + Raises: + AssertionError: If the test fails due to unexpected result type. + + Note: Assumes the `read_vtk` function is correctly implemented and checks if the result is an instance of TriaMesh. + """ + sample_vtk_file = ( + "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" + ) + + # Call the function with the sample VTK file + vtk_path = Path(sample_vtk_file) + triangular_mesh = read_vtk(vtk_path) + + # Assert that the result is an instance of TriaMesh + assert isinstance(triangular_mesh, TriaMesh) + + +def test_surf_to_vtk(sample_subjects_dir, sample_destination_dir): + subject_dir = Path(sample_subjects_dir) + sample_destination_dir = Path(sample_destination_dir) + # resulting_vtk_file = surf_to_vtk(subject_dir, sample_destination_dir) + + try: + trimesh = TriaMesh.read_fssurf(subject_dir) + if trimesh: + trimesh.write_vtk(sample_destination_dir) + else: + print("Failed to read .surf file") + except Exception as e: + print(f"An error occurred: {e}") From 6bc1240f90ce1c34bea828c1e59714767790ab65 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 15:39:17 +0200 Subject: [PATCH 06/46] resolving errors --- test_cases/test_asymmetry.py | 9 +++--- test_cases/test_brainprint.py | 54 +++++++++++++++-------------------- test_cases/test_surfaces.py | 8 ++---- 3 files changed, 31 insertions(+), 40 deletions(-) diff --git a/test_cases/test_asymmetry.py b/test_cases/test_asymmetry.py index 9d6229d..a1ce48b 100644 --- a/test_cases/test_asymmetry.py +++ b/test_cases/test_asymmetry.py @@ -1,10 +1,10 @@ from typing import Dict -import numpy as np + import pytest -from lapy import shapedna -from brainprint import asymmetry + + from brainprint.asymmetry import compute_asymmetry from brainprint.brainprint import run_brainprint @@ -26,7 +26,8 @@ def test_run_brainprint(sample_subjects_dir): Raises: AssertionError: If the test fails due to unexpected results. - Note: Assumes the `run_brainprint` function is correctly implemented and checks result types and eigenvalue matrix properties. + Note: + Assumes run_brainprint function verifies result types and eigenvalue matrix properties. """ subject_id = "bert" result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index 9a17068..b36ed9c 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -20,7 +20,7 @@ ) """ -In order to run the tests, please export the directory of freesurfer in your virtual environment +Don't forget to source Freesurfer export FREESURFER_HOME=/groups/ag-reuter/software-centos/fs72 source $FREESURFER_HOME/SetUpFreeSurfer.sh """ @@ -78,7 +78,7 @@ def test_brainprint_initialization(sample_subjects_dir): """ Test the initialization and run of the Brainprint class. - This test case validates the initialization of the Brainprint class and checks its attribute values. + This test validates Brainprint class initialization and attribute values. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. @@ -119,18 +119,17 @@ def test_apply_eigenvalues_options(tria_mesh_fixture): """ Test the apply_eigenvalues_options function. - This test validates the behavior of the apply_eigenvalues_options function by applying eigenvalues normalization. + This test validates apply_eigenvalues_options for eigenvalues normalization. Parameters: tria_mesh_fixture: Fixture providing a triangular mesh for testing. Raises: - AssertionError: If the test fails due to unexpected eigenvalues normalization results. - + AssertionError: For unexpected eigenvalues normalization failures. Note: - Assumes the `apply_eigenvalues_options` function is correctly implemented. - The 'norm' variable specifies the eigenvalues normalization method for testing. - - The test checks if the result 'eigenvalues' is None, indicating successful eigenvalues normalization. + - Test verifies 'eigenvalues' result as None for successful normalization. """ norm = "none" @@ -146,7 +145,7 @@ def test_compute_surface_brainprint(): """ Test the compute_surface_brainprint function. - This test validates the behavior of the compute_surface_brainprint function using a sample VTK path. + This test validates compute_surface_brainprint with a sample VTK path. Raises: AssertionError: If the test fails due to unexpected return types. @@ -154,7 +153,7 @@ def test_compute_surface_brainprint(): Note: - Assumes the `compute_surface_brainprint` function is correctly implemented. - Replace 'path' with the actual path to a VTK file for meaningful testing. - - The test checks that the result is a tuple and unpacks it into 'eigenvalues' and 'eigenvectors', then verifies their types. + - Test checks that result is a tuple, unpacks into 'eigenvalues' and 'eigenvectors', and verifies types. """ path = "/home/ashrafo/LaPy/data/cubeTria.vtk" @@ -173,13 +172,12 @@ def test_run_brainprint(sample_subjects_dir): """ Test the run_brainprint function. - This test validates the behavior of the run_brainprint function by running it with sample data. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. Raises: - AssertionError: If the test fails due to unexpected return types or eigenvalue matrix properties. + AssertionError: For unexpected return types or eigenvalue matrix properties. Note: - Assumes the `run_brainprint` function is correctly implemented. @@ -188,9 +186,9 @@ def test_run_brainprint(sample_subjects_dir): - 'eigenvalues' is a dictionary. - 'eigenvectors' is either None or a dictionary. - 'distances' is either None or a dictionary. - - If 'eigenvalues' is not None and the subject is found in it, further checks are performed on the eigenvalue matrix. - - It verifies that the matrix contains at least two rows. - - It ensures that the values in the first two rows are non-negative. + - If 'eigenvalues' isn't None and the subject is found, more eigenvalue matrix checks occur. + - It verifies that the matrix contains at least two rows. + - It ensures that the values in the first two rows are non-negative. """ subject_id = "bert" result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) @@ -221,7 +219,6 @@ def test_compute_brainprint(sample_subjects_dir): """ Test the compute_brainprint function. - This test validates the behavior of the compute_brainprint function using sample data. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. @@ -230,8 +227,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - - Assumes that the functions `validate_subject_dir`, `create_output_paths`, `create_surfaces`, - and `compute_brainprint` are correctly implemented and available in the test environment. + - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) @@ -250,27 +246,23 @@ def test_run_brainprint(sample_subjects_dir): """ Test the run_brainprint function. - This test case validates the behavior of the run_brainprint function by running it - with sample data and checking the properties of the results, including the types of - the returned values and the structure of the eigenvalue matrix. + Validates run_brainprint behavior by running it with sample data and checking results, + including return value types and eigenvalue matrix structure. Parameters: - sample_subjects_dir (str): The path to the sample subjects directory. + sample_subjects_dir (str): Path to the sample subjects directory. Raises: - AssertionError: If the test fails due to unexpected return types or eigenvalue matrix properties. + AssertionError: For unexpected return types or eigenvalue matrix properties. Note: - - This test assumes that the run_brainprint function is correctly implemented and - available in the test environment. - - The test checks the following: - - The result is a tuple. - - 'eigenvalues' is a dictionary. - - 'eigenvectors' is either None or a dictionary. - - 'distances' is either None or a dictionary. - - If 'eigenvalues' is not None and the subject is found in it, it further checks - that the eigenvalue matrix contains at least two rows and that the values in - the first two rows are assumed to be non-negative. + - Assumes run_brainprint is correctly implemented and available. + - Checks: + - Result is a tuple. + - 'eigenvalues' is a dict. + - 'eigenvectors' is None or a dict. + - 'distances' is None or a dict. + - If 'eigenvalues' isn't None and the subject is found, further checks the eigenvalue matrix. """ subject_id = "bert" result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index ad9e00f..8868c65 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -1,6 +1,6 @@ -import uuid + from pathlib import Path -from typing import Dict, List + import pytest from lapy import TriaMesh @@ -8,10 +8,8 @@ # from brainprint.utils.utils import run_shell_command from brainprint.surfaces import ( create_aseg_surface, - create_aseg_surfaces, create_cortical_surfaces, read_vtk, - surf_to_vtk, ) @@ -102,7 +100,7 @@ def test_read_vtk(): Raises: AssertionError: If the test fails due to unexpected result type. - Note: Assumes the `read_vtk` function is correctly implemented and checks if the result is an instance of TriaMesh. + Note: Assumes `read_vtk` is correctly implemented and validates TriaMesh result type. """ sample_vtk_file = ( "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" From 8ca2766fcfa0452e3e139b8d771c989583a823ce Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:03:54 +0200 Subject: [PATCH 07/46] fixing errors --- test_cases/test_asymmetry.py | 9 ++------- test_cases/test_brainprint.py | 17 +++++------------ test_cases/test_surfaces.py | 7 +------ 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/test_cases/test_asymmetry.py b/test_cases/test_asymmetry.py index a1ce48b..8c8bc78 100644 --- a/test_cases/test_asymmetry.py +++ b/test_cases/test_asymmetry.py @@ -1,10 +1,5 @@ -from typing import Dict - - import pytest - - from brainprint.asymmetry import compute_asymmetry from brainprint.brainprint import run_brainprint @@ -26,8 +21,8 @@ def test_run_brainprint(sample_subjects_dir): Raises: AssertionError: If the test fails due to unexpected results. - Note: - Assumes run_brainprint function verifies result types and eigenvalue matrix properties. + Note: + Assumes run_brainprint checks result types and eigenvalue matrix properties. """ subject_id = "bert" result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index b36ed9c..bd19d67 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -13,9 +13,6 @@ from brainprint.surfaces import create_surfaces from brainprint.utils.utils import ( create_output_paths, - export_brainprint_results, - test_freesurfer, - validate_environment, validate_subject_dir, ) @@ -78,8 +75,6 @@ def test_brainprint_initialization(sample_subjects_dir): """ Test the initialization and run of the Brainprint class. - This test validates Brainprint class initialization and attribute values. - Parameters: sample_subjects_dir (str): Path to the sample subjects directory. @@ -119,8 +114,6 @@ def test_apply_eigenvalues_options(tria_mesh_fixture): """ Test the apply_eigenvalues_options function. - This test validates apply_eigenvalues_options for eigenvalues normalization. - Parameters: tria_mesh_fixture: Fixture providing a triangular mesh for testing. @@ -153,7 +146,7 @@ def test_compute_surface_brainprint(): Note: - Assumes the `compute_surface_brainprint` function is correctly implemented. - Replace 'path' with the actual path to a VTK file for meaningful testing. - - Test checks that result is a tuple, unpacks into 'eigenvalues' and 'eigenvectors', and verifies types. + - Test checks result as tuple, unpacks 'eigenvalues' and 'eigenvectors', verifies types. """ path = "/home/ashrafo/LaPy/data/cubeTria.vtk" @@ -186,7 +179,7 @@ def test_run_brainprint(sample_subjects_dir): - 'eigenvalues' is a dictionary. - 'eigenvectors' is either None or a dictionary. - 'distances' is either None or a dictionary. - - If 'eigenvalues' isn't None and the subject is found, more eigenvalue matrix checks occur. + - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks occur. - It verifies that the matrix contains at least two rows. - It ensures that the values in the first two rows are non-negative. """ @@ -227,7 +220,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. + - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint available. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) @@ -246,8 +239,8 @@ def test_run_brainprint(sample_subjects_dir): """ Test the run_brainprint function. - Validates run_brainprint behavior by running it with sample data and checking results, - including return value types and eigenvalue matrix structure. + Validates run_brainprint using sample data, checks results, types, and eigenvalue matrix. + Parameters: sample_subjects_dir (str): Path to the sample subjects directory. diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index 8868c65..a96e89d 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -80,7 +80,6 @@ def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): # Call the function result = create_cortical_surfaces(subject_dir, destination) - # Assert that the result is a dictionary containing label names as keys and Path objects as values assert isinstance(result, dict) assert all(isinstance(value, Path) for value in result.values()) assert "lh-white-2d" in result @@ -102,9 +101,7 @@ def test_read_vtk(): Note: Assumes `read_vtk` is correctly implemented and validates TriaMesh result type. """ - sample_vtk_file = ( - "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" - ) + sample_vtk_file = ("../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk") # Call the function with the sample VTK file vtk_path = Path(sample_vtk_file) @@ -117,8 +114,6 @@ def test_read_vtk(): def test_surf_to_vtk(sample_subjects_dir, sample_destination_dir): subject_dir = Path(sample_subjects_dir) sample_destination_dir = Path(sample_destination_dir) - # resulting_vtk_file = surf_to_vtk(subject_dir, sample_destination_dir) - try: trimesh = TriaMesh.read_fssurf(subject_dir) if trimesh: From 9a281066c37231276321b764c70c3266f722d469 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:14:39 +0200 Subject: [PATCH 08/46] fixing errors again --- test_cases/test_brainprint.py | 94 +++++++++++++++++------------------ test_cases/test_surfaces.py | 8 +-- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index bd19d67..3e438ad 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -146,7 +146,7 @@ def test_compute_surface_brainprint(): Note: - Assumes the `compute_surface_brainprint` function is correctly implemented. - Replace 'path' with the actual path to a VTK file for meaningful testing. - - Test checks result as tuple, unpacks 'eigenvalues' and 'eigenvectors', verifies types. + - Test checks tuple result, unpacks 'eigenvalues' and 'eigenvectors', verifies types. """ path = "/home/ashrafo/LaPy/data/cubeTria.vtk" @@ -161,51 +161,51 @@ def test_compute_surface_brainprint(): ), "Eigenvectors is not None or a NumPy array" -def test_run_brainprint(sample_subjects_dir): - """ - Test the run_brainprint function. - - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - - Raises: - AssertionError: For unexpected return types or eigenvalue matrix properties. - - Note: - - Assumes the `run_brainprint` function is correctly implemented. - - The test checks: - - The result is a tuple. - - 'eigenvalues' is a dictionary. - - 'eigenvectors' is either None or a dictionary. - - 'distances' is either None or a dictionary. - - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks occur. - - It verifies that the matrix contains at least two rows. - - It ensures that the values in the first two rows are non-negative. - """ - subject_id = "bert" - result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) - eigenvalues, eigenvectors, distances = result - assert isinstance(result, tuple), "Return value is not a tuple" - assert isinstance(eigenvalues, dict), "Return value is not a dictionary" - assert eigenvectors is None or isinstance( - eigenvectors, dict - ), "Eigenvectors is not None or a NumPy array" - assert distances is None or isinstance( - eigenvectors, dict - ), "Distacnces is not None or a dictionary" - - # Check if "area" and "volume" are the first two rows in the eigenvalue matrix - if eigenvalues is not None and subject_id in eigenvalues: - eigenvalue_matrix = eigenvalues[subject_id] - assert ( - eigenvalue_matrix.shape[0] >= 2 - ), "Eigenvalue matrix has fewer than two rows" - - # Check the values of the first two rows are non-zero - assert np.all( - eigenvalue_matrix[:2] >= 0 - ), "Area and volume values are not non-negative" +# def test_run_brainprint(sample_subjects_dir): +# """ +# Test the run_brainprint function. + + +# Parameters: +# sample_subjects_dir (str): Path to the sample subjects directory. + +# Raises: +# AssertionError: For unexpected return types or eigenvalue matrix properties. + +# Note: +# - Assumes the `run_brainprint` function is correctly implemented. +# - The test checks: +# - The result is a tuple. +# - 'eigenvalues' is a dictionary. +# - 'eigenvectors' is either None or a dictionary. +# - 'distances' is either None or a dictionary. +# - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks. +# - It verifies that the matrix contains at least two rows. +# - It ensures that the values in the first two rows are non-negative. +# """ +# subject_id = "bert" +# result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) +# eigenvalues, eigenvectors, distances = result +# assert isinstance(result, tuple), "Return value is not a tuple" +# assert isinstance(eigenvalues, dict), "Return value is not a dictionary" +# assert eigenvectors is None or isinstance( +# eigenvectors, dict +# ), "Eigenvectors is not None or a NumPy array" +# assert distances is None or isinstance( +# eigenvectors, dict +# ), "Distacnces is not None or a dictionary" + +# # Check if "area" and "volume" are the first two rows in the eigenvalue matrix +# if eigenvalues is not None and subject_id in eigenvalues: +# eigenvalue_matrix = eigenvalues[subject_id] +# assert ( +# eigenvalue_matrix.shape[0] >= 2 +# ), "Eigenvalue matrix has fewer than two rows" + +# # Check the values of the first two rows are non-zero +# assert np.all( +# eigenvalue_matrix[:2] >= 0 +# ), "Area and volume values are not non-negative" def test_compute_brainprint(sample_subjects_dir): @@ -220,7 +220,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint available. + - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index a96e89d..b526111 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -72,7 +72,7 @@ def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): Note: - Assumes the `create_cortical_surfaces` function is correctly implemented. - - Validates the expected dictionary structure with label names as keys and Path objects. + - Validates dictionary structure with label names as keys and Path objects. - Verifies specific key-value pairs in the result. """ subject_dir = Path(sample_subjects_dir) @@ -99,12 +99,12 @@ def test_read_vtk(): Raises: AssertionError: If the test fails due to unexpected result type. - Note: Assumes `read_vtk` is correctly implemented and validates TriaMesh result type. + Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. """ - sample_vtk_file = ("../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk") + vtk_file = ("../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk") # Call the function with the sample VTK file - vtk_path = Path(sample_vtk_file) + vtk_path = Path(vtk_file) triangular_mesh = read_vtk(vtk_path) # Assert that the result is an instance of TriaMesh From 266ddbf688f11f3a2b0cb5766405f580ba9603f2 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:24:00 +0200 Subject: [PATCH 09/46] fixing errors --- test_cases/test_brainprint.py | 7 ++----- test_cases/test_surfaces.py | 4 +++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index 3e438ad..a1bb2d3 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -146,7 +146,7 @@ def test_compute_surface_brainprint(): Note: - Assumes the `compute_surface_brainprint` function is correctly implemented. - Replace 'path' with the actual path to a VTK file for meaningful testing. - - Test checks tuple result, unpacks 'eigenvalues' and 'eigenvectors', verifies types. + - Test checks tuple result, unpacks 'eigenvalues' & 'eigenvectors', verifies types. """ path = "/home/ashrafo/LaPy/data/cubeTria.vtk" @@ -220,7 +220,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. + Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) @@ -239,9 +239,6 @@ def test_run_brainprint(sample_subjects_dir): """ Test the run_brainprint function. - Validates run_brainprint using sample data, checks results, types, and eigenvalue matrix. - - Parameters: sample_subjects_dir (str): Path to the sample subjects directory. diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index b526111..6e0ca1b 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -101,7 +101,9 @@ def test_read_vtk(): Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. """ - vtk_file = ("../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk") + vtk_file = ( + "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" + ) # Call the function with the sample VTK file vtk_path = Path(vtk_file) From 40d88153bd37cc612ecd38d41815337031acdffd Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:30:35 +0200 Subject: [PATCH 10/46] resolving errors --- test_cases/test_brainprint.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index a1bb2d3..60afc94 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -131,7 +131,7 @@ def test_apply_eigenvalues_options(tria_mesh_fixture): evals=np.array([10, 2, 33]), method=norm, ) - assert eigenvalues == None + assert eigenvalues is None def test_compute_surface_brainprint(): @@ -220,7 +220,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - Assumes validate_subject_dir, create_output_paths, create_surfaces, and compute_brainprint are available. + Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_brainprint available. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) @@ -252,7 +252,7 @@ def test_run_brainprint(sample_subjects_dir): - 'eigenvalues' is a dict. - 'eigenvectors' is None or a dict. - 'distances' is None or a dict. - - If 'eigenvalues' isn't None and the subject is found, further checks the eigenvalue matrix. + - If 'eigenvalues' not None and subject found, further checks eigenvalue matrix. """ subject_id = "bert" result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) From 21f17e066a44e9f74a02c827974c588a780905d5 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:43:17 +0200 Subject: [PATCH 11/46] fixing line lenght error --- test_cases/test_brainprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index 60afc94..ee6ca73 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -220,7 +220,7 @@ def test_compute_brainprint(sample_subjects_dir): AssertionError: If the test fails due to unexpected return types. Note: - Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_brainprint available. + Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_BP. """ subject_id = "bert" subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) From 90d9f80f2f7e2e4bd2e7da9d9e1c9c79abff786d Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Thu, 12 Oct 2023 16:54:31 +0200 Subject: [PATCH 12/46] Isort and formatted --- test_cases/test_brainprint.py | 5 +---- test_cases/test_surfaces.py | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index ee6ca73..08c72b3 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -11,10 +11,7 @@ run_brainprint, ) from brainprint.surfaces import create_surfaces -from brainprint.utils.utils import ( - create_output_paths, - validate_subject_dir, -) +from brainprint.utils.utils import create_output_paths, validate_subject_dir """ Don't forget to source Freesurfer diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index 6e0ca1b..72a5f79 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -1,16 +1,11 @@ from pathlib import Path - import pytest from lapy import TriaMesh # from brainprint.utils.utils import run_shell_command -from brainprint.surfaces import ( - create_aseg_surface, - create_cortical_surfaces, - read_vtk, -) +from brainprint.surfaces import create_aseg_surface, create_cortical_surfaces, read_vtk # Create a fixture for a sample subjects_dir From 6411ffef4197c54728ee0204e116bcbb42960507 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Wed, 18 Oct 2023 09:58:57 +0200 Subject: [PATCH 13/46] Black run --- test_cases/test_surfaces.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py index 72a5f79..1b25305 100644 --- a/test_cases/test_surfaces.py +++ b/test_cases/test_surfaces.py @@ -1,4 +1,3 @@ - from pathlib import Path import pytest @@ -98,7 +97,7 @@ def test_read_vtk(): """ vtk_file = ( "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" - ) + ) # Call the function with the sample VTK file vtk_path = Path(vtk_file) From ced78666724fde1dbe7a532d32aa43d5d2607fa3 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Wed, 18 Oct 2023 10:04:51 +0200 Subject: [PATCH 14/46] spell correction --- test_cases/test_brainprint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py index 08c72b3..255bf4f 100644 --- a/test_cases/test_brainprint.py +++ b/test_cases/test_brainprint.py @@ -97,7 +97,7 @@ def test_brainprint_initialization(sample_subjects_dir): assert not brainprint.keep_temp assert not brainprint.use_cholmod - # Change subject id if requried + # Change subject id if required subject_id = "bert" result = brainprint.run(subject_id=subject_id) assert isinstance(result, dict) From 492919404075f299c7c27ab4ab081b4a5db1fd9d Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Wed, 18 Oct 2023 11:21:11 +0200 Subject: [PATCH 15/46] Modified relative paths and test files copied to utils/tests directory --- brainprint/utils/tests/test_asymmetry.py | 51 ++++ brainprint/utils/tests/test_brainprint.py | 275 ++++++++++++++++++++++ brainprint/utils/tests/test_surfaces.py | 122 ++++++++++ 3 files changed, 448 insertions(+) create mode 100644 brainprint/utils/tests/test_asymmetry.py create mode 100644 brainprint/utils/tests/test_brainprint.py create mode 100644 brainprint/utils/tests/test_surfaces.py diff --git a/brainprint/utils/tests/test_asymmetry.py b/brainprint/utils/tests/test_asymmetry.py new file mode 100644 index 0000000..4d66701 --- /dev/null +++ b/brainprint/utils/tests/test_asymmetry.py @@ -0,0 +1,51 @@ +import pytest + +from brainprint.asymmetry import compute_asymmetry +from brainprint.brainprint import run_brainprint + + +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing, replace this your local subject directory + # subjects_dir = "../../brainprint_test_data/subjects" + subjects_dir = "../../../../brainprint_test_data/subjects" + return subjects_dir + + +def test_run_brainprint(sample_subjects_dir): + """ + Test the run_brainprint function with sample data. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: + Assumes run_brainprint checks result types and eigenvalue matrix properties. + """ + subject_id = "bert" + result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + eigenvalues, eigenvectors, distances = result + + # Computed eigen values from run_brainprint + distances = compute_asymmetry(eigenvalues, distance="euc", skip_cortex=False) + + # Assert that distances is a dictionary + assert isinstance( + distances, dict + ), "Distances is not a dictionary with string keys and float values" + assert all( + value >= 0 for value in distances.values() + ), "Negative distance values found" + distances_with_cortex = compute_asymmetry( + eigenvalues, distance="euc", skip_cortex=False + ) + distances_without_cortex = compute_asymmetry( + eigenvalues, distance="euc", skip_cortex=True + ) + # Assert that the results differ when skip_cortex is True and False + assert ( + distances_with_cortex != distances_without_cortex + ), "Distances are the same with and without cortex" diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py new file mode 100644 index 0000000..3c9974f --- /dev/null +++ b/brainprint/utils/tests/test_brainprint.py @@ -0,0 +1,275 @@ +from pathlib import Path + +import numpy as np +import pytest +from lapy import TriaMesh, shapedna + +from brainprint import Brainprint +from brainprint.brainprint import ( + compute_brainprint, + compute_surface_brainprint, + run_brainprint, +) +from brainprint.surfaces import create_surfaces +from brainprint.utils.utils import create_output_paths, validate_subject_dir + +""" +Don't forget to source Freesurfer +export FREESURFER_HOME=/groups/ag-reuter/software-centos/fs72 +source $FREESURFER_HOME/SetUpFreeSurfer.sh +""" + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing + # subjects_dir = "../../brainprint_test_data/subjects" + subjects_dir = "../../../../brainprint_test_data/subjects" + return subjects_dir + + +@pytest.fixture +def tria_mesh_fixture(): + """ + Create a triangular mesh fixture with predefined points and triangles. + + Returns: + TriaMesh: An instance of the TriaMesh class with predefined data. + """ + points = np.array( + [ + [0.0, 0.0, 0.0], + [0, 1, 0], + [1, 1, 0], + [1, 0, 0], + [0, 0, 1], + [0, 1, 1], + [1, 1, 1], + [1, 0, 1], + ] + ) + trias = np.array( + [ + [0, 1, 2], + [2, 3, 0], + [4, 5, 6], + [6, 7, 4], + [0, 4, 7], + [7, 3, 0], + [0, 4, 5], + [5, 1, 0], + [1, 5, 6], + [6, 2, 1], + [3, 7, 6], + [6, 2, 3], + ] + ) + return TriaMesh(points, trias) + + +# Test the initialization of the Brainprint class +def test_brainprint_initialization(sample_subjects_dir): + """ + Test the initialization and run of the Brainprint class. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to incorrect attribute initialization. + + Note: + - Assumes Brainprint class is correctly implemented. + - Checks if Brainprint instance attributes are initialized as expected. + """ + # Create an instance of Brainprint + brainprint = Brainprint(sample_subjects_dir) + + # Check if the attributes are initialized correctly + assert brainprint.subjects_dir == sample_subjects_dir + assert brainprint.num == 50 + assert brainprint.norm == "none" + assert not brainprint.skip_cortex + assert not brainprint.reweight + assert not brainprint.keep_eigenvectors + assert not brainprint.asymmetry + assert brainprint.asymmetry_distance == "euc" + assert not brainprint.keep_temp + assert not brainprint.use_cholmod + + # Change subject id if required + subject_id = "bert" + result = brainprint.run(subject_id=subject_id) + assert isinstance(result, dict) + + # Check if the values in the dict are of type Dict[str, Path] + for key, value in result.items(): + assert isinstance(value, Path) + + +def test_apply_eigenvalues_options(tria_mesh_fixture): + """ + Test the apply_eigenvalues_options function. + + Parameters: + tria_mesh_fixture: Fixture providing a triangular mesh for testing. + + Raises: + AssertionError: For unexpected eigenvalues normalization failures. + Note: + - Assumes the `apply_eigenvalues_options` function is correctly implemented. + - The 'norm' variable specifies the eigenvalues normalization method for testing. + - Test verifies 'eigenvalues' result as None for successful normalization. + """ + norm = "none" + + eigenvalues = shapedna.normalize_ev( + geom=tria_mesh_fixture, + evals=np.array([10, 2, 33]), + method=norm, + ) + assert eigenvalues is None + + +def test_compute_surface_brainprint(): + """ + Test the compute_surface_brainprint function. + + This test validates compute_surface_brainprint with a sample VTK path. + + Raises: + AssertionError: If the test fails due to unexpected return types. + + Note: + - Assumes the `compute_surface_brainprint` function is correctly implemented. + - Replace 'path' with the actual path to a VTK file for meaningful testing. + - Test checks tuple result, unpacks 'eigenvalues' & 'eigenvectors', verifies types. + """ + + # path = "/home/ashrafo/LaPy/data/cubeTria.vtk" + path = "../../../../LaPy/data/cubeTria.vtk" + + # This path must be replace with the actival .vtk path + result = compute_surface_brainprint(path, num=50) + eigenvalues, eigenvectors = result + assert isinstance(result, tuple), "Return value is not a tuple" + assert isinstance(eigenvalues, np.ndarray), "Eigenvalues is not a numpyarray" + assert len(eigenvalues) == 52, "Eigenvalues has an incorrect length" + assert eigenvectors is None or isinstance( + eigenvectors, np.ndarray + ), "Eigenvectors is not None or a NumPy array" + + +# def test_run_brainprint(sample_subjects_dir): +# """ +# Test the run_brainprint function. + + +# Parameters: +# sample_subjects_dir (str): Path to the sample subjects directory. + +# Raises: +# AssertionError: For unexpected return types or eigenvalue matrix properties. + +# Note: +# - Assumes the `run_brainprint` function is correctly implemented. +# - The test checks: +# - The result is a tuple. +# - 'eigenvalues' is a dictionary. +# - 'eigenvectors' is either None or a dictionary. +# - 'distances' is either None or a dictionary. +# - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks. +# - It verifies that the matrix contains at least two rows. +# - It ensures that the values in the first two rows are non-negative. +# """ +# subject_id = "bert" +# result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) +# eigenvalues, eigenvectors, distances = result +# assert isinstance(result, tuple), "Return value is not a tuple" +# assert isinstance(eigenvalues, dict), "Return value is not a dictionary" +# assert eigenvectors is None or isinstance( +# eigenvectors, dict +# ), "Eigenvectors is not None or a NumPy array" +# assert distances is None or isinstance( +# eigenvectors, dict +# ), "Distacnces is not None or a dictionary" + +# # Check if "area" and "volume" are the first two rows in the eigenvalue matrix +# if eigenvalues is not None and subject_id in eigenvalues: +# eigenvalue_matrix = eigenvalues[subject_id] +# assert ( +# eigenvalue_matrix.shape[0] >= 2 +# ), "Eigenvalue matrix has fewer than two rows" + +# # Check the values of the first two rows are non-zero +# assert np.all( +# eigenvalue_matrix[:2] >= 0 +# ), "Area and volume values are not non-negative" + + +def test_compute_brainprint(sample_subjects_dir): + """ + Test the compute_brainprint function. + + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: If the test fails due to unexpected return types. + + Note: + Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_BP. + """ + subject_id = "bert" + subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) + destination = create_output_paths(subject_dir=subject_dir, destination=None) + surfaces = create_surfaces(subject_dir, destination, skip_cortex=False) + result = compute_brainprint(surfaces) + eigenvalues, eigenvectors = result + assert isinstance(result, tuple), "result is not a tuple" + assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" + assert eigenvectors is None or isinstance( + eigenvectors, dict + ), "eigenvectors are not none or dict type" + + +def test_run_brainprint(sample_subjects_dir): + """ + Test the run_brainprint function. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + + Raises: + AssertionError: For unexpected return types or eigenvalue matrix properties. + + Note: + - Assumes run_brainprint is correctly implemented and available. + - Checks: + - Result is a tuple. + - 'eigenvalues' is a dict. + - 'eigenvectors' is None or a dict. + - 'distances' is None or a dict. + - If 'eigenvalues' not None and subject found, further checks eigenvalue matrix. + """ + subject_id = "bert" + result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + eigenvalues, eigenvectors, distances = result + assert isinstance(result, tuple), "result is not tuple" + assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" + assert eigenvectors is None or isinstance( + eigenvectors, dict + ), "eigenvector is not none or dict type" + assert distances is None or isinstance(eigenvectors, dict) + + # Check if "area" and "volume" are the first two rows in the eigenvalue matrix + if eigenvalues is not None and subject_id in eigenvalues: + eigenvalue_matrix = eigenvalues[subject_id] + assert eigenvalue_matrix.shape[0] >= 2 # Ensure there are at least two rows + + # Check the values of the first two rows are non-zero + assert np.all( + eigenvalue_matrix[:2] >= 0 + ) # Assuming "area" and "volume" are positive values diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py new file mode 100644 index 0000000..5b99fae --- /dev/null +++ b/brainprint/utils/tests/test_surfaces.py @@ -0,0 +1,122 @@ +from pathlib import Path + +import pytest +from lapy import TriaMesh + +# from brainprint.utils.utils import run_shell_command +from brainprint.surfaces import create_aseg_surface, create_cortical_surfaces, read_vtk + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing, replace this your local subject directory + # subject_dir = "../../brainprint_test_data/bert" + subject_dir = "../../../../brainprint_test_data/bert" + return subject_dir + + +# Create a fixture for a sample subjects_dir +@pytest.fixture +def sample_destination_dir(): + # Use a temporary directory for testing, replace this your local subject directory + # destination = "../../brainprint_test_data/destination" + destination = "../../../../brainprint_test_data/destination" + return destination + + +def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): + """ + Test the create_aseg_surfaces function. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + sample_destination_dir (str): Path to the sample destination directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: + - Assumes the `create_aseg_surface` function is correctly implemented. + - Checks the result for non-None, path type, and existence of the result file. + - Verifies that the result file name matches the expected .vtk file name. + """ + + subject_dir = Path(sample_subjects_dir) + destination = Path(sample_destination_dir) + indices = ["label1", "label2"] + result = create_aseg_surface(subject_dir, destination, indices) + + assert result is not None, "The result is not None" + assert isinstance(result, Path), "The result is not a path" + assert result.exists(), "The result file does not exist" + + expected_file_name = "aseg.final.label1_label2.vtk" + assert result.name == expected_file_name, "The result file does not match .vtk file" + + +def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): + """ + Test the create_cortical_surfaces function. + + Parameters: + sample_subjects_dir (str): Path to the sample subjects directory. + sample_destination_dir (str): Path to the sample destination directory. + + Raises: + AssertionError: If the test fails due to unexpected results. + + Note: + - Assumes the `create_cortical_surfaces` function is correctly implemented. + - Validates dictionary structure with label names as keys and Path objects. + - Verifies specific key-value pairs in the result. + """ + subject_dir = Path(sample_subjects_dir) + destination = Path(sample_destination_dir) + # Call the function + result = create_cortical_surfaces(subject_dir, destination) + + assert isinstance(result, dict) + assert all(isinstance(value, Path) for value in result.values()) + assert "lh-white-2d" in result + assert result["lh-white-2d"] == destination / "surfaces" / "lh.white.vtk" + assert "rh-white-2d" in result + assert result["rh-white-2d"] == destination / "surfaces" / "rh.white.vtk" + assert "lh-pial-2d" in result + assert result["lh-pial-2d"] == destination / "surfaces" / "lh.pial.vtk" + assert "rh-pial-2d" in result + assert result["rh-pial-2d"] == destination / "surfaces" / "rh.pial.vtk" + + +def test_read_vtk(): + """ + Test the read_vtk function with a sample VTK file. + + Raises: + AssertionError: If the test fails due to unexpected result type. + + Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. + """ + vtk_file = ( + "../../../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" + ) + + # Call the function with the sample VTK file + vtk_path = Path(vtk_file) + triangular_mesh = read_vtk(vtk_path) + + # Assert that the result is an instance of TriaMesh + assert isinstance(triangular_mesh, TriaMesh) + + +def test_surf_to_vtk(sample_subjects_dir, sample_destination_dir): + subject_dir = Path(sample_subjects_dir) + sample_destination_dir = Path(sample_destination_dir) + try: + trimesh = TriaMesh.read_fssurf(subject_dir) + if trimesh: + trimesh.write_vtk(sample_destination_dir) + else: + print("Failed to read .surf file") + except Exception as e: + print(f"An error occurred: {e}") From 576eb2c66dcd720177e7b1db7dbe172fee92df53 Mon Sep 17 00:00:00 2001 From: engrosamaali91 Date: Wed, 18 Oct 2023 12:11:33 +0200 Subject: [PATCH 16/46] black run --- brainprint/utils/tests/test_surfaces.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index 5b99fae..a5f0415 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -97,9 +97,7 @@ def test_read_vtk(): Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. """ - vtk_file = ( - "../../../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" - ) + vtk_file = "../../../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" # Call the function with the sample VTK file vtk_path = Path(vtk_file) From 876275496ed254c2cbb7bb77b46ecfe82939435d Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Tue, 26 Mar 2024 15:50:46 +0100 Subject: [PATCH 17/46] Removed duplicate test files --- test_cases/test_asymmetry.py | 50 ------- test_cases/test_brainprint.py | 272 ---------------------------------- test_cases/test_surfaces.py | 120 --------------- 3 files changed, 442 deletions(-) delete mode 100644 test_cases/test_asymmetry.py delete mode 100644 test_cases/test_brainprint.py delete mode 100644 test_cases/test_surfaces.py diff --git a/test_cases/test_asymmetry.py b/test_cases/test_asymmetry.py deleted file mode 100644 index 8c8bc78..0000000 --- a/test_cases/test_asymmetry.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest - -from brainprint.asymmetry import compute_asymmetry -from brainprint.brainprint import run_brainprint - - -@pytest.fixture -def sample_subjects_dir(): - # Use a temporary directory for testing, replace this your local subject directory - subjects_dir = "../../brainprint_test_data/subjects" - return subjects_dir - - -def test_run_brainprint(sample_subjects_dir): - """ - Test the run_brainprint function with sample data. - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - - Raises: - AssertionError: If the test fails due to unexpected results. - - Note: - Assumes run_brainprint checks result types and eigenvalue matrix properties. - """ - subject_id = "bert" - result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) - eigenvalues, eigenvectors, distances = result - - # Computed eigen values from run_brainprint - distances = compute_asymmetry(eigenvalues, distance="euc", skip_cortex=False) - - # Assert that distances is a dictionary - assert isinstance( - distances, dict - ), "Distances is not a dictionary with string keys and float values" - assert all( - value >= 0 for value in distances.values() - ), "Negative distance values found" - distances_with_cortex = compute_asymmetry( - eigenvalues, distance="euc", skip_cortex=False - ) - distances_without_cortex = compute_asymmetry( - eigenvalues, distance="euc", skip_cortex=True - ) - # Assert that the results differ when skip_cortex is True and False - assert ( - distances_with_cortex != distances_without_cortex - ), "Distances are the same with and without cortex" diff --git a/test_cases/test_brainprint.py b/test_cases/test_brainprint.py deleted file mode 100644 index 255bf4f..0000000 --- a/test_cases/test_brainprint.py +++ /dev/null @@ -1,272 +0,0 @@ -from pathlib import Path - -import numpy as np -import pytest -from lapy import TriaMesh, shapedna - -from brainprint import Brainprint -from brainprint.brainprint import ( - compute_brainprint, - compute_surface_brainprint, - run_brainprint, -) -from brainprint.surfaces import create_surfaces -from brainprint.utils.utils import create_output_paths, validate_subject_dir - -""" -Don't forget to source Freesurfer -export FREESURFER_HOME=/groups/ag-reuter/software-centos/fs72 -source $FREESURFER_HOME/SetUpFreeSurfer.sh -""" - - -# Create a fixture for a sample subjects_dir -@pytest.fixture -def sample_subjects_dir(): - # Use a temporary directory for testing, replace this your local subject directory - subjects_dir = "../../brainprint_test_data/subjects" - return subjects_dir - - -@pytest.fixture -def tria_mesh_fixture(): - """ - Create a triangular mesh fixture with predefined points and triangles. - - Returns: - TriaMesh: An instance of the TriaMesh class with predefined data. - """ - points = np.array( - [ - [0.0, 0.0, 0.0], - [0, 1, 0], - [1, 1, 0], - [1, 0, 0], - [0, 0, 1], - [0, 1, 1], - [1, 1, 1], - [1, 0, 1], - ] - ) - trias = np.array( - [ - [0, 1, 2], - [2, 3, 0], - [4, 5, 6], - [6, 7, 4], - [0, 4, 7], - [7, 3, 0], - [0, 4, 5], - [5, 1, 0], - [1, 5, 6], - [6, 2, 1], - [3, 7, 6], - [6, 2, 3], - ] - ) - return TriaMesh(points, trias) - - -# Test the initialization of the Brainprint class -def test_brainprint_initialization(sample_subjects_dir): - """ - Test the initialization and run of the Brainprint class. - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - - Raises: - AssertionError: If the test fails due to incorrect attribute initialization. - - Note: - - Assumes Brainprint class is correctly implemented. - - Checks if Brainprint instance attributes are initialized as expected. - """ - # Create an instance of Brainprint - brainprint = Brainprint(sample_subjects_dir) - - # Check if the attributes are initialized correctly - assert brainprint.subjects_dir == sample_subjects_dir - assert brainprint.num == 50 - assert brainprint.norm == "none" - assert not brainprint.skip_cortex - assert not brainprint.reweight - assert not brainprint.keep_eigenvectors - assert not brainprint.asymmetry - assert brainprint.asymmetry_distance == "euc" - assert not brainprint.keep_temp - assert not brainprint.use_cholmod - - # Change subject id if required - subject_id = "bert" - result = brainprint.run(subject_id=subject_id) - assert isinstance(result, dict) - - # Check if the values in the dict are of type Dict[str, Path] - for key, value in result.items(): - assert isinstance(value, Path) - - -def test_apply_eigenvalues_options(tria_mesh_fixture): - """ - Test the apply_eigenvalues_options function. - - Parameters: - tria_mesh_fixture: Fixture providing a triangular mesh for testing. - - Raises: - AssertionError: For unexpected eigenvalues normalization failures. - Note: - - Assumes the `apply_eigenvalues_options` function is correctly implemented. - - The 'norm' variable specifies the eigenvalues normalization method for testing. - - Test verifies 'eigenvalues' result as None for successful normalization. - """ - norm = "none" - - eigenvalues = shapedna.normalize_ev( - geom=tria_mesh_fixture, - evals=np.array([10, 2, 33]), - method=norm, - ) - assert eigenvalues is None - - -def test_compute_surface_brainprint(): - """ - Test the compute_surface_brainprint function. - - This test validates compute_surface_brainprint with a sample VTK path. - - Raises: - AssertionError: If the test fails due to unexpected return types. - - Note: - - Assumes the `compute_surface_brainprint` function is correctly implemented. - - Replace 'path' with the actual path to a VTK file for meaningful testing. - - Test checks tuple result, unpacks 'eigenvalues' & 'eigenvectors', verifies types. - """ - - path = "/home/ashrafo/LaPy/data/cubeTria.vtk" - # This path must be replace with the actival .vtk path - result = compute_surface_brainprint(path, num=50) - eigenvalues, eigenvectors = result - assert isinstance(result, tuple), "Return value is not a tuple" - assert isinstance(eigenvalues, np.ndarray), "Eigenvalues is not a numpyarray" - assert len(eigenvalues) == 52, "Eigenvalues has an incorrect length" - assert eigenvectors is None or isinstance( - eigenvectors, np.ndarray - ), "Eigenvectors is not None or a NumPy array" - - -# def test_run_brainprint(sample_subjects_dir): -# """ -# Test the run_brainprint function. - - -# Parameters: -# sample_subjects_dir (str): Path to the sample subjects directory. - -# Raises: -# AssertionError: For unexpected return types or eigenvalue matrix properties. - -# Note: -# - Assumes the `run_brainprint` function is correctly implemented. -# - The test checks: -# - The result is a tuple. -# - 'eigenvalues' is a dictionary. -# - 'eigenvectors' is either None or a dictionary. -# - 'distances' is either None or a dictionary. -# - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks. -# - It verifies that the matrix contains at least two rows. -# - It ensures that the values in the first two rows are non-negative. -# """ -# subject_id = "bert" -# result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) -# eigenvalues, eigenvectors, distances = result -# assert isinstance(result, tuple), "Return value is not a tuple" -# assert isinstance(eigenvalues, dict), "Return value is not a dictionary" -# assert eigenvectors is None or isinstance( -# eigenvectors, dict -# ), "Eigenvectors is not None or a NumPy array" -# assert distances is None or isinstance( -# eigenvectors, dict -# ), "Distacnces is not None or a dictionary" - -# # Check if "area" and "volume" are the first two rows in the eigenvalue matrix -# if eigenvalues is not None and subject_id in eigenvalues: -# eigenvalue_matrix = eigenvalues[subject_id] -# assert ( -# eigenvalue_matrix.shape[0] >= 2 -# ), "Eigenvalue matrix has fewer than two rows" - -# # Check the values of the first two rows are non-zero -# assert np.all( -# eigenvalue_matrix[:2] >= 0 -# ), "Area and volume values are not non-negative" - - -def test_compute_brainprint(sample_subjects_dir): - """ - Test the compute_brainprint function. - - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - - Raises: - AssertionError: If the test fails due to unexpected return types. - - Note: - Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_BP. - """ - subject_id = "bert" - subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) - destination = create_output_paths(subject_dir=subject_dir, destination=None) - surfaces = create_surfaces(subject_dir, destination, skip_cortex=False) - result = compute_brainprint(surfaces) - eigenvalues, eigenvectors = result - assert isinstance(result, tuple), "result is not a tuple" - assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" - assert eigenvectors is None or isinstance( - eigenvectors, dict - ), "eigenvectors are not none or dict type" - - -def test_run_brainprint(sample_subjects_dir): - """ - Test the run_brainprint function. - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - - Raises: - AssertionError: For unexpected return types or eigenvalue matrix properties. - - Note: - - Assumes run_brainprint is correctly implemented and available. - - Checks: - - Result is a tuple. - - 'eigenvalues' is a dict. - - 'eigenvectors' is None or a dict. - - 'distances' is None or a dict. - - If 'eigenvalues' not None and subject found, further checks eigenvalue matrix. - """ - subject_id = "bert" - result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) - eigenvalues, eigenvectors, distances = result - assert isinstance(result, tuple), "result is not tuple" - assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" - assert eigenvectors is None or isinstance( - eigenvectors, dict - ), "eigenvector is not none or dict type" - assert distances is None or isinstance(eigenvectors, dict) - - # Check if "area" and "volume" are the first two rows in the eigenvalue matrix - if eigenvalues is not None and subject_id in eigenvalues: - eigenvalue_matrix = eigenvalues[subject_id] - assert eigenvalue_matrix.shape[0] >= 2 # Ensure there are at least two rows - - # Check the values of the first two rows are non-zero - assert np.all( - eigenvalue_matrix[:2] >= 0 - ) # Assuming "area" and "volume" are positive values diff --git a/test_cases/test_surfaces.py b/test_cases/test_surfaces.py deleted file mode 100644 index 1b25305..0000000 --- a/test_cases/test_surfaces.py +++ /dev/null @@ -1,120 +0,0 @@ -from pathlib import Path - -import pytest -from lapy import TriaMesh - -# from brainprint.utils.utils import run_shell_command -from brainprint.surfaces import create_aseg_surface, create_cortical_surfaces, read_vtk - - -# Create a fixture for a sample subjects_dir -@pytest.fixture -def sample_subjects_dir(): - # Use a temporary directory for testing, replace this your local subject directory - subject_dir = "../../brainprint_test_data/bert" - return subject_dir - - -# Create a fixture for a sample subjects_dir -@pytest.fixture -def sample_destination_dir(): - # Use a temporary directory for testing, replace this your local subject directory - destination = "../../brainprint_test_data/destination" - return destination - - -def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): - """ - Test the create_aseg_surfaces function. - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - sample_destination_dir (str): Path to the sample destination directory. - - Raises: - AssertionError: If the test fails due to unexpected results. - - Note: - - Assumes the `create_aseg_surface` function is correctly implemented. - - Checks the result for non-None, path type, and existence of the result file. - - Verifies that the result file name matches the expected .vtk file name. - """ - - subject_dir = Path(sample_subjects_dir) - destination = Path(sample_destination_dir) - indices = ["label1", "label2"] - result = create_aseg_surface(subject_dir, destination, indices) - - assert result is not None, "The result is not None" - assert isinstance(result, Path), "The result is not a path" - assert result.exists(), "The result file does not exist" - - expected_file_name = "aseg.final.label1_label2.vtk" - assert result.name == expected_file_name, "The result file does not match .vtk file" - - -def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): - """ - Test the create_cortical_surfaces function. - - Parameters: - sample_subjects_dir (str): Path to the sample subjects directory. - sample_destination_dir (str): Path to the sample destination directory. - - Raises: - AssertionError: If the test fails due to unexpected results. - - Note: - - Assumes the `create_cortical_surfaces` function is correctly implemented. - - Validates dictionary structure with label names as keys and Path objects. - - Verifies specific key-value pairs in the result. - """ - subject_dir = Path(sample_subjects_dir) - destination = Path(sample_destination_dir) - # Call the function - result = create_cortical_surfaces(subject_dir, destination) - - assert isinstance(result, dict) - assert all(isinstance(value, Path) for value in result.values()) - assert "lh-white-2d" in result - assert result["lh-white-2d"] == destination / "surfaces" / "lh.white.vtk" - assert "rh-white-2d" in result - assert result["rh-white-2d"] == destination / "surfaces" / "rh.white.vtk" - assert "lh-pial-2d" in result - assert result["lh-pial-2d"] == destination / "surfaces" / "lh.pial.vtk" - assert "rh-pial-2d" in result - assert result["rh-pial-2d"] == destination / "surfaces" / "rh.pial.vtk" - - -def test_read_vtk(): - """ - Test the read_vtk function with a sample VTK file. - - Raises: - AssertionError: If the test fails due to unexpected result type. - - Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. - """ - vtk_file = ( - "../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" - ) - - # Call the function with the sample VTK file - vtk_path = Path(vtk_file) - triangular_mesh = read_vtk(vtk_path) - - # Assert that the result is an instance of TriaMesh - assert isinstance(triangular_mesh, TriaMesh) - - -def test_surf_to_vtk(sample_subjects_dir, sample_destination_dir): - subject_dir = Path(sample_subjects_dir) - sample_destination_dir = Path(sample_destination_dir) - try: - trimesh = TriaMesh.read_fssurf(subject_dir) - if trimesh: - trimesh.write_vtk(sample_destination_dir) - else: - print("Failed to read .surf file") - except Exception as e: - print(f"An error occurred: {e}") From 4bf62d15b857c183c9b7083a4095fca20c3a9d3b Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 3 Apr 2024 14:54:29 +0200 Subject: [PATCH 18/46] Convert Path to str --- brainprint/surfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index bfc6f4a..b41449a 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -224,5 +224,5 @@ def surf_to_vtk(source: Path, destination: Path) -> Path: Path Resulting *.vtk* file. """ - TriaMesh.read_fssurf(source).write_vtk(destination) + TriaMesh.read_fssurf(source).write_vtk(str(destination)) return destination From 76405712a9019b1d1cf3b291a088401708469b23 Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 3 Apr 2024 16:11:56 +0200 Subject: [PATCH 19/46] Formatting --- brainprint/brainprint.py | 1 - 1 file changed, 1 deletion(-) diff --git a/brainprint/brainprint.py b/brainprint/brainprint.py index fd8665a..a2cbbd7 100644 --- a/brainprint/brainprint.py +++ b/brainprint/brainprint.py @@ -74,7 +74,6 @@ def compute_surface_brainprint( Parameters ---------- path : Path - Path to the *.vtk* surface path. return_eigenvectors : bool, optional Whether to store eigenvectors in the result (default is True). From 6ed374b5f3e0b5dd6b9992eca5e435ed4d789ecf Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 3 Apr 2024 17:10:55 +0200 Subject: [PATCH 20/46] Revised tests --- brainprint/utils/tests/test_asymmetry.py | 23 +-- brainprint/utils/tests/test_brainprint.py | 174 ++++++---------------- brainprint/utils/tests/test_surfaces.py | 61 +++++--- 3 files changed, 100 insertions(+), 158 deletions(-) diff --git a/brainprint/utils/tests/test_asymmetry.py b/brainprint/utils/tests/test_asymmetry.py index 4d66701..7c2e26e 100644 --- a/brainprint/utils/tests/test_asymmetry.py +++ b/brainprint/utils/tests/test_asymmetry.py @@ -6,18 +6,23 @@ @pytest.fixture def sample_subjects_dir(): - # Use a temporary directory for testing, replace this your local subject directory - # subjects_dir = "../../brainprint_test_data/subjects" - subjects_dir = "../../../../brainprint_test_data/subjects" + # Use a temporary directory for testing + subjects_dir = "data" return subjects_dir +@pytest.fixture +def sample_subject_id(): + # Use a temporary subject id for testing + subject_id = "bert" + return subject_id -def test_run_brainprint(sample_subjects_dir): +def test_compute_asymmetry(sample_subjects_dir, sample_subject_id): """ - Test the run_brainprint function with sample data. + Test the compute_asymmetry function with sample data. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. + sample_subject_id (str): Sampple subject id. Raises: AssertionError: If the test fails due to unexpected results. @@ -25,11 +30,11 @@ def test_run_brainprint(sample_subjects_dir): Note: Assumes run_brainprint checks result types and eigenvalue matrix properties. """ - subject_id = "bert" - result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) - eigenvalues, eigenvectors, distances = result - # Computed eigen values from run_brainprint + # Run brainprint + eigenvalues, eigenvectors, distances = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=sample_subject_id) + + # Compute asymmetry distances = compute_asymmetry(eigenvalues, distance="euc", skip_cortex=False) # Assert that distances is a dictionary diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py index 3c9974f..07a46f6 100644 --- a/brainprint/utils/tests/test_brainprint.py +++ b/brainprint/utils/tests/test_brainprint.py @@ -2,10 +2,11 @@ import numpy as np import pytest -from lapy import TriaMesh, shapedna +from lapy import TriaMesh from brainprint import Brainprint from brainprint.brainprint import ( + apply_eigenvalues_options, compute_brainprint, compute_surface_brainprint, run_brainprint, @@ -13,68 +14,39 @@ from brainprint.surfaces import create_surfaces from brainprint.utils.utils import create_output_paths, validate_subject_dir -""" -Don't forget to source Freesurfer -export FREESURFER_HOME=/groups/ag-reuter/software-centos/fs72 -source $FREESURFER_HOME/SetUpFreeSurfer.sh -""" - # Create a fixture for a sample subjects_dir @pytest.fixture def sample_subjects_dir(): # Use a temporary directory for testing - # subjects_dir = "../../brainprint_test_data/subjects" - subjects_dir = "../../../../brainprint_test_data/subjects" + subjects_dir = "data" return subjects_dir - +# Create a fixture for a sample subject_id @pytest.fixture -def tria_mesh_fixture(): - """ - Create a triangular mesh fixture with predefined points and triangles. +def sample_subject_id(): + # Use a temporary subject_id for testing + subject_id = "bert" + return subject_id - Returns: - TriaMesh: An instance of the TriaMesh class with predefined data. - """ - points = np.array( - [ - [0.0, 0.0, 0.0], - [0, 1, 0], - [1, 1, 0], - [1, 0, 0], - [0, 0, 1], - [0, 1, 1], - [1, 1, 1], - [1, 0, 1], - ] - ) - trias = np.array( - [ - [0, 1, 2], - [2, 3, 0], - [4, 5, 6], - [6, 7, 4], - [0, 4, 7], - [7, 3, 0], - [0, 4, 5], - [5, 1, 0], - [1, 5, 6], - [6, 2, 1], - [3, 7, 6], - [6, 2, 3], - ] - ) - return TriaMesh(points, trias) +# Create a fixture for a sample vtk_file +@pytest.fixture +def sample_vtk_file(sample_subjects_dir, sample_subject_id): + # Use a temporary file for testing + source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" + destination = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial.vtk" + TriaMesh.read_fssurf(source).write_vtk(str(destination)) + return str(destination) # Test the initialization of the Brainprint class -def test_brainprint_initialization(sample_subjects_dir): +def test_brainprint_initialization(sample_subjects_dir, sample_subject_id): """ Test the initialization and run of the Brainprint class. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. + sample_subject_id (str): Sample subject ID. Raises: AssertionError: If the test fails due to incorrect attribute initialization. @@ -98,9 +70,8 @@ def test_brainprint_initialization(sample_subjects_dir): assert not brainprint.keep_temp assert not brainprint.use_cholmod - # Change subject id if required - subject_id = "bert" - result = brainprint.run(subject_id=subject_id) + # + result = brainprint.run(sample_subject_id) assert isinstance(result, dict) # Check if the values in the dict are of type Dict[str, Path] @@ -108,52 +79,50 @@ def test_brainprint_initialization(sample_subjects_dir): assert isinstance(value, Path) -def test_apply_eigenvalues_options(tria_mesh_fixture): +def test_apply_eigenvalues_options(sample_vtk_file, norm="none", reweight=False): """ Test the apply_eigenvalues_options function. Parameters: - tria_mesh_fixture: Fixture providing a triangular mesh for testing. + sample_vtk_file (str): Path to sample vtk file. + norm (str): eigenvalues normalization method. + reweight (bool): eigenvalues reweighting. Raises: AssertionError: For unexpected eigenvalues normalization failures. + Note: - Assumes the `apply_eigenvalues_options` function is correctly implemented. - - The 'norm' variable specifies the eigenvalues normalization method for testing. - - Test verifies 'eigenvalues' result as None for successful normalization. + - Checks type of 'eigenvalues' for successful normalization. """ - norm = "none" - eigenvalues = shapedna.normalize_ev( - geom=tria_mesh_fixture, - evals=np.array([10, 2, 33]), - method=norm, - ) - assert eigenvalues is None + tria_mesh = TriaMesh.read_vtk(sample_vtk_file) + + eigenvalues = np.random.rand(50) + + new_eigenvalues = apply_eigenvalues_options(eigenvalues, triangular_mesh=tria_mesh, norm=norm, reweight=reweight) + + assert isinstance(new_eigenvalues, np.ndarray) -def test_compute_surface_brainprint(): +def test_compute_surface_brainprint(sample_vtk_file): """ Test the compute_surface_brainprint function. This test validates compute_surface_brainprint with a sample VTK path. + Parameters: + sample_vtk_file (str): Path to sample vtk file. + Raises: AssertionError: If the test fails due to unexpected return types. Note: - Assumes the `compute_surface_brainprint` function is correctly implemented. - - Replace 'path' with the actual path to a VTK file for meaningful testing. - Test checks tuple result, unpacks 'eigenvalues' & 'eigenvectors', verifies types. """ - # path = "/home/ashrafo/LaPy/data/cubeTria.vtk" - path = "../../../../LaPy/data/cubeTria.vtk" - - # This path must be replace with the actival .vtk path - result = compute_surface_brainprint(path, num=50) - eigenvalues, eigenvectors = result - assert isinstance(result, tuple), "Return value is not a tuple" + eigenvalues, eigenvectors = compute_surface_brainprint(sample_vtk_file, num=50) assert isinstance(eigenvalues, np.ndarray), "Eigenvalues is not a numpyarray" assert len(eigenvalues) == 52, "Eigenvalues has an incorrect length" assert eigenvectors is None or isinstance( @@ -161,60 +130,13 @@ def test_compute_surface_brainprint(): ), "Eigenvectors is not None or a NumPy array" -# def test_run_brainprint(sample_subjects_dir): -# """ -# Test the run_brainprint function. - - -# Parameters: -# sample_subjects_dir (str): Path to the sample subjects directory. - -# Raises: -# AssertionError: For unexpected return types or eigenvalue matrix properties. - -# Note: -# - Assumes the `run_brainprint` function is correctly implemented. -# - The test checks: -# - The result is a tuple. -# - 'eigenvalues' is a dictionary. -# - 'eigenvectors' is either None or a dictionary. -# - 'distances' is either None or a dictionary. -# - If 'eigenvalues' not None and subject found, more eigenvalue matrix checks. -# - It verifies that the matrix contains at least two rows. -# - It ensures that the values in the first two rows are non-negative. -# """ -# subject_id = "bert" -# result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) -# eigenvalues, eigenvectors, distances = result -# assert isinstance(result, tuple), "Return value is not a tuple" -# assert isinstance(eigenvalues, dict), "Return value is not a dictionary" -# assert eigenvectors is None or isinstance( -# eigenvectors, dict -# ), "Eigenvectors is not None or a NumPy array" -# assert distances is None or isinstance( -# eigenvectors, dict -# ), "Distacnces is not None or a dictionary" - -# # Check if "area" and "volume" are the first two rows in the eigenvalue matrix -# if eigenvalues is not None and subject_id in eigenvalues: -# eigenvalue_matrix = eigenvalues[subject_id] -# assert ( -# eigenvalue_matrix.shape[0] >= 2 -# ), "Eigenvalue matrix has fewer than two rows" - -# # Check the values of the first two rows are non-zero -# assert np.all( -# eigenvalue_matrix[:2] >= 0 -# ), "Area and volume values are not non-negative" - - -def test_compute_brainprint(sample_subjects_dir): +def test_compute_brainprint(sample_subjects_dir, sample_subject_id): """ Test the compute_brainprint function. - Parameters: sample_subjects_dir (str): Path to the sample subjects directory. + sample_subject_id (str): Sample subject ID. Raises: AssertionError: If the test fails due to unexpected return types. @@ -222,8 +144,8 @@ def test_compute_brainprint(sample_subjects_dir): Note: Assumes validate_subject_dir, create_output_paths, create_surfaces & compute_BP. """ - subject_id = "bert" - subject_dir = validate_subject_dir(sample_subjects_dir, subject_id) + + subject_dir = validate_subject_dir(sample_subjects_dir, sample_subject_id) destination = create_output_paths(subject_dir=subject_dir, destination=None) surfaces = create_surfaces(subject_dir, destination, skip_cortex=False) result = compute_brainprint(surfaces) @@ -235,12 +157,13 @@ def test_compute_brainprint(sample_subjects_dir): ), "eigenvectors are not none or dict type" -def test_run_brainprint(sample_subjects_dir): +def test_run_brainprint(sample_subjects_dir, sample_subject_id): """ Test the run_brainprint function. Parameters: sample_subjects_dir (str): Path to the sample subjects directory. + sample_subject_id (str): Sample subject ID. Raises: AssertionError: For unexpected return types or eigenvalue matrix properties. @@ -254,9 +177,10 @@ def test_run_brainprint(sample_subjects_dir): - 'distances' is None or a dict. - If 'eigenvalues' not None and subject found, further checks eigenvalue matrix. """ - subject_id = "bert" - result = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=subject_id) + + result = run_brainprint(sample_subjects_dir, sample_subject_id) eigenvalues, eigenvectors, distances = result + assert isinstance(result, tuple), "result is not tuple" assert isinstance(eigenvalues, dict), "eigenvalues are not dict type" assert eigenvectors is None or isinstance( @@ -265,8 +189,8 @@ def test_run_brainprint(sample_subjects_dir): assert distances is None or isinstance(eigenvectors, dict) # Check if "area" and "volume" are the first two rows in the eigenvalue matrix - if eigenvalues is not None and subject_id in eigenvalues: - eigenvalue_matrix = eigenvalues[subject_id] + if eigenvalues is not None and sample_subject_id in eigenvalues: + eigenvalue_matrix = eigenvalues[sample_subject_id] assert eigenvalue_matrix.shape[0] >= 2 # Ensure there are at least two rows # Check the values of the first two rows are non-zero diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index a5f0415..a300d06 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -1,31 +1,45 @@ -from pathlib import Path - import pytest -from lapy import TriaMesh -# from brainprint.utils.utils import run_shell_command +from lapy import TriaMesh +from pathlib import Path from brainprint.surfaces import create_aseg_surface, create_cortical_surfaces, read_vtk # Create a fixture for a sample subjects_dir @pytest.fixture def sample_subjects_dir(): - # Use a temporary directory for testing, replace this your local subject directory - # subject_dir = "../../brainprint_test_data/bert" - subject_dir = "../../../../brainprint_test_data/bert" + # Use a temporary directory for testing + subject_dir = "data" return subject_dir -# Create a fixture for a sample subjects_dir +# Create a fixture for a sample subject_id +@pytest.fixture +def sample_subject_id(): + # Use a temporary subject_id for testing + subject_id = "bert" + return subject_id + + +# Create a fixture for a sample destination_dir @pytest.fixture def sample_destination_dir(): - # Use a temporary directory for testing, replace this your local subject directory - # destination = "../../brainprint_test_data/destination" - destination = "../../../../brainprint_test_data/destination" + # Use a temporary directory for testing + destination = "data" return destination -def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): +# Create a fixture for a sample vtk_file +@pytest.fixture +def sample_vtk_file(sample_subjects_dir, sample_subject_id): + # Use a temporary file for testing + source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" + destination = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial.vtk" + TriaMesh.read_fssurf(source).write_vtk(str(destination)) + return str(destination) + + +def test_create_aseg_surfaces(sample_subjects_dir, sample_subject_id, sample_destination_dir): """ Test the create_aseg_surfaces function. @@ -42,8 +56,8 @@ def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): - Verifies that the result file name matches the expected .vtk file name. """ - subject_dir = Path(sample_subjects_dir) - destination = Path(sample_destination_dir) + subject_dir = Path(sample_subjects_dir) / sample_subject_id + destination = Path(sample_destination_dir) / sample_subject_id indices = ["label1", "label2"] result = create_aseg_surface(subject_dir, destination, indices) @@ -55,7 +69,7 @@ def test_create_aseg_surfaces(sample_subjects_dir, sample_destination_dir): assert result.name == expected_file_name, "The result file does not match .vtk file" -def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): +def test_create_cortical_surfaces(sample_subjects_dir, sample_subject_id, sample_destination_dir): """ Test the create_cortical_surfaces function. @@ -71,9 +85,9 @@ def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): - Validates dictionary structure with label names as keys and Path objects. - Verifies specific key-value pairs in the result. """ - subject_dir = Path(sample_subjects_dir) - destination = Path(sample_destination_dir) - # Call the function + + subject_dir = Path(sample_subjects_dir) / sample_subject_id + destination = Path(sample_destination_dir) / sample_subject_id result = create_cortical_surfaces(subject_dir, destination) assert isinstance(result, dict) @@ -88,7 +102,7 @@ def test_create_cortical_surfaces(sample_subjects_dir, sample_destination_dir): assert result["rh-pial-2d"] == destination / "surfaces" / "rh.pial.vtk" -def test_read_vtk(): +def test_read_vtk(sample_vtk_file): """ Test the read_vtk function with a sample VTK file. @@ -97,19 +111,18 @@ def test_read_vtk(): Note: Assumes `read_vtk` correctly implemented, validates TriaMesh result type. """ - vtk_file = "../../../../brainprint_test_data/destination/surfaces/aseg.final.label1_label2.vtk" # Call the function with the sample VTK file - vtk_path = Path(vtk_file) + vtk_path = Path(sample_vtk_file) triangular_mesh = read_vtk(vtk_path) # Assert that the result is an instance of TriaMesh assert isinstance(triangular_mesh, TriaMesh) -def test_surf_to_vtk(sample_subjects_dir, sample_destination_dir): - subject_dir = Path(sample_subjects_dir) - sample_destination_dir = Path(sample_destination_dir) +def test_surf_to_vtk(sample_subjects_dir, sample_subject_id, sample_destination_dir): + subject_dir = Path(sample_subjects_dir) / sample_subject_id + sample_destination_dir = Path(sample_destination_dir) / sample_subject_id try: trimesh = TriaMesh.read_fssurf(subject_dir) if trimesh: From 857b758ad540216abd460d7e2de9a269792682bc Mon Sep 17 00:00:00 2001 From: diersk Date: Wed, 3 Apr 2024 18:00:20 +0200 Subject: [PATCH 21/46] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 076dca8..04aba89 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ cortical parcellations or label files). ## Installation -Use the following code to install the latest release of LaPy into your local +Use the following code to install the latest release into your local Python package directory: `python3 -m pip install brainprint` From a03d203cf16063109459fb7df72732f2564961a9 Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 15:35:37 +0200 Subject: [PATCH 22/46] Added transform to surface RAS --- brainprint/surfaces.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index b41449a..9ad6087 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -44,10 +44,14 @@ def create_aseg_surface( # runs marching cube to extract surface vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5) + # convert to surface RAS + vertices = np.matmul(aseg.header.get_vox2ras_tkr(), np.append(vertices, np.ones((vertices.shape[0], 1)), axis=1).transpose()).transpose()[:,0:3] + # convert to vtk relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) + conversion_destination = destination / relative_path TriaMesh(v=vertices, t=trias).write_vtk(filename=conversion_destination) From 7d380557a3bc5f2eb0142c97155bd0d79f47a8a9 Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 15:57:12 +0200 Subject: [PATCH 23/46] Added keeping largest component, removing free vertices --- brainprint/surfaces.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 9ad6087..c864a5f 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -5,6 +5,7 @@ import nibabel as nb import numpy as np from pathlib import Path +from scipy import sparse as sp from lapy import TriaMesh @@ -47,13 +48,26 @@ def create_aseg_surface( # convert to surface RAS vertices = np.matmul(aseg.header.get_vox2ras_tkr(), np.append(vertices, np.ones((vertices.shape[0], 1)), axis=1).transpose()).transpose()[:,0:3] + # create tria mesh + aseg_mesh = TriaMesh(v=vertices, t=trias) + + # keep largest connected component + comps = sp.csgraph.connected_components(aseg_mesh.adj_sym, directed=False) + if comps[0] > 1: + comps_largest = np.argmax(np.unique(comps[1], return_counts=True)[1]) + vtcs_remove = np.where(comps[1] != comps_largest) + tria_keep = np.sum(np.isin(aseg_mesh.t, vtcs_remove), axis=1) == 0 + aseg_mesh.t = aseg_mesh.t[tria_keep, :] + + # remove free vertices + aseg_mesh.rm_free_vertices_() + # convert to vtk relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) - conversion_destination = destination / relative_path - TriaMesh(v=vertices, t=trias).write_vtk(filename=conversion_destination) + aseg_mesh.write_vtk(filename=conversion_destination) return conversion_destination From ac2822dc8d4c8546c3305ee9e4c613237422ae67 Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 18:00:18 +0200 Subject: [PATCH 24/46] Updated dummy test for create_aseg_surfaces --- brainprint/utils/tests/test_surfaces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index a300d06..6979f7a 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -58,14 +58,14 @@ def test_create_aseg_surfaces(sample_subjects_dir, sample_subject_id, sample_des subject_dir = Path(sample_subjects_dir) / sample_subject_id destination = Path(sample_destination_dir) / sample_subject_id - indices = ["label1", "label2"] + indices = ["255"] result = create_aseg_surface(subject_dir, destination, indices) assert result is not None, "The result is not None" assert isinstance(result, Path), "The result is not a path" assert result.exists(), "The result file does not exist" - expected_file_name = "aseg.final.label1_label2.vtk" + expected_file_name = "aseg.final.255.vtk" assert result.name == expected_file_name, "The result file does not match .vtk file" From c495db87b194c2d2fa09ee371e48fcc0328fd7ba Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 20:24:57 +0200 Subject: [PATCH 25/46] Formatting --- brainprint/surfaces.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index c864a5f..7794df6 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -29,7 +29,7 @@ def create_aseg_surface( Returns ------- Path - Path to the generated surface in VTK format + Path to the generated surface in VTK format. """ aseg_path = subject_dir / "mri/aseg.mgz" temp_name = "temp/aseg.{uid}".format(uid=uuid.uuid4()) From f7035d2b17291507ea3a98ad4aa2379e39190c25 Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 20:30:18 +0200 Subject: [PATCH 26/46] Black formatting --- brainprint/cli/__init__.py | 1 + brainprint/cli/help_text.py | 5 ++++- brainprint/cli/utils.py | 1 + brainprint/surfaces.py | 9 +++++++-- brainprint/utils/tests/test_asymmetry.py | 6 +++++- brainprint/utils/tests/test_brainprint.py | 8 ++++++-- brainprint/utils/tests/test_surfaces.py | 10 +++++++--- brainprint/utils/utils.py | 1 + 8 files changed, 32 insertions(+), 9 deletions(-) diff --git a/brainprint/cli/__init__.py b/brainprint/cli/__init__.py index e22217b..a9c8148 100644 --- a/brainprint/cli/__init__.py +++ b/brainprint/cli/__init__.py @@ -1,6 +1,7 @@ """ BrainPrint analysis CLI. """ + from ..brainprint import run_brainprint from .parser import parse_options diff --git a/brainprint/cli/help_text.py b/brainprint/cli/help_text.py index bd78c72..6dce772 100644 --- a/brainprint/cli/help_text.py +++ b/brainprint/cli/help_text.py @@ -1,6 +1,7 @@ """ Help text strings for the :mod:`brainprint.cli` module. """ + CLI_DESCRIPTION: str = ( "This program conducts a brainprint analysis of FreeSurfer output." ) @@ -20,7 +21,9 @@ ASYM_DISTANCE: str = ( "Distance measurement to use for asymmetry calculation (default: euc)" ) -CHOLMOD: str = "Use cholesky decomposition (faster) instead of LU decomposition (slower). May require manual install of scikit-sparse library. Default is LU decomposition." +CHOLMOD: str = ( + "Use cholesky decomposition (faster) instead of LU decomposition (slower). May require manual install of scikit-sparse library. Default is LU decomposition." +) KEEP_TEMP: str = ( "Whether to keep the temporary files directory or not, by default False" ) diff --git a/brainprint/cli/utils.py b/brainprint/cli/utils.py index 4709b3d..fa7ee98 100644 --- a/brainprint/cli/utils.py +++ b/brainprint/cli/utils.py @@ -1,6 +1,7 @@ """ Utility functions for the :mod:`brainprint.cli` module. """ + from . import help_text diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 7794df6..28f98ab 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -1,6 +1,7 @@ """ Utility module holding surface generation related functions. """ + import uuid import nibabel as nb import numpy as np @@ -11,6 +12,7 @@ from skimage.measure import marching_cubes + def create_aseg_surface( subject_dir: Path, destination: Path, indices: list[int] ) -> Path: @@ -37,7 +39,7 @@ def create_aseg_surface( # binarize on selected labels (creates temp indices_mask) aseg = nb.load(aseg_path) - indices_num = [ int(x) for x in indices ] + indices_num = [int(x) for x in indices] aseg_data_bin = np.isin(aseg.get_fdata(), indices_num).astype(np.float32) aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) nb.save(img=aseg_bin, filename=indices_mask) @@ -46,7 +48,10 @@ def create_aseg_surface( vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5) # convert to surface RAS - vertices = np.matmul(aseg.header.get_vox2ras_tkr(), np.append(vertices, np.ones((vertices.shape[0], 1)), axis=1).transpose()).transpose()[:,0:3] + vertices = np.matmul( + aseg.header.get_vox2ras_tkr(), + np.append(vertices, np.ones((vertices.shape[0], 1)), axis=1).transpose(), + ).transpose()[:, 0:3] # create tria mesh aseg_mesh = TriaMesh(v=vertices, t=trias) diff --git a/brainprint/utils/tests/test_asymmetry.py b/brainprint/utils/tests/test_asymmetry.py index 7c2e26e..49980ea 100644 --- a/brainprint/utils/tests/test_asymmetry.py +++ b/brainprint/utils/tests/test_asymmetry.py @@ -10,12 +10,14 @@ def sample_subjects_dir(): subjects_dir = "data" return subjects_dir + @pytest.fixture def sample_subject_id(): # Use a temporary subject id for testing subject_id = "bert" return subject_id + def test_compute_asymmetry(sample_subjects_dir, sample_subject_id): """ Test the compute_asymmetry function with sample data. @@ -32,7 +34,9 @@ def test_compute_asymmetry(sample_subjects_dir, sample_subject_id): """ # Run brainprint - eigenvalues, eigenvectors, distances = run_brainprint(subjects_dir=sample_subjects_dir, subject_id=sample_subject_id) + eigenvalues, eigenvectors, distances = run_brainprint( + subjects_dir=sample_subjects_dir, subject_id=sample_subject_id + ) # Compute asymmetry distances = compute_asymmetry(eigenvalues, distance="euc", skip_cortex=False) diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py index 07a46f6..1b708cf 100644 --- a/brainprint/utils/tests/test_brainprint.py +++ b/brainprint/utils/tests/test_brainprint.py @@ -22,6 +22,7 @@ def sample_subjects_dir(): subjects_dir = "data" return subjects_dir + # Create a fixture for a sample subject_id @pytest.fixture def sample_subject_id(): @@ -29,11 +30,12 @@ def sample_subject_id(): subject_id = "bert" return subject_id + # Create a fixture for a sample vtk_file @pytest.fixture def sample_vtk_file(sample_subjects_dir, sample_subject_id): # Use a temporary file for testing - source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" + source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" destination = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial.vtk" TriaMesh.read_fssurf(source).write_vtk(str(destination)) return str(destination) @@ -100,7 +102,9 @@ def test_apply_eigenvalues_options(sample_vtk_file, norm="none", reweight=False) eigenvalues = np.random.rand(50) - new_eigenvalues = apply_eigenvalues_options(eigenvalues, triangular_mesh=tria_mesh, norm=norm, reweight=reweight) + new_eigenvalues = apply_eigenvalues_options( + eigenvalues, triangular_mesh=tria_mesh, norm=norm, reweight=reweight + ) assert isinstance(new_eigenvalues, np.ndarray) diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index 6979f7a..eea6682 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -33,13 +33,15 @@ def sample_destination_dir(): @pytest.fixture def sample_vtk_file(sample_subjects_dir, sample_subject_id): # Use a temporary file for testing - source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" + source = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial" destination = Path(sample_subjects_dir) / sample_subject_id / "surf" / "lh.pial.vtk" TriaMesh.read_fssurf(source).write_vtk(str(destination)) return str(destination) -def test_create_aseg_surfaces(sample_subjects_dir, sample_subject_id, sample_destination_dir): +def test_create_aseg_surfaces( + sample_subjects_dir, sample_subject_id, sample_destination_dir +): """ Test the create_aseg_surfaces function. @@ -69,7 +71,9 @@ def test_create_aseg_surfaces(sample_subjects_dir, sample_subject_id, sample_des assert result.name == expected_file_name, "The result file does not match .vtk file" -def test_create_cortical_surfaces(sample_subjects_dir, sample_subject_id, sample_destination_dir): +def test_create_cortical_surfaces( + sample_subjects_dir, sample_subject_id, sample_destination_dir +): """ Test the create_cortical_surfaces function. diff --git a/brainprint/utils/utils.py b/brainprint/utils/utils.py index 53b89d0..144f8c1 100644 --- a/brainprint/utils/utils.py +++ b/brainprint/utils/utils.py @@ -1,6 +1,7 @@ """ Utilities for the :mod:`brainprint` module. """ + import os import shlex import subprocess From 35085ce76d348aece9bab5d98f03215d9337091c Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 20:32:50 +0200 Subject: [PATCH 27/46] Isort formatting --- brainprint/surfaces.py | 7 +++---- brainprint/utils/tests/test_surfaces.py | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 28f98ab..d8287ff 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -1,15 +1,14 @@ """ Utility module holding surface generation related functions. """ - import uuid -import nibabel as nb -import numpy as np from pathlib import Path from scipy import sparse as sp +import nibabel as nb +import numpy as np from lapy import TriaMesh - +from scipy import sparse as sp from skimage.measure import marching_cubes diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index eea6682..f6132f3 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -1,7 +1,8 @@ -import pytest +from pathlib import Path +import pytest from lapy import TriaMesh -from pathlib import Path + from brainprint.surfaces import create_aseg_surface, create_cortical_surfaces, read_vtk From 8bbb38eb5972f8df0f145a0572d5dcd498a3848c Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 5 Apr 2024 20:35:29 +0200 Subject: [PATCH 28/46] Ruff formatting --- brainprint/utils/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/brainprint/utils/utils.py b/brainprint/utils/utils.py index 144f8c1..343b8d4 100644 --- a/brainprint/utils/utils.py +++ b/brainprint/utils/utils.py @@ -2,9 +2,6 @@ Utilities for the :mod:`brainprint` module. """ -import os -import shlex -import subprocess from pathlib import Path import numpy as np From e238c9dab652aef43a54188f0cccb74851a6e408 Mon Sep 17 00:00:00 2001 From: diersk Date: Mon, 17 Jun 2024 10:14:45 +0200 Subject: [PATCH 29/46] Changed marching cube algorithm to Lorensen --- brainprint/surfaces.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index d8287ff..d9a4242 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -43,8 +43,15 @@ def create_aseg_surface( aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) nb.save(img=aseg_bin, filename=indices_mask) + # legacy code for running FreeSurfer's mri_pretess + #import subprocess + #subprocess.run(["cp", str(indices_mask), str(indices_mask).replace(".mgz", "-no_pretess.mgz")]) + ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(indices_mask).replace(".mgz", "-no_pretess.mgz"), str(indices_mask)]) + ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(subject_dir / "mri/norm.mgz"), str(indices_mask)]) + #aseg_data_bin = nb.load(indices_mask).get_fdata() + # runs marching cube to extract surface - vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5) + vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5, allow_degenerate=False, method="lorensen") # convert to surface RAS vertices = np.matmul( From 1206f9f4b47d72fbd6f3e330390f3646085c2a19 Mon Sep 17 00:00:00 2001 From: diersk Date: Mon, 17 Jun 2024 18:08:40 +0200 Subject: [PATCH 30/46] Removed ventricles, striatum; merged cerebellum cortex and cerebellum white matter --- brainprint/surfaces.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index d9a4242..bf3c1c9 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -102,25 +102,21 @@ def create_aseg_surfaces(subject_dir: Path, destination: Path) -> dict[str, Path # Define aseg labels # combined and individual aseg labels: - # - Left Striatum: left Caudate + Putamen + Accumbens - # - Right Striatum: right Caudate + Putamen + Accumbens # - CorpusCallosum: 5 subregions combined # - Cerebellum: brainstem + (left+right) cerebellum WM and GM - # - Ventricles: (left+right) lat.vent + inf.lat.vent + choroidplexus + 3rdVent + CSF + # - Left-Cerebellum: left cerebellum WM and GM + # - Right-Cerebellum: right cerebellum WM and GM # - Lateral-Ventricle: lat.vent + inf.lat.vent + choroidplexus # - 3rd-Ventricle: 3rd-Ventricle + CSF aseg_labels = { "CorpusCallosum": ["251", "252", "253", "254", "255"], "Cerebellum": ["7", "8", "16", "46", "47"], - "Ventricles": ["4", "5", "14", "24", "31", "43", "44", "63"], "3rd-Ventricle": ["14", "24"], "4th-Ventricle": ["15"], "Brain-Stem": ["16"], - "Left-Striatum": ["11", "12", "26"], "Left-Lateral-Ventricle": ["4", "5", "31"], - "Left-Cerebellum-White-Matter": ["7"], - "Left-Cerebellum-Cortex": ["8"], + "Left-Cerebellum": ["7", "8"], "Left-Thalamus-Proper": ["10"], "Left-Caudate": ["11"], "Left-Putamen": ["12"], @@ -129,10 +125,8 @@ def create_aseg_surfaces(subject_dir: Path, destination: Path) -> dict[str, Path "Left-Amygdala": ["18"], "Left-Accumbens-area": ["26"], "Left-VentralDC": ["28"], - "Right-Striatum": ["50", "51", "58"], "Right-Lateral-Ventricle": ["43", "44", "63"], - "Right-Cerebellum-White-Matter": ["46"], - "Right-Cerebellum-Cortex": ["47"], + "Right-Cerebellum": ["46", "47"], "Right-Thalamus-Proper": ["49"], "Right-Caudate": ["50"], "Right-Putamen": ["51"], From 07723f7b94704fcc71825f926a9749ea55ea461a Mon Sep 17 00:00:00 2001 From: Taha Abdullah Date: Wed, 12 Jun 2024 17:29:34 +0200 Subject: [PATCH 31/46] adding local runner functionality to pytest workflow - added new workflow 'pytest_local.yaml' to run pytest on a local runner - added environment variables for subject directory and subject id Notes: - local runner needs to be provisioned on the repository with the necessary environment variables for this workflow to work --- .github/workflows/pytest_local.yml | 54 +++++++++++++++++++++++ brainprint/surfaces.py | 3 ++ brainprint/utils/tests/test_asymmetry.py | 5 ++- brainprint/utils/tests/test_brainprint.py | 8 ++-- brainprint/utils/tests/test_surfaces.py | 12 ++--- 5 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/pytest_local.yml diff --git a/.github/workflows/pytest_local.yml b/.github/workflows/pytest_local.yml new file mode 100644 index 0000000..d07ca4e --- /dev/null +++ b/.github/workflows/pytest_local.yml @@ -0,0 +1,54 @@ +name: pytest_local +concurrency: + group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} + cancel-in-progress: true +on: + pull_request: + paths: + - '**.py' + push: + branches: [main] + paths: + - '**.py' + workflow_dispatch: + +jobs: + pytest: + timeout-minutes: 30 +# strategy: +# fail-fast: false +# matrix: +# os: [ubuntu, macos, windows] +# python-version: [3.8, 3.9, "3.10", "3.11"] +# name: ${{ matrix.os }} - py${{ matrix.python-version }} +# runs-on: ${{ matrix.os }}-latest + runs-on: self-hosted + defaults: + run: + shell: bash + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install package + run: | + python -m pip install --progress-bar off --upgrade pip setuptools wheel + python -m pip install --progress-bar off .[test] + - name: Display system information + run: brainprint-sys_info --developer + - name: Run pytest + run: pytest brainprint --cov=brainprint --cov-report=xml --cov-config=pyproject.toml +# - name: Upload to codecov +# if: ${{ matrix.os == 'ubuntu' && matrix.python-version == 3.9 }} +# uses: codecov/codecov-action@v4 +# with: +# files: ./coverage.xml +# flags: unittests # optional +# name: codecov-umbrella # optional +# fail_ci_if_error: true # optional (default = false) +# verbose: true # optional (default = false) +# token: ${{ secrets.CODECOV_TOKEN }} +# slug: deep-mi/BrainPrint diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index bf3c1c9..9a4bba6 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -5,6 +5,7 @@ from pathlib import Path from scipy import sparse as sp +import os import nibabel as nb import numpy as np from lapy import TriaMesh @@ -77,7 +78,9 @@ def create_aseg_surface( relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) + # os.makedirs(os.path.dirname(relative_path), exist_ok=True) conversion_destination = destination / relative_path + os.makedirs(os.path.dirname(conversion_destination), exist_ok=True) aseg_mesh.write_vtk(filename=conversion_destination) return conversion_destination diff --git a/brainprint/utils/tests/test_asymmetry.py b/brainprint/utils/tests/test_asymmetry.py index 49980ea..89a33bc 100644 --- a/brainprint/utils/tests/test_asymmetry.py +++ b/brainprint/utils/tests/test_asymmetry.py @@ -1,3 +1,4 @@ +import os import pytest from brainprint.asymmetry import compute_asymmetry @@ -7,14 +8,14 @@ @pytest.fixture def sample_subjects_dir(): # Use a temporary directory for testing - subjects_dir = "data" + subjects_dir = os.environ["SUBJECTS_DIR"] return subjects_dir @pytest.fixture def sample_subject_id(): # Use a temporary subject id for testing - subject_id = "bert" + subject_id = os.environ["SUBJECT_ID"] return subject_id diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py index 1b708cf..dc5cf8f 100644 --- a/brainprint/utils/tests/test_brainprint.py +++ b/brainprint/utils/tests/test_brainprint.py @@ -1,5 +1,6 @@ from pathlib import Path +import os import numpy as np import pytest from lapy import TriaMesh @@ -19,15 +20,14 @@ @pytest.fixture def sample_subjects_dir(): # Use a temporary directory for testing - subjects_dir = "data" + subjects_dir = os.environ["SUBJECTS_DIR"] return subjects_dir -# Create a fixture for a sample subject_id @pytest.fixture def sample_subject_id(): - # Use a temporary subject_id for testing - subject_id = "bert" + # Use a temporary subject id for testing + subject_id = os.environ["SUBJECT_ID"] return subject_id diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index f6132f3..ddae6f5 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -1,5 +1,6 @@ from pathlib import Path +import os import pytest from lapy import TriaMesh @@ -10,15 +11,14 @@ @pytest.fixture def sample_subjects_dir(): # Use a temporary directory for testing - subject_dir = "data" - return subject_dir + subjects_dir = os.environ["SUBJECTS_DIR"] + return subjects_dir -# Create a fixture for a sample subject_id @pytest.fixture def sample_subject_id(): - # Use a temporary subject_id for testing - subject_id = "bert" + # Use a temporary subject id for testing + subject_id = os.environ["SUBJECT_ID"] return subject_id @@ -26,7 +26,7 @@ def sample_subject_id(): @pytest.fixture def sample_destination_dir(): # Use a temporary directory for testing - destination = "data" + destination = os.environ["DESTINATION_DIR"] return destination From 43da98e4be51433e7686ae0bdab82865d2ef8470 Mon Sep 17 00:00:00 2001 From: Taha Abdullah Date: Tue, 18 Jun 2024 12:55:49 +0200 Subject: [PATCH 32/46] adding a new test to check for file existence in brainprint output - added new pytest to check if all required files exist in the brainprint output - removed comments from pytest_local.yaml --- .github/workflows/pytest_local.yml | 7 ----- brainprint/utils/tests/test_files.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 brainprint/utils/tests/test_files.py diff --git a/.github/workflows/pytest_local.yml b/.github/workflows/pytest_local.yml index d07ca4e..836ca25 100644 --- a/.github/workflows/pytest_local.yml +++ b/.github/workflows/pytest_local.yml @@ -15,13 +15,6 @@ on: jobs: pytest: timeout-minutes: 30 -# strategy: -# fail-fast: false -# matrix: -# os: [ubuntu, macos, windows] -# python-version: [3.8, 3.9, "3.10", "3.11"] -# name: ${{ matrix.os }} - py${{ matrix.python-version }} -# runs-on: ${{ matrix.os }}-latest runs-on: self-hosted defaults: run: diff --git a/brainprint/utils/tests/test_files.py b/brainprint/utils/tests/test_files.py new file mode 100644 index 0000000..08f980f --- /dev/null +++ b/brainprint/utils/tests/test_files.py @@ -0,0 +1,39 @@ +import os +import pytest + + +@pytest.fixture +def sample_subjects_dir(): + # Use a temporary directory for testing + subjects_dir = os.environ["SUBJECTS_DIR"] + return subjects_dir + + +@pytest.fixture +def sample_subject_id(): + # Use a temporary subject id for testing + subject_id = os.environ["SUBJECT_ID"] + return subject_id + + +def test_files_exist_in_directory(sample_subjects_dir, sample_subject_id): + subject_directory = os.path.join(sample_subjects_dir, sample_subject_id) + output_directory = os.path.join(subject_directory, "brainprint") + output_files = [sample_subject_id + ".brainprint.csv"] # replace with your expected files + + surface_directory = os.path.join(output_directory, "surfaces") + surface_files = ["aseg.final.10.vtk", "aseg.final.47.vtk", "aseg.final.11_12_26.vtk", "aseg.final.49.vtk", + "aseg.final.11.vtk", "aseg.final.50_51_58.vtk", "aseg.final.12.vtk", "aseg.final.50.vtk", + "aseg.final.13.vtk", "aseg.final.51.vtk", "aseg.final.14_24.vtk", "aseg.final.52.vtk", + "aseg.final.15.vtk", "aseg.final.53.vtk", "aseg.final.16.vtk", "aseg.final.54.vtk", + "aseg.final.17.vtk", "aseg.final.58.vtk", "aseg.final.18.vtk", "aseg.final.60.vtk", + "aseg.final.251_252_253_254_255.vtk", "aseg.final.7_8_16_46_47.vtk", "aseg.final.26.vtk", + "aseg.final.7.vtk", "aseg.final.28.vtk", "aseg.final.8.vtk", "aseg.final.43_44_63.vtk", + "lh.pial.vtk", "aseg.final.4_5_14_24_31_43_44_63.vtk", "lh.white.vtk", "aseg.final.4_5_31.vtk", + "rh.pial.vtk", "aseg.final.46.vtk", "rh.white.vtk"] + + for file in output_files: + assert os.path.isfile(os.path.join(output_directory, file)), f"{file} does not exist in the directory" + + for file in surface_files: + assert os.path.isfile(os.path.join(surface_directory, file)), f"{file} does not exist in the directory" From 31b14e3a0d273f0979f72257d0913d07fafd1464 Mon Sep 17 00:00:00 2001 From: Taha Abdullah Date: Tue, 18 Jun 2024 14:35:11 +0200 Subject: [PATCH 33/46] isort and black changes --- brainprint/surfaces.py | 11 +++-- brainprint/utils/tests/test_asymmetry.py | 1 + brainprint/utils/tests/test_brainprint.py | 2 +- brainprint/utils/tests/test_files.py | 58 ++++++++++++++++++----- brainprint/utils/tests/test_surfaces.py | 2 +- 5 files changed, 55 insertions(+), 19 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 9a4bba6..ca03382 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -5,7 +5,6 @@ from pathlib import Path from scipy import sparse as sp -import os import nibabel as nb import numpy as np from lapy import TriaMesh @@ -45,14 +44,16 @@ def create_aseg_surface( nb.save(img=aseg_bin, filename=indices_mask) # legacy code for running FreeSurfer's mri_pretess - #import subprocess - #subprocess.run(["cp", str(indices_mask), str(indices_mask).replace(".mgz", "-no_pretess.mgz")]) + # import subprocess + # subprocess.run(["cp", str(indices_mask), str(indices_mask).replace(".mgz", "-no_pretess.mgz")]) ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(indices_mask).replace(".mgz", "-no_pretess.mgz"), str(indices_mask)]) ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(subject_dir / "mri/norm.mgz"), str(indices_mask)]) - #aseg_data_bin = nb.load(indices_mask).get_fdata() + # aseg_data_bin = nb.load(indices_mask).get_fdata() # runs marching cube to extract surface - vertices, trias, _, _ = marching_cubes(volume=aseg_data_bin, level=0.5, allow_degenerate=False, method="lorensen") + vertices, trias, _, _ = marching_cubes( + volume=aseg_data_bin, level=0.5, allow_degenerate=False, method="lorensen" + ) # convert to surface RAS vertices = np.matmul( diff --git a/brainprint/utils/tests/test_asymmetry.py b/brainprint/utils/tests/test_asymmetry.py index 89a33bc..6db4443 100644 --- a/brainprint/utils/tests/test_asymmetry.py +++ b/brainprint/utils/tests/test_asymmetry.py @@ -1,4 +1,5 @@ import os + import pytest from brainprint.asymmetry import compute_asymmetry diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py index dc5cf8f..33192d0 100644 --- a/brainprint/utils/tests/test_brainprint.py +++ b/brainprint/utils/tests/test_brainprint.py @@ -1,6 +1,6 @@ +import os from pathlib import Path -import os import numpy as np import pytest from lapy import TriaMesh diff --git a/brainprint/utils/tests/test_files.py b/brainprint/utils/tests/test_files.py index 08f980f..73f52ed 100644 --- a/brainprint/utils/tests/test_files.py +++ b/brainprint/utils/tests/test_files.py @@ -1,4 +1,5 @@ import os + import pytest @@ -19,21 +20,54 @@ def sample_subject_id(): def test_files_exist_in_directory(sample_subjects_dir, sample_subject_id): subject_directory = os.path.join(sample_subjects_dir, sample_subject_id) output_directory = os.path.join(subject_directory, "brainprint") - output_files = [sample_subject_id + ".brainprint.csv"] # replace with your expected files + output_files = [ + sample_subject_id + ".brainprint.csv" + ] # replace with your expected files surface_directory = os.path.join(output_directory, "surfaces") - surface_files = ["aseg.final.10.vtk", "aseg.final.47.vtk", "aseg.final.11_12_26.vtk", "aseg.final.49.vtk", - "aseg.final.11.vtk", "aseg.final.50_51_58.vtk", "aseg.final.12.vtk", "aseg.final.50.vtk", - "aseg.final.13.vtk", "aseg.final.51.vtk", "aseg.final.14_24.vtk", "aseg.final.52.vtk", - "aseg.final.15.vtk", "aseg.final.53.vtk", "aseg.final.16.vtk", "aseg.final.54.vtk", - "aseg.final.17.vtk", "aseg.final.58.vtk", "aseg.final.18.vtk", "aseg.final.60.vtk", - "aseg.final.251_252_253_254_255.vtk", "aseg.final.7_8_16_46_47.vtk", "aseg.final.26.vtk", - "aseg.final.7.vtk", "aseg.final.28.vtk", "aseg.final.8.vtk", "aseg.final.43_44_63.vtk", - "lh.pial.vtk", "aseg.final.4_5_14_24_31_43_44_63.vtk", "lh.white.vtk", "aseg.final.4_5_31.vtk", - "rh.pial.vtk", "aseg.final.46.vtk", "rh.white.vtk"] + surface_files = [ + "aseg.final.10.vtk", + "aseg.final.47.vtk", + "aseg.final.11_12_26.vtk", + "aseg.final.49.vtk", + "aseg.final.11.vtk", + "aseg.final.50_51_58.vtk", + "aseg.final.12.vtk", + "aseg.final.50.vtk", + "aseg.final.13.vtk", + "aseg.final.51.vtk", + "aseg.final.14_24.vtk", + "aseg.final.52.vtk", + "aseg.final.15.vtk", + "aseg.final.53.vtk", + "aseg.final.16.vtk", + "aseg.final.54.vtk", + "aseg.final.17.vtk", + "aseg.final.58.vtk", + "aseg.final.18.vtk", + "aseg.final.60.vtk", + "aseg.final.251_252_253_254_255.vtk", + "aseg.final.7_8_16_46_47.vtk", + "aseg.final.26.vtk", + "aseg.final.7.vtk", + "aseg.final.28.vtk", + "aseg.final.8.vtk", + "aseg.final.43_44_63.vtk", + "lh.pial.vtk", + "aseg.final.4_5_14_24_31_43_44_63.vtk", + "lh.white.vtk", + "aseg.final.4_5_31.vtk", + "rh.pial.vtk", + "aseg.final.46.vtk", + "rh.white.vtk", + ] for file in output_files: - assert os.path.isfile(os.path.join(output_directory, file)), f"{file} does not exist in the directory" + assert os.path.isfile( + os.path.join(output_directory, file) + ), f"{file} does not exist in the directory" for file in surface_files: - assert os.path.isfile(os.path.join(surface_directory, file)), f"{file} does not exist in the directory" + assert os.path.isfile( + os.path.join(surface_directory, file) + ), f"{file} does not exist in the directory" diff --git a/brainprint/utils/tests/test_surfaces.py b/brainprint/utils/tests/test_surfaces.py index ddae6f5..d39d72e 100644 --- a/brainprint/utils/tests/test_surfaces.py +++ b/brainprint/utils/tests/test_surfaces.py @@ -1,6 +1,6 @@ +import os from pathlib import Path -import os import pytest from lapy import TriaMesh From 0400868fc1401ef77ad99c0e2652e64e209824d6 Mon Sep 17 00:00:00 2001 From: diersk Date: Fri, 12 Jul 2024 12:34:01 +0200 Subject: [PATCH 34/46] Removed uuid in filenames; formatting --- brainprint/surfaces.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index ca03382..4d2552a 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -1,7 +1,7 @@ """ Utility module holding surface generation related functions. """ -import uuid +import os from pathlib import Path from scipy import sparse as sp @@ -9,6 +9,7 @@ import numpy as np from lapy import TriaMesh from scipy import sparse as sp +from scipy import ndimage as sn from skimage.measure import marching_cubes @@ -33,7 +34,7 @@ def create_aseg_surface( Path to the generated surface in VTK format. """ aseg_path = subject_dir / "mri/aseg.mgz" - temp_name = "temp/aseg.{uid}".format(uid=uuid.uuid4()) + temp_name = "temp/aseg.{indices}".format(indices="_".join(indices)) indices_mask = destination / f"{temp_name}.mgz" # binarize on selected labels (creates temp indices_mask) @@ -42,6 +43,16 @@ def create_aseg_surface( aseg_data_bin = np.isin(aseg.get_fdata(), indices_num).astype(np.float32) aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) nb.save(img=aseg_bin, filename=indices_mask) + + # legacy code for applying mask smoothing + #k = 1.0 / np.sqrt(np.array([ + # [[3, 2, 3], [2, 1, 2], [3, 2, 3]], + # [[2, 1, 2], [1, 1, 1], [2, 1, 1]], + # [[3, 2, 3], [2, 1, 2], [3, 2, 3]], + #])) + #aseg_data_bin = sn.convolve(aseg_data_bin, k) + #aseg_data_bin = np.round(aseg_data_bin / np.sum(k)) + #nb.save(img=nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine), filename=str(indices_mask).replace(".mgz", "-filter.mgz")) # legacy code for running FreeSurfer's mri_pretess # import subprocess @@ -79,7 +90,7 @@ def create_aseg_surface( relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) - # os.makedirs(os.path.dirname(relative_path), exist_ok=True) + conversion_destination = destination / relative_path os.makedirs(os.path.dirname(conversion_destination), exist_ok=True) aseg_mesh.write_vtk(filename=conversion_destination) From e819b5f7fcfa7b63cd0cc50ba87ce5d2d8e5f2ba Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 23 Jul 2024 11:30:01 +0200 Subject: [PATCH 35/46] Updated README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 04aba89..21038e9 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,13 @@ asymmetry calculation is performed and/or for the eigenvectors (CLI `--evecs` fl ## Changes -There are some changes in functionality in comparison to the original [BrainPrint](https://github.com/Deep-MI/BrainPrint-legacy) +Since version 0.5.0, some changes break compatibility with earlier versions (0.4.0 and lower) as well as the [original BrainPrint](https://github.com/Deep-MI/BrainPrint-legacy). These changes include: + +- for the creation of surfaces from voxel-based segmentations, we have replaced FreeSurfer's marching cube algorithm by scikit-image's marching cube algorithm. Similarly, other FreeSurfer binaries have been replaced by custom Python functions. As a result, a parallel FreeSurfer installation is no longer a requirement for running the brainprint software. +- we have changed / removed the following composite structures from the brainprint shape descriptor: the left and right *striatum* (composite of caudate, putamen, and nucleus accumbens) and the left and right *ventricles* (composite of lateral, inferior lateral, 3rd ventricle, choroid plexus, and CSF) have been removed; the left and right *cerebellum-white-matter* and *cerebellum-cortex* have been merged into left and right *cerebellum*. +As a result of these changes, numerical values for the brainprint shape descriptor that are obtained from version 0.5.0 and higher are expected to differ from earlier versions when applied to the same data, but should remain highly correlated with earlier results. + +There are some changes in version 0.4.0 (and lower) in functionality in comparison to the original [BrainPrint](https://github.com/Deep-MI/BrainPrint-legacy) scripts: - currently no support for tetrahedral meshes From 147d7a11f3bc6640c4000c19dfec66057e8f0f9a Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 23 Jul 2024 11:44:06 +0200 Subject: [PATCH 36/46] Removed local tests --- .github/workflows/pytest_local.yml | 47 ------------------------------ 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/pytest_local.yml diff --git a/.github/workflows/pytest_local.yml b/.github/workflows/pytest_local.yml deleted file mode 100644 index 836ca25..0000000 --- a/.github/workflows/pytest_local.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: pytest_local -concurrency: - group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} - cancel-in-progress: true -on: - pull_request: - paths: - - '**.py' - push: - branches: [main] - paths: - - '**.py' - workflow_dispatch: - -jobs: - pytest: - timeout-minutes: 30 - runs-on: self-hosted - defaults: - run: - shell: bash - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Install package - run: | - python -m pip install --progress-bar off --upgrade pip setuptools wheel - python -m pip install --progress-bar off .[test] - - name: Display system information - run: brainprint-sys_info --developer - - name: Run pytest - run: pytest brainprint --cov=brainprint --cov-report=xml --cov-config=pyproject.toml -# - name: Upload to codecov -# if: ${{ matrix.os == 'ubuntu' && matrix.python-version == 3.9 }} -# uses: codecov/codecov-action@v4 -# with: -# files: ./coverage.xml -# flags: unittests # optional -# name: codecov-umbrella # optional -# fail_ci_if_error: true # optional (default = false) -# verbose: true # optional (default = false) -# token: ${{ secrets.CODECOV_TOKEN }} -# slug: deep-mi/BrainPrint From fd9cfdc7ef46550f2dd4f02e81a5881087b9cd75 Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 23 Jul 2024 18:05:36 +0200 Subject: [PATCH 37/46] Formatting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 21038e9..ffdd961 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ Since version 0.5.0, some changes break compatibility with earlier versions (0.4 - for the creation of surfaces from voxel-based segmentations, we have replaced FreeSurfer's marching cube algorithm by scikit-image's marching cube algorithm. Similarly, other FreeSurfer binaries have been replaced by custom Python functions. As a result, a parallel FreeSurfer installation is no longer a requirement for running the brainprint software. - we have changed / removed the following composite structures from the brainprint shape descriptor: the left and right *striatum* (composite of caudate, putamen, and nucleus accumbens) and the left and right *ventricles* (composite of lateral, inferior lateral, 3rd ventricle, choroid plexus, and CSF) have been removed; the left and right *cerebellum-white-matter* and *cerebellum-cortex* have been merged into left and right *cerebellum*. + As a result of these changes, numerical values for the brainprint shape descriptor that are obtained from version 0.5.0 and higher are expected to differ from earlier versions when applied to the same data, but should remain highly correlated with earlier results. There are some changes in version 0.4.0 (and lower) in functionality in comparison to the original [BrainPrint](https://github.com/Deep-MI/BrainPrint-legacy) From 13c45e71a986fd9b692f8fb5923be5d20bd0514a Mon Sep 17 00:00:00 2001 From: diersk Date: Tue, 23 Jul 2024 18:06:10 +0200 Subject: [PATCH 38/46] Removed deprecated structures --- brainprint/asymmetry.py | 17 +---------------- brainprint/cli/help_text.py | 9 ++------- brainprint/utils/tests/test_files.py | 9 ++------- 3 files changed, 5 insertions(+), 30 deletions(-) diff --git a/brainprint/asymmetry.py b/brainprint/asymmetry.py index d723c55..e643688 100644 --- a/brainprint/asymmetry.py +++ b/brainprint/asymmetry.py @@ -25,25 +25,10 @@ def compute_asymmetry( dict[str, float] {left_label}_{right_label}, distance. """ - # Define structures - - # combined and individual aseg labels: - # - Left Striatum: left Caudate + Putamen + Accumbens - # - Right Striatum: right Caudate + Putamen + Accumbens - # - CorpusCallosum: 5 subregions combined - # - Cerebellum: brainstem + (left+right) cerebellum WM and GM - # - Ventricles: (left+right) lat.vent + inf.lat.vent + choroidplexus + 3rdVent + CSF - # - Lateral-Ventricle: lat.vent + inf.lat.vent + choroidplexus - # - 3rd-Ventricle: 3rd-Ventricle + CSF structures_left_right = [ - ("Left-Striatum", "Right-Striatum"), ("Left-Lateral-Ventricle", "Right-Lateral-Ventricle"), - ( - "Left-Cerebellum-White-Matter", - "Right-Cerebellum-White-Matter", - ), - ("Left-Cerebellum-Cortex", "Right-Cerebellum-Cortex"), + ("Left-Cerebellum", "Right-Cerebellum"), ("Left-Thalamus-Proper", "Right-Thalamus-Proper"), ("Left-Caudate", "Right-Caudate"), ("Left-Putamen", "Right-Putamen"), diff --git a/brainprint/cli/help_text.py b/brainprint/cli/help_text.py index 6dce772..11ff8d6 100644 --- a/brainprint/cli/help_text.py +++ b/brainprint/cli/help_text.py @@ -47,14 +47,11 @@ CorpusCallosum [251, 252, 253, 254, 255] Cerebellum [7, 8, 16, 46, 47] -Ventricles [4, 5, 14, 24, 31, 43, 44, 63] 3rd-Ventricle [14, 24] 4th-Ventricle 15 Brain-Stem 16 -Left-Striatum [11, 12, 26] Left-Lateral-Ventricle [4, 5, 31] -Left-Cerebellum-White-Matter 7 -Left-Cerebellum-Cortex 8 +Left-Cerebellum [7, 8] Left-Thalamus-Proper 10 Left-Caudate 11 Left-Putamen 12 @@ -63,10 +60,8 @@ Left-Amygdala 18 Left-Accumbens-area 26 Left-VentralDC 28 -Right-Striatum [50, 51, 58] Right-Lateral-Ventricle [43, 44, 63] -Right-Cerebellum-White-Matter 46 -Right-Cerebellum-Cortex 47 +Right-Cerebellum [46, 47] Right-Thalamus-Proper 49 Right-Caudate 50 Right-Putamen 51 diff --git a/brainprint/utils/tests/test_files.py b/brainprint/utils/tests/test_files.py index 73f52ed..51127e3 100644 --- a/brainprint/utils/tests/test_files.py +++ b/brainprint/utils/tests/test_files.py @@ -27,11 +27,8 @@ def test_files_exist_in_directory(sample_subjects_dir, sample_subject_id): surface_directory = os.path.join(output_directory, "surfaces") surface_files = [ "aseg.final.10.vtk", - "aseg.final.47.vtk", - "aseg.final.11_12_26.vtk", "aseg.final.49.vtk", "aseg.final.11.vtk", - "aseg.final.50_51_58.vtk", "aseg.final.12.vtk", "aseg.final.50.vtk", "aseg.final.13.vtk", @@ -49,16 +46,14 @@ def test_files_exist_in_directory(sample_subjects_dir, sample_subject_id): "aseg.final.251_252_253_254_255.vtk", "aseg.final.7_8_16_46_47.vtk", "aseg.final.26.vtk", - "aseg.final.7.vtk", + "aseg.final.7_8.vtk", "aseg.final.28.vtk", - "aseg.final.8.vtk", "aseg.final.43_44_63.vtk", "lh.pial.vtk", - "aseg.final.4_5_14_24_31_43_44_63.vtk", "lh.white.vtk", "aseg.final.4_5_31.vtk", "rh.pial.vtk", - "aseg.final.46.vtk", + "aseg.final.46_47.vtk", "rh.white.vtk", ] From cf35d7f78a97df2e3a2f821aae75add9309ce190 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 17:19:29 +0200 Subject: [PATCH 39/46] Updated pytest workflow --- .github/workflows/pytest.yml | 38 +++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 8e42eff..663da44 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -3,23 +3,30 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} cancel-in-progress: true on: - pull_request: - paths: - - '**.py' - push: - branches: [main] - paths: - - '**.py' +# pull_request: +# paths: +# - '**.py' +# push: +# branches: [main] +# paths: +# - '**.py' workflow_dispatch: +env: + SUBJECTS_DIR: /home/runner/work/brainprint-tests/brainprint-tests/data + SUBJECT_ID: test + DESTINATION_DIR: /home/runner/work/brainprint-tests/brainprint-tests/dat +h jobs: pytest: timeout-minutes: 30 strategy: fail-fast: false matrix: - os: [ubuntu, macos, windows] - python-version: ["3.9", "3.10", "3.11", "3.12"] +# os: [ubuntu, macos, windows] +# python-version: [3.8, 3.9, "3.10", "3.11"] + os: [ubuntu] + python-version: ["3.10"] name: ${{ matrix.os }} - py${{ matrix.python-version }} runs-on: ${{ matrix.os }}-latest defaults: @@ -32,12 +39,25 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + architecture: 'x64' - name: Install package run: | python -m pip install --progress-bar off --upgrade pip setuptools wheel python -m pip install --progress-bar off .[test] + - name: Create data folders + run: | + mkdir -p data/test/mri + mkdir -p data/test/surf + mkdir -p data/test/temp - name: Display system information run: brainprint-sys_info --developer + - name: Download files + run: | + wget https://surfer.nmr.mgh.harvard.edu/pub/data/tutorial_data/buckner_data/tutorial_subjs/good_output/mri/aseg.mgz -O data/test/mri/aseg.mgz + wget https://surfer.nmr.mgh.harvard.edu/pub/data/tutorial_data/buckner_data/tutorial_subjs/good_output/surf/lh.white -O data/test/surf/lh.white + wget https://surfer.nmr.mgh.harvard.edu/pub/data/tutorial_data/buckner_data/tutorial_subjs/good_output/surf/rh.white -O data/test/surf/rh.white + wget https://surfer.nmr.mgh.harvard.edu/pub/data/tutorial_data/buckner_data/tutorial_subjs/good_output/surf/lh.pial -O data/test/surf/lh.pial + wget https://surfer.nmr.mgh.harvard.edu/pub/data/tutorial_data/buckner_data/tutorial_subjs/good_output/surf/rh.pial -O data/test/surf/rh.pial - name: Run pytest run: pytest brainprint --cov=brainprint --cov-report=xml --cov-config=pyproject.toml - name: Upload to codecov From d6d884c5e4b6f422e3bcbbaec75518f9bbac7d27 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 17:29:21 +0200 Subject: [PATCH 40/46] Updated pytest workflow --- .github/workflows/pytest.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 663da44..41897cc 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -11,12 +11,14 @@ on: # paths: # - '**.py' workflow_dispatch: + branches: [remove-freesurfer] + env: SUBJECTS_DIR: /home/runner/work/brainprint-tests/brainprint-tests/data SUBJECT_ID: test DESTINATION_DIR: /home/runner/work/brainprint-tests/brainprint-tests/dat -h + jobs: pytest: timeout-minutes: 30 From 576d065d54198ede43a971d660727e87eabd53cb Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 17:38:04 +0200 Subject: [PATCH 41/46] Updated pytest workflow --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 41897cc..54acf51 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -15,9 +15,9 @@ on: env: - SUBJECTS_DIR: /home/runner/work/brainprint-tests/brainprint-tests/data + SUBJECTS_DIR: /home/runner/work/BrainPrint/BrainPrint/data SUBJECT_ID: test - DESTINATION_DIR: /home/runner/work/brainprint-tests/brainprint-tests/dat + DESTINATION_DIR: /home/runner/work/BrainPrint/BrainPrint/dat jobs: pytest: From 23dd2ad79a0e058353f8300ba923480aa4b43dd3 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 19:00:30 +0200 Subject: [PATCH 42/46] Updated pytest workflow --- .github/workflows/pytest.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 54acf51..692721f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -17,7 +17,7 @@ on: env: SUBJECTS_DIR: /home/runner/work/BrainPrint/BrainPrint/data SUBJECT_ID: test - DESTINATION_DIR: /home/runner/work/BrainPrint/BrainPrint/dat + DESTINATION_DIR: /home/runner/work/BrainPrint/BrainPrint/data jobs: pytest: @@ -46,6 +46,12 @@ jobs: run: | python -m pip install --progress-bar off --upgrade pip setuptools wheel python -m pip install --progress-bar off .[test] + # For testing purposes and until the new lapy version is released, + # uninstall pypi package and reinstall from repo + - name: Get lapy from repo / branch + run: | + python -m pip uninstall lapy + python -m pip install git+https://github.com/deep-mi/lapy - name: Create data folders run: | mkdir -p data/test/mri From 09dd7fc48f7c147ec9dc8270204bd3d8a3972e75 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 19:04:44 +0200 Subject: [PATCH 43/46] Updated pytest workflow --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 692721f..90a67bf 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -50,7 +50,7 @@ jobs: # uninstall pypi package and reinstall from repo - name: Get lapy from repo / branch run: | - python -m pip uninstall lapy + python -m pip uninstall --yes lapy python -m pip install git+https://github.com/deep-mi/lapy - name: Create data folders run: | From 6fcbb818614b3e2a619a35d1bd4e7e83bbe9ca77 Mon Sep 17 00:00:00 2001 From: Kersten Diers Date: Fri, 30 Aug 2024 19:14:44 +0200 Subject: [PATCH 44/46] Black / ruff formatting --- brainprint/surfaces.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 4d2552a..6a6f03e 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -9,7 +9,6 @@ import numpy as np from lapy import TriaMesh from scipy import sparse as sp -from scipy import ndimage as sn from skimage.measure import marching_cubes @@ -43,16 +42,17 @@ def create_aseg_surface( aseg_data_bin = np.isin(aseg.get_fdata(), indices_num).astype(np.float32) aseg_bin = nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine) nb.save(img=aseg_bin, filename=indices_mask) - + # legacy code for applying mask smoothing - #k = 1.0 / np.sqrt(np.array([ + # from scipy import ndimage as sn + # k = 1.0 / np.sqrt(np.array([ # [[3, 2, 3], [2, 1, 2], [3, 2, 3]], # [[2, 1, 2], [1, 1, 1], [2, 1, 1]], # [[3, 2, 3], [2, 1, 2], [3, 2, 3]], - #])) - #aseg_data_bin = sn.convolve(aseg_data_bin, k) - #aseg_data_bin = np.round(aseg_data_bin / np.sum(k)) - #nb.save(img=nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine), filename=str(indices_mask).replace(".mgz", "-filter.mgz")) + # ])) + # aseg_data_bin = sn.convolve(aseg_data_bin, k) + # aseg_data_bin = np.round(aseg_data_bin / np.sum(k)) + # nb.save(img=nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine), filename=str(indices_mask).replace(".mgz", "-filter.mgz")) # legacy code for running FreeSurfer's mri_pretess # import subprocess @@ -90,7 +90,7 @@ def create_aseg_surface( relative_path = "surfaces/aseg.final.{indices}.vtk".format( indices="_".join(indices) ) - + conversion_destination = destination / relative_path os.makedirs(os.path.dirname(conversion_destination), exist_ok=True) aseg_mesh.write_vtk(filename=conversion_destination) From 120a408b96d46b6253e5ec9e26cd0a71c6da4e65 Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 4 Sep 2024 17:19:13 +0200 Subject: [PATCH 45/46] fix line too long and unused var in loop --- brainprint/surfaces.py | 18 +++++++++++++----- brainprint/utils/tests/test_brainprint.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/brainprint/surfaces.py b/brainprint/surfaces.py index 6a6f03e..d78570e 100644 --- a/brainprint/surfaces.py +++ b/brainprint/surfaces.py @@ -3,7 +3,6 @@ """ import os from pathlib import Path -from scipy import sparse as sp import nibabel as nb import numpy as np @@ -52,13 +51,22 @@ def create_aseg_surface( # ])) # aseg_data_bin = sn.convolve(aseg_data_bin, k) # aseg_data_bin = np.round(aseg_data_bin / np.sum(k)) - # nb.save(img=nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine), filename=str(indices_mask).replace(".mgz", "-filter.mgz")) + # nb.save(img=nb.MGHImage(dataobj=aseg_data_bin, affine=aseg.affine), \ + # filename=str(indices_mask).replace(".mgz", "-filter.mgz")) # legacy code for running FreeSurfer's mri_pretess # import subprocess - # subprocess.run(["cp", str(indices_mask), str(indices_mask).replace(".mgz", "-no_pretess.mgz")]) - ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(indices_mask).replace(".mgz", "-no_pretess.mgz"), str(indices_mask)]) - ##subprocess.run(["mri_pretess", str(indices_mask).replace(".mgz", "-no_pretess.mgz"), "pretess" , str(subject_dir / "mri/norm.mgz"), str(indices_mask)]) + # subprocess.run(["cp", str(indices_mask), \ + # str(indices_mask).replace(".mgz", "-no_pretess.mgz")]) + ##subprocess.run(["mri_pretess", \ + ## str(indices_mask).replace(".mgz", "-no_pretess.mgz"), \ + ## "pretess" , \ + ## str(indices_mask).replace(".mgz", "-no_pretess.mgz"), \ + ## str(indices_mask)]) + ##subprocess.run(["mri_pretess", \ + ## str(indices_mask).replace(".mgz", "-no_pretess.mgz"), \ + ## "pretess" , \ + ## str(subject_dir / "mri/norm.mgz"), str(indices_mask)]) # aseg_data_bin = nb.load(indices_mask).get_fdata() # runs marching cube to extract surface diff --git a/brainprint/utils/tests/test_brainprint.py b/brainprint/utils/tests/test_brainprint.py index 33192d0..7153002 100644 --- a/brainprint/utils/tests/test_brainprint.py +++ b/brainprint/utils/tests/test_brainprint.py @@ -77,7 +77,7 @@ def test_brainprint_initialization(sample_subjects_dir, sample_subject_id): assert isinstance(result, dict) # Check if the values in the dict are of type Dict[str, Path] - for key, value in result.items(): + for _key, value in result.items(): assert isinstance(value, Path) From 4b641e7ab6cfad2d09f35aa107c48ef61960965f Mon Sep 17 00:00:00 2001 From: Martin Reuter Date: Wed, 4 Sep 2024 17:26:52 +0200 Subject: [PATCH 46/46] run test on all PR and pushes to main, also use latest lapy release --- .github/workflows/pytest.yml | 22 +++++++--------------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 90a67bf..73dcdef 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -3,16 +3,14 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.ref }} cancel-in-progress: true on: -# pull_request: -# paths: -# - '**.py' -# push: -# branches: [main] -# paths: -# - '**.py' + pull_request: + paths: + - '**.py' + push: + branches: [main] + paths: + - '**.py' workflow_dispatch: - branches: [remove-freesurfer] - env: SUBJECTS_DIR: /home/runner/work/BrainPrint/BrainPrint/data @@ -46,12 +44,6 @@ jobs: run: | python -m pip install --progress-bar off --upgrade pip setuptools wheel python -m pip install --progress-bar off .[test] - # For testing purposes and until the new lapy version is released, - # uninstall pypi package and reinstall from repo - - name: Get lapy from repo / branch - run: | - python -m pip uninstall --yes lapy - python -m pip install git+https://github.com/deep-mi/lapy - name: Create data folders run: | mkdir -p data/test/mri diff --git a/pyproject.toml b/pyproject.toml index b21598e..f7a5613 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ 'numpy>=1.21', 'scipy!=1.13.0', 'pandas', - 'lapy >= 1.0.0, <2', + 'lapy >= 1.1.1', 'psutil', 'nibabel', 'scikit-image',