Beispiel #1
0
def test_head_and_neck_base_with_invalid_summed_probability_rule3() -> None:
    """
    Check that an invalid summed probability rule raises a ValueError.
    """
    ground_truth_ids = DEFAULT_HEAD_AND_NECK_GROUND_TRUTH_IDS
    summed_probability_rules = [
        SummedProbabilityRule("spinal_cord", "brainstem", "external2")
    ]
    with pytest.raises(ValueError) as e:
        assert HeadAndNeckBase(
            ground_truth_ids=ground_truth_ids,
            summed_probability_rules=summed_probability_rules)
    assert str(
        e.value
    ) == "SummedProbabilityRule.validate: external2 not in ground truth IDs"
def generate_random_summed_probability_rules(
        ground_truth_ids: List[str]) -> List[SummedProbabilityRule]:
    """
    Generate a list of random summed probability rules, if possible.
    """
    if len(ground_truth_ids) < 3:
        return []

    index0 = random.randint(2, len(ground_truth_ids) - 1)
    index1 = random.randint(1, index0 - 1)
    index2 = random.randint(0, index1 - 1)

    return [
        SummedProbabilityRule(ground_truth_ids[index1],
                              ground_truth_ids[index0],
                              ground_truth_ids[index2])
    ]
    """
    Check that an invalid slice exclusion rule raises an Exception.
    """
    ground_truth_ids = DEFAULT_HEAD_AND_NECK_GROUND_TRUTH_IDS
    with pytest.raises(Exception) as e:
        assert HeadAndNeckBase(local_dataset=Path("foo"),
                               ground_truth_ids=ground_truth_ids,
                               slice_exclusion_rules=slice_exclusion_rules)
    assert str(
        e.value
    ) == f"slice_exclusion_rules: {invalid_ground_truth} not in ground truth IDs"


@pytest.mark.parametrize(
    ["summed_probability_rules", "invalid_ground_truth"],
    [([SummedProbabilityRule("spinal_cord2", "brainstem", "external")
       ], "spinal_cord2"),
     ([SummedProbabilityRule("spinal_cord", "brainstem2", "external")
       ], "brainstem2"),
     ([SummedProbabilityRule("spinal_cord", "brainstem", "external2")
       ], "external2")])
