Skip to content

srproj_reader

data.dataset.srproj_reader

ImageLoader

ImageLoader(image_mode: Union[str, List[str]] = 'RGB', to_numpy: bool = True)

여러 형태의 데이터를 입력으로 받아, 전처리 과정을 거쳐 PIL Image 혹은 np.ndarray 로 return 합니다. 현재 지원하는 데이터는 다음과 같습니다. - 데이터 타입: [image path, np.ndarray, PIL Image] - color: ["RGB", "Gray"] - bit: [8, 16]

입력으로 받은 데이터에 따른 출력값은 다음과 같습니다. | image path | np.ndarray | PIL | RGB; 8 | PIL(RGB;8) | PIL(RGB;8) | PIL(RGB;8) | RGB;16 | PIL(RGB;8) | PIL(RGB;8) | - | Gray; 8 | PIL(L;8) | PIL(L;8) | PIL(L;8) | Gray;16 | PIL(L;8) | PIL(L;8) | PIL(L;8) |

Note1

PIL은 RGB;16을 지원하지 않습니다. 따라서 PIL(RGB;16)은 입력으로 들어올 수 없습니다.

Note2

PIL은 "I;16" 모드로 Gray;16을 지원하지만, PIL 내부의 convert 함수를 써서 Gray;8로 변환시 값이 overflow 나는 issue가 있습니다.

Note3

이미지는 기본적으로 np.ndarray로 로드되며, PIL.Image.Image로 로드하려면 to_numpy=False를 세팅하세요.

Note4

image_mode는 "RGB", "L" 중 하나를 지원하며, multipage (이미지 리스트) 인 경우 각 페이지의 모드를 리스트로 입력하세요. 예시: 1, 3번째 페이지는 RGB 이고 2번째 페이지는 L 인 경우 image_mode = ["RGB", "L", "RGB"]

Source code in SaigeToolkit/data/transform/image_load.py
def __init__(self, image_mode: Union[str, List[str]] = "RGB", to_numpy: bool = True) -> None:
    if isinstance(image_mode, str):
        image_mode = [image_mode]
    elif not set(image_mode).issubset(set(IMAGE_MODES)):
        raise ValueError
    self.image_mode = image_mode
    self.to_numpy = to_numpy

image_mode instance-attribute

image_mode = image_mode

to_numpy instance-attribute

to_numpy = to_numpy

__call__

__call__(image: Union[Image, ndarray, str, List], **data) -> Dict
Source code in SaigeToolkit/data/transform/image_load.py
def __call__(self, image: Union[Image.Image, np.ndarray, str, List], **data) -> Dict:
    return self.call_method(image=image, **data)

call_method

call_method(image: Union[Image, ndarray, str, List], **data) -> Dict

make [PIL Image or np.ndarray] from PIL.Image, np.ndarray, or path string

Parameters:

  • image (Union[Image, ndarray, str, List]) –

    PIL.Image, array, path string or list of it.

Returns:

  • Dict ( Dict ) –

    { "image": Union[PIL.Image.Image, np.ndarray, List[PIL.Image.Image], List[np.ndarray]], **input_dict, }

Source code in SaigeToolkit/data/transform/image_load.py
def call_method(self, image: Union[Image.Image, np.ndarray, str, List], **data) -> Dict:
    """make [PIL Image or np.ndarray] from PIL.Image, np.ndarray, or path string

    Args:
        image (Union[Image.Image, np.ndarray, str, List]): PIL.Image, array, path string or list of it.

    Returns:
        Dict:
            {
                "image": Union[PIL.Image.Image, np.ndarray, List[PIL.Image.Image], List[np.ndarray]],
                **input_dict,
            }
    """
    multipage = isinstance(image, List)
    if not multipage:
        image = [image]

    if len(image) != len(self.image_mode):
        raise ValueError("number of images should match len(image_mode)")

    # 이미지들을 각자의 모드로 로드 (RGB 또는 L).
    loaded_images = []
    for image_i, image_mode_i in zip(image, self.image_mode):
        image_i = self.load(image_i)
        image_i = convert_image_mode(image=image_i, mode=image_mode_i, copy=False)
        loaded_images.append(image_i)

    if multipage:
        # multipage 이미지들의 사이즈가 모두 동일해야함.
        image_sizes = set(read_image_size(item) for item in loaded_images)
        if len(image_sizes) > 1:
            raise ValueError("sizef of images in multipage should be identical")
    else:
        loaded_images = loaded_images[0]

    data["image"] = loaded_images
    return data

