diff --git a/.gitignore b/.gitignore index 95c2c8de..cedb1e45 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ otx_models/ *.jpg *.jpeg +*.JPEG *.png html_build/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ce8ea20..2de6caed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### What's Changed +* Refactor names/folders/objects for better verbosity by @GalyaZalesskaya in https://github.com/openvinotoolkit/openvino_xai/pull/5 * Support classification task by @negvet in https://github.com/intel-sandbox/openvino_xai/commit/dd5fd9b73fe8c12e2d741792043372bcd900a850 * Support detection task by @negvet in https://github.com/intel-sandbox/openvino_xai/commit/84f285f2f40a8b1fc50a8cd49798aae37afd58dc * Support Model API as inference engine by @negvet in https://github.com/intel-sandbox/openvino_xai/commit/5f575f122dedc0461975bd58f81e730a901a69a6 diff --git a/GETTING_STARTED.ipynb b/GETTING_STARTED.ipynb index 3d11cfda..869014fd 100644 --- a/GETTING_STARTED.ipynb +++ b/GETTING_STARTED.ipynb @@ -272,7 +272,7 @@ "# This code returns gray-scale unprocessed saliency map\n", "explanation = auto_explainer.explain(image)\n", "logger.info(f\"Auto example: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\")\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\")\n", "\n", "# Save saliency maps stored in `explanation` object\n", "output_dir = \"saliency_map/auto_explain/wo_postrocessing\"\n", @@ -403,7 +403,7 @@ "explanation = explainer.explain(image)\n", "logger.info(\n", " f\"White-Box example w/o explain_parameters: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\"\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\"\n", ")\n", "\n", "# Save saliency maps stored in `explanation` object\n", @@ -430,9 +430,9 @@ "`White Box explainer` can be configured with the following parameters:\n", "- `target_layer` - specifies the layer after which the XAI nodes should be inserted (the last convolutional layer is a good default option). Example: `/backbone/conv/conv.2/Div`. This parameter can be useful if `WhiteBoxExplainer` fails to find a place where to insert XAI branch.\n", "- `embed_normalization` - **default True** (for speed purposes), but you can disable embedding of normalization into the model.\n", - "- `explain_method_type` - **default reciprocam**:\n", + "- `explain_method` - **default reciprocam**:\n", "\n", - " For Classification models `White Box` algorithm supports 2 `XAIMethodType`:\n", + " For Classification models `White Box` algorithm supports 2 `Method`:\n", " - activationmap - returns a single saliency map regardless of the classes\n", " - reciprocam - returns saliency maps for each class the model can detect\n", "\n", @@ -471,13 +471,13 @@ ], "source": [ "from openvino_xai.model import XAIClassificationModel\n", - "from openvino_xai.parameters import ClassificationExplainParametersWB, XAIMethodType\n", + "from openvino_xai.parameters import ClassificationExplainParametersWB, Method\n", "\n", "# Parametrize White Box Explainer\n", "explain_parameters = ClassificationExplainParametersWB(\n", " # target_layer=\"/backbone/conv/conv.2/Div\", # OTX mobilenet_v3\n", " # target_layer=\"/backbone/features/final_block/activate/Mul\", # OTX efficientnet\n", - " explain_method_type=XAIMethodType.RECIPROCAM,\n", + " explain_method=Method.RECIPROCAM,\n", ")\n", "\n", "# Create an OpenVINO™ ModelAPI model wrapper for Classification model\n", @@ -491,7 +491,7 @@ "explanation = explainer.explain(image)\n", "logger.info(\n", " f\"White-Box example w/ explain_parameters: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\"\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\"\n", ")\n", "\n", "# Save saliency maps stored in `explanation` object\n", @@ -564,11 +564,11 @@ "source": [ "from openvino_xai.explain import WhiteBoxExplainer\n", "from openvino_xai.model import XAIClassificationModel\n", - "from openvino_xai.parameters import PostProcessParameters\n", + "from openvino_xai.parameters import VisualizationParameters\n", "from openvino_xai.saliency_map import TargetExplainGroup\n", "\n", "# Pass postprocessing parameters\n", - "post_processing_parameters = PostProcessParameters(overlay=True)\n", + "visualization_parameters = VisualizationParameters(overlay=True)\n", "\n", "# Create an OpenVINO™ ModelAPI model wrapper with XAI head inserted into the model graph\n", "model = XAIClassificationModel.create_model(model_path, model_type=\"Classification\")\n", @@ -578,11 +578,11 @@ "explainer = WhiteBoxExplainer(model)\n", "explanation = explainer.explain(image, \n", " TargetExplainGroup.PREDICTED_CLASSES, # default option, can be ommited\n", - " post_processing_parameters=post_processing_parameters\n", + " visualization_parameters=visualization_parameters\n", " )\n", "logger.info(\n", " f\"White-Box example w/ postprocessing: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\"\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\"\n", ")\n", "\n", "# Save saliency maps stored in `explanation` object\n", @@ -646,7 +646,7 @@ "from openvino_xai.saliency_map import TargetExplainGroup\n", "\n", "# Pass postprocessing parameters\n", - "post_processing_parameters = PostProcessParameters(overlay=True)\n", + "visualization_parameters = VisualizationParameters(overlay=True)\n", "\n", "# Create an OpenVINO™ ModelAPI model wrapper with XAI head inserted into the model graph\n", "model = XAIClassificationModel.create_model(model_path, model_type=\"Classification\")\n", @@ -666,7 +666,7 @@ "# TargetExplainGroup.PREDICTED_CLASSES)\n", "logger.info(\n", " f\"White-Box example w/ all_classes: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\"\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\"\n", ")\n", "\n", "# Save saliency maps stored in `explanation` object\n", @@ -752,7 +752,7 @@ ], "source": [ "from openvino.model_api.models import ClassificationModel\n", - "from openvino_xai.parameters import PostProcessParameters\n", + "from openvino_xai.parameters import VisualizationParameters\n", "from openvino_xai.explain import RISEExplainer\n", "\n", "# Create an OpenVINO™ ModelAPI model wrapper for Classification model\n", @@ -763,15 +763,15 @@ ")\n", "\n", "# Pass postprocessing parameters\n", - "post_processing_parameters = PostProcessParameters(overlay=True)\n", + "visualization_parameters = VisualizationParameters(overlay=True)\n", "\n", "# Explainer initialization and explanation\n", "explainer = RISEExplainer(model)\n", "# This code returns colored saliency map after processing\n", - "explanation = explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = explainer.explain(image, visualization_parameters=visualization_parameters)\n", "logger.info(\n", - " f\"Black-Box example w/ post_processing_parameters: generated {len(explanation.map)} \"\n", - " f\"classification saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\"\n", + " f\"Black-Box example w/ visualization_parameters: generated {len(explanation.map)} \"\n", + " f\"classification saliency maps of layout {explanation.layout} with shape {explanation.shape}.\"\n", ")\n", "\n", "# Save saliency maps stored in `explanation` object\n", @@ -828,19 +828,19 @@ ], "source": [ "explainer = RISEExplainer(model, num_cells=4, num_masks=1000)\n", - "explanation = explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = explainer.explain(image, visualization_parameters=visualization_parameters)\n", "explanation.save(\"saliency_map/black_box/4_cells_1000_masks\", image_name)\n", "\n", "explainer = RISEExplainer(model, num_cells=8, num_masks=5000)\n", - "explanation = explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = explainer.explain(image, visualization_parameters=visualization_parameters)\n", "explanation.save(\"saliency_map/black_box/8_cells_5000_masks\", image_name)\n", "\n", "explainer = RISEExplainer(model, num_cells=16, num_masks=10000)\n", - "explanation = explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = explainer.explain(image, visualization_parameters=visualization_parameters)\n", "explanation.save(\"saliency_map/black_box/16_cells_10000_masks\", image_name)\n", "\n", "explainer = RISEExplainer(model, num_cells=24, num_masks=15000)\n", - "explanation = explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = explainer.explain(image, visualization_parameters=visualization_parameters)\n", "explanation.save(\"saliency_map/black_box/24_cells_15000_masks\", image_name)" ] }, @@ -1083,12 +1083,12 @@ "\n", "# Explainer initialization and explanation\n", "auto_explainer = ClassificationAutoExplainer(model)\n", - "post_processing_parameters = PostProcessParameters(overlay=True)\n", + "visualization_parameters = VisualizationParameters(overlay=True)\n", "\n", "# This code returns gray-scale unprocessed saliency map\n", - "explanation = auto_explainer.explain(image, post_processing_parameters=post_processing_parameters)\n", + "explanation = auto_explainer.explain(image, visualization_parameters=visualization_parameters)\n", "logger.info(f\"Auto example: generated {len(explanation.map)} classification \"\n", - " f\"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}.\")\n", + " f\"saliency maps of layout {explanation.layout} with shape {explanation.shape}.\")\n", "\n", "# Save saliency maps stored in `explanation` object\n", "output_dir = \"saliency_map/timm_model\"\n", diff --git a/README.md b/README.md index 2669cfac..daf597fc 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ To explain [OpenVINO™](https://github.com/openvinotoolkit/openvino) Intermedia preprocessing function (and sometimes postprocessing). ```python -explainer = Explainer( +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) explanation = explainer(data, explanation_parameters) @@ -66,9 +66,8 @@ import cv2 import numpy as np import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType -from openvino_xai.explanation.explainer import Explainer -from openvino_xai.explanation.explanation_parameters import ExplanationParameters +import openvino_xai as xai +from openvino_xai.explainer.explanation_parameters import ExplanationParameters def preprocess_fn(x: np.ndarray) -> np.ndarray: @@ -82,9 +81,9 @@ def preprocess_fn(x: np.ndarray) -> np.ndarray: model = ov.Core().read_model("path/to/model.xml") # type: ov.Model # Explainer object will prepare and load the model once in the beginning -explainer = Explainer( +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) @@ -95,7 +94,7 @@ explanation_parameters = ExplanationParameters( ) explanation = explainer(image, explanation_parameters) -explanation: ExplanationResult +explanation: Explanation explanation.saliency_map: Dict[int: np.ndarray] # key - class id, value - processed saliency map e.g. 354x500x3 # Saving saliency maps diff --git a/docs/Usage.md b/docs/Usage.md index 535aece9..061ec3f6 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -21,9 +21,11 @@ Content: ## Explainer - interface to XAI algorithms ```python -explainer = Explainer( +import openvino_xai as xai + +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) explanation = explainer(data, explanation_parameters) @@ -43,9 +45,8 @@ import cv2 import numpy as np import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType -from openvino_xai.explanation.explainer import Explainer -from openvino_xai.explanation.explanation_parameters import ExplanationParameters +import openvino_xai as xai +from openvino_xai.explainer.explanation_parameters import ExplanationParameters def preprocess_fn(x: np.ndarray) -> np.ndarray: @@ -59,9 +60,9 @@ def preprocess_fn(x: np.ndarray) -> np.ndarray: model = ov.Core().read_model("path/to/model.xml") # type: ov.Model # Explainer object will prepare and load the model once in the beginning -explainer = Explainer( +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) @@ -90,11 +91,9 @@ import cv2 import numpy as np import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType, XAIMethodType -from openvino_xai.explanation.explainer import Explainer -from openvino_xai.explanation.explanation_parameters import ExplainMode, ExplanationParameters, TargetExplainGroup, - PostProcessParameters -from openvino_xai.insertion.insertion_parameters import ClassificationInsertionParameters +import openvino_xai as xai +from openvino_xai.explainer.parameters import ExplainMode, ExplanationParameters, TargetExplainGroup, VisualizationParameters +from openvino_xai.inserter.parameters import ClassificationInsertionParameters def preprocess_fn(x: np.ndarray) -> np.ndarray: @@ -111,13 +110,13 @@ model = ov.Core().read_model("path/to/model.xml") # type: ov.Model insertion_parameters = ClassificationInsertionParameters( # target_layer="last_conv_node_name", # target_layer - node after which XAI branch will be inserted embed_normalization=True, # True by default. If set to True, saliency map normalization is embedded in the model - explain_method_type=XAIMethodType.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs + explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs ) # Explainer object will prepare and load the model once in the beginning -explainer = Explainer( +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, @@ -131,7 +130,7 @@ explanation_parameters = ExplanationParameters( target_explain_group=TargetExplainGroup.CUSTOM, target_explain_labels=[11, 14], # target classes to explain, also ['dog', 'person'] is a valid input label_names=voc_labels, - post_processing_parameters=PostProcessParameters(overlay=True), # by default, saliency map overlay over image + visualization_parameters=VisualizationParameters(overlay=True), # by default, saliency map overlay over image ) explanation = explainer(image, explanation_parameters) @@ -154,9 +153,8 @@ import cv2 import numpy as np import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType -from openvino_xai.explanation.explainer import Explainer -from openvino_xai.explanation.explanation_parameters import ExplainMode, ExplanationParameters +import openvino_xai as xai +from openvino_xai.explainer.explanation_parameters import ExplainMode, ExplanationParameters def preprocess_fn(x: np.ndarray) -> np.ndarray: @@ -176,9 +174,9 @@ def postprocess_fn(x: ov.utils.data_helpers.wrappers.OVDict): model = ov.Core().read_model("path/to/model.xml") # type: ov.Model # Explainer object will prepare and load the model once in the beginning -explainer = Explainer( +explainer = xai.Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, postprocess_fn=postprocess_fn, explain_mode=ExplainMode.BLACKBOX, @@ -212,9 +210,9 @@ Note: original model outputs are not affected and the model should be inferable ```python import openvino.runtime as ov -import openvino_xai as ovxai -from openvino_xai.common.parameters import TaskType, XAIMethodType -from openvino_xai.insertion.insertion_parameters import ClassificationInsertionParameters + +import openvino_xai as xai +from openvino_xai.inserter.parameters import ClassificationInsertionParameters # Creating model @@ -224,13 +222,13 @@ model = ov.Core().read_model("path/to/model.xml") # type: ov.Model insertion_parameters = ClassificationInsertionParameters( # target_layer="last_conv_node_name", # target_layer - node after which XAI branch will be inserted embed_normalization=True, # True by default. If set to True, saliency map normalization is embedded in the model - explain_method_type=XAIMethodType.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs + explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs ) # Inserting XAI branch into the model graph -model_xai = ovxai.insert_xai( +model_xai = xai.insert_xai( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, insertion_parameters=insertion_parameters, ) # type: ov.Model diff --git a/docs/api/source/api.rst b/docs/api/source/api.rst index d0a23644..e59d5c87 100644 --- a/docs/api/source/api.rst +++ b/docs/api/source/api.rst @@ -22,9 +22,9 @@ To explain the model (getting saliency maps), use openvino_xai.explanation Algorithms ---------- -To access/modify implemented XAI methods, use openvino_xai.algorithms +To access/modify implemented XAI methods, use openvino_xai.methods -.. automodule:: openvino_xai.algorithms +.. automodule:: openvino_xai.methods :members: Insertion diff --git a/examples/run_classification.py b/examples/run_classification.py index ee18a9b1..689b554e 100644 --- a/examples/run_classification.py +++ b/examples/run_classification.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import argparse @@ -10,18 +10,14 @@ import numpy as np import openvino.runtime as ov -import openvino_xai as ovxai -from openvino_xai.common.parameters import TaskType, XAIMethodType +import openvino_xai as xai from openvino_xai.common.utils import logger -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, TargetExplainGroup, ) -from openvino_xai.insertion.insertion_parameters import ( - ClassificationInsertionParameters, -) +from openvino_xai.inserter.parameters import ClassificationInsertionParameters def get_argument_parser(): @@ -56,9 +52,9 @@ def explain_auto(args): model = ov.Core().read_model(args.model_path) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) @@ -74,7 +70,7 @@ def explain_auto(args): logger.info( f"explain_auto: Generated {len(explanation.saliency_map)} classification " - f"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}." + f"saliency maps of layout {explanation.layout} with shape {explanation.shape}." ) # Save saliency maps for visual inspection @@ -99,13 +95,13 @@ def explain_white_box(args): target_layer="/backbone/conv/conv.2/Div", # OTX mnet_v3 # target_layer="/backbone/features/final_block/activate/Mul", # OTX effnet embed_normalization=True, # True by default. If set to True, saliency map normalization is embedded in the model - explain_method_type=XAIMethodType.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs + explain_method=xai.Method.RECIPROCAM, # ReciproCAM is the default XAI method for CNNs ) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, # defaults to AUTO insertion_parameters=insertion_parameters, @@ -126,7 +122,7 @@ def explain_white_box(args): logger.info( f"explain_white_box: Generated {len(explanation.saliency_map)} classification " - f"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}." + f"saliency maps of layout {explanation.layout} with shape {explanation.shape}." ) # Save saliency maps for visual inspection @@ -146,9 +142,9 @@ def explain_black_box(args): model = ov.Core().read_model(args.model_path) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, postprocess_fn=postprocess_fn, explain_mode=ExplainMode.BLACKBOX, # defaults to AUTO @@ -173,7 +169,7 @@ def explain_black_box(args): logger.info( f"explain_black_box: Generated {len(explanation.saliency_map)} classification " - f"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}." + f"saliency maps of layout {explanation.layout} with shape {explanation.shape}." ) # Save saliency maps for visual inspection @@ -192,9 +188,9 @@ def explain_white_box_multiple_images(args): model = ov.Core().read_model(args.model_path) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, ) @@ -221,7 +217,7 @@ def explain_white_box_multiple_images(args): logger.info( f"explain_white_box_multiple_images: Generated {len(explanation)} explanations " - f"of layout {explanation[0].layout} with shape {explanation[0].sal_map_shape}." + f"of layout {explanation[0].layout} with shape {explanation[0].shape}." ) # Save saliency maps for visual inspection @@ -241,13 +237,13 @@ def explain_white_box_vit(args): insertion_parameters = ClassificationInsertionParameters( # target_layer="/layers.10/ffn/Add", # OTX deit-tiny # target_layer="/blocks/blocks.10/Add_1", # timm vit_base_patch8_224.augreg_in21k_ft_in1k - explain_method_type=XAIMethodType.VITRECIPROCAM, + explain_method=xai.Method.VITRECIPROCAM, ) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, # defaults to AUTO insertion_parameters=insertion_parameters, @@ -265,7 +261,7 @@ def explain_white_box_vit(args): logger.info( f"explain_white_box_vit: Generated {len(explanation.saliency_map)} classification " - f"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}." + f"saliency maps of layout {explanation.layout} with shape {explanation.shape}." ) # Save saliency maps for visual inspection @@ -284,9 +280,9 @@ def insert_xai(args): model = ov.Core().read_model(args.model_path) # insert XAI branch - model_xai = ovxai.insert_xai( + model_xai = xai.insert_xai( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, ) logger.info("insert_xai: XAI branch inserted into IR.") @@ -311,13 +307,13 @@ def insert_xai_w_params(args): target_layer="/backbone/conv/conv.2/Div", # OTX mnet_v3 # target_layer="/backbone/features/final_block/activate/Mul", # OTX effnet embed_normalization=True, - explain_method_type=XAIMethodType.RECIPROCAM, + explain_method=xai.Method.RECIPROCAM, ) # insert XAI branch - model_xai = ovxai.insert_xai( + model_xai = xai.insert_xai( model, - task_type=TaskType.CLASSIFICATION, + task=xai.Task.CLASSIFICATION, insertion_parameters=insertion_parameters, ) diff --git a/examples/run_detection.py b/examples/run_detection.py index ee1751f9..15ecf1ad 100644 --- a/examples/run_detection.py +++ b/examples/run_detection.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import argparse @@ -9,15 +9,14 @@ import numpy as np import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType, XAIMethodType +import openvino_xai as xai from openvino_xai.common.utils import logger -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, TargetExplainGroup, ) -from openvino_xai.insertion.insertion_parameters import DetectionInsertionParameters +from openvino_xai.inserter.parameters import DetectionInsertionParameters def get_argument_parser(): @@ -67,13 +66,13 @@ def main(argv): target_layer=cls_head_output_node_names, # num_anchors=[1, 1, 1, 1, 1], saliency_map_size=(23, 23), # Optional - explain_method_type=XAIMethodType.DETCLASSPROBABILITYMAP, # Optional + explain_method=xai.Method.DETCLASSPROBABILITYMAP, # Optional ) # Create explainer object - explainer = Explainer( + explainer = xai.Explainer( model=model, - task_type=TaskType.DETECTION, + task=xai.Task.DETECTION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, # defaults to AUTO insertion_parameters=insertion_parameters, @@ -91,7 +90,7 @@ def main(argv): logger.info( f"Generated {len(explanation.saliency_map)} detection " - f"saliency maps of layout {explanation.layout} with shape {explanation.sal_map_shape}." + f"saliency maps of layout {explanation.layout} with shape {explanation.shape}." ) # Save saliency maps for visual inspection diff --git a/notebooks/xai_classification/xai_classification.ipynb b/notebooks/xai_classification/xai_classification.ipynb index 94228dab..ccc8efec 100644 --- a/notebooks/xai_classification/xai_classification.ipynb +++ b/notebooks/xai_classification/xai_classification.ipynb @@ -73,16 +73,15 @@ "import numpy as np\n", "import openvino.runtime as ov\n", "\n", - "import openvino_xai as ovxai\n", - "from openvino_xai.common.parameters import TaskType, XAIMethodType\n", + "import openvino_xai as xai\n", + "from openvino_xai.common.parameters import Task, Method\n", "from openvino_xai.common.utils import retrieve_otx_model\n", - "from openvino_xai.explanation.explanation_parameters import (\n", - " ExplainMode, ExplanationParameters, PostProcessParameters,\n", + "from openvino_xai.explainer.parameters import (\n", + " ExplainMode, ExplanationParameters, VisualizationParameters,\n", " TargetExplainGroup)\n", - "from openvino_xai.explanation.explanation_result import ExplanationResult\n", - "from openvino_xai.explanation.utils import InferenceResult\n", - "from openvino_xai.insertion.insertion_parameters import \\\n", - " ClassificationInsertionParameters" + "from openvino_xai.explainer.explanation import Explanation\n", + "from openvino_xai.explainer.utils import InferenceResult\n", + "from openvino_xai.inserter.parameters import ClassificationInsertionParameters" ] }, { @@ -267,9 +266,9 @@ "source": [ "# Insert XAI branch\n", "model_xai: ov.Model\n", - "model_xai = ovxai.insert_xai(\n", + "model_xai = xai.insert_xai(\n", " model,\n", - " task_type=TaskType.CLASSIFICATION,\n", + " task=xai.Task.CLASSIFICATION,\n", ")" ] }, @@ -314,10 +313,10 @@ "\n", "`embed_normalization` **default True** (for speed purposes), this parameter adds normalization to the XAI branch, which results in being able to visualize saliency maps right away without further postprocessing.\n", "\n", - "`explain_method_type` can be:\n", + "`explain_method` can be:\n", "\n", - "- `XAIMethodType.RECIPROCAM` - to receive per-class saliency maps\n", - "- `XAIMethodType.ACTIVATIONMAP` - to receive one saliency map for all classes that shows zones of model's attention" + "- `Method.RECIPROCAM` - to receive per-class saliency maps\n", + "- `Method.ACTIVATIONMAP` - to receive one saliency map for all classes that shows zones of model's attention" ] }, { @@ -342,7 +341,7 @@ " # target_layer=\"/backbone/conv/conv.2/Div\", # OTX mnet_v3\n", " # target_layer=\"/backbone/features/final_block/activate/Mul\", # OTX effnet\n", " embed_normalization=True,\n", - " explain_method_type=XAIMethodType.RECIPROCAM,\n", + " explain_method=Method.RECIPROCAM,\n", ")\n", "\n", "# Create original ov.Model\n", @@ -350,9 +349,9 @@ "\n", "# insert XAI branch\n", "model_xai: ov.Model\n", - "model_xai = ovxai.insert_xai(\n", + "model_xai = xai.insert_xai(\n", " model,\n", - " task_type=TaskType.CLASSIFICATION,\n", + " task=xai.Task.CLASSIFICATION,\n", " insertion_parameters=insertion_parameters,\n", ")" ] @@ -381,9 +380,9 @@ "metadata": {}, "source": [ "```python\n", - "model_inferrer = ovxai.explanation.model_inferrer.ClassificationModelInferrer(model_xai)\n", + "model_inferrer = xai.explanation.model_inferrer.ClassificationModelInferrer(model_xai)\n", "# Usage:\n", - "inference_result = model_inferrer(image) # inference_result: ovxai.explanation.utils.InferenceResult\n", + "inference_result = model_inferrer(image) # inference_result: xai.explanation.utils.InferenceResult\n", "```" ] }, @@ -585,7 +584,7 @@ "For more convenient saliency map processing, you can use OpenVINO XAI API:\n", "- To return saliency maps only for predicted classes using `TargetExplainGroup.PREDICTIONS`\n", "- To return predictions and its confidence with `explanation.prediction`\n", - "- To apply built-in postprocessing with overlay/resize using `PostProcessParameters`\n", + "- To apply built-in postprocessing with overlay/resize using `VisualizationParameters`\n", "- To choose mode for explaining (whitemode or blackmode) using `ExplainMode`" ] }, @@ -627,7 +626,7 @@ " # by default, explains only predicted classes\n", " target_explain_group=TargetExplainGroup.PREDICTIONS,\n", " # by default, saliency map overlays over image\n", - " post_processing_parameters=PostProcessParameters(overlay=True),\n", + " visualization_parameters=VisualizationParameters(overlay=True),\n", ")" ] }, @@ -647,18 +646,18 @@ ], "source": [ "# Generate processed saliency map via .explain(model_inferrer, image) call\n", - "explanation = ovxai.explain(\n", + "explanation = xai.explain(\n", " model_inferrer=model_inferrer,\n", " data=cv2.imread(image_path),\n", " explanation_parameters=explanation_parameters,\n", ")\n", - "explanation: ExplanationResult\n", + "explanation: Explanation\n", "explanation.saliency_map # Dict[int: np.ndarray] where key - class id, value - processed saliency map e.g. 354x500x3\n", "\n", "print(\n", " f\"Saliency maps were generated for the following classes: {list(explanation.saliency_map.keys())}\"\n", ")\n", - "print(f\"Saliency map size: {explanation.sal_map_shape}\")" + "print(f\"Saliency map size: {explanation.shape}\")" ] }, { @@ -714,7 +713,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "In case of explanation `explain_method_type==XAIMethodType.ACTIVATIONMAP`, instead of saliency map for each class the activation map is returned as `explanation.saliency_map[\"per_image_map\"]`." + "In case of explanation `explain_method==Method.ACTIVATIONMAP`, instead of saliency map for each class the activation map is returned as `explanation.saliency_map[\"per_image_map\"]`." ] }, { @@ -782,18 +781,18 @@ "# The rest of explanation process for is the same as for WhiteBox\n", "\n", "# Generate processed saliency map via .explain(model_inferrer, image) call\n", - "explanation = ovxai.explain(\n", + "explanation = xai.explain(\n", " model_inferrer=model_inferrer,\n", " data=cv2.imread(image_path),\n", " explanation_parameters=explanation_parameters,\n", ")\n", - "explanation: ExplanationResult\n", + "explanation: Explanation\n", "explanation.saliency_map # Dict[int: np.ndarray] where key - class id, value - processed saliency map e.g. 354x500x3\n", "\n", "print(\n", " f\"Saliency maps were generated for the following classes: {list(explanation.saliency_map.keys())}\"\n", ")\n", - "print(f\"Saliency map size: {explanation.sal_map_shape}\")" + "print(f\"Saliency map size: {explanation.shape}\")" ] }, { @@ -896,14 +895,14 @@ "source": [ "# Create explanation_parameters\n", "explanation_parameters = ExplanationParameters(\n", - " post_processing_parameters=PostProcessParameters(overlay=True),\n", + " visualization_parameters=VisualizationParameters(overlay=True),\n", ")\n", "\n", "# Run explanation on images in \"image_folder_path\"\n", "for image_path in img_files:\n", " image = cv2.imread(image_path)\n", " try:\n", - " explanation = ovxai.explain(\n", + " explanation = xai.explain(\n", " model_inferrer=model_inferrer,\n", " data=image,\n", " explanation_parameters=explanation_parameters,\n", @@ -1016,7 +1015,7 @@ "source": [ "# Create explanation_parameters, adding list with ImageNet labels\n", "explanation_parameters = ExplanationParameters(\n", - " post_processing_parameters=PostProcessParameters(overlay=True),\n", + " visualization_parameters=VisualizationParameters(overlay=True),\n", " explain_target_names=imagenet_labels,\n", ")\n", "\n", diff --git a/notebooks/xai_saliency_map_interpretation/xai_saliency_map_interpretation.ipynb b/notebooks/xai_saliency_map_interpretation/xai_saliency_map_interpretation.ipynb index 6ccf005e..0e335815 100644 --- a/notebooks/xai_saliency_map_interpretation/xai_saliency_map_interpretation.ipynb +++ b/notebooks/xai_saliency_map_interpretation/xai_saliency_map_interpretation.ipynb @@ -74,13 +74,12 @@ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import openvino.runtime as ov\n", + "import openvino_xai as xai\n", "\n", - "import openvino_xai as ovxai\n", - "from openvino_xai.common.parameters import TaskType\n", "from openvino_xai.common.utils import retrieve_otx_model\n", - "from openvino_xai.explanation.explanation_parameters import (\n", - " ExplanationParameters, PostProcessParameters)\n", - "from openvino_xai.explanation.model_inferrer import ActivationType" + "from openvino_xai.explainer.parameters import (\n", + " ExplanationParameters, VisualizationParameters)\n", + "from openvino_xai.explainer.utils import ActivationType" ] }, { @@ -223,9 +222,9 @@ "\n", "# Insert XAI branch\n", "model_xai: ov.Model\n", - "model_xai = ovxai.insert_xai(\n", + "model_xai = xai.insert_xai(\n", " model,\n", - " task_type=TaskType.CLASSIFICATION,\n", + " task=xai.Task.CLASSIFICATION,\n", ")" ] }, @@ -246,7 +245,7 @@ "metadata": {}, "outputs": [], "source": [ - "model_inferrer = ovxai.explanation.model_inferrer.ClassificationModelInferrer(\n", + "model_inferrer = xai.explanation.model_inferrer.ClassificationModelInferrer(\n", " model_xai,\n", " # Preprocessing for timm models\n", " mean=np.array([123.675, 116.28, 103.53]),\n", @@ -321,7 +320,7 @@ "source": [ "# Create explanation_parameters, adding list with ImageNet labels\n", "explanation_parameters = ExplanationParameters(\n", - " post_processing_parameters=PostProcessParameters(overlay=True),\n", + " visualization_parameters=VisualizationParameters(overlay=True),\n", " explain_target_names=imagenet_labels,\n", ")\n", "\n", @@ -329,7 +328,7 @@ "for image_path in img_files:\n", " image = cv2.imread(image_path)\n", " try:\n", - " explanation = ovxai.explain(\n", + " explanation = xai.explain(\n", " model_inferrer=model_inferrer,\n", " data=image,\n", " explanation_parameters=explanation_parameters,\n", @@ -456,7 +455,7 @@ " usecase_conf_thr = usecases_image_paths[usecase][\"confidence\"]\n", "\n", " explanation_parameters = ExplanationParameters(\n", - " post_processing_parameters=PostProcessParameters(overlay=True),\n", + " visualization_parameters=VisualizationParameters(overlay=True),\n", " confidence_threshold=usecase_conf_thr,\n", " explain_target_names=imagenet_labels,\n", " )\n", @@ -467,7 +466,7 @@ " folder_name = image_name.split(\"_\")[0]\n", " gt_class = label_mapping[folder_name]\n", " try:\n", - " explanation = ovxai.explain(\n", + " explanation = xai.explain(\n", " model_inferrer=model_inferrer,\n", " data=image,\n", " explanation_parameters=explanation_parameters,\n", diff --git a/openvino_xai/__init__.py b/openvino_xai/__init__.py index d4b949d3..7664848e 100644 --- a/openvino_xai/__init__.py +++ b/openvino_xai/__init__.py @@ -1,12 +1,12 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 """ OpenVINO-XAI library for explaining OpenVINO™ IR models. """ -from .insertion import insert_xai +from .common.parameters import Method, Task +from .explainer.explainer import Explainer +from .inserter import insert_xai -__all__ = [ - "insert_xai", -] +__all__ = ["Explainer", "insert_xai", "Method", "Task"] diff --git a/openvino_xai/common/__init__.py b/openvino_xai/common/__init__.py index a9f69f4e..f3012c1c 100644 --- a/openvino_xai/common/__init__.py +++ b/openvino_xai/common/__init__.py @@ -1,14 +1,14 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 """ Common parameters and utils. """ -from openvino_xai.common.parameters import TaskType, XAIMethodType +from openvino_xai.common.parameters import Method, Task from openvino_xai.common.utils import has_xai, scale __all__ = [ - "TaskType", - "XAIMethodType", + "Task", + "Method", "has_xai", "scale", ] diff --git a/openvino_xai/common/parameters.py b/openvino_xai/common/parameters.py index d439f68e..a0180550 100644 --- a/openvino_xai/common/parameters.py +++ b/openvino_xai/common/parameters.py @@ -1,10 +1,10 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from enum import Enum -class TaskType(Enum): +class Task(Enum): """ Enum representing the different task types: @@ -17,7 +17,7 @@ class TaskType(Enum): DETECTION = "detection" -class XAIMethodType(Enum): +class Method(Enum): """ Enum representing the different XAI methods: @@ -37,18 +37,18 @@ class XAIMethodType(Enum): WhiteBoxXAIMethods = { - XAIMethodType.ACTIVATIONMAP, - XAIMethodType.RECIPROCAM, - XAIMethodType.DETCLASSPROBABILITYMAP, + Method.ACTIVATIONMAP, + Method.RECIPROCAM, + Method.DETCLASSPROBABILITYMAP, } BlackBoxXAIMethods = { - XAIMethodType.RISE, + Method.RISE, } ClassificationXAIMethods = { - XAIMethodType.ACTIVATIONMAP, - XAIMethodType.RECIPROCAM, - XAIMethodType.RISE, + Method.ACTIVATIONMAP, + Method.RECIPROCAM, + Method.RISE, } DetectionXAIMethods = { - XAIMethodType.DETCLASSPROBABILITYMAP, + Method.DETCLASSPROBABILITYMAP, } diff --git a/openvino_xai/common/utils.py b/openvino_xai/common/utils.py index c6d5c50d..4f2bd8b5 100644 --- a/openvino_xai/common/utils.py +++ b/openvino_xai/common/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 """ Common functionality. diff --git a/openvino_xai/explainer/__init__.py b/openvino_xai/explainer/__init__.py new file mode 100644 index 00000000..3f5fba71 --- /dev/null +++ b/openvino_xai/explainer/__init__.py @@ -0,0 +1,28 @@ +# Copyright (C) 2023-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +""" +Interface for getting explanation. +""" +from openvino_xai.explainer.explainer import Explainer +from openvino_xai.explainer.explanation import Explanation, Layout +from openvino_xai.explainer.parameters import ( + ExplainMode, + ExplanationParameters, + TargetExplainGroup, + VisualizationParameters, +) +from openvino_xai.explainer.visualizer import Visualizer, colormap, overlay, resize + +__all__ = [ + "Explainer", + "ExplainMode", + "TargetExplainGroup", + "VisualizationParameters", + "ExplanationParameters", + "Layout", + "Explanation", + "Visualizer", + "resize", + "colormap", + "overlay", +] diff --git a/openvino_xai/explanation/explain.py b/openvino_xai/explainer/explainer.py similarity index 81% rename from openvino_xai/explanation/explain.py rename to openvino_xai/explainer/explainer.py index 18f60797..cf075408 100644 --- a/openvino_xai/explanation/explain.py +++ b/openvino_xai/explainer/explainer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from typing import Callable @@ -7,23 +7,23 @@ import openvino.runtime as ov import openvino_xai -from openvino_xai.algorithms.black_box.black_box_methods import RISE -from openvino_xai.common.parameters import TaskType +from openvino_xai import Task from openvino_xai.common.utils import ( SALIENCY_MAP_OUTPUT_NAME, IdentityPreprocessFN, has_xai, logger, ) -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.explanation import Explanation +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, TargetExplainGroup, ) -from openvino_xai.explanation.explanation_result import ExplanationResult -from openvino_xai.explanation.post_process import PostProcessor -from openvino_xai.explanation.utils import get_explain_target_indices -from openvino_xai.insertion.insertion_parameters import InsertionParameters +from openvino_xai.explainer.utils import get_explain_target_indices +from openvino_xai.explainer.visualizer import Visualizer +from openvino_xai.inserter.parameters import InsertionParameters +from openvino_xai.methods.black_box.black_box_methods import RISE class Explainer: @@ -35,8 +35,8 @@ class Explainer: :param model: Original model. :type model: ov.Model - :param task_type: Type of the task. - :type task_type: TaskType + :param task: Type of the task: CLASSIFICATION or DETECTION. + :type task: Task :param preprocess_fn: Preprocessing function, identity function by default (assume input images are already preprocessed by user). :type preprocess_fn: Callable[[np.ndarray], np.ndarray] | IdentityPreprocessFN @@ -51,7 +51,7 @@ class Explainer: def __init__( self, model: ov.Model, - task_type: TaskType, + task: Task, preprocess_fn: Callable[[np.ndarray], np.ndarray] = IdentityPreprocessFN(), postprocess_fn: Callable[[ov.utils.data_helpers.wrappers.OVDict], np.ndarray] = None, explain_mode: ExplainMode = ExplainMode.AUTO, @@ -59,7 +59,7 @@ def __init__( ) -> None: self.model = model self.compiled_model: ov.ie_api.CompiledModel | None = None - self.task_type = task_type + self.task = task if isinstance(preprocess_fn, IdentityPreprocessFN): logger.info( @@ -111,7 +111,7 @@ def _set_explain_mode(self) -> None: def _insert_xai(self) -> None: logger.info("Model does not have XAI - trying to insert XAI to use white-box mode.") # Do we need to keep the original model? - self.model = openvino_xai.insert_xai(self.model, self.task_type, self.insertion_parameters) + self.model = openvino_xai.insert_xai(self.model, self.task, self.insertion_parameters) def _load_model(self) -> None: self.compiled_model = ov.Core().compile_model(self.model, "CPU") @@ -121,7 +121,7 @@ def __call__( data: np.ndarray, explanation_parameters: ExplanationParameters, **kwargs, - ) -> ExplanationResult: + ) -> Explanation: """Explainer call that generates processed explanation result.""" # TODO (negvet): support output_shape as argument among other post process parameters if self.explain_mode == ExplainMode.WHITEBOX: @@ -129,13 +129,13 @@ def __call__( else: saliency_map = self._generate_saliency_map_black_box(data, explanation_parameters, **kwargs) - explanation_result = ExplanationResult( + explanation = Explanation( saliency_map=saliency_map, target_explain_group=explanation_parameters.target_explain_group, target_explain_labels=explanation_parameters.target_explain_labels, label_names=explanation_parameters.label_names, ) - return self._post_process(explanation_result, data, explanation_parameters) + return self._visualize(explanation, data, explanation_parameters) def model_forward(self, x: np.ndarray) -> ov.utils.data_helpers.wrappers.OVDict: """Forward pass of the compiled model. Applies preprocess_fn.""" @@ -158,7 +158,7 @@ def _generate_saliency_map_black_box( explanation_parameters.target_explain_labels, explanation_parameters.label_names, ) - if self.task_type == TaskType.CLASSIFICATION: + if self.task == Task.CLASSIFICATION: saliency_map = RISE.run( self.compiled_model, self.preprocess_fn, @@ -168,21 +168,23 @@ def _generate_saliency_map_black_box( **kwargs, ) return saliency_map - raise ValueError(f"Task type {self.task_type} is not supported in the black-box mode.") + raise ValueError(f"Task type {self.task} is not supported in the black-box mode.") - def _post_process(self, explanation_result, data, explanation_parameters): + def _visualize( + self, explanation: Explanation, data: np.ndarray, explanation_parameters: ExplanationParameters + ) -> Explanation: if not isinstance(self.preprocess_fn, IdentityPreprocessFN): # Assume if preprocess_fn is provided - input data is original image - explanation_result = PostProcessor( - explanation=explanation_result, + explanation = Visualizer( + explanation=explanation, original_input_image=data, - post_processing_parameters=explanation_parameters.post_processing_parameters, + visualization_parameters=explanation_parameters.visualization_parameters, ).run() else: # preprocess_fn is not provided - assume input data is processed - explanation_result = PostProcessor( - explanation=explanation_result, + explanation = Visualizer( + explanation=explanation, output_size=data.shape[:2], # resize to model input by default - post_processing_parameters=explanation_parameters.post_processing_parameters, + visualization_parameters=explanation_parameters.visualization_parameters, ).run() - return explanation_result + return explanation diff --git a/openvino_xai/explanation/explanation_result.py b/openvino_xai/explainer/explanation.py similarity index 78% rename from openvino_xai/explanation/explanation_result.py rename to openvino_xai/explainer/explanation.py index 39466cc5..e6d25c13 100644 --- a/openvino_xai/explanation/explanation_result.py +++ b/openvino_xai/explainer/explanation.py @@ -1,7 +1,8 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import os +from enum import Enum from pathlib import Path from typing import Dict, List @@ -9,16 +10,13 @@ import numpy as np from openvino_xai.common.utils import logger -from openvino_xai.explanation.explanation_parameters import ( - SaliencyMapLayout, - TargetExplainGroup, -) -from openvino_xai.explanation.utils import get_explain_target_indices +from openvino_xai.explainer.parameters import TargetExplainGroup +from openvino_xai.explainer.utils import get_explain_target_indices -class ExplanationResult: +class Explanation: """ - ExplanationResult selects target saliency maps, holds it and its layout. + Explanation selects target saliency maps, holds it and its layout. :param saliency_map: Raw saliency map. :param target_explain_group: Defines targets to explain: all, only predictions, custom list, per-image. @@ -41,7 +39,7 @@ def __init__( self._saliency_map = self._format_sal_map_as_dict(saliency_map) if "per_image_map" in self._saliency_map: - self.layout = SaliencyMapLayout.ONE_MAP_PER_IMAGE_GRAY + self.layout = Layout.ONE_MAP_PER_IMAGE_GRAY if target_explain_group != TargetExplainGroup.IMAGE: logger.warning( f"Setting target_explain_group to TargetExplainGroup.IMAGE, {target_explain_group} " @@ -54,7 +52,7 @@ def __init__( "TargetExplainGroup.IMAGE supports only single (global) saliency map per image. " "But multiple saliency maps are available." ) - self.layout = SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_GRAY + self.layout = Layout.MULTIPLE_MAPS_PER_IMAGE_GRAY self.target_explain_group = target_explain_group if self.target_explain_group == TargetExplainGroup.CUSTOM: @@ -72,10 +70,10 @@ def saliency_map(self, saliency_map: Dict[int | str, np.ndarray]): self._saliency_map = saliency_map @property - def sal_map_shape(self): + def shape(self): idx = next(iter(self._saliency_map)) - sal_map_shape = self._saliency_map[idx].shape - return sal_map_shape + shape = self._saliency_map[idx].shape + return shape @staticmethod def _check_saliency_map(saliency_map: np.ndarray): @@ -111,7 +109,7 @@ def _select_target_saliency_maps( target_explain_labels: List[int | str] | None = None, label_names: List[str] | None = None, ) -> Dict[int | str, np.ndarray]: - assert self.layout == SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_GRAY + assert self.layout == Layout.MULTIPLE_MAPS_PER_IMAGE_GRAY explain_target_indices = self._select_target_indices( self.target_explain_group, target_explain_labels, @@ -152,3 +150,38 @@ def save(self, dir_path: Path | str, name: str | None = None) -> None: else: target_name = str(cls_idx) cv2.imwrite(os.path.join(dir_path, f"{save_name}_target_{target_name}.jpg"), img=map_to_save) + + +class Layout(Enum): + """ + Enum describes different saliency map layouts. + + Saliency map can have the following layout: + ONE_MAP_PER_IMAGE_GRAY - BHW - one map per image + ONE_MAP_PER_IMAGE_COLOR - BHWC - one map per image, colormapped + MULTIPLE_MAPS_PER_IMAGE_GRAY - BNHW - multiple maps per image + MULTIPLE_MAPS_PER_IMAGE_COLOR - BNHWC - multiple maps per image, colormapped + """ + + ONE_MAP_PER_IMAGE_GRAY = "one_map_per_image_gray" + ONE_MAP_PER_IMAGE_COLOR = "one_map_per_image_color" + MULTIPLE_MAPS_PER_IMAGE_GRAY = "multiple_maps_per_image_gray" + MULTIPLE_MAPS_PER_IMAGE_COLOR = "multiple_maps_per_image_color" + + +GRAY_LAYOUTS = { + Layout.ONE_MAP_PER_IMAGE_GRAY, + Layout.MULTIPLE_MAPS_PER_IMAGE_GRAY, +} +COLOR_MAPPED_LAYOUTS = { + Layout.ONE_MAP_PER_IMAGE_COLOR, + Layout.MULTIPLE_MAPS_PER_IMAGE_COLOR, +} +MULTIPLE_MAP_LAYOUTS = { + Layout.MULTIPLE_MAPS_PER_IMAGE_GRAY, + Layout.MULTIPLE_MAPS_PER_IMAGE_COLOR, +} +ONE_MAP_LAYOUTS = { + Layout.ONE_MAP_PER_IMAGE_GRAY, + Layout.ONE_MAP_PER_IMAGE_COLOR, +} diff --git a/openvino_xai/explanation/explanation_parameters.py b/openvino_xai/explainer/parameters.py similarity index 61% rename from openvino_xai/explanation/explanation_parameters.py rename to openvino_xai/explainer/parameters.py index 854e8141..3688179c 100644 --- a/openvino_xai/explanation/explanation_parameters.py +++ b/openvino_xai/explainer/parameters.py @@ -5,7 +5,7 @@ from enum import Enum from typing import List -from openvino_xai.common.parameters import XAIMethodType +from openvino_xai.common.parameters import Method class ExplainMode(Enum): @@ -39,9 +39,9 @@ class TargetExplainGroup(Enum): @dataclass -class PostProcessParameters: +class VisualizationParameters: """ - PostProcessParameters parametrize postprocessing of saliency maps. + VisualizationParameters parametrize postprocessing of saliency maps. :parameter scale: If True, scale saliency map into [0, 255] range (filling the whole range). By default, scaling is embedded into the IR model. @@ -76,49 +76,14 @@ class ExplanationParameters: :type target_explain_labels: List[int | str] | None :param label_names: List of all label names. :type label_names: List[str] | None - :parameter post_processing_parameters: Post-process parameters. - :type post_processing_parameters: PostProcessParameters + :parameter visualization_parameters: Post-process parameters. + :type visualization_parameters: VisualizationParameters :param black_box_method: Defines black-box method type. - :type black_box_method: XAIMethodType + :type black_box_method: Method """ target_explain_group: TargetExplainGroup = TargetExplainGroup.CUSTOM target_explain_labels: List[int | str] | None = None label_names: List[str] | None = None - post_processing_parameters: PostProcessParameters | None = None - black_box_method: XAIMethodType = XAIMethodType.RISE - - -class SaliencyMapLayout(Enum): - """ - Enum describes different saliency map layouts. - - Saliency map can have the following layout: - ONE_MAP_PER_IMAGE_GRAY - BHW - one map per image - ONE_MAP_PER_IMAGE_COLOR - BHWC - one map per image, colormapped - MULTIPLE_MAPS_PER_IMAGE_GRAY - BNHW - multiple maps per image - MULTIPLE_MAPS_PER_IMAGE_COLOR - BNHWC - multiple maps per image, colormapped - """ - - ONE_MAP_PER_IMAGE_GRAY = "one_map_per_image_gray" - ONE_MAP_PER_IMAGE_COLOR = "one_map_per_image_color" - MULTIPLE_MAPS_PER_IMAGE_GRAY = "multiple_maps_per_image_gray" - MULTIPLE_MAPS_PER_IMAGE_COLOR = "multiple_maps_per_image_color" - - -GRAY_LAYOUTS = { - SaliencyMapLayout.ONE_MAP_PER_IMAGE_GRAY, - SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_GRAY, -} -COLOR_MAPPED_LAYOUTS = { - SaliencyMapLayout.ONE_MAP_PER_IMAGE_COLOR, - SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_COLOR, -} -MULTIPLE_MAP_LAYOUTS = { - SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_GRAY, - SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_COLOR, -} -ONE_MAP_LAYOUTS = { - SaliencyMapLayout.ONE_MAP_PER_IMAGE_GRAY, - SaliencyMapLayout.ONE_MAP_PER_IMAGE_COLOR, -} + visualization_parameters: VisualizationParameters | None = None + black_box_method: Method = Method.RISE diff --git a/openvino_xai/explanation/utils.py b/openvino_xai/explainer/utils.py similarity index 98% rename from openvino_xai/explanation/utils.py rename to openvino_xai/explainer/utils.py index 8ad9d9ae..608d6dac 100644 --- a/openvino_xai/explanation/utils.py +++ b/openvino_xai/explainer/utils.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from enum import Enum from functools import partial diff --git a/openvino_xai/explanation/post_process.py b/openvino_xai/explainer/visualizer.py similarity index 76% rename from openvino_xai/explanation/post_process.py rename to openvino_xai/explainer/visualizer.py index b7d30728..534f553c 100644 --- a/openvino_xai/explanation/post_process.py +++ b/openvino_xai/explainer/visualizer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from typing import Dict, List, Tuple @@ -7,15 +7,15 @@ import numpy as np from openvino_xai.common.utils import scale -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.explanation import ( COLOR_MAPPED_LAYOUTS, GRAY_LAYOUTS, MULTIPLE_MAP_LAYOUTS, ONE_MAP_LAYOUTS, - PostProcessParameters, - SaliencyMapLayout, + Explanation, + Layout, ) -from openvino_xai.explanation.explanation_result import ExplanationResult +from openvino_xai.explainer.parameters import VisualizationParameters def resize(saliency_map: np.ndarray, output_size: Tuple[int, int]) -> np.ndarray: @@ -43,53 +43,53 @@ def overlay(saliency_map: np.ndarray, input_image: np.ndarray, overlay_weight: f return res -class PostProcessor: +class Visualizer: """ - PostProcessor implements post-processing for the explanation result. + Visualizer implements post-processing for the saliency map in explanation result. :param explanation: Explanation result object. - :type explanation: ExplanationResult + :type explanation: Explanation :param original_input_image: Input original_input_image. :type original_input_image: np.ndarray :param output_size: Output size used for resize operation. :type output_size: Tuple[int, int] - :param post_processing_parameters: Parameters that define post-processing. - :type post_processing_parameters: PostProcessParameters + :param visualization_parameters: Parameters that define post-processing for saliency map. + :type visualization_parameters: VisualizationParameters """ def __init__( self, - explanation: ExplanationResult, + explanation: Explanation, original_input_image: np.ndarray = None, output_size: Tuple[int, int] = None, - post_processing_parameters: PostProcessParameters | None = None, + visualization_parameters: VisualizationParameters | None = None, ): self._explanation = explanation self._saliency_map_np: np.ndarray | None = None self._original_input_image = original_input_image self._output_size = output_size - if post_processing_parameters is None: - post_processing_parameters = PostProcessParameters(resize=True, colormap=True) - self._scale = post_processing_parameters.scale - self._resize = post_processing_parameters.resize - self._colormap = post_processing_parameters.colormap - self._overlay = post_processing_parameters.overlay - self._overlay_weight = post_processing_parameters.overlay_weight + if visualization_parameters is None: + visualization_parameters = VisualizationParameters(resize=True, colormap=True) + self._scale = visualization_parameters.scale + self._resize = visualization_parameters.resize + self._colormap = visualization_parameters.colormap + self._overlay = visualization_parameters.overlay + self._overlay_weight = visualization_parameters.overlay_weight @property - def layout(self) -> SaliencyMapLayout: + def layout(self) -> Layout: return self._explanation.layout @layout.setter - def layout(self, layout: SaliencyMapLayout): + def layout(self, layout: Layout): self._explanation.layout = layout - def run(self) -> ExplanationResult: + def run(self) -> Explanation: """ Saliency map postprocess method. - Applies some op ordering logic, depending on PostProcessParameters. - Returns ExplainResult object with processed saliency map, that can have one of SaliencyMapLayout layouts. + Applies some op ordering logic, depending on VisualizationParameters. + Returns ExplainResult object with processed saliency map, that can have one of Layout layouts. """ saliency_map_dict = self._explanation.saliency_map class_idx_to_return = list(saliency_map_dict.keys()) @@ -143,19 +143,17 @@ def _apply_resize(self) -> None: def _apply_colormap(self) -> None: if self._saliency_map_np.dtype != np.uint8: - raise ValueError( - "Colormap requires saliency map to has uint8 dtype. Enable 'scale' flag for PostProcessor." - ) + raise ValueError("Colormap requires saliency map to has uint8 dtype. Enable 'scale' flag for Visualizer.") if self.layout not in GRAY_LAYOUTS: raise ValueError( f"Saliency map to colormap has to be grayscale. The layout must be in {GRAY_LAYOUTS}, " f"but got {self.layout}." ) self._saliency_map_np = colormap(self._saliency_map_np) - if self.layout == SaliencyMapLayout.ONE_MAP_PER_IMAGE_GRAY: - self.layout = SaliencyMapLayout.ONE_MAP_PER_IMAGE_COLOR - if self.layout == SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_GRAY: - self.layout = SaliencyMapLayout.MULTIPLE_MAPS_PER_IMAGE_COLOR + if self.layout == Layout.ONE_MAP_PER_IMAGE_GRAY: + self.layout = Layout.ONE_MAP_PER_IMAGE_COLOR + if self.layout == Layout.MULTIPLE_MAPS_PER_IMAGE_GRAY: + self.layout = Layout.MULTIPLE_MAPS_PER_IMAGE_COLOR def _apply_overlay(self) -> None: assert self.layout in COLOR_MAPPED_LAYOUTS, "Color mapped saliency map are expected for overlay." diff --git a/openvino_xai/explanation/__init__.py b/openvino_xai/explanation/__init__.py deleted file mode 100644 index 0233e483..00000000 --- a/openvino_xai/explanation/__init__.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright (C) 2023 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 -""" -Interface for getting explanation. -""" -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( - ExplainMode, - ExplanationParameters, - PostProcessParameters, - SaliencyMapLayout, - TargetExplainGroup, -) -from openvino_xai.explanation.explanation_result import ExplanationResult -from openvino_xai.explanation.post_process import ( - PostProcessor, - colormap, - overlay, - resize, -) - -__all__ = [ - "Explainer", - "ExplainMode", - "TargetExplainGroup", - "PostProcessParameters", - "ExplanationParameters", - "SaliencyMapLayout", - "ExplanationResult", - "PostProcessor", - "resize", - "colormap", - "overlay", -] diff --git a/openvino_xai/insertion/__init__.py b/openvino_xai/inserter/__init__.py similarity index 66% rename from openvino_xai/insertion/__init__.py rename to openvino_xai/inserter/__init__.py index c601d4e3..109e8a05 100644 --- a/openvino_xai/insertion/__init__.py +++ b/openvino_xai/inserter/__init__.py @@ -1,10 +1,10 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 """ Interface for inserting XAI branch into OV IR. """ -from openvino_xai.insertion.insert_xai_into_model import insert_xai -from openvino_xai.insertion.insertion_parameters import ( +from openvino_xai.inserter.inserter import insert_xai +from openvino_xai.inserter.parameters import ( ClassificationInsertionParameters, DetectionInsertionParameters, InsertionParameters, diff --git a/openvino_xai/insertion/insert_xai_into_model.py b/openvino_xai/inserter/inserter.py similarity index 75% rename from openvino_xai/insertion/insert_xai_into_model.py rename to openvino_xai/inserter/inserter.py index 7d89881b..efbc240e 100644 --- a/openvino_xai/insertion/insert_xai_into_model.py +++ b/openvino_xai/inserter/inserter.py @@ -1,33 +1,33 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import openvino.runtime as ov from openvino.preprocess import PrePostProcessor -from openvino_xai.algorithms.white_box.create_method import ( +from openvino_xai import Task +from openvino_xai.common.utils import SALIENCY_MAP_OUTPUT_NAME, has_xai, logger +from openvino_xai.inserter.parameters import InsertionParameters +from openvino_xai.methods.white_box.create_method import ( create_white_box_classification_explain_method, create_white_box_detection_explain_method, ) -from openvino_xai.common.parameters import TaskType -from openvino_xai.common.utils import SALIENCY_MAP_OUTPUT_NAME, has_xai, logger -from openvino_xai.insertion.insertion_parameters import InsertionParameters def insert_xai( model: ov.Model, - task_type: TaskType, + task: Task, insertion_parameters: InsertionParameters | None = None, ) -> ov.Model: """ Function that inserts XAI branch into IR. Usage: - model_xai = openvino_xai.insert_xai(model, task_type=TaskType.CLASSIFICATION) + model_xai = openvino_xai.insert_xai(model, task=Task.CLASSIFICATION) :param model: Original IR. :type model: ov.Model | str - :param task_type: Type of the task. - :type task_type: TaskType + :param task: Type of the task: CLASSIFICATION or DETECTION. + :type task: Task :param insertion_parameters: Insertion parameters that parametrize white-box method, that will be inserted into the model graph (optional). :type insertion_parameters: InsertionParameters @@ -38,7 +38,7 @@ def insert_xai( logger.info("Provided IR model already contains XAI branch, return it as-is.") return model - model_xai = _insert_xai_branch_into_model(model, task_type, insertion_parameters) + model_xai = _insert_xai_branch_into_model(model, task, insertion_parameters) if not has_xai(model_xai): raise RuntimeError("Insertion of the XAI branch into the model was not successful.") @@ -48,14 +48,14 @@ def insert_xai( def _insert_xai_branch_into_model( - model: ov.Model, task_type: TaskType, insertion_parameters: InsertionParameters | None + model: ov.Model, task: Task, insertion_parameters: InsertionParameters | None ) -> ov.Model: - if task_type == TaskType.CLASSIFICATION: + if task == Task.CLASSIFICATION: explain_method = create_white_box_classification_explain_method(model, insertion_parameters) # type: ignore - elif task_type == TaskType.DETECTION: + elif task == Task.DETECTION: explain_method = create_white_box_detection_explain_method(model, insertion_parameters) # type: ignore else: - raise ValueError(f"Model type {task_type} is not supported") + raise ValueError(f"Model type {task} is not supported") xai_output_node = explain_method.generate_xai_branch() model_ori_outputs = model.outputs diff --git a/openvino_xai/insertion/parse_model.py b/openvino_xai/inserter/model_parser.py similarity index 98% rename from openvino_xai/insertion/parse_model.py rename to openvino_xai/inserter/model_parser.py index deb8c29a..b1a30c60 100644 --- a/openvino_xai/insertion/parse_model.py +++ b/openvino_xai/inserter/model_parser.py @@ -1,11 +1,11 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from typing import Callable, List import openvino.runtime as ov -from openvino_xai.insertion.insertion_parameters import ModelType +from openvino_xai.inserter.parameters import ModelType class IRParser: diff --git a/openvino_xai/insertion/insertion_parameters.py b/openvino_xai/inserter/parameters.py similarity index 78% rename from openvino_xai/insertion/insertion_parameters.py rename to openvino_xai/inserter/parameters.py index 1a9ccb19..c427f349 100644 --- a/openvino_xai/insertion/insertion_parameters.py +++ b/openvino_xai/inserter/parameters.py @@ -1,11 +1,11 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Tuple -from openvino_xai.common.parameters import XAIMethodType +from openvino_xai.common.parameters import Method @dataclass @@ -23,8 +23,8 @@ class ClassificationInsertionParameters(InsertionParameters): :type target_layer: str :parameter embed_normalization: If set to True, saliency map normalization is embedded in the model. :type embed_normalization: bool - :parameter explain_method_type: Explain method to use for model explanation. - :type explain_method_type: openvino_xai.common.parameters.XAIMethodType + :parameter explain_method: Explain method to use for model explanation. + :type explain_method: openvino_xai.common.parameters.Method :parameter white_box_method_kwargs: Defines white-box method kwargs. :type white_box_method_kwargs: dict """ @@ -32,7 +32,7 @@ class ClassificationInsertionParameters(InsertionParameters): target_layer: str | None = None embed_normalization: bool | None = True - explain_method_type: XAIMethodType = XAIMethodType.RECIPROCAM + explain_method: Method = Method.RECIPROCAM white_box_method_kwargs: Dict = field(default_factory=dict) @@ -49,8 +49,8 @@ class DetectionInsertionParameters(InsertionParameters): :type saliency_map_size: Tuple[int, int] | List[int] :parameter embed_normalization: If set to True, saliency map normalization is embedded in the model. :type embed_normalization: bool - :parameter explain_method_type: Explain method to use for model explanation. - :type explain_method_type: XAIMethodType + :parameter explain_method: Explain method to use for model explanation. + :type explain_method: Method """ target_layer: List[str] @@ -58,7 +58,7 @@ class DetectionInsertionParameters(InsertionParameters): saliency_map_size: Tuple[int, int] | List[int] = (23, 23) embed_normalization: bool = True - explain_method_type: XAIMethodType = XAIMethodType.DETCLASSPROBABILITYMAP + explain_method: Method = Method.DETCLASSPROBABILITYMAP class ModelType(Enum): diff --git a/openvino_xai/algorithms/__init__.py b/openvino_xai/methods/__init__.py similarity index 72% rename from openvino_xai/algorithms/__init__.py rename to openvino_xai/methods/__init__.py index 29cdac7b..34bf33f2 100644 --- a/openvino_xai/algorithms/__init__.py +++ b/openvino_xai/methods/__init__.py @@ -1,10 +1,10 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 """ XAI algorithms. """ -from openvino_xai.algorithms.black_box.black_box_methods import RISE -from openvino_xai.algorithms.white_box.white_box_methods import ( +from openvino_xai.methods.black_box.black_box_methods import RISE +from openvino_xai.methods.white_box.white_box_methods import ( ActivationMapXAIMethod, DetClassProbabilityMapXAIMethod, FeatureMapPerturbationBase, diff --git a/openvino_xai/algorithms/black_box/__init__.py b/openvino_xai/methods/black_box/__init__.py similarity index 100% rename from openvino_xai/algorithms/black_box/__init__.py rename to openvino_xai/methods/black_box/__init__.py diff --git a/openvino_xai/algorithms/black_box/black_box_methods.py b/openvino_xai/methods/black_box/black_box_methods.py similarity index 99% rename from openvino_xai/algorithms/black_box/black_box_methods.py rename to openvino_xai/methods/black_box/black_box_methods.py index 99c61b50..25096acc 100644 --- a/openvino_xai/algorithms/black_box/black_box_methods.py +++ b/openvino_xai/methods/black_box/black_box_methods.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from abc import ABC diff --git a/openvino_xai/algorithms/white_box/__init__.py b/openvino_xai/methods/white_box/__init__.py similarity index 100% rename from openvino_xai/algorithms/white_box/__init__.py rename to openvino_xai/methods/white_box/__init__.py diff --git a/openvino_xai/algorithms/white_box/create_method.py b/openvino_xai/methods/white_box/create_method.py similarity index 82% rename from openvino_xai/algorithms/white_box/create_method.py rename to openvino_xai/methods/white_box/create_method.py index c28d42ea..9674222e 100644 --- a/openvino_xai/algorithms/white_box/create_method.py +++ b/openvino_xai/methods/white_box/create_method.py @@ -1,22 +1,22 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import openvino.runtime as ov -from openvino_xai.algorithms.white_box.white_box_methods import ( +from openvino_xai.common.parameters import Method +from openvino_xai.common.utils import logger +from openvino_xai.inserter.parameters import ( + ClassificationInsertionParameters, + DetectionInsertionParameters, +) +from openvino_xai.methods.white_box.white_box_methods import ( ActivationMapXAIMethod, DetClassProbabilityMapXAIMethod, ReciproCAMXAIMethod, ViTReciproCAMXAIMethod, WhiteBoxXAIMethodBase, ) -from openvino_xai.common.parameters import XAIMethodType -from openvino_xai.common.utils import logger -from openvino_xai.insertion.insertion_parameters import ( - ClassificationInsertionParameters, - DetectionInsertionParameters, -) def create_white_box_classification_explain_method( @@ -40,15 +40,15 @@ def create_white_box_classification_explain_method( logger.info("Using ReciproCAM method (for CNNs).") return ReciproCAMXAIMethod(model) - explain_method_type = insertion_parameters.explain_method_type - if explain_method_type == XAIMethodType.RECIPROCAM: + explain_method = insertion_parameters.explain_method + if explain_method == Method.RECIPROCAM: logger.info("Using ReciproCAM method (for CNNs).") return ReciproCAMXAIMethod( model, insertion_parameters.target_layer, insertion_parameters.embed_normalization, ) - if explain_method_type == XAIMethodType.VITRECIPROCAM: + if explain_method == Method.VITRECIPROCAM: logger.info("Using ViTReciproCAM method (for vision transformers).") return ViTReciproCAMXAIMethod( model, @@ -56,12 +56,12 @@ def create_white_box_classification_explain_method( insertion_parameters.embed_normalization, **insertion_parameters.white_box_method_kwargs, ) - if explain_method_type == XAIMethodType.ACTIVATIONMAP: + if explain_method == Method.ACTIVATIONMAP: logger.info("Using ActivationMap method (for CNNs).") return ActivationMapXAIMethod( model, insertion_parameters.target_layer, insertion_parameters.embed_normalization ) - raise ValueError(f"Requested explanation method {explain_method_type} is not implemented.") + raise ValueError(f"Requested explanation method {explain_method} is not implemented.") def create_white_box_detection_explain_method( @@ -79,8 +79,8 @@ def create_white_box_detection_explain_method( if insertion_parameters is None: raise ValueError("insertion_parameters is required for the detection models.") - explain_method_type = insertion_parameters.explain_method_type - if explain_method_type == XAIMethodType.DETCLASSPROBABILITYMAP: + explain_method = insertion_parameters.explain_method + if explain_method == Method.DETCLASSPROBABILITYMAP: return DetClassProbabilityMapXAIMethod( model, insertion_parameters.target_layer, @@ -88,4 +88,4 @@ def create_white_box_detection_explain_method( insertion_parameters.saliency_map_size, insertion_parameters.embed_normalization, ) - raise ValueError(f"Requested explanation method {explain_method_type} is not implemented.") + raise ValueError(f"Requested explanation method {explain_method} is not implemented.") diff --git a/openvino_xai/algorithms/white_box/white_box_methods.py b/openvino_xai/methods/white_box/white_box_methods.py similarity index 98% rename from openvino_xai/algorithms/white_box/white_box_methods.py rename to openvino_xai/methods/white_box/white_box_methods.py index f4debac3..171cd7ab 100644 --- a/openvino_xai/algorithms/white_box/white_box_methods.py +++ b/openvino_xai/methods/white_box/white_box_methods.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from abc import ABC, abstractmethod @@ -8,9 +8,9 @@ import openvino.runtime as ov from openvino.runtime import opset10 as opset -from openvino_xai.explanation.explanation_parameters import TargetExplainGroup -from openvino_xai.insertion.insertion_parameters import ModelType -from openvino_xai.insertion.parse_model import IRParserCls +from openvino_xai.explainer.parameters import TargetExplainGroup +from openvino_xai.inserter.model_parser import IRParserCls +from openvino_xai.inserter.parameters import ModelType class WhiteBoxXAIMethodBase(ABC): diff --git a/openvino_xai/utils/model_export.py b/openvino_xai/utils/model_export.py index c1490fa3..9f700e98 100644 --- a/openvino_xai/utils/model_export.py +++ b/openvino_xai/utils/model_export.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import openvino diff --git a/tests/e2e/test_classification_timm.py b/tests/e2e/test_classification_timm.py index 6ef34e15..da0bd857 100644 --- a/tests/e2e/test_classification_timm.py +++ b/tests/e2e/test_classification_timm.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import csv @@ -10,24 +10,22 @@ import openvino.runtime as ov import pytest -from openvino_xai.common.parameters import TaskType, XAIMethodType -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.common.parameters import Method, Task +from openvino_xai.explainer.explainer import Explainer +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, - PostProcessParameters, TargetExplainGroup, + VisualizationParameters, ) -from openvino_xai.explanation.post_process import PostProcessor -from openvino_xai.explanation.utils import ( +from openvino_xai.explainer.utils import ( ActivationType, get_postprocess_fn, get_preprocess_fn, get_score, ) -from openvino_xai.insertion.insertion_parameters import ( - ClassificationInsertionParameters, -) +from openvino_xai.explainer.visualizer import Visualizer +from openvino_xai.inserter.parameters import ClassificationInsertionParameters from openvino_xai.utils.model_export import export_to_ir, export_to_onnx timm = pytest.importorskip("timm") @@ -178,15 +176,15 @@ def test_classification_white_box(self, model_id, dump_maps=True): model = ov.Core().read_model(ir_path) if model_id in LIMITED_DIVERSE_SET_OF_CNN_MODELS: - explain_method_type = XAIMethodType.RECIPROCAM + explain_method = Method.RECIPROCAM elif model_id in LIMITED_DIVERSE_SET_OF_VISION_TRANSFORMER_MODELS: - explain_method_type = XAIMethodType.VITRECIPROCAM + explain_method = Method.VITRECIPROCAM else: raise ValueError insertion_parameters = ClassificationInsertionParameters( embed_normalization=False, - explain_method_type=explain_method_type, + explain_method=explain_method, ) mean_values = [(item * 255) for item in model_cfg["mean"]] @@ -201,7 +199,7 @@ def test_classification_white_box(self, model_id, dump_maps=True): explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, # defaults to AUTO insertion_parameters=insertion_parameters, @@ -211,16 +209,16 @@ def test_classification_white_box(self, model_id, dump_maps=True): explanation_parameters = ExplanationParameters( target_explain_group=TargetExplainGroup.CUSTOM, target_explain_labels=[target_class], - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) image = cv2.imread("tests/assets/cheetah_person.jpg") explanation = explainer(image, explanation_parameters) assert explanation is not None - assert explanation.sal_map_shape[-1] > 1 and explanation.sal_map_shape[-2] > 1 - print(f"{model_id}: Generated classification saliency maps with shape {explanation.sal_map_shape}.") + assert explanation.shape[-1] > 1 and explanation.shape[-2] > 1 + print(f"{model_id}: Generated classification saliency maps with shape {explanation.shape}.") self.update_report("report_wb.csv", model_id, "True", "True", "True") - raw_shape = explanation.sal_map_shape + raw_shape = explanation.shape shape_str = "H=" + str(raw_shape[0]) + ", W=" + str(raw_shape[1]) self.update_report("report_wb.csv", model_id, "True", "True", "True", shape_str) @@ -233,11 +231,11 @@ def test_classification_white_box(self, model_id, dump_maps=True): raw_sal_map[-1, 0] = np.mean(np.delete(raw_sal_map[-2:, :2].flatten(), 2)) raw_sal_map[-1, -1] = np.mean(np.delete(raw_sal_map[-2:, -2:].flatten(), 3)) explanation.saliency_map[target_class] = raw_sal_map - post_processing_parameters = PostProcessParameters(scale=True, overlay=True) - post_processor = PostProcessor( + visualization_parameters = VisualizationParameters(scale=True, overlay=True) + post_processor = Visualizer( explanation=explanation, data=image, - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, ) explanation = post_processor.run() @@ -296,7 +294,7 @@ def test_classification_black_box(self, model_id, dump_maps=True): explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=preprocess_fn, postprocess_fn=postprocess_fn, explain_mode=ExplainMode.BLACKBOX, # defaults to AUTO @@ -315,10 +313,10 @@ def test_classification_black_box(self, model_id, dump_maps=True): ) assert explanation is not None - assert explanation.sal_map_shape[-1] > 1 and explanation.sal_map_shape[-2] > 1 - print(f"{model_id}: Generated classification saliency maps with shape {explanation.sal_map_shape}.") + assert explanation.shape[-1] > 1 and explanation.shape[-2] > 1 + print(f"{model_id}: Generated classification saliency maps with shape {explanation.shape}.") self.update_report("report_bb.csv", model_id, "True", "True", "True") - shape = explanation.sal_map_shape + shape = explanation.shape shape_str = "H=" + str(shape[0]) + ", W=" + str(shape[1]) self.update_report("report_bb.csv", model_id, "True", "True", "True", shape_str) diff --git a/tests/integration/test_classification.py b/tests/integration/test_classification.py index da677ebb..45b24ac6 100644 --- a/tests/integration/test_classification.py +++ b/tests/integration/test_classification.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from pathlib import Path @@ -8,20 +8,18 @@ import openvino.runtime as ov import pytest -import openvino_xai as ovxai -from openvino_xai.common.parameters import TaskType, XAIMethodType +import openvino_xai as xai +from openvino_xai.common.parameters import Method, Task from openvino_xai.common.utils import has_xai, retrieve_otx_model -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.explainer import Explainer +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, - PostProcessParameters, TargetExplainGroup, + VisualizationParameters, ) -from openvino_xai.explanation.utils import get_postprocess_fn, get_preprocess_fn -from openvino_xai.insertion.insertion_parameters import ( - ClassificationInsertionParameters, -) +from openvino_xai.explainer.utils import get_postprocess_fn, get_preprocess_fn +from openvino_xai.inserter.parameters import ClassificationInsertionParameters MODELS = [ "mlc_mobilenetv3_large_voc", # verified @@ -99,12 +97,12 @@ def test_vitreciprocam( model = ov.Core().read_model(model_path) insertion_parameters = ClassificationInsertionParameters( embed_normalization=embed_normalization, - explain_method_type=XAIMethodType.VITRECIPROCAM, + explain_method=Method.VITRECIPROCAM, ) explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, # type: ignore explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, @@ -113,7 +111,7 @@ def test_vitreciprocam( if target_explain_group == TargetExplainGroup.ALL: explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -132,7 +130,7 @@ def test_vitreciprocam( explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, target_explain_labels=[target_class], - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -157,12 +155,12 @@ def test_reciprocam( model = ov.Core().read_model(model_path) insertion_parameters = ClassificationInsertionParameters( embed_normalization=embed_normalization, - explain_method_type=XAIMethodType.RECIPROCAM, + explain_method=Method.RECIPROCAM, ) explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, # type: ignore explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, @@ -171,7 +169,7 @@ def test_reciprocam( if target_explain_group == TargetExplainGroup.ALL: explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -192,7 +190,7 @@ def test_reciprocam( explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, target_explain_labels=[target_class], - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -210,19 +208,19 @@ def test_activationmap(self, model_name: str, embed_normalization: bool): model = ov.Core().read_model(model_path) insertion_parameters = ClassificationInsertionParameters( embed_normalization=embed_normalization, - explain_method_type=XAIMethodType.ACTIVATIONMAP, + explain_method=Method.ACTIVATIONMAP, ) explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, # type: ignore explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, ) explanation_parameters = ExplanationParameters( - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) if model_name in self._ref_sal_maps_activationmap and embed_normalization: @@ -251,7 +249,7 @@ def test_classification_postprocessing( explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, # type: ignore explain_mode=ExplainMode.WHITEBOX, ) @@ -259,12 +257,12 @@ def test_classification_postprocessing( explain_targets = None if target_explain_group == TargetExplainGroup.CUSTOM: explain_targets = [1] - post_processing_parameters = PostProcessParameters(overlay=overlay) + visualization_parameters = VisualizationParameters(overlay=overlay) explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, target_explain_labels=explain_targets, # type: ignore - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -274,9 +272,9 @@ def test_classification_postprocessing( assert len(explanation.saliency_map) == len(explain_targets) assert 1 in explanation.saliency_map if overlay: - assert explanation.sal_map_shape == (354, 500, 3) + assert explanation.shape == (354, 500, 3) else: - assert explanation.sal_map_shape == (7, 7) + assert explanation.shape == (7, 7) for map_ in explanation.saliency_map.values(): assert map_.min() == 0, f"{map_.min()}" assert map_.max() in {254, 255}, f"{map_.max()}" @@ -288,14 +286,14 @@ def test_two_sequential_norms(self): explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, explain_mode=ExplainMode.WHITEBOX, ) explanation_parameters = ExplanationParameters( target_explain_group=TargetExplainGroup.ALL, - post_processing_parameters=PostProcessParameters(scale=True), + visualization_parameters=VisualizationParameters(scale=True), ) explanation = explainer(self.image, explanation_parameters) @@ -344,20 +342,20 @@ def test_classification_black_box_postprocessing( explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, # type: ignore postprocess_fn=get_postprocess_fn(), # type: ignore explain_mode=ExplainMode.BLACKBOX, ) - post_processing_parameters = PostProcessParameters( + visualization_parameters = VisualizationParameters( overlay=overlay, ) if target_explain_group == TargetExplainGroup.CUSTOM: target_class = 1 explanation_parameters = ExplanationParameters( - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, target_explain_group=target_explain_group, target_explain_labels=[target_class], ) @@ -379,7 +377,7 @@ def test_classification_black_box_postprocessing( if target_explain_group == TargetExplainGroup.ALL: explanation_parameters = ExplanationParameters( - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, target_explain_group=target_explain_group, ) explanation = explainer( @@ -392,10 +390,10 @@ def test_classification_black_box_postprocessing( assert explanation is not None if overlay: assert len(explanation.saliency_map) == MODEL_NUM_CLASSES[model_name] - assert explanation.sal_map_shape == (354, 500, 3) + assert explanation.shape == (354, 500, 3) else: assert len(explanation.saliency_map) == MODEL_NUM_CLASSES[model_name] - assert explanation.sal_map_shape == (224, 224) + assert explanation.shape == (224, 224) if scale: for map_ in explanation.saliency_map.values(): assert map_.min() == 0, f"{map_.min()}" @@ -409,21 +407,21 @@ def test_classification_black_box_xai_model_as_input(self): retrieve_otx_model(self.data_dir, DEFAULT_CLS_MODEL) model_path = self.data_dir / "otx_models" / (DEFAULT_CLS_MODEL + ".xml") model = ov.Core().read_model(model_path) - model_xai = ovxai.insert_xai( + model_xai = xai.insert_xai( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, ) assert has_xai(model_xai), "Updated IR model should has XAI head." explainer = Explainer( model=model_xai, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, postprocess_fn=get_postprocess_fn(), explain_mode=ExplainMode.BLACKBOX, ) explanation_parameters = ExplanationParameters( - post_processing_parameters=PostProcessParameters(overlay=False), + visualization_parameters=VisualizationParameters(overlay=False), target_explain_labels=[0], ) explanation = explainer( diff --git a/tests/integration/test_detection.py b/tests/integration/test_detection.py index af340247..4da26aaf 100644 --- a/tests/integration/test_detection.py +++ b/tests/integration/test_detection.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from pathlib import Path @@ -10,23 +10,23 @@ import openvino.runtime as ov import pytest -from openvino_xai.algorithms.white_box.create_method import ( - create_white_box_detection_explain_method, -) -from openvino_xai.algorithms.white_box.white_box_methods import ( - DetClassProbabilityMapXAIMethod, -) -from openvino_xai.common.parameters import TaskType, XAIMethodType +from openvino_xai.common.parameters import Method, Task from openvino_xai.common.utils import retrieve_otx_model -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.explanation_parameters import ( +from openvino_xai.explainer.explainer import Explainer +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, - PostProcessParameters, TargetExplainGroup, + VisualizationParameters, +) +from openvino_xai.explainer.utils import get_preprocess_fn +from openvino_xai.inserter.parameters import DetectionInsertionParameters +from openvino_xai.methods.white_box.create_method import ( + create_white_box_detection_explain_method, +) +from openvino_xai.methods.white_box.white_box_methods import ( + DetClassProbabilityMapXAIMethod, ) -from openvino_xai.explanation.utils import get_preprocess_fn -from openvino_xai.insertion.insertion_parameters import DetectionInsertionParameters MODEL_CONFIGS = addict.Addict( { @@ -101,7 +101,7 @@ def test_detclassprobabilitymap(self, model_name, embed_normalization, target_ex target_layer=cls_head_output_node_names, num_anchors=MODEL_CONFIGS[model_name].anchors, saliency_map_size=self._sal_map_size, - explain_method_type=XAIMethodType.DETCLASSPROBABILITYMAP, + explain_method=Method.DETCLASSPROBABILITYMAP, ) preprocess_fn = get_preprocess_fn( @@ -110,7 +110,7 @@ def test_detclassprobabilitymap(self, model_name, embed_normalization, target_ex ) explainer = Explainer( model=model, - task_type=TaskType.DETECTION, + task=Task.DETECTION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, # defaults to AUTO insertion_parameters=insertion_parameters, @@ -121,7 +121,7 @@ def test_detclassprobabilitymap(self, model_name, embed_normalization, target_ex target_explain_group=target_explain_group, target_explain_labels=target_class_list, # w/o postrocessing - post_processing_parameters=PostProcessParameters(), + visualization_parameters=VisualizationParameters(), ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None @@ -150,7 +150,7 @@ def test_detection_postprocessing(self, target_explain_group): model, insertion_parameters = self.get_default_model_and_insertion_parameters() target_class_list = [1] if target_explain_group == TargetExplainGroup.CUSTOM else None - post_processing_parameters = PostProcessParameters(overlay=True) + visualization_parameters = VisualizationParameters(overlay=True) preprocess_fn = get_preprocess_fn( input_size=MODEL_CONFIGS[DEFAULT_DET_MODEL].input_size, @@ -158,7 +158,7 @@ def test_detection_postprocessing(self, target_explain_group): ) explainer = Explainer( model=model, - task_type=TaskType.DETECTION, + task=Task.DETECTION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, @@ -167,11 +167,11 @@ def test_detection_postprocessing(self, target_explain_group): explanation_parameters = ExplanationParameters( target_explain_group=target_explain_group, target_explain_labels=target_class_list, - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, ) explanation = explainer(self.image, explanation_parameters) assert explanation is not None - assert explanation.sal_map_shape == (480, 640, 3) + assert explanation.shape == (480, 640, 3) if target_explain_group == TargetExplainGroup.ALL: assert len(explanation.saliency_map) == MODEL_CONFIGS[DEFAULT_DET_MODEL].num_classes if target_explain_group == TargetExplainGroup.CUSTOM: @@ -188,7 +188,7 @@ def test_two_sequential_norms(self): ) explainer = Explainer( model=model, - task_type=TaskType.DETECTION, + task=Task.DETECTION, preprocess_fn=preprocess_fn, explain_mode=ExplainMode.WHITEBOX, insertion_parameters=insertion_parameters, @@ -196,7 +196,7 @@ def test_two_sequential_norms(self): explanation_parameters = ExplanationParameters( target_explain_group=TargetExplainGroup.ALL, - post_processing_parameters=PostProcessParameters(scale=True), + visualization_parameters=VisualizationParameters(scale=True), ) explanation = explainer(self.image, explanation_parameters) @@ -227,6 +227,6 @@ def get_default_model_and_insertion_parameters(self): target_layer=cls_head_output_node_names, num_anchors=MODEL_CONFIGS[DEFAULT_DET_MODEL].anchors, saliency_map_size=self._sal_map_size, - explain_method_type=XAIMethodType.DETCLASSPROBABILITYMAP, + explain_method=Method.DETCLASSPROBABILITYMAP, ) return model, insertion_parameters diff --git a/tests/unit/algorithms/black_box/__init__.py b/tests/unit/algorithms/black_box/__init__.py deleted file mode 100644 index 6a16273c..00000000 --- a/tests/unit/algorithms/black_box/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# Copyright (C) 2023 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/unit/common/test_utils.py b/tests/unit/common/test_utils.py index 78a7faaf..70f2fdc1 100644 --- a/tests/unit/common/test_utils.py +++ b/tests/unit/common/test_utils.py @@ -5,9 +5,9 @@ import openvino.runtime as ov -from openvino_xai.common.parameters import TaskType +from openvino_xai.common.parameters import Task from openvino_xai.common.utils import has_xai, retrieve_otx_model -from openvino_xai.insertion.insert_xai_into_model import insert_xai +from openvino_xai.inserter.inserter import insert_xai from tests.integration.test_classification import DEFAULT_CLS_MODEL DARA_DIR = Path(".data") @@ -23,7 +23,7 @@ def test_has_xai(): model_xai = insert_xai( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, ) assert has_xai(model_xai) diff --git a/tests/unit/explanation/test_explainer.py b/tests/unit/explanation/test_explainer.py index ea84baf9..df9f303c 100644 --- a/tests/unit/explanation/test_explainer.py +++ b/tests/unit/explanation/test_explainer.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from pathlib import Path @@ -7,17 +7,17 @@ import openvino.runtime as ov import pytest -from openvino_xai.common.parameters import TaskType +from openvino_xai.common.parameters import Task from openvino_xai.common.utils import retrieve_otx_model -from openvino_xai.explanation import ( +from openvino_xai.explainer.explainer import Explainer +from openvino_xai.explainer.parameters import ( ExplainMode, ExplanationParameters, TargetExplainGroup, ) -from openvino_xai.explanation.explain import Explainer -from openvino_xai.explanation.utils import get_postprocess_fn, get_preprocess_fn -from openvino_xai.insertion import ClassificationInsertionParameters -from openvino_xai.insertion.insert_xai_into_model import insert_xai +from openvino_xai.explainer.utils import get_postprocess_fn, get_preprocess_fn +from openvino_xai.inserter.inserter import insert_xai +from openvino_xai.inserter.parameters import ClassificationInsertionParameters from tests.unit.explanation.test_explanation_utils import VOC_NAMES MODEL_NAME = "mlc_mobilenetv3_large_voc" @@ -56,12 +56,12 @@ def test_explainer(self, explain_mode, target_explain_group, with_xai_originally if with_xai_originally: model = insert_xai( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, ) explainer = Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, postprocess_fn=get_postprocess_fn(), explain_mode=explain_mode, @@ -88,7 +88,7 @@ def test_explainer_wo_preprocessing(self): explainer = Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, ) explanation_parameters = ExplanationParameters( @@ -103,7 +103,7 @@ def test_explainer_wo_preprocessing(self): model = ov.Core().read_model(model_path) explainer = Explainer( model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, explain_mode=ExplainMode.BLACKBOX, postprocess_fn=get_postprocess_fn(), ) @@ -127,7 +127,7 @@ def test_auto_black_box_fallback(self): ) explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, explain_mode=ExplainMode.AUTO, insertion_parameters=insertion_parameters, @@ -140,7 +140,7 @@ def test_auto_black_box_fallback(self): ) explainer = Explainer( model=model, - task_type=TaskType.CLASSIFICATION, + task=Task.CLASSIFICATION, preprocess_fn=self.preprocess_fn, postprocess_fn=get_postprocess_fn(), explain_mode=ExplainMode.AUTO, diff --git a/tests/unit/explanation/test_explanation_result.py b/tests/unit/explanation/test_explanation.py similarity index 55% rename from tests/unit/explanation/test_explanation_result.py rename to tests/unit/explanation/test_explanation.py index 19a99561..aa36a6fa 100644 --- a/tests/unit/explanation/test_explanation_result.py +++ b/tests/unit/explanation/test_explanation.py @@ -5,18 +5,18 @@ import numpy as np -from openvino_xai.explanation import TargetExplainGroup -from openvino_xai.explanation.explanation_result import ExplanationResult +from openvino_xai.explainer.explanation import Explanation +from openvino_xai.explainer.parameters import TargetExplainGroup from tests.unit.explanation.test_explanation_utils import VOC_NAMES SALIENCY_MAPS = (np.random.rand(1, 20, 5, 5) * 255).astype(np.uint8) SALIENCY_MAPS_IMAGE = (np.random.rand(1, 5, 5) * 255).astype(np.uint8) -class TestExplanationResult: +class TestExplanation: def test_target_explain_labels(self): explain_targets = [0, 2] - explanation_result_indices = ExplanationResult( + explanation_indices = Explanation( SALIENCY_MAPS, target_explain_group=TargetExplainGroup.CUSTOM, target_explain_labels=explain_targets, @@ -24,54 +24,54 @@ def test_target_explain_labels(self): ) explain_targets = ["aeroplane", "bird"] - explanation_result_names = ExplanationResult( + explanation_names = Explanation( SALIENCY_MAPS, target_explain_group=TargetExplainGroup.CUSTOM, target_explain_labels=explain_targets, label_names=VOC_NAMES, ) - sm_indices = explanation_result_indices.saliency_map - sm_names = explanation_result_names.saliency_map + sm_indices = explanation_indices.saliency_map + sm_names = explanation_names.saliency_map assert len(sm_indices) == len(sm_names) assert set(sm_indices.keys()) == set(sm_names.keys()) == {0, 2} def test_target_explain_group_image(self): - explanation_result = ExplanationResult( + explanation = Explanation( SALIENCY_MAPS_IMAGE, target_explain_group=TargetExplainGroup.IMAGE, ) - assert "per_image_map" in explanation_result.saliency_map - assert len(explanation_result.saliency_map) == 1 - assert explanation_result.saliency_map["per_image_map"].shape == (5, 5) + assert "per_image_map" in explanation.saliency_map + assert len(explanation.saliency_map) == 1 + assert explanation.saliency_map["per_image_map"].shape == (5, 5) - def test_sal_map_shape(self): - explanation_result = self._get_explanation_result() - assert explanation_result.sal_map_shape == (5, 5) + def test_shape(self): + explanation = self._get_explanation() + assert explanation.shape == (5, 5) def test_save(self, tmp_path): save_path = tmp_path / "saliency_maps" - explanation_result = self._get_explanation_result() - explanation_result.save(save_path, "test_map") + explanation = self._get_explanation() + explanation.save(save_path, "test_map") assert os.path.isfile(save_path / "test_map_target_aeroplane.jpg") assert os.path.isfile(save_path / "test_map_target_bird.jpg") - explanation_result = self._get_explanation_result(label_names=None) - explanation_result.save(save_path, "test_map") + explanation = self._get_explanation(label_names=None) + explanation.save(save_path, "test_map") assert os.path.isfile(save_path / "test_map_target_0.jpg") assert os.path.isfile(save_path / "test_map_target_2.jpg") - explanation_result = self._get_explanation_result(saliency_maps=SALIENCY_MAPS_IMAGE, label_names=None) - explanation_result.save(save_path, "test_map") + explanation = self._get_explanation(saliency_maps=SALIENCY_MAPS_IMAGE, label_names=None) + explanation.save(save_path, "test_map") assert os.path.isfile(save_path / "test_map.jpg") - def _get_explanation_result(self, saliency_maps=SALIENCY_MAPS, label_names=VOC_NAMES): + def _get_explanation(self, saliency_maps=SALIENCY_MAPS, label_names=VOC_NAMES): explain_targets = [0, 2] - explanation_result = ExplanationResult( + explanation = Explanation( saliency_maps, target_explain_group=TargetExplainGroup.CUSTOM, target_explain_labels=explain_targets, label_names=label_names, ) - return explanation_result + return explanation diff --git a/tests/unit/explanation/test_explanation_utils.py b/tests/unit/explanation/test_explanation_utils.py index c3090087..95ed6fe5 100644 --- a/tests/unit/explanation/test_explanation_utils.py +++ b/tests/unit/explanation/test_explanation_utils.py @@ -1,10 +1,10 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import numpy as np import pytest -from openvino_xai.explanation.utils import ( +from openvino_xai.explainer.utils import ( ActivationType, get_explain_target_indices, get_score, diff --git a/tests/unit/explanation/test_postprocessing.py b/tests/unit/explanation/test_visualization.py similarity index 83% rename from tests/unit/explanation/test_postprocessing.py rename to tests/unit/explanation/test_visualization.py index 41080faa..dab9381e 100644 --- a/tests/unit/explanation/test_postprocessing.py +++ b/tests/unit/explanation/test_visualization.py @@ -5,13 +5,12 @@ import pytest from openvino_xai.common.utils import get_min_max, scale -from openvino_xai.explanation import colormap, overlay, resize -from openvino_xai.explanation.explanation_parameters import ( - PostProcessParameters, +from openvino_xai.explainer.explanation import Explanation +from openvino_xai.explainer.parameters import ( TargetExplainGroup, + VisualizationParameters, ) -from openvino_xai.explanation.explanation_result import ExplanationResult -from openvino_xai.explanation.post_process import PostProcessor +from openvino_xai.explainer.visualizer import Visualizer, colormap, overlay, resize SALIENCY_MAPS = [ (np.random.rand(1, 5, 5) * 255).astype(np.uint8), @@ -81,7 +80,7 @@ def test_overlay(): assert (overlayed_image == expected_output).all() -class TestPostProcessor: +class TestVisualizer: @pytest.mark.parametrize("saliency_maps", SALIENCY_MAPS) @pytest.mark.parametrize("target_explain_group", TARGET_EXPLAIN_GROUPS) @pytest.mark.parametrize("scale", [True, False]) @@ -89,7 +88,7 @@ class TestPostProcessor: @pytest.mark.parametrize("colormap", [True, False]) @pytest.mark.parametrize("overlay", [True, False]) @pytest.mark.parametrize("overlay_weight", [0.5, 0.3]) - def test_postprocessor( + def test_Visualizer( self, saliency_maps, target_explain_group, @@ -99,7 +98,7 @@ def test_postprocessor( overlay, overlay_weight, ): - post_processing_parameters = PostProcessParameters( + visualization_parameters = VisualizationParameters( scale=scale, resize=resize, colormap=colormap, @@ -115,16 +114,16 @@ def test_postprocessor( if saliency_maps.ndim == 3: target_explain_group = TargetExplainGroup.IMAGE explain_targets = None - explanation_result = ExplanationResult( + explanation = Explanation( saliency_maps, target_explain_group=target_explain_group, target_explain_labels=explain_targets ) - raw_sal_map_dims = len(explanation_result.sal_map_shape) + raw_sal_map_dims = len(explanation.shape) original_input_image = np.ones((20, 20, 3)) - post_processor = PostProcessor( - explanation=explanation_result, + post_processor = Visualizer( + explanation=explanation, original_input_image=original_input_image, - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, ) explanation = post_processor.run() @@ -132,7 +131,7 @@ def test_postprocessor( expected_dims = raw_sal_map_dims if colormap or overlay: expected_dims += 1 - assert len(explanation.sal_map_shape) == expected_dims + assert len(explanation.shape) == expected_dims if scale and not colormap and not overlay: for map_ in explanation.saliency_map.values(): @@ -143,13 +142,13 @@ def test_postprocessor( assert map_.shape[:2] == original_input_image.shape[:2] if target_explain_group == TargetExplainGroup.IMAGE and not overlay: - explanation_result = ExplanationResult( + explanation = Explanation( saliency_maps, target_explain_group=target_explain_group, target_explain_labels=explain_targets ) - post_processor = PostProcessor( - explanation=explanation_result, + post_processor = Visualizer( + explanation=explanation, output_size=(20, 20), - post_processing_parameters=post_processing_parameters, + visualization_parameters=visualization_parameters, ) saliency_map_processed_output_size = post_processor.run() maps_data = explanation.saliency_map diff --git a/tests/unit/insertion/test_insertion.py b/tests/unit/insertion/test_insertion.py index 89d9ff53..ca46fc2a 100644 --- a/tests/unit/insertion/test_insertion.py +++ b/tests/unit/insertion/test_insertion.py @@ -5,9 +5,9 @@ from openvino import runtime as ov from openvino_xai import insert_xai -from openvino_xai.common.parameters import TaskType, XAIMethodType +from openvino_xai.common.parameters import Method, Task from openvino_xai.common.utils import has_xai, retrieve_otx_model -from openvino_xai.insertion.insertion_parameters import DetectionInsertionParameters +from openvino_xai.inserter.parameters import DetectionInsertionParameters from tests.integration.test_classification import DATA_DIR, MODELS from tests.integration.test_detection import DEFAULT_DET_MODEL, MODEL_CONFIGS @@ -23,7 +23,7 @@ def test_insertion_classification(model_name): if model_name == "classification_model_with_xai_head": assert has_xai(model_ir), "Input IR model should have XAI head." - model_with_xai = insert_xai(model_ir, TaskType.CLASSIFICATION) + model_with_xai = insert_xai(model_ir, Task.CLASSIFICATION) assert has_xai(model_with_xai), "Updated IR model should has XAI head." @@ -37,10 +37,10 @@ def test_insertion_detection(): insertion_parameters = DetectionInsertionParameters( target_layer=cls_head_output_node_names, num_anchors=MODEL_CONFIGS[DEFAULT_DET_MODEL].anchors, - explain_method_type=XAIMethodType.DETCLASSPROBABILITYMAP, + explain_method=Method.DETCLASSPROBABILITYMAP, ) - model_with_xai = insert_xai(model, TaskType.DETECTION, insertion_parameters) + model_with_xai = insert_xai(model, Task.DETECTION, insertion_parameters) assert has_xai(model_with_xai), "Updated IR model should has XAI head." diff --git a/tests/unit/insertion/test_model_parser.py b/tests/unit/insertion/test_model_parser.py index 1c80265a..618c4fb8 100644 --- a/tests/unit/insertion/test_model_parser.py +++ b/tests/unit/insertion/test_model_parser.py @@ -1,11 +1,11 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 import openvino.runtime as ov from openvino_xai.common.utils import retrieve_otx_model -from openvino_xai.insertion.insertion_parameters import ModelType -from openvino_xai.insertion.parse_model import IRParserCls +from openvino_xai.inserter.model_parser import IRParserCls +from openvino_xai.inserter.parameters import ModelType from tests.integration.test_classification import DATA_DIR, DEFAULT_CLS_MODEL diff --git a/tests/unit/insertion/test_parameters.py b/tests/unit/insertion/test_parameters.py index b123b754..12e8b3c9 100644 --- a/tests/unit/insertion/test_parameters.py +++ b/tests/unit/insertion/test_parameters.py @@ -1,8 +1,8 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 -from openvino_xai.common.parameters import XAIMethodType -from openvino_xai.insertion.insertion_parameters import ( +from openvino_xai.common.parameters import Method +from openvino_xai.inserter.parameters import ( ClassificationInsertionParameters, DetectionInsertionParameters, ) @@ -12,7 +12,7 @@ def test_classification_insertion_parameters(): cls_insertion_params = ClassificationInsertionParameters() assert cls_insertion_params.target_layer is None assert cls_insertion_params.embed_normalization - assert cls_insertion_params.explain_method_type == XAIMethodType.RECIPROCAM + assert cls_insertion_params.explain_method == Method.RECIPROCAM def test_detection_insertion_parameters(): @@ -21,4 +21,4 @@ def test_detection_insertion_parameters(): assert det_insertion_params.num_anchors == [5, 5, 5] assert det_insertion_params.saliency_map_size == (23, 23) assert det_insertion_params.embed_normalization - assert det_insertion_params.explain_method_type == XAIMethodType.DETCLASSPROBABILITYMAP + assert det_insertion_params.explain_method == Method.DETCLASSPROBABILITYMAP diff --git a/tests/unit/algorithms/__init__.py b/tests/unit/methods/__init__.py similarity index 100% rename from tests/unit/algorithms/__init__.py rename to tests/unit/methods/__init__.py diff --git a/tests/unit/methods/black_box/__init__.py b/tests/unit/methods/black_box/__init__.py new file mode 100644 index 00000000..88d510f7 --- /dev/null +++ b/tests/unit/methods/black_box/__init__.py @@ -0,0 +1,2 @@ +# Copyright (C) 2023-2024 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 diff --git a/tests/unit/algorithms/black_box/test_black_box_method.py b/tests/unit/methods/black_box/test_black_box_method.py similarity index 87% rename from tests/unit/algorithms/black_box/test_black_box_method.py rename to tests/unit/methods/black_box/test_black_box_method.py index b0ce2514..88ac8311 100644 --- a/tests/unit/algorithms/black_box/test_black_box_method.py +++ b/tests/unit/methods/black_box/test_black_box_method.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from pathlib import Path @@ -8,9 +8,9 @@ import openvino.runtime as ov import pytest -from openvino_xai.algorithms.black_box.black_box_methods import RISE from openvino_xai.common.utils import retrieve_otx_model -from openvino_xai.explanation.utils import get_postprocess_fn, get_preprocess_fn +from openvino_xai.explainer.utils import get_postprocess_fn, get_preprocess_fn +from openvino_xai.methods.black_box.black_box_methods import RISE from tests.integration.test_classification import DEFAULT_CLS_MODEL diff --git a/tests/unit/algorithms/white_box/__init__.py b/tests/unit/methods/white_box/__init__.py similarity index 100% rename from tests/unit/algorithms/white_box/__init__.py rename to tests/unit/methods/white_box/__init__.py diff --git a/tests/unit/algorithms/white_box/test_create_method.py b/tests/unit/methods/white_box/test_create_method.py similarity index 87% rename from tests/unit/algorithms/white_box/test_create_method.py rename to tests/unit/methods/white_box/test_create_method.py index dbcb3e48..cbac746b 100644 --- a/tests/unit/algorithms/white_box/test_create_method.py +++ b/tests/unit/methods/white_box/test_create_method.py @@ -6,22 +6,22 @@ import openvino.runtime as ov import pytest -from openvino_xai.algorithms.white_box.create_method import ( +from openvino_xai.common.parameters import Method +from openvino_xai.common.utils import retrieve_otx_model +from openvino_xai.inserter.parameters import ( + ClassificationInsertionParameters, + DetectionInsertionParameters, +) +from openvino_xai.methods.white_box.create_method import ( create_white_box_classification_explain_method, create_white_box_detection_explain_method, ) -from openvino_xai.algorithms.white_box.white_box_methods import ( +from openvino_xai.methods.white_box.white_box_methods import ( ActivationMapXAIMethod, DetClassProbabilityMapXAIMethod, ReciproCAMXAIMethod, ViTReciproCAMXAIMethod, ) -from openvino_xai.common.parameters import XAIMethodType -from openvino_xai.common.utils import retrieve_otx_model -from openvino_xai.insertion.insertion_parameters import ( - ClassificationInsertionParameters, - DetectionInsertionParameters, -) from tests.integration.test_classification import DEFAULT_CLS_MODEL from tests.integration.test_detection import DEFAULT_DET_MODEL, MODEL_CONFIGS @@ -43,20 +43,20 @@ def test_create_wb_cls_cnn_method(): assert isinstance(explain_method, ReciproCAMXAIMethod) insertion_parameters = ClassificationInsertionParameters( - explain_method_type=XAIMethodType.RECIPROCAM, + explain_method=Method.RECIPROCAM, ) explain_method = create_white_box_classification_explain_method(model_cnn, insertion_parameters) assert isinstance(explain_method, ReciproCAMXAIMethod) insertion_parameters = ClassificationInsertionParameters( - explain_method_type=XAIMethodType.ACTIVATIONMAP, + explain_method=Method.ACTIVATIONMAP, ) explain_method = create_white_box_classification_explain_method(model_cnn, insertion_parameters) assert isinstance(explain_method, ActivationMapXAIMethod) with pytest.raises(Exception) as exc_info: insertion_parameters = ClassificationInsertionParameters( - explain_method_type="abc", + explain_method="abc", ) explain_method = create_white_box_classification_explain_method(model_cnn, insertion_parameters) assert str(exc_info.value) == "Requested explanation method abc is not implemented." @@ -67,7 +67,7 @@ def test_create_wb_cls_vit_method(): model_path = DATA_DIR / "otx_models" / (VIT_MODEL + ".xml") model_vit = ov.Core().read_model(model_path) insertion_parameters = ClassificationInsertionParameters( - explain_method_type=XAIMethodType.VITRECIPROCAM, + explain_method=Method.VITRECIPROCAM, ) explain_method = create_white_box_classification_explain_method(model_vit, insertion_parameters) assert isinstance(explain_method, ViTReciproCAMXAIMethod) @@ -84,7 +84,7 @@ def test_create_wb_det_cnn_method(): target_layer=cls_head_output_node_names, num_anchors=MODEL_CONFIGS[DEFAULT_DET_MODEL].anchors, saliency_map_size=sal_map_size, - explain_method_type=XAIMethodType.DETCLASSPROBABILITYMAP, + explain_method=Method.DETCLASSPROBABILITYMAP, ) explain_method = create_white_box_detection_explain_method(model, insertion_parameters) @@ -99,7 +99,7 @@ def test_create_wb_det_cnn_method(): target_layer=cls_head_output_node_names, num_anchors=MODEL_CONFIGS[DEFAULT_DET_MODEL].anchors, saliency_map_size=sal_map_size, - explain_method_type="abc", + explain_method="abc", ) explain_method = create_white_box_detection_explain_method(model, insertion_parameters) assert str(exc_info.value) == "Requested explanation method abc is not implemented." diff --git a/tests/unit/algorithms/white_box/test_white_box_method.py b/tests/unit/methods/white_box/test_white_box_method.py similarity index 99% rename from tests/unit/algorithms/white_box/test_white_box_method.py rename to tests/unit/methods/white_box/test_white_box_method.py index 61e1e75a..89761c10 100644 --- a/tests/unit/algorithms/white_box/test_white_box_method.py +++ b/tests/unit/methods/white_box/test_white_box_method.py @@ -1,4 +1,4 @@ -# Copyright (C) 2023 Intel Corporation +# Copyright (C) 2023-2024 Intel Corporation # SPDX-License-Identifier: Apache-2.0 from pathlib import Path @@ -7,13 +7,13 @@ import openvino.runtime as ov import pytest -from openvino_xai.algorithms.white_box.white_box_methods import ( +from openvino_xai.common.utils import retrieve_otx_model +from openvino_xai.methods.white_box.white_box_methods import ( ActivationMapXAIMethod, DetClassProbabilityMapXAIMethod, ReciproCAMXAIMethod, ViTReciproCAMXAIMethod, ) -from openvino_xai.common.utils import retrieve_otx_model from tests.integration.test_classification import DEFAULT_CLS_MODEL from tests.integration.test_detection import DEFAULT_DET_MODEL