Skip to content

Changed workflow for setting the masking value in the skull stripping#171

Open
nicmuenster wants to merge 10 commits into
mainfrom
brain_extractor_mask_value
Open

Changed workflow for setting the masking value in the skull stripping#171
nicmuenster wants to merge 10 commits into
mainfrom
brain_extractor_mask_value

Conversation

@nicmuenster

Copy link
Copy Markdown
Collaborator

This change implements the the same process as in the defacing step to either use a global custom value for all inputs or the minimum value within the input image as the background value when masking an inage

… process to either a global custom value for all inputs or the minimum value within the input image
@brainless-bot

brainless-bot Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

🤖 Code Formatting Reminder

Hello there! 👋 It looks like the code in this pull request might benefit from some formatting improvements.
Fix the issues locally or use our auto format action by commenting /format on this PR!

Code style: black

@nicmuenster

Copy link
Copy Markdown
Collaborator Author

/format

@brainless-bot

brainless-bot Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

🤖 I will now format your code with black. Check the status here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns skull-stripping mask application with the defacing workflow by supporting either a global masking/background value or a per-image minimum when filling voxels outside the brain mask.

Changes:

  • Added masking_value support to BrainExtractor and updated apply_mask() to fill outside-mask voxels using either the provided global value or the per-image minimum.
  • Updated HDBetExtractor and SynthStripExtractor constructors to accept/forward masking_value.
  • Minor whitespace cleanup in the registration package init.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
brainles_preprocessing/registration/__init__.py Removes stray blank lines/whitespace.
brainles_preprocessing/brain_extraction/brain_extractor.py Introduces masking_value state and updates mask application to use background fill logic consistent with defacing.
brainles_preprocessing/brain_extraction/synthstrip.py Adds masking_value parameter to the extractor constructor (currently not applied during extract() output writing).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +26
def __init__(
self, border: int = 1, masking_value: Optional[Union[int, float]] = None
):

Copilot AI Jan 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

masking_value is added to the constructor and forwarded to BrainExtractor, but SynthStripExtractor.extract() never uses self.masking_value when writing the masked output (it still hard-codes bg = np.min([0, img_data.min()])). This makes the new parameter ineffective and the docstring misleading. Update extract() to fill background using the same masking_value/per-image-min logic as BrainExtractor.apply_mask (or drop the parameter if not supported).

Copilot uses AI. Check for mistakes.
Comment thread brainles_preprocessing/brain_extraction/brain_extractor.py Outdated
Comment on lines +83 to +93
# check whether a global masking value was passed, otherwise choose minimum
if self.masking_value is None:
current_masking_value = np.min(input_data)
else:
current_masking_value = (
np.array(self.masking_value).astype(input_data.dtype).item()
)
# Apply mask (element-wise either input or masking value)
masked_data = np.where(
mask_data.astype(bool), input_data, current_masking_value
)

Copilot AI Jan 28, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apply_mask() now supports a global masking_value and falls back to per-image minimum when unset, but the existing unit test only asserts that an output file is created. Please extend tests to assert that voxels outside the mask are set to the expected value for both cases (default min-per-image and a custom masking_value).

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@neuronflow
neuronflow requested a review from uturkbey January 28, 2026 13:48

@neuronflow neuronflow left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need changes here?

@nicmuenster

Copy link
Copy Markdown
Collaborator Author

From my understanding, updating the synthstrip init is still necessary, given that while the center modality might be handled without the apply_mask function in the preprocessor workflow (as seen here)

atlas_mask = self.center_modality.extract_brain_region( brain_extractor=self.brain_extractor, bet_dir_path=bet_dir, use_gpu=self.use_gpu, )

the moving modalities are then afterwards still handled by the apply_mask function:

for moving_modality in self.moving_modalities: moving_modality.apply_bet_mask( brain_extractor=self.brain_extractor, mask_path=atlas_mask, bet_dir=bet_dir, )
One thing to consider however, is that in the synthstrip workflow the minimum between 0 and the input min is taken, so min values above 0 would be handled slightly different now, but this missmatch actually also exists for the current state as well.

@neuronflow

Copy link
Copy Markdown
Collaborator

@nicmuenster merge conflict needs to be resolved :)

@brainless-bot

brainless-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Code Formatting Reminder

Hello there! 👋 It looks like the code in this pull request might benefit from some formatting improvements.
Fix the issues locally or use our auto format action by commenting /format on this PR!

Code style: black

@brainless-bot

brainless-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🤖 Code Formatting Reminder

Hello there! 👋 It looks like the code in this pull request might benefit from some formatting improvements.
Fix the issues locally or use our auto format action by commenting /format on this PR!

Code style: black

@nicmuenster

Copy link
Copy Markdown
Collaborator Author

/format

@brainless-bot

brainless-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

🤖 I will now format your code with black. Check the status here.

