Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Get basic image metadata from image properties #49

Merged
merged 32 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e669ac1
Add utilities to get image metadata
Inokinoki Aug 2, 2024
42699c3
Add meta auto based on basic meta data of images
Inokinoki Aug 2, 2024
e3727d3
Refactor HF dataset to always have numpy array instead of PIL image
Inokinoki Aug 5, 2024
c203b57
Refactor HF ppl model to convert numpy array to PIL image
Inokinoki Aug 5, 2024
c79f751
Allow to set global mode for an HF ppl model for PIL conversion
Inokinoki Aug 5, 2024
fc9fb89
Correct height and width in np array
Inokinoki Aug 5, 2024
8454941
Add brightness
Inokinoki Aug 5, 2024
39b63ea
Add average color and contrast
Inokinoki Aug 5, 2024
8cd7eac
Add entropy
Inokinoki Aug 5, 2024
92cb6c0
Fix test due to missing raw HF image in PIL format
Inokinoki Aug 5, 2024
c9201b2
Merge branch 'main' into basic-internal-metadata
rabah-khalek Aug 7, 2024
4a1795d
Merge branch 'main' into basic-internal-metadata
rabah-khalek Aug 7, 2024
a586fce
Merge branch 'main' into basic-internal-metadata
rabah-khalek Aug 7, 2024
0360ee8
Merge branch 'main' of github.com:Giskard-AI/giskard-vision into basi…
Inokinoki Aug 8, 2024
0cf2773
Adapt object detection models with image mode to convert PIL image
Inokinoki Aug 8, 2024
94a767b
Rewrite `get_meta` to concatenate basic metadata
Inokinoki Aug 8, 2024
30e9255
Fix import of issues
Inokinoki Aug 8, 2024
d0ad7ee
Fix name masking in ffhq
Inokinoki Aug 8, 2024
454588e
Expand dicts in ffhq face detection
Inokinoki Aug 8, 2024
4f9b8e4
Should database be integer instead of float?
Inokinoki Aug 8, 2024
d694609
Use uint8 for image numpy test data
Inokinoki Aug 9, 2024
8d05c85
Convert and assume RGB uint8 image as input
Inokinoki Aug 9, 2024
c5e4d4f
Fix cached dataloader counter after calling `get_image` in `get_meta`
Inokinoki Aug 9, 2024
e83c2cb
Unify issue groups
Inokinoki Aug 9, 2024
061e07e
Save images from `get_raw_hf_image` to get image path in HF
Inokinoki Aug 9, 2024
e1cbd65
Merge branch 'main' of github.com:Giskard-AI/giskard-vision into basi…
Inokinoki Aug 9, 2024
0a1fde0
Recover issue meta groups as in `main`
Inokinoki Aug 9, 2024
c03ebd7
Clarify the dataloader access time changes
Inokinoki Aug 9, 2024
d0f2b55
Use AttributesIssueMeta for basic meta
Inokinoki Aug 12, 2024
94c51b5
Use AttributesIssueMeta for depth basic meta
Inokinoki Aug 12, 2024
49b6968
Fix format
Inokinoki Aug 12, 2024
70e80f3
Fix the issue group of brightness
Inokinoki Aug 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions giskard_vision/core/dataloaders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
from abc import ABC, abstractmethod
from typing import List, Optional

import cv2
import numpy as np

from giskard_vision.core.dataloaders.meta import MetaData
from giskard_vision.core.dataloaders.meta import (
MetaData,
get_brightness,
get_entropy,
get_image_channel_number,
get_image_size,
)
from giskard_vision.core.detectors.base import IssueGroup

from ..types import TypesBase
Expand All @@ -17,6 +24,10 @@
"Performance",
description="The data are filtered by metadata like emotion, head pose, or exposure value to detect performance issues.",
)
AttributesIssueMeta = IssueGroup(
"Attributes",
description="The data are filtered by the image attributes like width, height, or brightness value to detect issues.",
)


