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

[GSK-2569, GSK-2585, GSK-2658] Scan for computer vision #27

Merged
merged 73 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
1b3bb4a
ENH: add report generation for vision tasks
bmalezieux Jan 16, 2024
4d66288
UPD: add missing files
bmalezieux Jan 17, 2024
3804f1d
FIX: linting and format
bmalezieux Jan 17, 2024
70c3982
Merge branch 'main' into GSK-2569-report-gen
rabah-khalek Jan 17, 2024
d7702e2
UPD: change API for metrics
bmalezieux Jan 17, 2024
fe5425a
UPD: forgot to save the file
bmalezieux Jan 17, 2024
805f18f
UPD: add detectors in the package + example notebook
bmalezieux Jan 17, 2024
084247c
Merge branch 'GSK-2569-report-gen' of github.com:Giskard-AI/giskard-v…
bmalezieux Jan 17, 2024
c99216b
UPD: new base method for vision detectors, custom detectors only requ…
bmalezieux Jan 17, 2024
44f3925
FIX: add all facial parts in detector
bmalezieux Jan 17, 2024
6fe9c16
Merge branch 'main' into GSK-2569-report-gen
rabah-khalek Jan 17, 2024
00de9f9
added proposition with headpose estimation as example
rabah-khalek Jan 17, 2024
8921fc9
UPG: complete new API for detectors + add examples
bmalezieux Jan 18, 2024
cdbda72
FIX: remove useless init file
bmalezieux Jan 18, 2024
9ed8655
UPD: add decorators
bmalezieux Jan 18, 2024
5b86011
removed copied decorator.py
rabah-khalek Jan 18, 2024
0b89904
UPD: usenew scan from giskard
bmalezieux Jan 19, 2024
53ccccb
FIX: dependencies
bmalezieux Jan 19, 2024
786e614
FIX: notebook
bmalezieux Jan 19, 2024
a43568f
UPD: update info displayed in report
bmalezieux Jan 19, 2024
4e71c01
UPD: safe import + docstrings
bmalezieux Jan 19, 2024
21d4c53
small fix to glob imports
rabah-khalek Jan 19, 2024
01a1f6e
Merge pull request #29 from Giskard-AI/GSK-2569-report-gen-prop
rabah-khalek Jan 19, 2024
fb72c99
pdm format
rabah-khalek Jan 19, 2024
618eeda
TEST: add test for detectors
bmalezieux Jan 19, 2024
2fa1347
Merge branch 'GSK-2569-report-gen' of github.com:Giskard-AI/giskard-v…
bmalezieux Jan 19, 2024
63eb666
FIX: safe import in test
bmalezieux Jan 19, 2024
3f20b8a
UPD: new decorator for safe import
bmalezieux Jan 19, 2024
285a948
FIX: remove useless print
bmalezieux Jan 19, 2024
cd43f32
Update giskard_vision/landmark_detection/detectors/cropping_detector.py
bmalezieux Jan 22, 2024
0984fdf
Update giskard_vision/landmark_detection/detectors/cropping_detector.py
bmalezieux Jan 22, 2024
7663f2d
Update giskard_vision/landmark_detection/detectors/base.py
bmalezieux Jan 22, 2024
2c778ca
Update giskard_vision/landmark_detection/detectors/transformation_blu…
bmalezieux Jan 22, 2024
5cba860
Update giskard_vision/landmark_detection/detectors/transformation_col…
bmalezieux Jan 22, 2024
07ba80d
Update giskard_vision/landmark_detection/detectors/transformation_res…
bmalezieux Jan 22, 2024
4552db2
Update giskard_vision/landmark_detection/detectors/ethnicity_bias_det…
bmalezieux Jan 22, 2024
5045625
Update giskard_vision/landmark_detection/detectors/head_pose_detector.py
bmalezieux Jan 22, 2024
01f39a2
UPD: fix comments from review
bmalezieux Jan 22, 2024
e2f272c
ENH: add custom scan report for images
bmalezieux Jan 23, 2024
da43a39
FIX: format
bmalezieux Jan 23, 2024
ac9b268
UPD: fixed rgb + decreased size
bmalezieux Jan 23, 2024
a851683
UPD: display only one image instead of two for slicing
bmalezieux Jan 23, 2024
4a0ceb2
FIX: format
bmalezieux Jan 23, 2024
6d9f91d
WIP: try adding score sorting to display images
bmalezieux Jan 23, 2024
e48775c
FIX: selection of worst image working
bmalezieux Jan 24, 2024
202b440
UPD: comply with new changes in giskard + review comments
bmalezieux Jan 24, 2024
e5886e7
FIX: change to comply with new Issue API
bmalezieux Jan 24, 2024
20b2ffe
FIX: pdm files
bmalezieux Jan 24, 2024
24fc825
UPD: add custom warning messages
bmalezieux Jan 25, 2024
599cc17
Merge pull request #30 from Giskard-AI/GSK-2658-custom-scan
rabah-khalek Jan 25, 2024
3afb549
WIP: trying to display images in notebooks
bmalezieux Jan 25, 2024
b3cb393
FIX: tests
bmalezieux Jan 25, 2024
092e83c
UPD: embedding images into html so that they show up in notebooks
bmalezieux Jan 25, 2024
f9f8cc4
Merge branch 'main' into GSK-2569-report-gen
rabah-khalek Jan 26, 2024
c857c5c
UPD: add html file for all 300w
bmalezieux Jan 26, 2024
99812a9
Merge branch 'GSK-2569-report-gen' of github.com:Giskard-AI/giskard-v…
bmalezieux Jan 26, 2024
0e8c75f
FIX: bug in merge
bmalezieux Jan 26, 2024
3a02f9d
UPD: results for 300w indoor
bmalezieux Jan 26, 2024
ec75712
FIX: remove marks for ffhq
bmalezieux Jan 26, 2024
662b287
Merge branch 'main' into GSK-2569-report-gen
rabah-khalek Jan 26, 2024
d15268d
Merge branch 'main' into GSK-2569-report-gen
bmalezieux Jan 29, 2024
b58137d
UPD: fix review comments + add scan API
bmalezieux Jan 29, 2024
f104192
FIX: review
bmalezieux Jan 29, 2024
40d9e9f
FIX: adding typings to scan
bmalezieux Jan 29, 2024
9929b28
FIX: linting
bmalezieux Jan 29, 2024
64daa13
FIX: remove rank_data from several metrics
bmalezieux Jan 30, 2024
2b214a8
FIX: reviews
bmalezieux Jan 31, 2024
b533052
Regenerating pdm.lock
Jan 31, 2024
477a958
FIX: format
bmalezieux Jan 31, 2024
3fb6ffe
FIX: import in scanner.__init__.py
bmalezieux Jan 31, 2024
9369573
FIX: change variable name dataloader -> dataset
bmalezieux Jan 31, 2024
bc410cb
Update giskard_vision/__init__.py
rabah-khalek Jan 31, 2024
636dae1
FIX: thresholds + other small fixes from reviews
bmalezieux Jan 31, 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
1,567 changes: 1,567 additions & 0 deletions examples/landmark_detection/example_scan.ipynb

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions examples/landmark_detection/example_scan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# %%
from giskard_vision.landmark_detection.models.wrappers import OpenCVWrapper
from giskard_vision.landmark_detection.dataloaders.loaders import DataLoader300W

