Instance segmentation

Hey guys,
The next code is for object detection:

pascal = DataBlock(blocks=(ImageBlock, BBoxBlock, BBoxLblBlock),
                 splitter=RandomSplitter(),
                 get_items=get_train_imgs, 
                 getters=getters,
                 item_tfms=item_tfms,
                 batch_tfms=batch_tfms,
                 n_inp=1)

What do I need to change for instance segmentation?

In instance segmentation each box has a mask

In adittion, torchvision.models.detection.maskrccn_resnet50 returns two losses at training mode. How can i prevent fastai2 from calculating them?

I never tried to use fastai2 for image segmentation although I’m planning to do it sooner. It may be blocks=(ImageBlock, BBoxBlock, BBoxLblBlock, MaskBlock). Then, your model should return. BBox, label and the mask. Finally, you adapt the MaskRCNN predictions to what fastai2 expects with a callaback in the after_preds event.

For the losses, I would create another callback averaging both of them in the after_losses callback event.

I think that DataBlock API is not compatible.

Should the mask has the full image size or just the bounding box size?

I tried:

manual = DataBlock(blocks=(ImageBlock,BBoxBlock, BBoxLblBlock,MaskBlock(codes)),
                   get_items=partial(get_image_files,folders=[name1]),
                   getters=getters,
                   splitter=RandomSplitter(valid_pct=0.1,seed=2020),
                   item_tfms=Resize((size,size)),
                   batch_tfms=Normalize.from_stats(*imagenet_stats),
                   n_inp=1
                  )
manual.summary(path_images)
dls = manual.dataloaders(path_images,bs=bs)
dls.show_batch(figsize=(12, 9))

The error is giving to me is:

Setting-up type transforms pipelines
Collecting items from ../datasets/Images
Found 621 items
2 datasets of sizes 559,62
Setting up Pipeline: <lambda> -> PILBase.create
Setting up Pipeline: get_bbox -> TensorBBox.create
Setting up Pipeline: get_bbox_label -> MultiCategorize
Setting up Pipeline: get_mask -> PILBase.create

Building one sample
  Pipeline: <lambda> -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying <lambda> gives
      ../datasets/Images/manual/165.png
    applying PILBase.create gives
      PILImage mode=RGB size=1002x1004
  Pipeline: get_bbox -> TensorBBox.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox gives
      [[425, 387, 641, 591]]
    applying TensorBBox.create gives
      TensorBBox of size 1x4
  Pipeline: get_bbox_label -> MultiCategorize
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox_label gives
      [Car]
    applying MultiCategorize gives
      TensorMultiCategory([1])
  Pipeline: get_mask -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_mask gives
      ../datasets/Labels/manual/165.png
    applying PILBase.create gives
      PILMask mode=L size=1002x1004

Final sample: (PILImage mode=RGB size=1002x1004, TensorBBox([[425., 387., 641., 591.]]), TensorMultiCategory([1]), PILMask mode=L size=1002x1004)


Setting up after_item: Pipeline: BBoxLabeler -> AddMaskCodes -> PointScaler -> Resize -> ToTensor
Setting up before_batch: Pipeline: bb_pad
Setting up after_batch: Pipeline: IntToFloatTensor -> Normalize
Could not do one pass in your dataloader, there is something wrong in it

Building one batch
Applying item_tfms to the first sample:
  Pipeline: BBoxLabeler -> AddMaskCodes -> PointScaler -> Resize -> ToTensor
    starting from
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying BBoxLabeler gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying AddMaskCodes gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying PointScaler gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying Resize gives
      (PILImage mode=RGB size=1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1002)
    applying ToTensor gives
      (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002)

Adding the next 3 samples

Applying before_batch to the list of samples
  Pipeline: bb_pad
    starting from
      [(TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002)]
    applying bb_pad failed.
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-20-2ee01b8bef18> in <module>
      7                    n_inp=1
      8                   )
----> 9 manual.summary(path_images)
     10 dls = manual.dataloaders(path_images,bs=bs)
     11 dls.show_batch(figsize=(12, 9))

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastai2/data/block.py in summary(self, source, bs, show_batch, **kwargs)
    171     if len([f for f in dls.train.before_batch.fs if f.name != 'noop'])!=0:
    172         print("\nApplying before_batch to the list of samples")
--> 173         s = _apply_pipeline(dls.train.before_batch, s)
    174     else: print("\nNo before_batch transform to apply")
    175 

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastai2/data/block.py in _apply_pipeline(p, x)
    131         except Exception as e:
    132             print(f"    applying {name} failed.")
--> 133             raise e
    134     return x
    135 

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastai2/data/block.py in _apply_pipeline(p, x)
    127         name = f.name
    128         try:
--> 129             x = f(x)
    130             if name != "noop": print(f"    applying {name} gives\n      {_short_repr(x)}")
    131         except Exception as e:

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastcore/transform.py in __call__(self, x, **kwargs)
     70     @property
     71     def name(self): return getattr(self, '_name', _get_name(self))
---> 72     def __call__(self, x, **kwargs): return self._call('encodes', x, **kwargs)
     73     def decode  (self, x, **kwargs): return self._call('decodes', x, **kwargs)
     74     def __repr__(self): return f'{self.name}: {self.encodes} {self.decodes}'

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastcore/transform.py in _call(self, fn, x, split_idx, **kwargs)
     80     def _call(self, fn, x, split_idx=None, **kwargs):
     81         if split_idx!=self.split_idx and self.split_idx is not None: return x
---> 82         return self._do_call(getattr(self, fn), x, **kwargs)
     83 
     84     def _do_call(self, f, x, **kwargs):

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastcore/transform.py in _do_call(self, f, x, **kwargs)
     84     def _do_call(self, f, x, **kwargs):
     85         if not _is_tuple(x):
---> 86             return x if f is None else retain_type(f(x, **kwargs), x, f.returns_none(x))
     87         res = tuple(self._do_call(f, x_, **kwargs) for x_ in x)
     88         return retain_type(res, x)

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastcore/dispatch.py in __call__(self, *args, **kwargs)
     96         if not f: return args[0]
     97         if self.inst is not None: f = MethodType(f, self.inst)
---> 98         return f(*args, **kwargs)
     99 
    100     def __get__(self, inst, owner):

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastai2/vision/data.py in bb_pad(samples, pad_idx)
     31 def bb_pad(samples, pad_idx=0):
     32     "Function that collect `samples` of labelled bboxes and adds padding with `pad_idx`."
---> 33     samples = [(s[0], *clip_remove_empty(*s[1:])) for s in samples]
     34     max_len = max([len(s[2]) for s in samples])
     35     def _f(img,bbox,lbl):

~/anaconda3/envs/BrainTumourSegmentation/lib/python3.7/site-packages/fastai2/vision/data.py in <listcomp>(.0)
     31 def bb_pad(samples, pad_idx=0):
     32     "Function that collect `samples` of labelled bboxes and adds padding with `pad_idx`."
---> 33     samples = [(s[0], *clip_remove_empty(*s[1:])) for s in samples]
     34     max_len = max([len(s[2]) for s in samples])
     35     def _f(img,bbox,lbl):

TypeError: clip_remove_empty() takes 2 positional arguments but 3 were given

Looks like bb_pad is being added to mask!

I made a post about this actually. You need to manually adjust bbox_pad so that it takes a different position to allow for the segmentation mask. Let me try to find it

@WaterKnight see here Fastai v2 vision

2 Likes

I am goign to read it them! Thank you!

I saw that you posted the next code:

myBBoxBlock = TransformBlock(type_tfms=TensorBBox.create, item_tfms=PointScaler, dls_kwargs = {'before_batch': mybb_pad})

Do you have the code of mybb_pad??

Do you have a notebook will the full maskrcnn-pipeline? It will be very helpfull for all the beginners. I saw some people that would like to make Instance Segmentation with Mask-RCNN

I don’t. Else I would’ve updated it :slight_smile: Mybb_pad is just bb_pad with the change I described.

Edit: @WaterKnight it’s the code that’s linked there. (The if statement) for the modification

@muellerzr thank you!

Then, I need to add this transform block to batch tfms, right?

No, you’d replace BBoxBlock with it

My bad. I read it from my phone and I saw TransformBlock, so I thought that it was a transformer :man_facepalming:

EDIT

I am using it as follows @muellerzr :

def mybb_pad(samples, pad_idx=0):
    "Function that collect `samples` of labelled bboxes and adds padding with `pad_idx`."
    if len(samples[0]) > 3:
      samples = [(s[0], *clip_remove_empty(*s[1:3])) for s in samples]
    else:
      samples = [(s[0], *clip_remove_empty(*s[1:])) for s in samples]
    max_len = max([len(s[2]) for s in samples])
    def _f(img,bbox,lbl):
        bbox = torch.cat([bbox,bbox.new_zeros(max_len-bbox.shape[0], 4)])
        lbl  = torch.cat([lbl, lbl .new_zeros(max_len-lbl .shape[0])+pad_idx])
        return img,bbox,lbl
    return [_f(*s) for s in samples]
BBoxBlockSegmentation=TransformBlock(type_tfms=TensorBBox.create, item_tfms=PointScaler, dls_kwargs = {'before_batch': mybb_pad})
manual = DataBlock(blocks=(ImageBlock,BBoxBlockSegmentation, BBoxLblBlock,MaskBlock(codes)),
                   get_items=partial(get_image_files,folders=[name1]),
                   getters=getters,
                   splitter=RandomSplitter(valid_pct=0.1,seed=2020),
                   item_tfms=Resize((size,size)),
                   batch_tfms=Normalize.from_stats(*imagenet_stats),
                   n_inp=1
                  )