class DataIteratorBase(ABC):
Expand Down Expand Up @@ -120,7 +131,7 @@ def meta_none(self) -> Optional[TypesBase.meta]:
Returns default for meta data if it is None.

Returns:
Optional[np.ndarray]: Default for meta data.
Optional[TypesBase.meta]: Default for meta data.
"""
return None

Expand All @@ -146,7 +157,41 @@ def get_meta(self, idx: int) -> Optional[TypesBase.meta]:
Returns:
Optional[TypesBase.meta]: Meta information for the given index.
"""
return None
img = self.get_image(idx)
if img.dtype != np.uint8:
# Normalize image to 0-255 range with uint8
img = (img * 255 % 255).astype(np.uint8)

gray_img = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
size = get_image_size(img)
nb_channels = get_image_channel_number(img)
avg_color = np.mean(img, axis=(0, 1))

return MetaData(
data={
"height": size[0],
"width": size[1],
"nb_channels": nb_channels,
"brightness": get_brightness(img),
"average_color_r": avg_color[0],
"average_color_g": avg_color[1] if avg_color.shape[0] > 0 else avg_color[0],
"average_color_b": avg_color[2] if avg_color.shape[0] > 0 else avg_color[0],
"contrast": np.max(gray_img) - np.min(gray_img),
"entropy": get_entropy(img),
},
categories=["nb_channels"],
issue_groups={
"width": AttributesIssueMeta,
"height": AttributesIssueMeta,
"nb_channels": AttributesIssueMeta,
"brightness": AttributesIssueMeta,
"average_color_r": AttributesIssueMeta,
"average_color_g": AttributesIssueMeta,
"average_color_b": AttributesIssueMeta,
"contrast": AttributesIssueMeta,
"entropy": AttributesIssueMeta,
},
)

def get_labels_with_default(self, idx: int) -> np.ndarray:
"""
Expand Down
42 changes: 39 additions & 3 deletions giskard_vision/core/dataloaders/hf.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
import os
import shutil
import tempfile
from abc import abstractmethod
from typing import Optional

from giskard_vision.core.dataloaders.base import DataIteratorBase
from PIL.Image import Image as PILImage

from giskard_vision.core.dataloaders.base import AttributesIssueMeta, DataIteratorBase
from giskard_vision.core.dataloaders.meta import MetaData, get_pil_image_depth
from giskard_vision.utils.errors import GiskardError, GiskardImportError


Expand All @@ -31,7 +35,11 @@ class HFDataLoader(DataIteratorBase):
"""

def __init__(
self, hf_id: str, hf_config: Optional[str] = None, hf_split: str = "test", name: Optional[str] = None
self,
hf_id: str,
hf_config: Optional[str] = None,
hf_split: str = "test",
name: Optional[str] = None,
) -> None:
"""
Initializes the general HuggingFace Datasets instance.
Expand Down Expand Up @@ -90,7 +98,7 @@ def get_image_path(self, idx: int) -> str:
str: Image path
"""

image = self.ds[idx]["image"]
image = self.get_raw_hf_image(idx)
image_path = os.path.join(self.temp_folder, f"image_{idx}.png")
image.save(image_path)

Expand All @@ -101,3 +109,31 @@ def cleanup(self):
Clean the temporary folder
"""
shutil.rmtree(self.temp_folder)

@abstractmethod
def get_raw_hf_image(self, idx: int) -> PILImage:
Inokinoki marked this conversation as resolved.
Show resolved Hide resolved
"""
Retrieves the raw image at the specified index in the HF dataset.
Args:
idx (int): Index of the image