def test_head_and_neck_base_with_invalid_summed_probability_rule(
        summed_probability_rules: List[SummedProbabilityRule],
        invalid_ground_truth: str) -> None:
    """
    Check that an invalid summed probability rule raises a ValueError.
    """
    ground_truth_ids = DEFAULT_HEAD_AND_NECK_GROUND_TRUTH_IDS
    with pytest.raises(ValueError) as e:
        assert HeadAndNeckBase(
            local_dataset=Path("foo"),
 def __init__(self, num_structures: int = 0, **kwargs: Any) -> None:
     """
     :param num_structures: number of structures from STRUCTURE_LIST to predict (default: all structures)
     :param kwargs: other args from subclass
     """
     # Number of training epochs
     num_epochs = 120
     # Number of structures to predict; if positive but less than the length of STRUCTURE_LIST, the relevant prefix
     # of STRUCTURE_LIST will be predicted.
     if num_structures <= 0 or num_structures > len(STRUCTURE_LIST):
         num_structures = len(STRUCTURE_LIST)
     ground_truth_ids = STRUCTURE_LIST[:num_structures]
     colours = COLOURS[:num_structures]
     fill_holes = FILL_HOLES[:num_structures]
     ground_truth_ids_display_names = [f"zz_{x}" for x in ground_truth_ids]
     # The amount of GPU memory required increases with both the number of structures and the
     # number of feature channels. The following is a sensible default to avoid out-of-memory,
     # but you can override is by passing in another (singleton list) value for feature_channels
     # from a subclass.
     num_feature_channels = 32 if num_structures <= 20 else 26
     bg_weight = 0.02 if len(ground_truth_ids) > 1 else 0.25
     # In case of vertical overlap between brainstem and spinal_cord, we separate them
     # by converting brainstem voxels to cord, as the latter is clinically more sensitive.
     # We do the same to separate SPC and MPC; in this case, the direction of change is unimportant,
     # so we choose SPC-to-MPC arbitrarily.
     slice_exclusion_rules = []
     summed_probability_rules = []
     if "brainstem" in ground_truth_ids and "spinal_cord" in ground_truth_ids:
         slice_exclusion_rules.append(
             SliceExclusionRule("brainstem", "spinal_cord", False))
         if "external" in ground_truth_ids:
             summed_probability_rules.append(
                 SummedProbabilityRule("spinal_cord", "brainstem",
                                       "external"))
     if "spc_muscle" in ground_truth_ids and "mpc_muscle" in ground_truth_ids:
         slice_exclusion_rules.append(
             SliceExclusionRule("spc_muscle", "mpc_muscle", False))
         if "external" in ground_truth_ids:
             summed_probability_rules.append(
                 SummedProbabilityRule("mpc_muscle", "spc_muscle",
                                       "external"))
     if "optic_chiasm" in ground_truth_ids and "pituitary_gland" in ground_truth_ids:
         slice_exclusion_rules.append(
             SliceExclusionRule("optic_chiasm", "pituitary_gland", True))
         if "external" in ground_truth_ids:
             summed_probability_rules.append(
                 SummedProbabilityRule("optic_chiasm", "pituitary_gland",
                                       "external"))
     super().__init__(
         should_validate=False,  # we'll validate after kwargs are added
         num_gpus=4,
         num_epochs=num_epochs,
         save_start_epoch=num_epochs,
         save_step_epoch=num_epochs,
         architecture="UNet3D",
         kernel_size=3,
         train_batch_size=4,
         inference_batch_size=1,
         feature_channels=[num_feature_channels],
         crop_size=(96, 288, 288),
         test_crop_size=(144, 512, 512),
         inference_stride_size=(72, 256, 256),
         image_channels=["ct"],
         norm_method=PhotometricNormalizationMethod.CtWindow,
         level=50,
         window=600,
         start_epoch=0,
         l_rate=1e-3,
         min_l_rate=1e-5,
         l_rate_polynomial_gamma=0.9,
         optimizer_type=OptimizerType.Adam,
         opt_eps=1e-4,
         adam_betas=(0.9, 0.999),
         momentum=0.9,
         test_diff_epochs=1,
         test_step_epochs=1,
         use_mixed_precision=True,
         use_model_parallel=True,
         monitoring_interval_seconds=0,
         num_dataload_workers=4,
         loss_type=SegmentationLoss.Mixture,
         mixture_loss_components=[
             MixtureLossComponent(0.5, SegmentationLoss.Focal, 0.2),
             MixtureLossComponent(0.5, SegmentationLoss.SoftDice, 0.1)
         ],
         ground_truth_ids=ground_truth_ids,
         ground_truth_ids_display_names=ground_truth_ids_display_names,
         largest_connected_component_foreground_classes=ground_truth_ids,
         colours=colours,
         fill_holes=fill_holes,
         class_weights=equally_weighted_classes(
             ground_truth_ids, background_weight=bg_weight),
         slice_exclusion_rules=slice_exclusion_rules,
         summed_probability_rules=summed_probability_rules,
     )
     self.add_and_validate(kwargs)
Beispiel #5
0
                         [([], [])])
def test_slice_exclusion_rules_none(
        model_config: SegmentationModelBase) -> None:
    """
    Test `apply_slice_exclusion_rules` if no rule is provided
    """
    # create a copy as apply_slice_exclusion_rules modifies in place
    segmentation_copy = np.copy(segmentation_single_overlap)
    image_util.apply_slice_exclusion_rules(model_config, segmentation_copy)
    assert np.array_equal(segmentation_copy, segmentation_single_overlap)


# noinspection PyTestParametrized
@pytest.mark.parametrize(
    "slice_exclusion_rules, summed_probability_rules",
    [([], [SummedProbabilityRule("region1", "region2", "region3")])])
def test_apply_summed_probability_rules_incorrect_input(
        model_config: SegmentationModelBase) -> None:
    """
    Test `apply_summed_probability_rules` with invalid inputs
    """
    posteriors = np.zeros(shape=(2, 4, 3, 3, 3))
    segmentation = image_util.posteriors_to_segmentation(posteriors)

    with pytest.raises(ValueError):
        # noinspection PyTypeChecker
        image_util.apply_summed_probability_rules(model_config,
                                                  posteriors=posteriors,
                                                  segmentation=None)

    with pytest.raises(ValueError):