manual.summary(path_images)
dls = manual.dataloaders(path_images,bs=bs)
dls.show_batch(figsize=(12, 9))

Summary works perfectly now.

Setting-up type transforms pipelines
Collecting items from ../datasets/Images
Found 621 items
2 datasets of sizes 559,62
Setting up Pipeline: <lambda> -> PILBase.create
Setting up Pipeline: get_bbox -> TensorBBox.create
Setting up Pipeline: get_bbox_label -> MultiCategorize
Setting up Pipeline: get_mask -> PILBase.create

Building one sample
  Pipeline: <lambda> -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying <lambda> gives
      ../datasets/Images/manual/165.png
    applying PILBase.create gives
      PILImage mode=RGB size=1002x1004
  Pipeline: get_bbox -> TensorBBox.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox gives
      [[425, 387, 641, 591]]
    applying TensorBBox.create gives
      TensorBBox of size 1x4
  Pipeline: get_bbox_label -> MultiCategorize
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox_label gives
      [Class1]
    applying MultiCategorize gives
      TensorMultiCategory([1])
  Pipeline: get_mask -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_mask gives
      ../datasets/Labels/manual/165.png
    applying PILBase.create gives
      PILMask mode=L size=1002x1004

Final sample: (PILImage mode=RGB size=1002x1004, TensorBBox([[425., 387., 641., 591.]]), TensorMultiCategory([1]), PILMask mode=L size=1002x1004)


Setting up after_item: Pipeline: BBoxLabeler -> AddMaskCodes -> PointScaler -> Resize -> ToTensor
Setting up before_batch: Pipeline: mybb_pad
Setting up after_batch: Pipeline: IntToFloatTensor -> Normalize

Building one batch
Applying item_tfms to the first sample:
  Pipeline: BBoxLabeler -> AddMaskCodes -> PointScaler -> Resize -> ToTensor
    starting from
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying BBoxLabeler gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying AddMaskCodes gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying PointScaler gives
      (PILImage mode=RGB size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1004)
    applying Resize gives
      (PILImage mode=RGB size=1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), PILMask mode=L size=1002x1002)
    applying ToTensor gives
      (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002)

Adding the next 3 samples

Applying before_batch to the list of samples
  Pipeline: mybb_pad
    starting from
      [(TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002), (TensorImage of size 3x1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]), TensorMask of size 1002x1002)]
    applying mybb_pad gives
      [(TensorImage of size 3x1002x1002, Tensor of size 1x4, tensor([1])), (TensorImage of size 3x1002x1002, Tensor of size 1x4, tensor([1])), (TensorImage of size 3x1002x1002, Tensor of size 1x4, tensor([1])), (TensorImage of size 3x1002x1002, Tensor of size 1x4, tensor([1]))]

Collating items in a batch

Applying batch_tfms to the batch built
  Pipeline: IntToFloatTensor -> Normalize
    starting from
      (TensorImage of size 4x3x1002x1002, Tensor of size 4x1x4, Tensor of size 4x1)
    applying IntToFloatTensor gives
      (TensorImage of size 4x3x1002x1002, Tensor of size 4x1x4, Tensor of size 4x1)
    applying Normalize gives
      (TensorImage of size 4x3x1002x1002, Tensor of size 4x1x4, Tensor of size 4x1)

However, show_bach give this error:


---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-24-e43ac76275a7> in <module>
      9 manual.summary(path_images)
     10 dls = manual.dataloaders(path_images,bs=bs)
---> 11 dls.show_batch(figsize=(12, 9))

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/core.py in show_batch(self, b, max_n, ctxs, show, unique, **kwargs)
     97         if b is None: b = self.one_batch()
     98         if not show: return self._pre_show_batch(b, max_n=max_n)
---> 99         show_batch(*self._pre_show_batch(b, max_n=max_n), ctxs=ctxs, max_n=max_n, **kwargs)
    100         if unique: self.get_idxs = old_get_idxs
    101 

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/core.py in _pre_show_batch(self, b, max_n)
     87         b = self.decode(b)
     88         if hasattr(b, 'show'): return b,None,None
---> 89         its = self._decode_batch(b, max_n, full=False)
     90         if not is_listy(b): b,its = [b],L((o,) for o in its)
     91         return detuplify(b[:self.n_inp]),detuplify(b[self.n_inp:]),its

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/core.py in _decode_batch(self, b, max_n, full)
     81         f = self.after_item.decode
     82         f = compose(f, partial(getattr(self.dataset,'decode',noop), full = full))
