Skip to content

Commit

Permalink
Merge branch 'main' into 51-implementing-2-models
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxence Guindon authored Apr 8, 2024
2 parents f4060e1 + e370ee7 commit abffb0b
Show file tree
Hide file tree
Showing 4 changed files with 340 additions and 38 deletions.
1 change: 1 addition & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from collections import namedtuple
from cryptography.fernet import Fernet
import azure_storage.azure_storage_api as azure_storage_api

from custom_exceptions import (
DeleteDirectoryRequestError,
ListDirectoriesRequestError,
Expand Down
102 changes: 102 additions & 0 deletions model/color_palette.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
Contains the colors palettes uses to colors the boxes.
"""
import numpy as np

# Find color by name or hex code: https://www.color-name.com

# primary_colors
# Color name (in order):
# Alphabet Red, Nokia Blue, Discord Green,
# Cashmere Purple, Supreme Orange, Middle Yellow,
# Oak Brown, deep Pink, Simple Gray

primary_colors = {
"hex": (
"#ED1C24",
"#005AFF",
"#57F287",
"#664E88",
"#EA871E",
"#FFEB00",
"#87633A",
"#FF1493",
"#676765"),
"rgb": (
(237, 28, 36),
(0, 90, 255),
(87, 242, 135),
(102, 78, 136),
(234, 135, 30),
(255, 235, 0),
(135, 99, 58),
(255, 20, 147),
(103, 103, 101))
}

# light_colors
# Color name (in order):
# Light Baby Blue, Light Red, Whole Foods Green,
# Light Purple Blue, Clear Orange, Mid Yellow,
# Bakery Brown, Lotus Pink, Neutral Light Gray

light_colors = {
"hex": (
"#A6DAF4",
"#FF7F7F",
"#006F46",
"#BD86D2",
"#FCBF8D",
"#DDD59D",
"#BF8654",
"#EAD0D6",
"#CACACA"
),
"rgb": (
(166, 218, 244),
(255, 127, 127),
(0, 111, 70),
(189, 134, 210),
(252, 191, 141),
(221, 213, 157),
(191, 134, 84),
(234, 208, 214),
(202, 202, 202)
)
}

def mixing_palettes(dict1: dict, dict2: dict) -> dict:
"""
Mixes two color palettes together.
"""
if dict1.keys() != dict2.keys():
raise ValueError("The keys of the two dictionaries must be the same.")

return {key: dict1[key] + dict2[key] for key in dict1.keys()}

def shades_colors(base_color: str | tuple, num_shades = 5, lighten_factor = 0.15, darken_factor = 0.1) -> tuple:
"""
Generate shades of a color based on the base color.
Args:
base_color (str | tuple): Hex color value (e.g., "#B4FBB8") or RGB tuple.
num_shades (int): Number of shades to generate (default is 5).
lighten_factor (float): Factor to lighten the base color (0 to 1, default is 0.15).
darken_factor (float): Factor to darken the base color (0 to 1, default is 0.1).
Returns:
tuple: RGB tuples representing the shade color.
"""
def hex_to_rgb(hex_value):
hex_value = hex_value.lstrip("#")
return tuple(int(hex_value[i:i + len(hex_value) // 3], 16) for i in range(0, len(hex_value), len(hex_value) // 3))

is_hex = base_color[0]
if is_hex == "#":
base_color = hex_to_rgb(base_color)

base_rgb = np.array(base_color)
shades = [base_rgb * (1 - lighten_factor * i) * (1 - darken_factor * (num_shades - i)) for i in range(num_shades)]
color = [tuple(base_rgb - shade.astype(int)) for shade in shades][-1]

return color if is_hex != '#' else f"#{color[0]:02x}{color[1]:02x}{color[2]:02x}"
146 changes: 108 additions & 38 deletions model/inference.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,63 @@

"""
This file contains the generic inference function that processes the data at the end
of a given pipeline.
This module provides functions to process the inference results from a given
pipelines.
It returns the processed inference results with additional information such as
overlapping boxes, label occurrence, and colors for each species found.
The colors can be returned in HEX or RGB format depending on the frontend preference.
"""

import numpy as np

from custom_exceptions import ProcessInferenceResultError
from model.color_palette import primary_colors, light_colors, mixing_palettes, shades_colors


def generator(list_length):
for i in range(list_length):
yield i

async def process_inference_results(data, imageDims):

async def process_inference_results(
data: dict,
imageDims: list[int, int],
area_ratio: float = 0.5,
color_format: str = "hex"
) -> dict:
"""
processes the pipeline (last output) inference results to add additional attributes
to the inference results that are used in the frontend
Process the inference results by performing various operations on the data.
Indicate if there are overlapping boxes and calculates the label
occurrence. Boxes can overlap if their common area is greater than the
area_ratio (default = 0.5) of the area of each box.
Args:
data (dict): The inference result data.
imageDims (tuple): The dimensions of the image.
area_ratio (float): The area ratio of a box to consider in the box
overlap claculation.
color_format (str): Specified the format representation of the color.
Support hex and rgb.
Returns:
dict: The processed inference result data.
Raises:
ProcessInferenceResultError: If there is an error processing the
inference results.
"""
try:
data = data
for i, box in enumerate(data[0]["boxes"]):
# set default overlapping attribute to false for each box
data[0]["boxes"][i]["overlapping"] = False
# set default overlappingindices to empty array for each box
data[0]["boxes"][i]["overlappingIndices"] = []
boxes = data[0]['boxes']
colors = mixing_palettes(primary_colors, light_colors).get(color_format)

# Perform operations on each box in the data
for i, box in enumerate(boxes):
# Set default overlapping attribute to false for each box
boxes[i]["overlapping"] = False
# Set default overlapping indices to empty array for each box
boxes[i]["overlappingIndices"] = []

# Perform calculations on box coordinates
box["box"]["bottomX"] = int(
np.clip(box["box"]["bottomX"] * imageDims[0], 5, imageDims[0] - 5)
)
Expand All @@ -32,42 +70,74 @@ async def process_inference_results(data, imageDims):
box["box"]["topY"] = int(
np.clip(box["box"]["topY"] * imageDims[1], 5, imageDims[1] - 5)
)
# check if there any overlapping boxes, if so, put the lower score box
# in the overlapping key
for i, box in enumerate(data[0]["boxes"]):
for j, box2 in enumerate(data[0]["boxes"]):

# Check if there are any overlapping boxes, if so, put the lower score
# box in the overlapping key
for i, box in enumerate(boxes):
for j, box2 in enumerate(boxes):
if j > i:
if (
box["box"]["bottomX"] >= box2["box"]["topX"]
and box["box"]["bottomY"] >= box2["box"]["topY"]
and box["box"]["topX"] <= box2["box"]["bottomX"]
and box["box"]["topY"] <= box2["box"]["bottomY"]
):
if box["score"] >= box2["score"]:
data[0]["boxes"][j]["overlapping"] = True
data[0]["boxes"][i]["overlappingIndices"].append(j + 1)
# Calculate the common region of the two boxes to determine
# if they are overlapping
area_box = (box["box"]["bottomX"] - box["box"]["topX"]) \
* (box["box"]["bottomY"] - box["box"]["topY"])
area_candidate = (box2["box"]["bottomX"] - box2["box"]["topX"]) \
* (box2["box"]["bottomY"] - box2["box"]["topY"])

intersection_topX = max(
box["box"]["topX"], box2["box"]["topX"])
intersection_topY = max(
box["box"]["topY"], box2["box"]["topY"])
intersection_bottomX = min(
box["box"]["bottomX"], box2["box"]["bottomX"])
intersection_bottomY = min(
box["box"]["bottomY"], box2["box"]["bottomY"])

width = max(0, intersection_bottomX - intersection_topX)
height = max(0, intersection_bottomY - intersection_topY)

common_area = width * height

if common_area >= area_box * area_ratio \
and common_area >= area_candidate * area_ratio:
# box2 is the lower score box
if box2["score"] < box["score"]:
boxes[j]["overlapping"] = True
boxes[i]["overlappingIndices"].append(j + 1)
box2["box"]["bottomX"] = box["box"]["bottomX"]
box2["box"]["bottomY"] = box["box"]["bottomY"]
box2["box"]["topX"] = box["box"]["topX"]
box2["box"]["topY"] = box["box"]["topY"]
else:
data[0]["boxes"][i]["overlapping"] = True
data[0]["boxes"][i]["overlappingIndices"].append(j + 1)
# box is the lower score box
elif box["score"] < box2["score"]:
boxes[i]["overlapping"] = True
boxes[i]["overlappingIndices"].append(j + 1)
box["box"]["bottomX"] = box2["box"]["bottomX"]
box["box"]["bottomY"] = box2["box"]["bottomY"]
box["box"]["topX"] = box2["box"]["topX"]
box["box"]["topY"] = box2["box"]["topY"]
labelOccurrence = {}
for i, box in enumerate(data[0]["boxes"]):
if box["label"] not in labelOccurrence:
labelOccurrence[box["label"]] = 1

# Calculate label occurrence
gen = generator(i) # Number of individual seed (boxes)
label_occurrence = {}
label_colors = {}
for i, box in enumerate(boxes):
if i >= len(colors):
colors = colors + (shades_colors(colors[next(gen)]),)

if box["label"] not in label_occurrence:
label_occurrence[box["label"]] = 1
label_colors[box["label"]] = colors[i]
box["color"] = colors[i]
else:
labelOccurrence[box["label"]] += 1
data[0]["labelOccurrence"] = labelOccurrence
# add totalBoxes attribute to the inference results
data[0]["totalBoxes"] = sum(1 for box in data[0]["boxes"])
label_occurrence[box["label"]] += 1
color = label_colors[box["label"]]
box["color"] = color

data[0]["labelOccurrence"] = label_occurrence
data[0]["totalBoxes"] = sum(1 for _ in data[0]["boxes"])

return data

except ProcessInferenceResultError as error:
except (KeyError, TypeError, IndexError) as error:
print(error)
return False
raise ProcessInferenceResultError("Error processing inference results") from error
Loading

0 comments on commit abffb0b

Please sign in to comment.