from giskard_vision.scanner import scan

# %%
model = OpenCVWrapper()
dl_ref = DataLoader300W(dir_path="./datasets/300W/sample/")

results = scan(model, dl_ref)


# %%

results.to_html(filename="example_vision_300w.html")
# %%

# %%
594 changes: 594 additions & 0 deletions examples/landmark_detection/example_vision_opencv_300w_all.html

Large diffs are not rendered by default.

527 changes: 527 additions & 0 deletions examples/landmark_detection/example_vision_opencv_300w_indoor.html

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions giskard_vision/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from giskard_vision.landmark_detection import detectors

from . import landmark_detection

__all__ = [
"landmark_detection",
]
__all__ = ["landmark_detection", "detectors"]
162 changes: 162 additions & 0 deletions giskard_vision/detectors/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
from abc import abstractmethod
from dataclasses import dataclass
from typing import Any, List, Optional, Sequence, Tuple

from ..utils.errors import GiskardImportError


@dataclass
class ScanResult:
"""
Minimum requirement for Issues to work properly

Attributes:
name (str): Details on transformation or slice
group (str): Group name for the issue
metric_name (str): Name of the metric
metric_value (float): Value of the metric on sliced or transformed dataset
metric_reference_value (float): Value of the metric on original dataset
issue_level (IssueLevel): Level of issue [Issue.MAJOR, Issue.MEDIUM, Issue.MINOR]
slice_size (int): Number of samples in sliced or transformed dataset

Methods:
get_meta_required() -> dict:
Returns a dictionary containing meta information required by giskard Issue
"""