---> 83         return L(batch_to_samples(b, max_n=max_n)).map(f)
     84 
     85     def _pre_show_batch(self, b, max_n=9):

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in map(self, f, *args, **kwargs)
    373              else f.format if isinstance(f,str)
    374              else f.__getitem__)
--> 375         return self._new(map(g, self))
    376 
    377     def filter(self, f, negate=False, **kwargs):

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in _new(self, items, *args, **kwargs)
    324     @property
    325     def _xtra(self): return None
--> 326     def _new(self, items, *args, **kwargs): return type(self)(items, *args, use_list=None, **kwargs)
    327     def __getitem__(self, idx): return self._get(idx) if is_indexer(idx) else L(self._get(idx), use_list=None)
    328     def copy(self): return self._new(self.items.copy())

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in __call__(cls, x, *args, **kwargs)
     45             return x
     46 
---> 47         res = super().__call__(*((x,) + args), **kwargs)
     48         res._newchk = 0
     49         return res

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in __init__(self, items, use_list, match, *rest)
    315         if items is None: items = []
    316         if (use_list is not None) or not _is_array(items):
--> 317             items = list(items) if use_list else _listify(items)
    318         if match is not None:
    319             if is_coll(match): match = len(match)

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in _listify(o)
    251     if isinstance(o, list): return o
    252     if isinstance(o, str) or _is_array(o): return [o]
--> 253     if is_iter(o): return list(o)
    254     return [o]
    255 

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in __call__(self, *args, **kwargs)
    217             if isinstance(v,_Arg): kwargs[k] = args.pop(v.i)
    218         fargs = [args[x.i] if isinstance(x, _Arg) else x for x in self.pargs] + args[self.maxi+1:]
--> 219         return self.fn(*fargs, **kwargs)
    220 
    221 # Cell

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/utils.py in _inner(x, *args, **kwargs)
    346     if order is not None: funcs = funcs.sorted(order)
    347     def _inner(x, *args, **kwargs):
--> 348         for f in L(funcs): x = f(x, *args, **kwargs)
    349         return x
    350     return _inner

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/core.py in decode(self, o, full)
    294     def __iter__(self): return (self[i] for i in range(len(self)))
    295     def __repr__(self): return coll_repr(self)
--> 296     def decode(self, o, full=True): return tuple(tl.decode(o_, full=full) for o_,tl in zip(o,tuplify(self.tls, match=o)))
    297     def subset(self, i): return type(self)(tls=L(tl.subset(i) for tl in self.tls), n_inp=self.n_inp)
    298     def _new(self, items, *args, **kwargs): return super()._new(items, tfms=self.tfms, do_setup=False, **kwargs)

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/utils.py in tuplify(o, use_list, match)
    132 def tuplify(o, use_list=False, match=None):
    133     "Make `o` a tuple"
--> 134     return tuple(L(o, use_list=use_list, match=match))
    135 
    136 # Cell

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in __call__(cls, x, *args, **kwargs)
     45             return x
     46 
---> 47         res = super().__call__(*((x,) + args), **kwargs)
     48         res._newchk = 0
     49         return res

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/foundation.py in __init__(self, items, use_list, match, *rest)
    319             if is_coll(match): match = len(match)
    320             if len(items)==1: items = items*match
--> 321             else: assert len(items)==match, 'Match length mismatch'
    322         super().__init__(items)
    323 

AssertionError: Match length mismatch

Is it possible to solve this error?

Show will probably not work out of the box. You’ll need to make an adjustment to show each (as you have two outputs) I think.

Can you grab a batch of data? (Via .one_batch())

This returns one_batch()