Beispiel #6
0
    def __init__(self, num_structures: Optional[int] = None, **kwargs: Any) -> None:
        """
        Creates a new instance of the class.
        :param num_structures: number of structures from STRUCTURE_LIST to predict (default: all structures)
        :param kwargs: Additional arguments that will be passed through to the SegmentationModelBase constructor.
        """
        # Number of structures to predict; if positive but less than the length of STRUCTURE_LIST, the relevant prefix
        # of STRUCTURE_LIST will be predicted.
        if (num_structures is not None) and \
                (num_structures <= 0 or num_structures > len(STRUCTURE_LIST)):
            raise ValueError(f"num structures must be between 0 and {len(STRUCTURE_LIST)}")
        if num_structures is None:
            logging.info(f'Setting num_structures to: {len(STRUCTURE_LIST)}')
            num_structures = len(STRUCTURE_LIST)
        ground_truth_ids = STRUCTURE_LIST[:num_structures]
        if "ground_truth_ids_display_names" in kwargs:
            ground_truth_ids_display_names = kwargs.pop("ground_truth_ids_display_names")
        else:
            logging.info('Using default ground_truth_ids_display_names')
            ground_truth_ids_display_names = [f"zz_{x}" for x in ground_truth_ids]
        if "colours" in kwargs:
            colours = kwargs.pop("colours")
        else:
            logging.info('Using default colours')
            colours = COLOURS[:num_structures]
        if "fill_holes" in kwargs:
            fill_holes = kwargs.pop("fill_holes")
        else:
            logging.info('Using default fill_holes')
            fill_holes = [True] * num_structures
        # The amount of GPU memory required increases with both the number of structures and the
        # number of feature channels. The following is a sensible default to avoid out-of-memory,
        # but you can override is by passing in another (singleton list) value for feature_channels
        # from a subclass.
        if "num_feature_channels" in kwargs:
            num_feature_channels = kwargs.pop("num_feature_channels")
        else:
            logging.info('Using default num_feature_channels')
            num_feature_channels = 32 if num_structures <= 20 else 26
        bg_weight = 0.02 if len(ground_truth_ids) > 1 else 0.25
        if "class_weights" in kwargs:
            class_weights = kwargs.pop("class_weights")
        else:
            logging.info('Using default class_weights')
            class_weights = equally_weighted_classes(ground_truth_ids, background_weight=bg_weight)
        # In case of vertical overlap between brainstem and spinal_cord, we separate them
        # by converting brainstem voxels to cord, as the latter is clinically more sensitive.
        # We do the same to separate SPC and MPC; in this case, the direction of change is unimportant,
        # so we choose SPC-to-MPC arbitrarily.
        if "slice_exclusion_rules" in kwargs:
            slice_exclusion_rules = kwargs.pop("slice_exclusion_rules")
        else:
            logging.info('Using default slice_exclusion_rules')
            slice_exclusion_rules = []
            if "brainstem" in ground_truth_ids and "spinal_cord" in ground_truth_ids:
                slice_exclusion_rules.append(SliceExclusionRule("brainstem", "spinal_cord", False))
            if "spc_muscle" in ground_truth_ids and "mpc_muscle" in ground_truth_ids:
                slice_exclusion_rules.append(SliceExclusionRule("spc_muscle", "mpc_muscle", False))
            if "optic_chiasm" in ground_truth_ids and "pituitary_gland" in ground_truth_ids:
                slice_exclusion_rules.append(SliceExclusionRule("optic_chiasm", "pituitary_gland", True))

        if "summed_probability_rules" in kwargs:
            summed_probability_rules = kwargs.pop("summed_probability_rules")
        else:
            logging.info('Using default summed_probability_rules')
            summed_probability_rules = []
            if "brainstem" in ground_truth_ids and "spinal_cord" in ground_truth_ids and \
                    "external" in ground_truth_ids:
                summed_probability_rules.append(SummedProbabilityRule("spinal_cord", "brainstem", "external"))
            if "spc_muscle" in ground_truth_ids and "mpc_muscle" in ground_truth_ids and \
                    "external" in ground_truth_ids:
                summed_probability_rules.append(SummedProbabilityRule("mpc_muscle", "spc_muscle", "external"))
            if "optic_chiasm" in ground_truth_ids and "pituitary_gland" in ground_truth_ids and \
                    "external" in ground_truth_ids:
                summed_probability_rules.append(SummedProbabilityRule("optic_chiasm", "pituitary_gland", "external"))
        super().__init__(
            ground_truth_ids=ground_truth_ids,
            ground_truth_ids_display_names=ground_truth_ids_display_names,
            colours=colours,
            fill_holes=fill_holes,
            class_weights=class_weights,
            slice_exclusion_rules=slice_exclusion_rules,
            summed_probability_rules=summed_probability_rules,
            num_feature_channels=num_feature_channels,
            **kwargs)