name: str
group: str
metric_name: str
metric_value: float
metric_reference_value: float
issue_level: str
slice_size: int
filename_examples: Optional[Sequence[str]]
relative_delta: float

def get_meta_required(self) -> dict:
# Get the meta required by the original scan API
deviation = f"{self.relative_delta * 100:+.2f}% than global"
return {
"metric": self.metric_name,
"metric_value": self.metric_value,
"metric_reference_value": self.metric_reference_value,
"deviation": deviation,
"slice_size": self.slice_size,
}


class DetectorVisionBase:
"""
Abstract class for Vision Detectors

Methods:
run(model: Any, dataset: Any, features: Optional[Any], issue_levels: Tuple[IssueLevel]) -> Sequence[Issues]:
Returns a list of giskard Issue to feed to the scan.

get_issues(model: Any, dataset: Any, results: List[ScanResult], issue_levels: Tuple[IssueLevel]):
Returns a list of giskard Issue from results output by get_results

get_results(model: Any, dataset: Any) -> List[ScanResult]
Abstract method that returns a list of ScanResult objects containing
evaluation results for the scan.
"""

group: str
warning_messages: dict
issue_level_threshold: float = 0.2
deviation_threshold: float = 0.05

def run(
self,
model: Any,
dataset: Any,
features: Optional[Any] = None,
issue_levels: Tuple[Any] = None,
embed: bool = True,
) -> Sequence[Any]:
results = self.get_results(model, dataset)
issues = self.get_issues(model, dataset, results=results, issue_levels=issue_levels, embed=embed)
return issues

def get_issues(
self, model: Any, dataset: Any, results: List[ScanResult], issue_levels: Tuple[Any], embed: bool = True
) -> Sequence[Any]:
"""
Returns a list of giskard Issue from results output by get_results

Args:
model (Any): model
dataset (Any): dataset
results (List[ScanResult]): results output by get_results
issue_levels (Tuple[IssueLevel]): issue levels that will be displayed (IssueLevel.MAJOR, IssueLevel.MEDIUM)

Returns:
Sequence[Issue]
"""

issues = []

try:
from giskard.scanner.issues import Issue, IssueGroup, IssueLevel

from .example_manager import ImagesExampleManager

except (ImportError, ModuleNotFoundError) as e:
raise GiskardImportError(["giskard"]) from e

if issue_levels is None:
issue_levels = (IssueLevel.MAJOR, IssueLevel.MEDIUM)

for result in results:
if result.issue_level in issue_levels:
issues.append(
Issue(
model,
dataset,
level=result.issue_level,
slicing_fn=result.name,
group=IssueGroup(
result.group,
self.warning_messages[result.group] if result.group in self.warning_messages else "",
),
meta=result.get_meta_required(),
scan_examples=ImagesExampleManager(result.filename_examples, embed=embed),
display_footer_info=False,
)
)

return issues