(TensorImage([[[[-0.5938, -0.2856, -0.3712,  ..., -1.2788, -1.2788, -1.1247],
           [-0.6623, -0.2856, -0.2856,  ..., -1.2788, -1.2788, -1.1247],
           [-0.5938, -0.3712, -0.3712,  ..., -1.2788, -1.2788, -1.1247],
           ...,
           [-1.3644, -1.2103, -1.2103,  ..., -0.9705, -1.0562, -0.9020],
           [-1.3644, -1.2103, -1.2103,  ..., -1.0562, -1.0562, -0.8164],
           [-1.3644, -1.2103, -1.2103,  ..., -1.0562, -1.0562, -0.8164]],
 
          [[-0.4776, -0.1625, -0.2500,  ..., -1.1779, -1.1779, -1.0203],
           [-0.5476, -0.1625, -0.1625,  ..., -1.1779, -1.1779, -1.0203],
           [-0.4776, -0.2500, -0.2500,  ..., -1.1779, -1.1779, -1.0203],
           ...,
           [-1.2654, -1.1078, -1.1078,  ..., -0.8627, -0.9503, -0.7927],
           [-1.2654, -1.1078, -1.1078,  ..., -0.9503, -0.9503, -0.7052],
           [-1.2654, -1.1078, -1.1078,  ..., -0.9503, -0.9503, -0.7052]],
 
          [[-0.2532,  0.0605, -0.0267,  ..., -0.9504, -0.9504, -0.7936],
           [-0.3230,  0.0605,  0.0605,  ..., -0.9504, -0.9504, -0.7936],
           [-0.2532, -0.0267, -0.0267,  ..., -0.9504, -0.9504, -0.7936],
           ...,
           [-1.0376, -0.8807, -0.8807,  ..., -0.6367, -0.7238, -0.5670],
           [-1.0376, -0.8807, -0.8807,  ..., -0.7238, -0.7238, -0.4798],
           [-1.0376, -0.8807, -0.8807,  ..., -0.7238, -0.7238, -0.4798]]],
 
 
         [[[-0.2513,  0.1939,  0.2796,  ...,  0.5536,  0.4508,  0.8104],
           [-0.1657,  0.1939,  0.2796,  ...,  0.5536,  0.5536,  0.8104],
           [-0.1657,  0.1939,  0.1939,  ...,  0.5536,  0.5536,  0.8104],
           ...,
           [-0.9705, -0.6965, -0.6965,  ..., -0.6109, -0.6965, -0.4397],
           [-0.9705, -0.6965, -0.6965,  ..., -0.6109, -0.6109, -0.4397],
           [-0.9705, -0.6965, -0.6109,  ..., -0.6109, -0.6965, -0.4397]],
 
          [[-0.1275,  0.3277,  0.4153,  ...,  0.6954,  0.5903,  0.9580],
           [-0.0399,  0.3277,  0.4153,  ...,  0.6954,  0.6954,  0.9580],
           [-0.0399,  0.3277,  0.3277,  ...,  0.6954,  0.6954,  0.9580],
           ...,
           [-0.8627, -0.5826, -0.5826,  ..., -0.4951, -0.5826, -0.3200],
           [-0.8627, -0.5826, -0.5826,  ..., -0.4951, -0.4951, -0.3200],
           [-0.8627, -0.5826, -0.4951,  ..., -0.4951, -0.5826, -0.3200]],
 
          [[ 0.0953,  0.5485,  0.6356,  ...,  0.9145,  0.8099,  1.1759],
           [ 0.1825,  0.5485,  0.6356,  ...,  0.9145,  0.9145,  1.1759],
           [ 0.1825,  0.5485,  0.5485,  ...,  0.9145,  0.9145,  1.1759],
           ...,
           [-0.6367, -0.3578, -0.3578,  ..., -0.2707, -0.3578, -0.0964],
           [-0.6367, -0.3578, -0.3578,  ..., -0.2707, -0.2707, -0.0964],
           [-0.6367, -0.3578, -0.2707,  ..., -0.2707, -0.3578, -0.0964]]]],
        device='cuda:0'),
 TensorBBox([[[-0.2495, -0.2914,  0.2774,  0.2515]],
 
         [[-0.1776, -0.1876,  0.2415,  0.2854]]], device='cuda:0'),
 TensorMultiCategory([[1],
         [1]], device='cuda:0'))

@muellerzr looks like Mask is missing!

Why is this happening? .summary() worked properly.

Moved to a new topic since it started to be a lot for the chat.

3 Likes

@WaterKnight try making the Mask block the first block. The order matters here

@muellerzr With the next order:

manual = DataBlock(blocks=(ImageBlock,MaskBlock(codes),BBoxBlockSegmentation, BBoxLblBlock),
                   get_items=partial(get_image_files,folders=[dataset1]),
                   getters=getters,
                   splitter=RandomSplitter(valid_pct=0.1,seed=2020),
                   item_tfms=Resize((size,size)),
                   batch_tfms=Normalize.from_stats(*imagenet_stats),
                   n_inp=1
                  )
manual.summary(path_images)
dls = manual.dataloaders(path_images,bs=bs)
dls.one_batch()

Different error

Setting-up type transforms pipelines
Collecting items from ../datasets/Images
Found 621 items
2 datasets of sizes 559,62
Setting up Pipeline: <lambda> -> PILBase.create
Setting up Pipeline: get_mask -> PILBase.create
Setting up Pipeline: get_bbox -> TensorBBox.create
Setting up Pipeline: get_bbox_label -> MultiCategorize

Building one sample
  Pipeline: <lambda> -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying <lambda> gives
      ../datasets/Images/manual/165.png
    applying PILBase.create gives
      PILImage mode=RGB size=1002x1004
  Pipeline: get_mask -> PILBase.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_mask gives
      ../datasets/Labels/manual/165.png
    applying PILBase.create gives
      PILMask mode=L size=1002x1004
  Pipeline: get_bbox -> TensorBBox.create
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox gives
      [[425, 387, 641, 591]]
    applying TensorBBox.create gives
      TensorBBox of size 1x4
  Pipeline: get_bbox_label -> MultiCategorize
    starting from
      ../datasets/Images/manual/165.png
    applying get_bbox_label gives
      [Class1]
    applying MultiCategorize gives
      TensorMultiCategory([1])

