Background
I am trying to build DataBlock for a Kaggle dataset (Human Protein Atlas 2019). Its a multi-labels classification problem with metadata stored as a csv like this:
Id Target
00070df0-bbc3-11e8-b2bc-ac1f6b6435d0 16 0
000a6c98-bb9b-11e8-b2b9-ac1f6b6435d0 7 1 2 0
000a9596-bbc4-11e8-b2bc-ac1f6b6435d0 5
Id
is the ID to a protein sample, and Target
is the target mutli-labels. Each protein sample is associated with 4 image paths (e.g. ./00070df0-bbc3-11e8-b2bc-ac1f6b6435d0_red.png
, ./00070df0-bbc3-11e8-b2bc-ac1f6b6435d0_green.png
, ./00070df0-bbc3-11e8-b2bc-ac1f6b6435d0_blue.png
, ./00070df0-bbc3-11e8-b2bc-ac1f6b6435d0_yellow.png
)
Objectives
I wanna build a simple DataBlock
to read the data. As a starter, I target to read 3 of the image paths and concatenate them into a 3-channel image (for each protein sample). So firstly I defined a function that maps a Id
to a tuple of 3 image paths.
import os
from pathlib import Path
from functools import partial
from typing import Tuple, List
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import torch
from fastai.vision.all import *
TRAIN_DIR = Path('/kaggle/input/human-protein-atlas-image-classification')
def get_channel_paths(row: pd.Series, is_full: bool = False) -> Tuple[Path, Path, Path]:
img_id = row.Id
colors = ['red', 'green', 'blue']
if is_full:
colors += ['yellow']
channel_fns = tuple(map(lambda color: TRAIN_DIR/'train'/f'{img_id}_{color}.png', colors))
assert os.path.isfile(channel_fns[0])
return channel_fns
and then I tried to alter PILBase.create
behavior so that I can reuse ImageBlock
for my DataBlock
. The way I altered it is to enable this method to handle tuple of Paths as input, as follows:
@patch(cls_method = True)
def create(cls: PILBase, fn: (Path,str,Tensor,ndarray,bytes,Tuple[str, str, str]), **kwargs)->None:
"Open an `Image` from path `fn`"
if isinstance(fn,TensorImage): fn = fn.permute(1,2,0).type(torch.uint8)
if isinstance(fn, TensorMask): fn = fn.type(torch.uint8)
if isinstance(fn,Tensor): fn = fn.numpy()
# handle tuple of image paths for HPA 2019
if isinstance(fn,tuple):
channel_imgs = [Image.open(img_path) for img_path in fn]
fn = np.stack(channel_imgs, axis = -1)
if isinstance(fn,ndarray): return cls(Image.fromarray(fn))
if isinstance(fn,bytes): fn = io.BytesIO(fn)
return cls(load_image(fn, **merge(cls._open_args, kwargs)))
Finally, I create my DataBlock
as follows (omit batch_tfms and other args for simplicity and debugging purpose):
data_blk = DataBlock(blocks = (ImageBlock, MultiCategoryBlock),
get_x = partial(get_channel_paths, is_full = False),
get_y = ColReader(1, label_delim = ' '))
ds = data_blk.datasets(df)
ds.train[0]
Problem
Unexpectedly, I got the following outcome from the above command. I expect the output is a tuple of ImageTensor
and TensorMultiCategory
, but got tuple of Path as first entry instead:
((Path('/kaggle/input/human-protein-atlas-image-classification/train/04e6f8f8-bb9b-11e8-b2b9-ac1f6b6435d0_red.png'),
Path('/kaggle/input/human-protein-atlas-image-classification/train/04e6f8f8-bb9b-11e8-b2b9-ac1f6b6435d0_green.png'),
Path('/kaggle/input/human-protein-atlas-image-classification/train/04e6f8f8-bb9b-11e8-b2b9-ac1f6b6435d0_blue.png')),
TensorMultiCategory([1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]))
I suspected the cause is that my dispatched PILBase.create
failed to be in place in this DataBlock
call. Any advice on how to resolve this?