Skip to content

Commit 0f24572

Browse files
authored
Detector (#10)
* adding detector * integration tests passing * added unit tests * revoking mypy cripple * found that batched detection does not work * added batch testing * addressed pr comments
1 parent 8b953b7 commit 0f24572

File tree

14 files changed

+1305
-102
lines changed

14 files changed

+1305
-102
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
redis_data/appendonlydir/
22
redis_data/dump.rdb
3-
logs/
3+
logs/
4+
.cache

directai_fastapi/Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
FROM pytorch/pytorch:2.2.0-cuda12.1-cudnn8-runtime
1+
FROM pytorch/pytorch:2.2.0-cuda12.1-cudnn8-devel
22
WORKDIR /directai_fastapi
33

44
RUN apt-get update
55
RUN apt-get install libgl1 libglib2.0-0 libsm6 libxrender1 libxext6 -y
66

7+
RUN apt-get install git -y
8+
79
RUN apt-get install cmake build-essential -y
810
COPY requirements.txt .
911
RUN pip install -r requirements.txt

directai_fastapi/modeling/distributed_backend.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,59 @@
99
from typing import List
1010
from pydantic_models import ClassifierResponse, SingleDetectionResponse
1111
from modeling.image_classifier import ZeroShotImageClassifierWithFeedback
12+
from modeling.object_detector import ZeroShotObjectDetectorWithFeedback
1213

1314

1415
serve.start(http_options={"port": 8100})
1516

1617

1718
@serve.deployment
1819
class ObjectDetector:
19-
async def __call__(self, image: Image.Image) -> List[List[SingleDetectionResponse]]:
20-
# Placeholder implementation
21-
single_detection = {
22-
"tlbr": [0.0, 0.0, 1.0, 1.0],
23-
"score": random.random(),
24-
"class": "dog",
25-
}
26-
sdr = SingleDetectionResponse.parse_obj(single_detection)
27-
return [[sdr]]
20+
def __init__(self) -> None:
21+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
22+
self.model = ZeroShotObjectDetectorWithFeedback(device=device)
23+
24+
async def __call__(
25+
self,
26+
image: bytes,
27+
labels: list[str],
28+
inc_sub_labels_dict: dict[str, list[str]],
29+
exc_sub_labels_dict: dict[str, list[str]] | None = None,
30+
label_conf_thres: dict[str, float] | None = None,
31+
augment_examples: bool = True,
32+
nms_thre: float = 0.4,
33+
run_class_agnostic_nms: bool = False,
34+
) -> list[SingleDetectionResponse]:
35+
with torch.inference_mode(), torch.autocast(str(self.model.device)):
36+
batched_predicted_boxes = self.model(
37+
image,
38+
labels=labels,
39+
inc_sub_labels_dict=inc_sub_labels_dict,
40+
exc_sub_labels_dict=exc_sub_labels_dict,
41+
label_conf_thres=label_conf_thres,
42+
augment_examples=augment_examples,
43+
nms_thre=nms_thre,
44+
run_class_agnostic_nms=run_class_agnostic_nms,
45+
)
46+
47+
# since we are processing a single image, the output has batch size 1, so we can safely index into it
48+
per_label_boxes = batched_predicted_boxes[0]
49+
50+
# predicted_boxes is a list in order of labels, with each box of the form [x1, y1, x2, y2, confidence]
51+
detection_responses = []
52+
for label, boxes in zip(labels, per_label_boxes):
53+
for detection in boxes:
54+
det_dict = {
55+
"tlbr": detection[:4].tolist(),
56+
"score": detection[4].item(),
57+
"class_": label,
58+
}
59+
single_detection_response = SingleDetectionResponse.parse_obj(
60+
det_dict
61+
)
62+
detection_responses.append(single_detection_response)
63+
64+
return detection_responses
2865

2966

3067
@serve.deployment
@@ -41,8 +78,7 @@ async def __call__(
4178
exc_sub_labels_dict: dict[str, list[str]] | None = None,
4279
augment_examples: bool = True,
4380
) -> ClassifierResponse:
44-
45-
with torch.no_grad(), torch.autocast(str(self.model.device)):
81+
with torch.inference_mode(), torch.autocast(str(self.model.device)):
4682
raw_scores = self.model(
4783
image,
4884
labels=labels,

directai_fastapi/modeling/image_classifier.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from modeling.tensor_utils import (
99
batch_encode_cache_missed_list_elements,
1010
image_bytes_to_tensor,
11+
squish_labels,
1112
)
1213
from modeling.prompt_templates import noop_hypothesis_formats, many_hypothesis_formats
1314
from lru import LRU
@@ -134,35 +135,6 @@ def encode_text(self, text: list[str], augment: bool = True) -> torch.Tensor:
134135
self.not_augmented_label_encoding_cache,
135136
)
136137

137-
def squish_labels(
138-
self,
139-
labels: list[str],
140-
inc_sub_labels_dict: dict[str, list[str]],
141-
exc_sub_labels_dict: dict[str, list[str]],
142-
) -> tuple[list[str], dict[str, int]]:
143-
# build one list of labels to encode, without duplicates
144-
# and lists / dicts containing the indices of each label
145-
# and the indices of each label's sub-labels
146-
all_labels_to_inds: dict[str, int] = {}
147-
all_labels = []
148-
149-
for label in labels:
150-
inc_subs = inc_sub_labels_dict.get(label)
151-
if inc_subs is not None:
152-
for inc_sub in inc_subs:
153-
if inc_sub not in all_labels_to_inds:
154-
all_labels_to_inds[inc_sub] = len(all_labels_to_inds)
155-
all_labels.append(inc_sub)
156-
157-
exc_subs = exc_sub_labels_dict.get(label)
158-
if exc_subs is not None:
159-
for exc_sub in exc_subs:
160-
if exc_sub not in all_labels_to_inds:
161-
all_labels_to_inds[exc_sub] = len(all_labels_to_inds)
162-
all_labels.append(exc_sub)
163-
164-
return all_labels, all_labels_to_inds
165-
166138
def forward(
167139
self,
168140
image: torch.Tensor | bytes,
@@ -189,7 +161,7 @@ def forward(
189161
label: excs for label, excs in exc_sub_labels_dict.items() if len(excs) > 0
190162
}
191163

192-
all_labels, all_labels_to_inds = self.squish_labels(
164+
all_labels, all_labels_to_inds = squish_labels(
193165
labels, inc_sub_labels_dict, exc_sub_labels_dict
194166
)
195167
text_features = self.encode_text(all_labels, augment=augment_examples)

0 commit comments

Comments
 (0)