Final sample: (PILImage mode=RGB size=1002x1004, PILMask mode=L size=1002x1004, TensorBBox([[425., 387., 641., 591.]]), TensorMultiCategory([1]))


Setting up after_item: Pipeline: AddMaskCodes -> BBoxLabeler -> PointScaler -> Resize -> ToTensor
Setting up before_batch: Pipeline: mybb_pad
Setting up after_batch: Pipeline: IntToFloatTensor -> Normalize
Could not do one pass in your dataloader, there is something wrong in it

Building one batch
Applying item_tfms to the first sample:
  Pipeline: AddMaskCodes -> BBoxLabeler -> PointScaler -> Resize -> ToTensor
    starting from
      (PILImage mode=RGB size=1002x1004, PILMask mode=L size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]))
    applying AddMaskCodes gives
      (PILImage mode=RGB size=1002x1004, PILMask mode=L size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]))
    applying BBoxLabeler gives
      (PILImage mode=RGB size=1002x1004, PILMask mode=L size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]))
    applying PointScaler gives
      (PILImage mode=RGB size=1002x1004, PILMask mode=L size=1002x1004, TensorBBox of size 1x4, TensorMultiCategory([1]))
    applying Resize gives
      (PILImage mode=RGB size=1002x1002, PILMask mode=L size=1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]))
    applying ToTensor gives
      (TensorImage of size 3x1002x1002, TensorMask of size 1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]))

Adding the next 3 samples

Applying before_batch to the list of samples
  Pipeline: mybb_pad
    starting from
      [(TensorImage of size 3x1002x1002, TensorMask of size 1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1])), (TensorImage of size 3x1002x1002, TensorMask of size 1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1])), (TensorImage of size 3x1002x1002, TensorMask of size 1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1])), (TensorImage of size 3x1002x1002, TensorMask of size 1002x1002, TensorBBox of size 1x4, TensorMultiCategory([1]))]
    applying mybb_pad failed.
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-22-e40df62d36e3> in <module>
      7                    n_inp=1
      8                   )
----> 9 manual.summary(path_images)
     10 dls = manual.dataloaders(path_images,bs=bs)
     11 dls.one_batch()

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/block.py in summary(self, source, bs, show_batch, **kwargs)
    171     if len([f for f in dls.train.before_batch.fs if f.name != 'noop'])!=0:
    172         print("\nApplying before_batch to the list of samples")
--> 173         s = _apply_pipeline(dls.train.before_batch, s)
    174     else: print("\nNo before_batch transform to apply")
    175 

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/block.py in _apply_pipeline(p, x)
    131         except Exception as e:
    132             print(f"    applying {name} failed.")
--> 133             raise e
    134     return x
    135 

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/data/block.py in _apply_pipeline(p, x)
    127         name = f.name
    128         try:
--> 129             x = f(x)
    130             if name != "noop": print(f"    applying {name} gives\n      {_short_repr(x)}")
    131         except Exception as e:

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/transform.py in __call__(self, x, **kwargs)
     70     @property
     71     def name(self): return getattr(self, '_name', _get_name(self))
---> 72     def __call__(self, x, **kwargs): return self._call('encodes', x, **kwargs)
     73     def decode  (self, x, **kwargs): return self._call('decodes', x, **kwargs)
     74     def __repr__(self): return f'{self.name}: {self.encodes} {self.decodes}'

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/transform.py in _call(self, fn, x, split_idx, **kwargs)
     80     def _call(self, fn, x, split_idx=None, **kwargs):
     81         if split_idx!=self.split_idx and self.split_idx is not None: return x
---> 82         return self._do_call(getattr(self, fn), x, **kwargs)
     83 
     84     def _do_call(self, f, x, **kwargs):

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/transform.py in _do_call(self, f, x, **kwargs)
     84     def _do_call(self, f, x, **kwargs):
     85         if not _is_tuple(x):
---> 86             return x if f is None else retain_type(f(x, **kwargs), x, f.returns_none(x))
     87         res = tuple(self._do_call(f, x_, **kwargs) for x_ in x)
     88         return retain_type(res, x)

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastcore/dispatch.py in __call__(self, *args, **kwargs)
     96         if not f: return args[0]
     97         if self.inst is not None: f = MethodType(f, self.inst)
---> 98         return f(*args, **kwargs)
     99 
    100     def __get__(self, inst, owner):

<ipython-input-13-ef644dab5e4a> in mybb_pad(samples, pad_idx)
      2     "Function that collect `samples` of labelled bboxes and adds padding with `pad_idx`."
      3     if len(samples[0]) > 3:
----> 4       samples = [(s[0], *clip_remove_empty(*s[1:3])) for s in samples]
      5     else:
      6       samples = [(s[0], *clip_remove_empty(*s[1:])) for s in samples]

<ipython-input-13-ef644dab5e4a> in <listcomp>(.0)
      2     "Function that collect `samples` of labelled bboxes and adds padding with `pad_idx`."
      3     if len(samples[0]) > 3:
----> 4       samples = [(s[0], *clip_remove_empty(*s[1:3])) for s in samples]
      5     else:
      6       samples = [(s[0], *clip_remove_empty(*s[1:])) for s in samples]

~/anaconda3/envs/seg/lib/python3.7/site-packages/fastai2/vision/data.py in clip_remove_empty(bbox, label)
     26     bbox = torch.clamp(bbox, -1, 1)
     27     empty = ((bbox[...,2] - bbox[...,0])*(bbox[...,3] - bbox[...,1]) < 0.)
---> 28     return (bbox[~empty], label[~empty])
     29 
     30 # Cell

IndexError: The shape of the mask [1002] at index 0 does not match the shape of the indexed tensor [1, 4] at index 0

I’ll have to take a look at this later tonight and get back with you :slight_smile:

1 Like

Nice!! :blush:

Thank you very much for your help.

I am going to take a deep look into the Mask-RCNN model that is available in torchvision. This model returns the losses instead of the predictions.

1 Like

I am going to adjust Mask-RCNN code from torchvision with the aim of that this model returns the predictions, so that we train as usual with fastai. We are just going to need to update the loss_func.

@muellerzr did you found the solution??

For getting Torchvision Mask-RCNN work with learner class I think that the proper way is subclassing Learner.

I have added a topic where I am explaining my concerns and problems that I am struggling with.

Did you have time to look at it?? If not, no problem. Thank you for all your help. :smiley:

I am trying to create a new block for MaskRCNN. It’s working.

However, Masks are not getting resized. You can take a look here.

UPDATE with the progress.

Getting near to solution!!

Dataloader is working:

class MaskRCNN(dict):
    
    @classmethod
    def create(cls, dictionary): 
        return cls(dict({x:dictionary[x] for x in dictionary.keys()}))
    
    def show(self, ctx=None, **kwargs): 
        dictionary = self
        
        boxes = dictionary["boxes"]
        labels = dictionary["labels"]
        masks = dictionary["masks"]
        
        result = masks
        return show_image(result, ctx=ctx, **kwargs)

def MaskRCNNBlock(): 
    return TransformBlock(type_tfms=MaskRCNN.create, batch_tfms=IntToFloatTensor)

def get_bbox(o):
    label_path = get_y_fn(o)
    mask=PILMask.create(label_path)
    pos = np.where(mask)
    xmin = np.min(pos[1])
    xmax = np.max(pos[1])
    ymin = np.min(pos[0])
    ymax = np.max(pos[0])
    
    return TensorBBox.create([xmin, ymin, xmax, ymax])
    
def get_bbox_label(o):
    
    return TensorCategory([1])
    
    
def get_mask(o):
    label_path = get_y_fn(o)
    mask=PILMask.create(label_path)
    mask=image2tensor(mask)
    return TensorMask(mask)

def get_dict(o):
    return {"boxes": get_bbox(o), "labels": get_bbox_label(o),"masks": get_mask(o)}
    

getters = [lambda o: o, get_dict]

maskrccnnDataBlock = DataBlock(
    blocks=(ImageBlock, MaskRCNNBlock),
    get_items=partial(get_image_files,folders=[manual_name]),
    getters=getters,
    splitter=RandomSplitter(valid_pct=0.1,seed=2020),
    item_tfms=Resize((size,size)),
    batch_tfms=Normalize.from_stats(*imagenet_stats)
)
maskrccnnDataBlock.summary(path_images)
dls = maskrccnnDataBlock.dataloaders(path_images,bs=bs)

Testing if data works with model:

b = dls.one_batch()

from torchvision.models.detection.mask_rcnn import *
model=maskrcnn_resnet50_fpn(num_classes=2,min_size=1002,max_size=1002)
model.train()
model = model.to("cuda")

image,target=b
images=[]
for aux in image:
    images.append(aux)
targets= []
for i in range(len(target["masks"])):
    targets.append({"boxes": target["boxes"][i], "labels": target["labels"][i],"masks": target["masks"][i]})
output=model(images,targets)
output

model.eval()
output=model(images)
output

This works. So I decided to create a subclass of Learner for making compatible with all FastAI Library:

class Mask_RCNN_Learner(Learner):
    def __init__(self, dls, model, loss_func=None, opt_func=Adam, lr=defaults.lr, splitter=trainable_params, cbs=None,
                 metrics=None, path=None, model_dir='models', wd=None, wd_bn_bias=False, train_bn=True,
                 moms=(0.95,0.85,0.95)):
        super().__init__(dls, model, loss_func, opt_func, lr, splitter, cbs,
                 metrics, path, model_dir, wd, wd_bn_bias, train_bn,
                 moms)
      
    def all_batches(self):
        self.n_iter = len(self.dl)
        for o in enumerate(self.dl): self.one_batch(*o)

    def one_batch(self, i, b):
        self.iter = i
        try:
            self._split(b);                                  self('begin_batch')
            images =[]
            for aux in self.xb:
                images.append(aux)
            targets= []
            for i in range(len(self.yb["masks"])):
                targets.append({"boxes": target["boxes"][i], "labels": target["labels"][i],"masks": target["masks"][i]})
            loss_dict = self.model(images,targets);       self('after_pred')
            if len(self.yb) == 0: return
            loss = sum(loss for loss in loss_dict.values())
            self.loss = loss;                                self('after_loss')
            if not self.training: return
            self.loss.backward();                            self('after_backward')
            self.opt.step();                                 self('after_step')
            self.opt.zero_grad()
        except CancelBatchException:                         self('after_cancel_batch')
        finally:                                             self('after_batch')

    def _do_begin_fit(self, n_epoch):
        self.n_epoch,self.loss = n_epoch,tensor(0.);         self('begin_fit')

    def _do_epoch_train(self):
        try:
            self.dl = self.dls.train;                        self('begin_train')
            self.all_batches()
        except CancelTrainException:                         self('after_cancel_train')
        finally:                                             self('after_train')

    def _do_epoch_validate(self, ds_idx=1, dl=None):
        if dl is None: dl = self.dls[ds_idx]
        try:
            self.dl = dl;                                    self('begin_validate')
            with torch.no_grad(): self.all_batches()
        except CancelValidException:                         self('after_cancel_validate')
        finally:                                             self('after_validate')                                              
    
    @log_args(but='cbs')
    def fit(self, n_epoch, lr=None, wd=None, cbs=None, reset_opt=False):
        with self.added_cbs(cbs):
            if reset_opt or not self.opt: self.create_opt()
            if wd is None: wd = self.wd
            if wd is not None: self.opt.set_hypers(wd=wd)
            self.opt.set_hypers(lr=self.lr if lr is None else lr)

            try:
                self._do_begin_fit(n_epoch)
                for epoch in range(n_epoch):
                    try:
                        self.epoch=epoch;          self('begin_epoch')
                        self._do_epoch_train()
                        self._do_epoch_validate()
                    except CancelEpochException:   self('after_cancel_epoch')
                    finally:                       self('after_epoch')

            except CancelFitException:             self('after_cancel_fit')
            finally:                               self('after_fit')  
                
    def validate(self, ds_idx=1, dl=None, cbs=None):
        if dl is None: dl = self.dls[ds_idx]
        with self.added_cbs(cbs), self.no_logging(), self.no_mbar():
            self(_before_epoch)
            self._do_epoch_validate(ds_idx, dl)
            self(_after_epoch)
        return getattr(self, 'final_record', None)

Just to mention which are the changes:

 self._split(b);                                  self('begin_batch')
            images =[]
            for aux in self.xb:
                images.append(aux)
            targets= []
            for i in range(len(self.yb["masks"])):
                targets.append({"boxes": target["boxes"][i], "labels": target["labels"][i],"masks": target["masks"][i]})
            loss_dict = self.model(images,targets);       self('after_pred')
            if len(self.yb) == 0: return
            loss = sum(loss for loss in loss_dict.values())

The learner construction:

from torchvision.models.detection.mask_rcnn import *
model=maskrcnn_resnet50_fpn(num_classes=2,min_size=1002,max_size=1002)
model.train()
model = model.to("cuda")
learn = Mask_RCNN_Learner(dls=dls, model=model,loss_func=nn.L1Loss(),
                wd=1e-1).to_fp16()
learn.fit_one_cycle(5, 1e-3)

Gives this error:

Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
Traceback (most recent call last):
Traceback (most recent call last):
Traceback (most recent call last):
_pickle.PicklingError: Can't pickle <class '__main__.MaskRCNN'>: it's not the same object as __main__.MaskRCNN
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
Traceback (most recent call last):
_pickle.PicklingError: Can't pickle <class '__main__.MaskRCNN'>: it's not the same object as __main__.MaskRCNN
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
Traceback (most recent call last):
Traceback (most recent call last):
Traceback (most recent call last):
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
  File "/home/david/anaconda3/envs/seg/lib/python3.7/multiprocessing/queues.py", line 236, in _feed
    obj = _ForkingPickler.dumps(obj)

So, that’s were I am stucked right now. If this works, just need to figurate how modify the metrics computing

2 Likes