fepegar / torchio

Medical imaging toolkit for deep learning
https://torchio.org
Apache License 2.0
2.07k stars 241 forks source link

Allow non-image type label in subject #112

Closed nwschurink closed 4 years ago

nwschurink commented 4 years ago

Is your feature request related to a problem? Please describe. I am planning on building a classification network, where the network needs to classify multimodality images of tumor into either benign or malignant. For each tumor I have 3 images e.g. T1, T2 and DWI and each tumor has 1 binary label associated to it e.g. [0,1].

At this moment it is only possible to add a 'label' of the Class "Image" to a subject. Which in my case does not make sense as this is not a segmentation but a classification task.

Describe the solution you'd like Allow the Subject class to also accept a new Label class, which can contain a string, int, list or something like that.

OR

Another easy to implement option would be to allow setting an optional label attribute to the class in the init. Something in the sense of:

def __init__(self, *images: Image, name: str = '', label: TypeLabel):
        self._parse_images(images)
        super().__init__(images)
        self.name = name
        self.label = label

With TypeLabel = Union[str, int, float] or something similar to that.

Describe alternatives you've considered I have considered creating my own labellist, but it makes more sense to integrate the label into an instance of Subject.

fepegar commented 4 years ago

Hi @nwschurink,

I'm planning to add a classification example soon. That will probably give me some ideas to improve the library for the task.

For now, I think you can create a class that inherits from ImagesDataset. Would this work for you?

import torchio

class ClassificationDataset(torchio.ImagesDataset):
    def get_sample_dict_from_subject(self, subject: Subject):
        sample = super().get_sample_dict_from_subject(subject)
        sample['diagnosis'] = self.get_label_from_subject()
        return sample

    def get_label_from_subject(self, subject):
        label = get_label(subject.name)  # defined somewhere else

subject = torchio.Subject(
    torchio.Image('t1', 'subject_a.t1.nii.gz', torchio.INTENSITY),
    torchio.Image('t2', 'subject_a.t2.nii.gz', torchio.INTENSITY),
    torchio.Image('dwi', 'subject_a.dwi.nii.gz', torchio.INTENSITY),
    name='subject_a',
)

subjects = [subject]

dataset = ClassificationDataset(subjects)
sample = dataset[0]
label = sample['diagnosis']

Maybe I can add **kwargs to Image and Subject, in case the user wants to add custom fields to the subject samples or the images within.

nwschurink commented 4 years ago

Hi @fepegar ,

Thanks for the code snippet! I was planning on writing my own class but I'm still a bit new on using class inheritence so this definitely helps me out a lot.

I think your suggestion of adding **kwargs could be a nice sollution indeed, it would allow to add whatever custom field you'd like.

Thanks!

fepegar commented 4 years ago

I'm also learning :)

Glad that helped!

nwschurink commented 4 years ago

Hi @fepegar,

Thanks for adding the functionality, and so fast! Awesome!

I have 1 question remaining on the implementation. Just a suggestion, but maybe you think otherwise.

The kwargs now get assigned as self.kwargs = kwargs in the __init__ method. As a result, if someone likes to access e.g. a given keyword argument such as label=1 they wil have to access it using Subject.kwargs['label']

Wouldn't it be easier to replace this line with one of following 2 options:

self.__dict__.update(kwargs)

or

for key, value in kwargs.items():
      setattr(self, key, value)

This would allow accessing the keyword arguments using the keyword name e.g. Subject.label similar to how you'd access the subject name through Subject.name.

fepegar commented 4 years ago

You're absolutely right!

fepegar commented 4 years ago

Could you please submit a PR for this?

nwschurink commented 4 years ago

Will submit a PR in a moment ;)