@abstractmethod
def get_results(self, model: Any, dataset: Any) -> List[ScanResult]:
"""Returns a list of ScanResult
ScanResult should contain
name : str
Details on transformation or slice
group : IssueGroup
Group name for the issue
metric_name : str
Name of the metric
metric_value : float
Value of the metric on sliced or transformed dataset
metric_reference_value : float
Value of the metric on original dataset
issue_level : IssueLevel
Level of issue [Issue.MAJOR, Issue.MEDIUM, Issue.MINOR]
slice_size : int
Number of samples in sliced or transformed dataset

Parameters
----------
model : Any
Model to be evaluated
dataset : Any
Dataset on which to evaluate the model

Returns
-------
List[ScanResult]
List of ScanResult objects containing evaluation results
"""
...
76 changes: 76 additions & 0 deletions giskard_vision/detectors/example_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import base64
from abc import ABC, abstractmethod
from copy import deepcopy
from typing import Any

import cv2


class ExampleManager(ABC):
"""
Abstract class to manage examples from different data types.
"""

@abstractmethod
def add_examples(self, example: Any):
...

@abstractmethod
def head(self, n: int):
...

@abstractmethod
def to_html(self):
...


class ImagesExampleManager:
"""
Class to manage images examples
"""

def __init__(self, examples: list = [], embed: bool = True):
self.examples = examples
self.embed = embed

def add_examples(self, example):
if isinstance(example, list):
self.examples += example
else:
self.examples.append(example)

def head(self, n):
"""
Returns a new example manager keeping only n first examples

Args:
n (int): number of elements to keep

Returns:
ExampleManager: new example manager with n first examples
"""
return type(self)(deepcopy(self.examples[:n]), embed=self.embed)

def __len__(self):
return len(self.examples)

def to_html(self, **kwargs):
html = '<div style="display:flex;justify-content:space-around">'
for elt in self.examples:
html += f'<img src="data:image/png;base64,{self._encode(elt) if self.embed else elt}" style="width:30%">'
html += "</div>"
return html

def _encode(self, path_img: str):
"""
Encode images into base64 to embed them in the html file

Args:
path_img (str): Path towards the image

Returns:
str: Image encoded in base64
"""
img = cv2.imread(path_img)
png_img = cv2.imencode(".png", img)
return base64.b64encode(png_img[1]).decode("utf-8")
2 changes: 2 additions & 0 deletions giskard_vision/landmark_detection/dataloaders/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,8 @@ class DataLoaderWrapper(DataIteratorBase):
_wrapped_dataloader (DataIteratorBase): The wrapped data loader instance.
"""

dataloader_type: str = "standard"

def __init__(self, dataloader: DataIteratorBase) -> None:
"""
Initializes the DataLoaderWrapper with a given DataIteratorBase instance.
Expand Down
2 changes: 1 addition & 1 deletion giskard_vision/landmark_detection/dataloaders/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import cv2
import numpy as np

from giskard_vision.landmark_detection.utils.errors import GiskardImportError
from giskard_vision.utils.errors import GiskardImportError

from .base import DataIteratorBase, DataLoaderBase

Expand Down
4 changes: 3 additions & 1 deletion giskard_vision/landmark_detection/dataloaders/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
resize_image,
resize_marks,
)
from giskard_vision.landmark_detection.utils.errors import GiskardImportError
from giskard_vision.utils.errors import GiskardImportError

from .base import DataIteratorBase, DataLoaderWrapper, SingleLandmarkData

Expand Down Expand Up @@ -362,6 +362,8 @@ class FilteredDataLoader(DataLoaderWrapper):
FilteredDataLoader: Filtered data loader instance.
"""

dataloader_type = "filter"

@property
def name(self):
"""
Expand Down
15 changes: 15 additions & 0 deletions giskard_vision/landmark_detection/detectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from .cropping_detector import CroppingDetectorLandmark
from .ethnicity_bias_detector import EthnicityDetectorLandmark
from .head_pose_detector import HeadPoseDetectorLandmark
from .transformation_blurring_detector import TransformationBlurringDetectorLandmark
from .transformation_color_detector import TransformationColorDetectorLandmark
from .transformation_resize_detector import TransformationResizeDetectorLandmark

__all__ = [
"CroppingDetectorLandmark",
"HeadPoseDetectorLandmark",
"TransformationBlurringDetectorLandmark",
"TransformationColorDetectorLandmark",
"TransformationResizeDetectorLandmark",
"EthnicityDetectorLandmark",
]
Loading
Loading