from fastcore.test import test_eq, test_fail
from fastMONAI.utils import make_output_spec, standard_inference_config
class _BinaryInferenceModel(torch.nn.Module):
def forward(self, x):
return x[:, :1]
_binary_config = standard_inference_config(
apply_reorder=False,
target_spacing=None,
item_tfms=[{
'name': 'PadOrCrop', 'size': [6, 6, 6],
'padding_mode': 0, 'mask_name': None,
}],
output_spec=make_output_spec('binary_segmentation', threshold=0.5),
)
_binary_input = torch.linspace(-1, 1, 4 ** 3).reshape(1, 4, 4, 4)
_binary_pred = predict_from_config(_BinaryInferenceModel(), _binary_config, _binary_input)
test_eq(tuple(_binary_pred.shape), (1, 6, 6, 6))
test_eq(_binary_pred.dtype, torch.int64)
_classification_config = standard_inference_config(
apply_reorder=False,
target_spacing=None,
item_tfms=[],
output_spec=make_output_spec('classification', classes=['negative', 'positive']),
)
class _ClassificationInferenceModel(torch.nn.Module):
def forward(self, x):
return torch.tensor([[-1.0, 2.0]], device=x.device)
test_eq(
predict_from_config(
_ClassificationInferenceModel(), _classification_config, torch.zeros(1, 4, 4, 4)
),
'positive',
)
test_fail(
lambda: inference_from_config(_BinaryInferenceModel(), _classification_config),
contains='only supports',
)
_regression_config = standard_inference_config(
apply_reorder=False,
target_spacing=None,
item_tfms=[],
output_spec=make_output_spec('regression', output_shape=[1]),
)
class _RegressionInferenceModel(torch.nn.Module):
def forward(self, x):
return torch.tensor([[42.0]], device=x.device)
test_eq(
predict_from_config(
_RegressionInferenceModel(), _regression_config, torch.zeros(1, 4, 4, 4)
),
torch.tensor([42.0]),
)
test_fail(
lambda: predict_from_config(_BinaryInferenceModel(), _binary_config, torch.zeros(4, 4, 4)),
contains='channel-first',
)Vision inference
save_series_pred
def save_series_pred(
series_obj, save_dir, val:str='1234'
):Saves series prediction with updated DICOM UIDs.
load_system_resources
def load_system_resources(
models_path, learner_fn, variables_fn
):Load necessary resources like learner and variables.
load_model_resources
def load_model_resources(
model_path, device:str='cpu'
):Load a safe model artifact and its embedded standard inference config.
This is the Safetensors-only counterpart to load_system_resources.
predict_from_config
def predict_from_config(
model, inference_config, input
):Apply serialized item transforms, execute one input, and decode its output contract.
inference_from_config
def inference_from_config(
model, inference_config, fn:(<class 'str'>, <class 'pathlib.Path'>)='',
save_path:(<class 'str'>, <class 'pathlib.Path'>)=None, org_img:NoneType=None, input_img:NoneType=None,
org_size:NoneType=None
):Run model-only binary/multiclass segmentation and restore original image space.
inference
def inference(
learn_inf, apply_reorder, target_spacing, fn:(<class 'str'>, <class 'pathlib.Path'>)='',
save_path:(<class 'str'>, <class 'pathlib.Path'>)=None, org_img:NoneType=None, input_img:NoneType=None,
org_size:NoneType=None
):Predict on new data using exported model.
compute_binary_tumor_volume
def compute_binary_tumor_volume(
mask_data:Image
):Compute the volume of the tumor in milliliters (ml).
Post-processing
refine_binary_pred_mask
def refine_binary_pred_mask(
pred_mask, remove_size:(<class 'int'>, <class 'float'>), percentage:float=0.2, verbose:bool=False
)->Tensor:Removes small objects from the predicted binary mask.
Args: pred_mask: The (already binary) predicted mask to clean up. remove_size: Absolute reference object size in voxels (required, > 0). Objects smaller than remove_size * percentage are removed. percentage: Fraction of remove_size used as the cutoff. Defaults to 0.2. verbose: If True, print the number of components. Defaults to False.
Returns: The processed mask with small objects removed.
from fastcore.test import test_eq, test_fail
# large 4x4x4 (64 vox) + small 2x2x2 (8 vox) disjoint cubes
_m = np.zeros((10, 10, 10), dtype=np.uint8)
_m[1:5, 1:5, 1:5] = 1
_m[7:9, 7:9, 7:9] = 1
# cutoff 64*0.2=12.8 removes the small cube, keeps the large
_out = refine_binary_pred_mask(_m, remove_size=64, percentage=0.2)
test_eq(float(_out.sum()), 64.)
test_eq(float(_out[7:9, 7:9, 7:9].sum()), 0.)
# cutoff 8*0.2=1.6 keeps both cubes
_out2 = refine_binary_pred_mask(_m, remove_size=8, percentage=0.2)
test_eq(float(_out2.sum()), 72.)
# empty mask returns zeros
_empty = np.zeros((4, 4, 4), dtype=np.uint8)
test_eq(float(refine_binary_pred_mask(_empty, remove_size=8).sum()), 0.)
# remove_size is required and must be positive
test_fail(lambda: refine_binary_pred_mask(_m))
test_fail(lambda: refine_binary_pred_mask(_m, remove_size=None))
test_fail(lambda: refine_binary_pred_mask(_m, remove_size=0))Parameter `min_size` is deprecated since version 0.26.0 and will be removed in 2.0.0 (or later). To avoid this warning, please use the parameter `max_size` instead. For more details, see the documentation of `remove_small_objects`. Note that the new threshold removes objects smaller than **or equal to** its value, while the previous parameter only removed smaller ones.
keep_largest
def keep_largest(
pred_mask:Tensor
)->Tensor:Keep only the largest connected component in a binary mask.
Args: pred_mask: Binary prediction mask tensor.
Returns: Binary mask with only the largest connected component.