load

load(image: Union[Image, ndarray, str]) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load(self, image: Union[Image.Image, np.ndarray, str]) -> Union[Image.Image, np.ndarray]:
    if isinstance(image, Image.Image):
        image = self.load_from_pil(image)
    elif isinstance(image, np.ndarray):
        image = self.load_from_array(image)
    elif isinstance(image, str):
        image = self.load_from_path(image)
    return image

load_from_pil

load_from_pil(image: Image) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load_from_pil(self, image: Image.Image) -> Union[Image.Image, np.ndarray]:
    if self.to_numpy:
        image = np.array(image, dtype=np.uint16 if self._is_16bit(image) else np.uint8)

    if self._is_16bit(image):
        image = self._convert_16_to_8(image)

    return image

load_from_array

load_from_array(array: ndarray) -> Union[Image, ndarray]
Source code in SaigeToolkit/data/transform/image_load.py
def load_from_array(self, array: np.ndarray) -> Union[Image.Image, np.ndarray]:
    if array.ndim == 3 and array.shape[-1] == 1:  # grayscale shape HW1 -> HW
        array = np.squeeze(array, axis=2)

    if self._is_16bit(array):
        array = self._convert_16_to_8(array)

    if self.to_numpy:
        out = array
    else:
        out = Image.fromarray(array)

    return out

load_from_path

load_from_path(path: str) -> Union[Image, ndarray]

Loading function using PIL.Image library. This function is introduced because {exif_transpose} must be processed. {exif_transpose} fix rotated image binary data using {EXIF} information. Without {exif_transpose}, network would accidently learn randomly rotated image data.

Parameters:

  • path (str) –

    path to image file

Returns:

  • Union[Image, ndarray]

    Union[Image.Image, np.ndarray]: image

Source code in SaigeToolkit/data/transform/image_load.py
def load_from_path(self, path: str) -> Union[Image.Image, np.ndarray]:
    """Loading function using PIL.Image library.
    This function is introduced because {exif_transpose} must be processed.
    {exif_transpose} fix rotated image binary data using {EXIF} information.
    Without {exif_transpose}, network would accidently learn randomly rotated image data.

    Args:
        path (str): path to image file

    Returns:
        Union[Image.Image, np.ndarray]: image
    """
    if path.endswith(SAIGE_GENERATED_IMAGE_EXTENSIONS):
        image = self._load_from_encrypted_file_path(path)
    else:
        image = self._load_from_plain_file_path(path)

    if self.to_numpy:
        image = np.array(image, dtype=np.uint16 if self._is_16bit(image) else np.uint8)

    if self._is_16bit(image):
        image = self._convert_16_to_8(image)

    return image

_load_from_plain_file_path staticmethod

_load_from_plain_file_path(path: str) -> Image

Loading function using PIL.Image library. This function is introduced because {exif_transpose} must be processed. {exif_transpose} fix rotated image binary data using {EXIF} information. Without {exif_transpose}, network would accidently learn randomly rotated image data.

Parameters:

  • path (str) –

    path to image file

Returns:

  • Image

    Image.Image: image

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _load_from_plain_file_path(path: str) -> Image.Image:
    """Loading function using PIL.Image library.
    This function is introduced because {exif_transpose} must be processed.
    {exif_transpose} fix rotated image binary data using {EXIF} information.
    Without {exif_transpose}, network would accidently learn randomly rotated image data.

    Args:
        path (str): path to image file

    Returns:
        Image.Image: image
    """

    try:
        with open(path, "rb") as f:
            image = Image.open(f)
            image = ImageOps.exif_transpose(image)
    except Exception as e:
        raise InvalidImageFileError(f"[path] {path}") from e
    return image

_load_from_encrypted_file_path staticmethod

_load_from_encrypted_file_path(path: str) -> Image

Loading function using PIL.Image library and decrypting encrypted image file.

Parameters:

  • path (str) –

    encrypted image file path

Returns:

  • Image

    Image.Image: decrypted image

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _load_from_encrypted_file_path(path: str) -> Image.Image:
    """Loading function using PIL.Image library and decrypting encrypted image file.

    Args:
        path (str): encrypted image file path

    Returns:
        Image.Image: decrypted image
    """
    try:
        image = decrypt_load_image(path)
    except Exception as e:
        raise InvalidImageFileError(f"[path] {path}") from e
    return image

_convert_16_to_8 staticmethod

_convert_16_to_8(image: Union[Image, ndarray]) -> Union[Image, ndarray]

이미지 픽셀당 비트수를 16에서 8로 변화합니다. 입출력 데이터 타입이 같습니다. (pil로 받으면 pil을 ndarray로 받을 시 ndarray를 출력합니다.)

Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _convert_16_to_8(image: Union[Image.Image, np.ndarray]) -> Union[Image.Image, np.ndarray]:
    """
    이미지 픽셀당 비트수를 16에서 8로 변화합니다.
    입출력 데이터 타입이 같습니다. (pil로 받으면 pil을 ndarray로 받을 시 ndarray를 출력합니다.)
    """

    def _convert_16_to_8_np(array: np.ndarray) -> np.ndarray:
        return (array >> 8).astype(np.uint8)

    if isinstance(image, Image.Image):
        assert "I" in image.mode
        array_16bit = np.array(image, dtype=np.uint16)
        array_8bit = _convert_16_to_8_np(array_16bit)
        ret_image = Image.fromarray(array_8bit)
    elif isinstance(image, np.ndarray):
        assert image.dtype == np.uint16
        ret_image = _convert_16_to_8_np(image)
    else:
        raise NotImplementedError

    return ret_image

_is_16bit staticmethod

_is_16bit(image: Union[Image, ndarray]) -> bool
Source code in SaigeToolkit/data/transform/image_load.py
@staticmethod
def _is_16bit(image: Union[Image.Image, np.ndarray]) -> bool:
    if isinstance(image, Image.Image) and "I" in image.mode:
        return True
    elif isinstance(image, np.ndarray) and image.dtype == np.uint16:
        return True
    return False

read_image_size

read_image_size(image: Union[Image, Tensor, ndarray]) -> ImageSizeType

image size: (W, H)

Source code in SaigeToolkit/data/transform/image_function.py
def read_image_size(image: Union[Image.Image, torch.Tensor, np.ndarray]) -> ImageSizeType:
    """image size: (W, H)"""
    if isinstance(image, Image.Image):
        return image.size
    elif isinstance(image, torch.Tensor) and image.ndim in (2, 3, 4):  # HW, CHW, BCHW
        return (image.shape[-1], image.shape[-2])
    elif isinstance(image, np.ndarray) and image.ndim in (2, 3):  # HW, HWC
        return (image.shape[1], image.shape[0])
    else:
        raise NotImplementedError

path_interpreter

path_interpreter(code: str) -> Tuple[str, str, str, str]

data path interpreter

Parameters:

  • code (str) –

    dataset code (rule: {domain}-{source}-{category}) (ex) sample dataset for cls: "test_directory-sample-cls")

Raises:

  • RuntimeError

    invalid classifier keys (code) for srproj code.

  • Exception

    wrong type of input code

Returns:

  • Tuple[str, str, str, str]

    Tuple[str, str, str, str]: absolute path for dataset, domain, source, category

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def path_interpreter(code: str) -> Tuple[str, str, str, str]:
    """data path interpreter

    Args:
        code (str): dataset code (rule: {domain}-{source}-{category})
            (ex) sample dataset for cls: "test_directory-sample-cls")

    Raises:
        RuntimeError: invalid classifier keys (code) for srproj code.
        Exception: wrong type of input code

    Returns:
        Tuple[str, str, str, str]: absolute path for dataset, domain, source, category
    """

    if os.path.isdir(code) or os.path.isfile(code):
        # correct directory/file
        path = code
        dir_path = os.path.dirname(os.path.dirname(path))
        # NOTE: don't know which separator is used in the path string
        _separator = "/" if "/" in dir_path else "\\" if "\\" in dir_path else os.sep
        keys = dir_path.split(_separator)
        keys = keys + ["dummy" for _ in range(3 - len(keys))]
        domain, source, category = keys[-3:]
    else:
        # wrong directory/file/code
        raise Exception(f"Wrong Directory / File / Code: {code}")

    return path, domain, source, category

read_srproj

read_srproj(code: str, split: str, two_class: bool = False, mode: Optional[str] = None, use_absolute_path: bool = False, srproj_image_directory: Optional[str] = None, system_image_directory: Optional[str] = None, get_class_colors: bool = False, skip_problematic_files: bool = False) -> Tuple[List[Dict], int, Union[List[str], List[Dict]]]

read srproj dataset from file-system.

Parameters:

  • code (str) –

    dataset code.

  • split (str) –

    split dataset, "Training" or "Validation".

  • two_class (bool, default: False ) –

    load data as two class mode (ex) normal <-> abnormal)

  • mode (Optional[str], default: None ) –

    select data label type.

  • use_absolute_path (bool, default: False ) –

    use the image path in srproj as is.

  • srproj_image_directory (Optional[str], default: None ) –

    srproj 파일에 작성된 이미지 경로를 현재 시스템 상의 이미지 경로로 변환하기 위해 치환해야 하는 이미지 폴더 경로. None이면 SaigeDatabase 규칙 사용.

  • system_image_directory (Optional[str], default: None ) –

    코드가 구동되는 시스템 상의 이미지 폴더 경로. None이면 SaigeDatabase 규칙 사용.

  • get_class_colors(bool)

    srproj에 정의된 각 클래스의 color를 받을지 여부. True이면

  • skip_problematic_files(bool)

    srproj 파일에 작성된 이미지 경로로부터 이미지들을 읽을 때, 해당 파라미터가 True 라면, 파일이 실제 존재하지 않거나, 읽다가 문제가 발생할 경우 해당 파일을 제외하고 나머지 파일을 계속 읽음. 만약, False라면 Error를 raise 함.

Returns:

  • Tuple[List[Dict], int, Union[List[str], List[Dict]]]

    Tuple[List[Dict], int, Union[List[str], List[Dict]]]: list of meta data dicts, number of classes, and list of class names or dict

Note

.sproj 파일에는 각 이미지들의 경로와 라벨 정보 등이 포함되어 있습니다. 이때 이미지 경로는 해당 파일을 생성한 PC 기준의 절대 경로로 작성되어 있기 때문에, 현재 이 코드가 실행되는 시스템에서의 경로로 변환해 주어야 합니다. 현재 이미지 경로 변환 옵션은 3가지가 존재합니다. 1. sproj를 생성한 PC에서 코드를 실행하는 경우, use_absolute_path= True로 세팅하면 srproj에 작성된 이미지 경로를 그대로 사용합니다. 2. 이미지와 srproj가 SaigeDatabase 룰에 맞게 구성된 경우, 이미지는 images 폴더에 하위에 있고 (root/images/.../xxx.png) srproj는 images 폴더보다 한 단계 아래 경로에 있어야 합니다 (root/projects/xxx.srproj) 이때는srproj_image_directory= None,system_image_directory= None 으로 설정합니다. 3. 이미지와 srproj가 SaigeDatabase 룰을 따르지 않는 경우,srproj_image_directorysystem_image_directory`를 설정하면 srproj에 작성된 이미지 경로를 다음 규칙으로 변환합니다: {srproj_image_directory}/.../xxx.png -> {system_image_directory}/.../xxx.png

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def read_srproj(
    code: str,
    split: str,
    two_class: bool = False,
    mode: Optional[str] = None,
    use_absolute_path: bool = False,
    srproj_image_directory: Optional[str] = None,
    system_image_directory: Optional[str] = None,
    get_class_colors: bool = False,
    skip_problematic_files: bool = False,
) -> Tuple[List[Dict], int, Union[List[str], List[Dict]]]:
    """read srproj dataset from file-system.

    Args:
        code (str): dataset code.
        split (str): split dataset, "Training" or "Validation".
        two_class (bool): load data as two class mode (ex) normal <-> abnormal)
        mode (Optional[str]): select data label type.
        use_absolute_path (bool): use the image path in srproj as is.
        srproj_image_directory (Optional[str]): srproj 파일에 작성된 이미지 경로를 현재 시스템 상의 이미지 경로로 변환하기 위해
                                                치환해야 하는 이미지 폴더 경로. None이면 SaigeDatabase 규칙 사용.
        system_image_directory (Optional[str]): 코드가 구동되는 시스템 상의 이미지 폴더 경로. None이면 SaigeDatabase 규칙 사용.
        get_class_colors(bool): srproj에 정의된 각 클래스의 color를 받을지 여부. True이면
        skip_problematic_files(bool): srproj 파일에 작성된 이미지 경로로부터 이미지들을 읽을 때, 해당 파라미터가 True 라면,
                                      파일이 실제 존재하지 않거나, 읽다가 문제가 발생할 경우 해당 파일을 제외하고 나머지 파일을 계속 읽음.
                                      만약, False라면 Error를 raise 함.

    Returns:
        Tuple[List[Dict], int, Union[List[str], List[Dict]]]:
            list of meta data dicts, number of classes, and list of class names or dict

    Note:
        .sproj 파일에는 각 이미지들의 경로와 라벨 정보 등이 포함되어 있습니다.
        이때 이미지 경로는 해당 파일을 생성한 PC 기준의 절대 경로로 작성되어 있기 때문에, 현재 이 코드가 실행되는 시스템에서의 경로로 변환해 주어야 합니다.
        현재 이미지 경로 변환 옵션은 3가지가 존재합니다.
        1. sproj를 생성한 PC에서 코드를 실행하는 경우,
            use_absolute_path` = True로 세팅하면 srproj에 작성된 이미지 경로를 그대로 사용합니다.
        2. 이미지와 srproj가 SaigeDatabase 룰에 맞게 구성된 경우,
            이미지는 images 폴더에 하위에 있고 (root/images/.../xxx.png)
            srproj는 images 폴더보다 한 단계 아래 경로에 있어야 합니다 (root/projects/xxx.srproj)
            이때는 `srproj_image_directory` = None, `system_image_directory` = None 으로 설정합니다.
        3. 이미지와 srproj가 SaigeDatabase 룰을 따르지 않는 경우,
            `srproj_image_directory`와 `system_image_directory`를 설정하면
            srproj에 작성된 이미지 경로를 다음 규칙으로 변환합니다:
            {srproj_image_directory}/.../xxx.png -> {system_image_directory}/.../xxx.png

    """

    path, domain, source, category = path_interpreter(code)

    # Check if "path" is directory
    if os.path.isdir(path):
        srproj_list = [os.path.join(path, f) for f in os.listdir(path) if f.endswith(".srproj")]
        path = np.random.choice(srproj_list)

    # Read srproj
    with open(path, encoding="utf-8") as file:
        txt = file.read()
    srproj = xmltodict.parse(txt, dict_constructor=dict)["Project"]

    # Config mode
    if mode is None:
        mode = srproj["Type"]

    # Return empty list when dummy.srproj
    if srproj["ImageGroup"]["Image"] is None:
        return [], 0, []

    # Config files
    image_list = (
        srproj["ImageGroup"]["Image"]
        if isinstance(srproj["ImageGroup"]["Image"], list)
        else [srproj["ImageGroup"]["Image"]]
    )
    assert split in [
        "Training",
        "Validation",
        "All",
    ], "Split for *.srproj Should be 'Training' or 'Validation'"
    if split == "All":
        files = [l for l in image_list]
    else:
        files = [l for l in image_list if l["SplitState"] == split]
    files = check_label(files, mode)

    n_classes = 0

    image_path_converter = lambda srproj_image_path: srproj_image_path
    if not use_absolute_path:
        if srproj_image_directory is None:
            srproj_image_path = (
                files[0]["Path"] if "Path" in files[0] else files[0]["PathGroup"]["Path"][0]
            )
            srproj_image_path = srproj_image_path.replace("\\", os.sep)
            srproj_image_directory = srproj_image_path.split(os.sep + "images" + os.sep)[0]
        srproj_image_directory = srproj_image_directory.replace("\\", os.sep)

        if system_image_directory is None:
            system_image_directory = os.path.dirname(os.path.dirname(path))

        assert os.path.isdir(
            system_image_directory
        ), f"Image data should be in following directory: {system_image_directory}"

        image_path_converter = lambda srproj_image_path: os.path.join(
            system_image_directory,
            os.path.relpath(srproj_image_path.replace("\\", os.sep), srproj_image_directory),
        )

    _image_loader = ImageLoader()

    def _update_file_information(file: Dict) -> bool:
        image_path = file["image_path"]
        if isinstance(image_path, List):
            image_path = image_path[0]

        if os.path.isfile(image_path):
            try:
                _image = _image_loader(image_path)
                img_size = read_image_size(_image["image"])
            except (IOError, OSError):
                if skip_problematic_files:
                    return False
                raise error.InvalidImageFileError
        else:
            if skip_problematic_files:
                print(f"image not found: {file['image_path']}")
                return False
            raise error.FileNotFoundError

        file.update(
            {
                "domain": domain,
                "source": source,
                "category": category,
                "image_size": img_size,
                "label_extension": "srproj",
                "mode": mode,
            }
        )

        if mode == "Classification":
            lbl = int(file["ClassIndexOfLabel"])
            if two_class:
                lbl = 1 if (lbl > 0) else lbl

            file["ClassIndexOfLabel"] = lbl + n_classes

        elif mode == "MultiLabelClassification":
            lbl = list(file["ClassIndexOfLabel"])
            file["ClassIndexOfLabel"] = [int(l) + n_classes for l in lbl]

        elif mode == "Segmentation":
            if str(file["LabelGroup"]["IsNormal"]) in ["False", "false", "FALSE"]:
                lbl_list = (
                    file["LabelGroup"]["Label"]
                    if isinstance(file["LabelGroup"]["Label"], list)
                    else [file["LabelGroup"]["Label"]]
                )
                for l in lbl_list:
                    lbl = 1 if two_class else int(l["ClassIndex"]) + 1
                    l["ClassIndex"] = lbl + n_classes

        return True

    cleaned_files = []
    for f in files:
        if "Path" in f:
            f["image_path"] = image_path_converter(f["Path"])
        else:
            f["image_path"] = [image_path_converter(_path) for _path in f["PathGroup"]["Path"]]

        is_updated = _update_file_information(f)
        if is_updated:
            cleaned_files.append(f)

    # Config n_classes
    classes = []
    if "ClassGroup" in srproj:
        n_classes = int(srproj["ClassGroup"]["NumberOfClasses"])
        class_list = (
            srproj["ClassGroup"]["Class"]
            if isinstance(srproj["ClassGroup"]["Class"], list)
            else [srproj["ClassGroup"]["Class"]]
        )
        if get_class_colors:
            classes = [
                {
                    "name": class_["Name"],
                    "color": convert_srproj_class_color_to_rgb(int(class_["Color"])),
                }
                for class_ in class_list
            ]
        else:
            classes = [class_["Name"] for class_ in class_list]
    elif "CharacterGroup" in srproj:
        classes = srproj["CharacterGroup"]["Included"]["@xmlns"]
        n_classes = len(classes) + 1

    if mode in ["Segmentation"]:
        if isinstance(classes[0], dict):
            classes.insert(0, {"name": "background", "color": (0, 0, 0)})
        else:
            classes.insert(0, "background")
        n_classes = len(classes)

    if two_class:
        classes = ["Normal", "Defect"]
        n_classes = len(classes)

    return cleaned_files, n_classes, classes

check_label

check_label(files: List[Dict], mode: str) -> List[Dict]

check all data label is valid. If any data is corrupted, pop out from meta data list.

Parameters:

  • files (List[Dict]) –

    meta data list to be checked

  • mode (str) –

    label data type

Returns:

  • List[Dict]

    List[Dict]: cleaned meta data list

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def check_label(files: List[Dict], mode: str) -> List[Dict]:
    """check all data label is valid.
    If any data is corrupted, pop out from meta data list.

    Args:
        files (List[Dict]): meta data list to be checked
        mode (str): label data type

    Returns:
        List[Dict]: cleaned meta data list
    """
    for i in range(len(files) - 1, -1, -1):
        if mode == "Classification":
            if int(files[i]["ClassIndexOfLabel"]) == -1:
                files.pop(i)
        elif mode == "MultiLabelClassification":
            if not isinstance(files[i]["ClassIndexOfLabel"], list):
                files[i]["ClassIndexOfLabel"] = [files[i]["ClassIndexOfLabel"]]

        elif mode == "OpticalCharacterRecognition":
            if int(files[i]["LabelGroup"].get("NumberOfLabels", 0)) == 0:
                continue
            labels = (
                files[i]["LabelGroup"]["Label"]
                if isinstance(files[i]["LabelGroup"]["Label"], list)
                else [files[i]["LabelGroup"]["Label"]]
            )
            for idx in range(len(labels) - 1, -1, -1):
                # XXX: Temporary implementation for memory issue
                for key, value in labels[idx]["Coordinate"].items():
                    labels[idx]["Coordinate"][key] = float(value)

                if labels[idx]["Characters"] == '"value4"':
                    labels.pop(idx)
                elif not labels[idx]["Characters"]:
                    labels.pop(idx)
                else:
                    continue

                files[i]["LabelGroup"]["Label"] = labels
                files[i]["LabelGroup"]["NumberOfLabels"] = len(labels)

        else:
            if str(files[i]["LabelGroup"]["IsNormal"]) in [
                "False",
                "false",
                "FALSE",
            ]:
                if int(files[i]["LabelGroup"].get("NumberOfLabels", 0)) == 0:
                    files.pop(i)
    return files

load_srproj_label

load_srproj_label(file: Dict, use_crop_fn: Optional[bool] = False, **kwargs) -> Dict

load single data dict from srproj meta data.

Parameters:

  • file (Dict) –

    single meta data dict

  • use_crop_fn (bool, default: False ) –

    Whether to use the crop function. Defaults to False.

Raises:

  • NotImplementedError

    only four modes are available. ["Classification", "Detection", "Segmentation", "OpticalCharacterRecognition"]

Returns:

  • Dict ( Dict ) –

    label data as dict

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def load_srproj_label(file: Dict, use_crop_fn: Optional[bool] = False, **kwargs) -> Dict:
    """load single data dict from srproj meta data.

    Args:
        file (Dict): single meta data dict
        use_crop_fn (bool, optional): Whether to use the crop function.
            Defaults to False.

    Raises:
        NotImplementedError: only four modes are available.
            ["Classification", "Detection", "Segmentation", "OpticalCharacterRecognition"]

    Returns:
        Dict: label data as dict
    """
    mode = file["mode"]

    # make label for the right mode
    if mode == "Classification":
        label = load_label_cls(file, **kwargs)

    elif mode == "Detection":
        label = load_label_det(file, **kwargs)

    elif mode == "Segmentation":
        # In Segmentation task, need polygon data to use the crop function.
        label = load_label_seg(file, return_polygon=use_crop_fn, **kwargs)

    elif mode == "OpticalCharacterRecognition":
        label = load_label_ocr(file, **kwargs)

    else:
        raise NotImplementedError(f"Mode {mode} not implemented")

    return label

load_label_cls

load_label_cls(file: Dict) -> Dict
Source code in SaigeToolkit/data/dataset/srproj_reader.py
def load_label_cls(file: Dict) -> Dict:
    return {"class": file["ClassIndexOfLabel"]}

load_label_det

load_label_det(file: Dict) -> Dict

load detection label

Parameters:

  • file (Dict) –

    meta data for detection label

Returns:

  • Dict ( Dict ) –

    detection label data dict

    {
        "bboxes": [
            [x, y, w, h],
            ...,
        ],
        "labels": List[int],
    }
    

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def load_label_det(file: Dict) -> Dict:
    """load detection label

    Args:
        file (Dict): meta data for detection label

    Returns:
        Dict: detection label data dict
            ```python
            {
                "bboxes": [
                    [x, y, w, h],
                    ...,
                ],
                "labels": List[int],
            }
            ```
    """
    srproj_label_group = file["LabelGroup"]
    label = {"bboxes": [], "labels": []}

    if str(srproj_label_group["IsNormal"]).lower() == "false":
        srproj_labels = srproj_label_group["Label"]
        if not isinstance(srproj_labels, list):
            srproj_labels = [srproj_labels]
        for srproj_label in srproj_labels:
            x = int(srproj_label["Coordinate"]["@X"])
            y = int(srproj_label["Coordinate"]["@Y"])
            w = int(srproj_label["Coordinate"]["@Width"])
            h = int(srproj_label["Coordinate"]["@Height"])
            label["bboxes"].append([x, y, w, h])
            label["labels"].append(int(srproj_label["ClassIndex"]))

    return label

load_label_seg

load_label_seg(file: Dict, return_polygon: bool = True, mask_to_pil: bool = False) -> Dict

load segmentation label

Parameters:

  • file (Dict) –

    meta data for segmentation label

  • return_polygon (bool, default: True ) –

    whether return polygon data. Defaults to True.

  • mask_to_pil (bool, default: False ) –

    whether return mask as PIL.Image. Defaults to False.

Returns:

  • Dict ( Dict ) –

    segmentation label data dict

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def load_label_seg(file: Dict, return_polygon: bool = True, mask_to_pil: bool = False) -> Dict:
    """load segmentation label

    Args:
        file (Dict): meta data for segmentation label
        return_polygon (bool, optional): whether return polygon data. Defaults to True.
        mask_to_pil (bool, optional): whether return mask as PIL.Image. Defaults to False.

    Returns:
        Dict: segmentation label data dict
    """
    w, h = file["image_size"]
    lbl = np.zeros((h, w), dtype=np.uint8)

    lbl_list = file["LabelGroup"]

    if str(lbl_list["IsNormal"]) in ["False", "false", "FALSE"]:
        lbl_list = lbl_list["Label"] if isinstance(lbl_list["Label"], list) else [lbl_list["Label"]]
        contour = []
        for l in lbl_list:
            canvas = np.zeros((h, w), dtype=np.uint8)
            color = l["ClassIndex"]

            contour_list = (
                l["ContourGroup"]["Contour"]
                if isinstance(l["ContourGroup"]["Contour"], list)
                else [l["ContourGroup"]["Contour"]]
            )

            # Contour functions in cv2 gets list type contours
            contours = {"Outer": [], "Inner": []}
            for c_l in contour_list:
                contours[c_l["@Type"]].append(
                    np.array([[int(p["@X"]), int(p["@Y"])] for p in c_l["Point"]])
                )

            # One Outer per One Label
            cv2.fillPoly(canvas, pts=contours["Outer"], color=color)

            # Many Inner could exist in One Label
            if contours["Inner"]:
                cv2.fillPoly(canvas, pts=contours["Inner"], color=0)

            nonzeros = canvas > 0
            lbl[nonzeros] = canvas[nonzeros]
            contour.append(contours["Outer"][0])
    else:
        contour = []

    if mask_to_pil:
        lbl = Image.fromarray(lbl)

    lbl = {"mask": lbl}

    if return_polygon:
        lbl.update({"polygons": contour})

    return lbl

load_label_ocr

load_label_ocr(file: Dict) -> Dict

load ocr label

Parameters:

  • file (Dict) –

    meta data for ocr label

Returns:

  • Dict ( Dict ) –

    ocr label data dict

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def load_label_ocr(file: Dict) -> Dict:
    """load ocr label

    Args:
        file (Dict): meta data for ocr label

    Returns:
        Dict: ocr label data dict
    """
    lbl_list = file["LabelGroup"]

    if int(lbl_list.get("NumberOfLabels", 0)) == 0:
        return {"polygons": [], "strings": [], "ignore": []}

    pols, strs = [], []

    lbl_list = lbl_list["Label"] if isinstance(lbl_list["Label"], list) else [lbl_list["Label"]]
    for l in lbl_list:
        if not l["Characters"]:
            continue
        # if self.ignore_whitespace:
        #     l["Characters"] = l["Characters"].replace(" ", "")
        strs.append(l["Characters"])
        n_point = max(
            [
                int(key.replace("@Vertex", "").replace("X", "").replace("Y", ""))
                for key in l["Coordinate"].keys()
                if "@Vertex" in key
            ]
        )
        pols.append(
            np.array(
                [
                    [
                        float(l["Coordinate"][f"@Vertex{idx + 1}X"]),
                        float(l["Coordinate"][f"@Vertex{idx + 1}Y"]),
                    ]
                    for idx in range(n_point)
                ]
            )
        )

    strings, kie_labels = [], []
    for string in strs:
        string, kie_label = _refine_kie_labels(string)
        strings.append(string)
        kie_labels.append(kie_label)

    ignore = [s is None for s in strs]

    lbl = {"polygons": pols, "strings": strings, "kie_labels": kie_labels, "ignore": ignore}

    return lbl

_refine_kie_labels

_refine_kie_labels(string)
Source code in SaigeToolkit/data/dataset/srproj_reader.py
def _refine_kie_labels(string):
    # split kie_label and string_label
    if string.startswith('"key'):
        index = string[1:].find('"') + 1

        kie_label = 2 * int(string[4:index]) + 1 if index > 4 else 0
        string = string[index + 1 :]

    elif string.startswith('"value'):
        index = string[1:].find('"') + 1

        kie_label = 2 * int(string[6:index]) + 2 if index > 6 else 0
        string = string[index + 1 :]

    else:
        kie_label = 0

    if kie_label < 0:
        kie_label = 0

    return string, kie_label

merge_srproj_classes

merge_srproj_classes(classes1, classes2)
Source code in SaigeToolkit/data/dataset/srproj_reader.py
def merge_srproj_classes(classes1, classes2):
    if classes1 == classes2:
        return classes1

    if len(classes1) > len(classes2):
        classes1, classes2 = classes2, classes1

    if classes2[: len(classes1)] != classes1:
        raise RuntimeError("two srproj cannot be mixed")

    return classes2

convert_srproj_class_color_to_rgb

convert_srproj_class_color_to_rgb(argb: int) -> Tuple[int, int, int]

srproj의 각 클래스 색 코드를 rgb 값으로 변환합니다.

Source code in SaigeToolkit/data/dataset/srproj_reader.py
def convert_srproj_class_color_to_rgb(argb: int) -> Tuple[int, int, int]:
    """srproj의 각 클래스 색 코드를 rgb 값으로 변환합니다."""
    a = (argb >> 24) & 255
    r = (argb >> 16) & 255
    g = (argb >> 8) & 255
    b = argb & 255
    return (r, g, b)