Python plugin adding the possibility of changing params after compute

Hi all,

We are working on adapting our algorithms to be python plugins running in ImFusion. Something we noticed is that when we run the algorithm once (click on compute), then when we change the parameters of the algorithm (e.g. tx, ty, tz) via the UI, this change is not taken into account. It might be that we are doing something wrong when defining the parameters. The code for the plugin is provided below:

class DRRGenerationAlgorithm(imfusion.Algorithm):
    def __init__(self, image: imfusion.SharedImageSet, mask = None):
        super().__init__()
        self.image = image
        self.mask = mask
        self.imageset_out = imfusion.SharedImageSet()
        if self.mask == None:
            self.add_param('vessel_mask_path', Path(""), attributes="label: 'Vessel Segmentation Mask', tooltip: 'Path to label for vessel compensation'")
        self.add_param('algorithm_choice',value=0, attributes="label:'Algorithm', min: 0, max: 2, tooltip: 'Available Algorithms: \n 0: DRR generation \n 1: DRR generation with reference (requires vessel segmentation mask) \n 2: DRR generation with masks (requires vessel segmentation mask)'")
        self.add_param('preprocess',value=0, attributes="label:'Preprocess Image', min: 0, max: 1, tooltip: 'Remove the Table from the Image \n 0: No \n 1: Yes'")
        self.add_param('xray_device',value=0, attributes="label:'X-Ray Device', min: 0, max: 2, tooltip: '0: Loopx_C \n 1: Loopx \n 2: C-Arm'")
        self.add_param('alpha',value=0, attributes="label:'Rotation X', min: 0, max:360")
        self.add_param('beta',value=0, attributes="label:'Rotation Y', min: 0, max:360")
        self.add_param('gamma',value=0, attributes="label:'Rotation Z', min: 0, max:360")
        self.add_param('tx',value=0, attributes="label:'Translation X'")
        self.add_param('ty',value=0, attributes="label:'Translation Y'")
        self.add_param('tz',value=0, attributes="label:'Translation Z'")
        self.sitk_path = None
    
    @classmethod
    def convert_input(cls, data):
        if len(data) == 1 and isinstance(data[0], imfusion.SharedImageSet):
            """Single Image Case"""
            return data, None
        if len(data) == 2:
            """Correction Label Mask Case"""
            image, mask = data
            # Set default to 1 to make sure
            if not isinstance(image, imfusion.SharedImageSet):
                raise imfusion.IncompatibleError('First input must be a volume')

            if not isinstance(mask, imfusion.SharedImageSet) or mask.modality != imfusion.Data.Modality.LABEL:
                raise imfusion.IncompatibleError('Second input must be a label map')
            return [image], [mask]
        raise imfusion.IncompatibleError('Requires exactly one image or two images with a label map')

    def preprocess_table(self):
        image_imfusion = self.image[0][0]
        image_np, spacing = self.parse_image(image_imfusion)
        sitk_image = numpy_to_sitk(image_np, spacing)
        self.sitk_path = run_remove_table_with_preloaded_image(sitk_image)
        return imfusion.Algorithm.Status.SUCCESS
    
    def parse_image(self,image, ret_nifti=False):
        affine = image.world_to_image_matrix
        spacing = image.spacing
        image_np = image.numpy()
        new_affine = affine.copy()
        for i in range(3):  # Assuming 3D image
            new_affine[i, i] = spacing[i]  # Update the diagonal to new spacing
        nifti_image = nib.Nifti1Image(image_np, affine=new_affine)
        if ret_nifti:
            return nifti_image
        else:
            return image_np, spacing
    
    def parse_dof(self):
        self.dof = [
            float(self.alpha),
            float(self.beta),
            float(self.gamma),
            float(self.tx),
            float(self.ty),
            float(self.tz)
        ]

    def compute(self):

        self.parse_dof()
        if self.preprocess != 0:
            self.preprocess_table()
        
        elif hasattr(self, 'vessel_mask_path') and self.vessel_mask_path != Path("") and os.path.exists(self.vessel_mask_path):
            # There is probably an alternative... 
            self.mask = imfusion.load(str(self.vessel_mask_path))

        if self.mask:
            mask_imfusion = self.mask[0][0]
            image_np, spacing = self.parse_image(mask_imfusion)
            sitk_image = numpy_to_sitk(image_np, spacing)
            self.vessel_mask_path = "/tmp/label_mask.nii"
            sitk.WriteImage(sitk_image, self.vessel_mask_path)

        if self.sitk_path == None:
            """Case: No processing of image"""
            image_imfusion = self.image[0][0]
            image_np, spacing = self.parse_image(image_imfusion)
            sitk_image = numpy_to_sitk(image_np, spacing)
            self.sitk_path = "/tmp/vol_drr_generation.nii" 
            sitk.WriteImage(sitk_image, self.sitk_path)
        if self.algorithm_choice > 0 and self.vessel_mask_path == Path(""):
            logging.warning("No Vessel Segmentation Mask. Running Algorithm 0")
            self.algorithm_choice = 0
            
        imgs_numpy = diffdeepdrr(self.sitk_path, self.vessel_mask_path, self.dof, algorithm_choice=self.algorithm_choice,  xray_device=self.xray_device, device=device)
        for img_numpy in imgs_numpy:
            if len(img_numpy.shape) == 2: img_numpy = np.expand_dims(img_numpy, axis=-1)
            img_imfusion = imfusion.SharedImage(img_numpy)
            self.imageset_out.add(img_imfusion)
        

    def output(self):
        return [self.imageset_out]

Also, one further question, how can we change the text shown on the compute button in a python plugin?

Hi Farid,

You’re doing it the right way so this must be a bug that was not caught by our tests.
I can reproduce it on my computer. We will try to fix it as soon as possible and let you know, thank you for reporting it!

Thanks Raphael,

regarding renaming the compute button, is it possible?
another question, we get this warning in the terminal, when we load the python plugin:

[Base.Algorithm] Attempting to insert action with id 'compute' but an action with that id already exists. Overwriting.

Can you please tell me what we should modify to fix it?

Many thanks,
Farid

Sorry I missed the second part of your question.
There is currently no way to rename the Compute button, I would have to think about the cleanest way to implement that feature.

Regarding the warning that you have: it is a small bug and it does not mean anything, so you can safely ignore it until we fix it.

1 Like

Thanks for the fast reply!