Returns:
PIL.Image.Image: The image instance.
"""
...

def get_meta(self, idx: int) -> MetaData:
meta = super().get_meta(idx)
img = self.get_raw_hf_image(idx)

return MetaData(
data={
**meta.data,
"depth": get_pil_image_depth(img),
},
categories=["depth"] + meta.categories,
issue_groups={
**meta.issue_groups,
"depth": AttributesIssueMeta,
},
)
99 changes: 98 additions & 1 deletion giskard_vision/core/dataloaders/meta.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple

import cv2
import numpy as np
from PIL.Image import Image as PILImage

from giskard_vision.core.detectors.base import IssueGroup

Expand Down Expand Up @@ -140,3 +144,96 @@ def get_categories(self) -> Optional[List[str]]:
Optional[List[str]]: The categories of the metadata, or None if no categories were provided.
"""
return self.categories


def get_image_size(image: np.ndarray) -> Tuple[int, int]:
"""
Utitlity to create metadata with image size.

Args:
image (np.ndarray): The numpy ndarray representation of an image.

Returns:
Tuple[int, int]: The image size, width and height.
"""
return image.shape[:2]


def get_image_channel_number(image: np.ndarray) -> int:
"""
Utitlity to create metadata with image channel number.

Args:
image (np.ndarray): The numpy ndarray representation of an image.

Returns:
int: The image channel number.
"""
shape = image.shape
return shape[2] if len(shape) > 2 else 1


def get_pil_image_depth(image: PILImage) -> int:
"""
Utitlity to create metadata with image depth.

Args:
image (PILImage): The PIL Image object.

Returns:
int: The image depth.
"""
mode = image.mode
if mode == "1":
return 1
elif mode == "L":
return 8
elif mode == "P":
return 8
elif mode == "RGB":
return 24
elif mode == "RGBA":
return 32
elif mode == "CMYK":
return 32
elif mode == "YCbCr":
return 24
elif mode == "LAB":
return 24
elif mode == "HSV":
return 24
elif mode == "I":
return 32
elif mode == "F":
return 32
return 0


def get_brightness(image: np.ndarray) -> float:
"""
Utitlity to create metadata with image brightness.

Args:
image (np.ndarray): The numpy ndarray representation of an image.

Returns:
float: The image brightness normalized to 1.
"""
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
return np.mean(hsv[:, :, 2]) / 255


def get_entropy(image: np.ndarray) -> float:
"""
Utitlity to create metadata with image entropy.

Args:
image (np.ndarray): The numpy ndarray representation of an image.

Returns:
float: The image entropy.
"""
hist = cv2.calcHist([image], [0], None, [256], [0, 256])
hist /= hist.sum()
# Add eps to avoid log(0)
return -np.sum(hist * np.log2(hist + np.finfo(float).eps))
11 changes: 3 additions & 8 deletions giskard_vision/core/detectors/metadata_scan_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
import numpy as np
import pandas as pd

from giskard_vision.core.detectors.base import (
DetectorVisionBase,
IssueGroup,
ScanResult,
)
from giskard_vision.core.dataloaders.base import PerformanceIssueMeta
from giskard_vision.core.detectors.base import DetectorVisionBase, ScanResult
from giskard_vision.core.tests.base import MetricBase
from giskard_vision.utils.errors import GiskardImportError

Expand Down Expand Up @@ -46,9 +43,7 @@ class MetaDataScanDetector(DetectorVisionBase):
metric: MetricBase = None
metric_type: str = None
metric_direction: str = "better_lower"
issue_group = IssueGroup(
name="Performance", description="The data are filtered by metadata to detect performance issues."
)
issue_group = PerformanceIssueMeta

def __init__(self) -> None:
super().__init__()
Expand Down
6 changes: 3 additions & 3 deletions giskard_vision/core/models/hf_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ def __init__(
"""init method that accepts a model object, number of landmarks and dimensions

Args:
model_id (str): Hugging Face model ID
name (Optional[str]): name of the model
pipeline_task (HFPipelineTask): HuggingFace pipeline task
model_id (str): Hugging Face model ID.
name (Optional[str]): name of the model.
pipeline_task (HFPipelineTask): HuggingFace pipeline task.

Raises:
GiskardImportError: If there are missing Hugging Face dependencies.
Expand Down
Loading
Loading