@nicmuenster
nicmuenster marked this pull request as ready for review July 24, 2026 10:55
@neuronflow
neuronflow requested a review from Copilot July 24, 2026 12:04
@neuronflow

Copy link
Copy Markdown
Collaborator

@uturkbey do you have time to look into this?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

brainles_preprocessing/brain_extraction/brain_extractor.py:85

  • The masking workflow now has two branches (explicit masking_value vs per-image minimum) and changes the masking semantics from multiplication to np.where(..., current_masking_value), but existing tests only assert that an output file is created. Please add assertions covering the new behavior (e.g., that voxels outside the mask equal np.min(input_data) when masking_value=None, and equal the provided value when masking_value is set) to prevent regressions.
        # check whether a global masking value was passed, otherwise choose minimum
        if self.masking_value is None:
            current_masking_value = np.min(input_data)
        else:
            current_masking_value = (

Comment on lines 13 to +17


class HDBetExtractor(BrainExtractor):
def __init__(self, masking_value: Optional[Union[int, float]] = None):
"""

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

brainles_preprocessing/brain_extraction/synthstrip.py:39

  • masking_value is now accepted and passed into BrainExtractor, but SynthStrip still hard-codes the masked background value inside extract() (see bg = np.min([0, img_data.min()]) and img_data[mask == 0] = bg at lines 225-226). This means center-modality skull-stripped outputs written by SynthStripExtractor.extract() will ignore the new masking_value behavior described in the docstring/PR.
            border (int): Mask border threshold in mm. Defaults to 1.
            masking_value (Optional[Union[int, float]], optional): global value to be inserted in the masked areas. Default is None which leads to the minimum of each respective image.

        """
        super().__init__(masking_value=masking_value)

brainles_preprocessing/brain_extraction/brain_extractor.py:79

  • The new masking_value behavior in apply_mask() (global override vs per-image minimum) is not covered by assertions in the existing tests. Current tests only check that an output file is created, so regressions in the chosen background value or dtype casting would go unnoticed.
        # check whether a global masking value was passed, otherwise choose minimum
        if self.masking_value is None:
            current_masking_value = np.min(input_data)
        else:
            current_masking_value = (

Comment on lines +4 to +6
from typing import Optional, Union
from enum import Enum
import numpy as np
Comment on lines +20 to +23
Args:
masking_value (Optional[Union[int, float]], optional): global value to be inserted in the masked areas. Default is None which leads to the minimum of each respective image.
"""
super().__init__(masking_value=masking_value)
@neuronflow

Copy link
Copy Markdown
Collaborator

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)
brainles_preprocessing/brain_extraction/synthstrip.py:39

  • masking_value is now accepted and passed into BrainExtractor, but SynthStrip still hard-codes the masked background value inside extract() (see bg = np.min([0, img_data.min()]) and img_data[mask == 0] = bg at lines 225-226). This means center-modality skull-stripped outputs written by SynthStripExtractor.extract() will ignore the new masking_value behavior described in the docstring/PR.
            border (int): Mask border threshold in mm. Defaults to 1.
            masking_value (Optional[Union[int, float]], optional): global value to be inserted in the masked areas. Default is None which leads to the minimum of each respective image.

        """
        super().__init__(masking_value=masking_value)

brainles_preprocessing/brain_extraction/brain_extractor.py:79

  • The new masking_value behavior in apply_mask() (global override vs per-image minimum) is not covered by assertions in the existing tests. Current tests only check that an output file is created, so regressions in the chosen background value or dtype casting would go unnoticed.
        # check whether a global masking value was passed, otherwise choose minimum
        if self.masking_value is None:
            current_masking_value = np.min(input_data)
        else:
            current_masking_value = (

@nicmuenster please also have a look at the surpressed comments

@nicmuenster

Copy link
Copy Markdown
Collaborator Author

I implmented the first comment, but I am not sure about the second one to be honest. I would also maybe first implement the modality specific masking values before creating specific tests for edge cases

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

brainles_preprocessing/brain_extraction/brain_extractor.py:6

  • Enum is imported but not used in this module. Keeping unused imports can confuse readers and may fail linting if/when a linter is added.
from enum import Enum
import numpy as np

brainles_preprocessing/brain_extraction/brain_extractor.py:85

  • New masking behavior (global masking_value vs per-image minimum) is implemented here, but existing tests only assert that an output file is created. Add unit coverage that verifies masked voxels are set to the configured value, and that None uses the input image minimum.
        # check whether a global masking value was passed, otherwise choose minimum
        if self.masking_value is None:
            current_masking_value = np.min(input_data)
        else:
            current_masking_value = (
                np.array(self.masking_value).astype(input_data.dtype).item()
            )
        # Apply mask (element-wise either input or masking value)
        masked_data = np.where(
            mask_data.astype(bool), input_data, current_masking_value
        )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants