-
Notifications
You must be signed in to change notification settings - Fork 140
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #620 from roboflow/camera-focus-block
Adding block to assist in setting camera focus
- Loading branch information
Showing
16 changed files
with
378 additions
and
44 deletions.
There are no files selected for viewing
Empty file.
162 changes: 162 additions & 0 deletions
162
inference/core/workflows/core_steps/classical_cv/camera_focus/v1.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
from typing import List, Literal, Optional, Tuple, Type, Union | ||
|
||
import cv2 | ||
import numpy as np | ||
from pydantic import AliasChoices, ConfigDict, Field | ||
|
||
from inference.core.workflows.core_steps.visualizations.common.base import ( | ||
OUTPUT_IMAGE_KEY, | ||
) | ||
from inference.core.workflows.execution_engine.entities.base import ( | ||
OutputDefinition, | ||
WorkflowImageData, | ||
) | ||
from inference.core.workflows.execution_engine.entities.types import ( | ||
BATCH_OF_IMAGES_KIND, | ||
FLOAT_KIND, | ||
StepOutputImageSelector, | ||
WorkflowImageSelector, | ||
) | ||
from inference.core.workflows.prototypes.block import ( | ||
BlockResult, | ||
WorkflowBlock, | ||
WorkflowBlockManifest, | ||
) | ||
|
||
SHORT_DESCRIPTION: str = "Helps focus a camera by providing a focus measure." | ||
LONG_DESCRIPTION: str = """ | ||
This block calculate the Brenner function score which is a measure of the texture in the image. | ||
An in-focus image has a high Brenner function score, and contains texture at a smaller scale than | ||
an out-of-focus image. Conversely, an out-of-focus image has a low Brenner function score, and | ||
does not contain small-scale texture. | ||
""" | ||
|
||
|
||
class CameraFocusManifest(WorkflowBlockManifest): | ||
type: Literal["roboflow_core/camera_focus@v1"] | ||
model_config = ConfigDict( | ||
json_schema_extra={ | ||
"name": "Camera Focus", | ||
"version": "v1", | ||
"short_description": SHORT_DESCRIPTION, | ||
"long_description": LONG_DESCRIPTION, | ||
"license": "Apache-2.0", | ||
"block_type": "classical_computer_vision", | ||
} | ||
) | ||
|
||
image: Union[WorkflowImageSelector, StepOutputImageSelector] = Field( | ||
title="Input Image", | ||
description="The input image for this step.", | ||
examples=["$inputs.image", "$steps.cropping.crops"], | ||
validation_alias=AliasChoices("image", "images"), | ||
) | ||
|
||
@classmethod | ||
def describe_outputs(cls) -> List[OutputDefinition]: | ||
return [ | ||
OutputDefinition( | ||
name=OUTPUT_IMAGE_KEY, | ||
kind=[ | ||
BATCH_OF_IMAGES_KIND, | ||
], | ||
), | ||
OutputDefinition( | ||
name="focus_measure", | ||
kind=[ | ||
FLOAT_KIND, | ||
], | ||
), | ||
] | ||
|
||
@classmethod | ||
def get_execution_engine_compatibility(cls) -> Optional[str]: | ||
return ">=1.0.0,<2.0.0" | ||
|
||
|
||
class CameraFocusBlockV1(WorkflowBlock): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
|
||
@classmethod | ||
def get_manifest(cls) -> Type[CameraFocusManifest]: | ||
return CameraFocusManifest | ||
|
||
def run(self, image: WorkflowImageData, *args, **kwargs) -> BlockResult: | ||
# Calculate the Brenner measure | ||
brenner_image, brenner_value = calculate_brenner_measure(image.numpy_image) | ||
|
||
output = WorkflowImageData( | ||
parent_metadata=image.parent_metadata, | ||
workflow_root_ancestor_metadata=image.workflow_root_ancestor_metadata, | ||
numpy_image=brenner_image, | ||
) | ||
|
||
return { | ||
OUTPUT_IMAGE_KEY: output, | ||
"focus_measure": brenner_value, | ||
} | ||
|
||
|
||
def calculate_brenner_measure( | ||
input_image: np.ndarray, | ||
text_color: Tuple[int, int, int] = (255, 255, 255), | ||
text_thickness: int = 2, | ||
) -> Tuple[np.ndarray, float]: | ||
""" | ||
Brenner's focus measure. | ||
Parameters | ||
---------- | ||
input_image : np.ndarray | ||
The input image in grayscale. | ||
text_color : Tuple[int, int, int], optional | ||
The color of the text displaying the Brenner value, in BGR format. Default is white (255, 255, 255). | ||
text_thickness : int, optional | ||
The thickness of the text displaying the Brenner value. Default is 2. | ||
Returns | ||
------- | ||
Tuple[np.ndarray, float] | ||
The Brenner image and the Brenner value. | ||
""" | ||
# Convert image to grayscale if it has 3 channels | ||
if len(input_image.shape) == 3: | ||
input_image = cv2.cvtColor(input_image, cv2.COLOR_BGR2GRAY) | ||
|
||
# Convert image to 16-bit integer format | ||
converted_image = input_image.astype(np.int16) | ||
|
||
# Get the dimensions of the image | ||
height, width = converted_image.shape | ||
|
||
# Initialize two matrices for horizontal and vertical focus measures | ||
horizontal_diff = np.zeros((height, width)) | ||
vertical_diff = np.zeros((height, width)) | ||
|
||
# Calculate horizontal and vertical focus measures | ||
horizontal_diff[:, : width - 2] = np.clip( | ||
converted_image[:, 2:] - converted_image[:, :-2], 0, None | ||
) | ||
vertical_diff[: height - 2, :] = np.clip( | ||
converted_image[2:, :] - converted_image[:-2, :], 0, None | ||
) | ||
|
||
# Calculate final focus measure | ||
focus_measure = np.max((horizontal_diff, vertical_diff), axis=0) ** 2 | ||
|
||
# Convert focus measure matrix to 8-bit for visualization | ||
focus_measure_image = ((focus_measure / focus_measure.max()) * 255).astype(np.uint8) | ||
|
||
# Display the Brenner value on the top left of the image | ||
cv2.putText( | ||
focus_measure_image, | ||
f"Focus value: {focus_measure.mean():.2f}", | ||
(10, 30), | ||
cv2.FONT_HERSHEY_SIMPLEX, | ||
1, | ||
text_color, | ||
text_thickness, | ||
) | ||
|
||
return focus_measure_image, focus_measure.mean() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.