diff --git a/.github/workflows/model_servers.yaml b/.github/workflows/model_servers.yaml index 6997c44cd..a6bd31adc 100644 --- a/.github/workflows/model_servers.yaml +++ b/.github/workflows/model_servers.yaml @@ -44,6 +44,12 @@ jobs: directory: whispercpp platforms: linux/amd64,linux/arm64 no_gpu: 1 + - image_name: object_detection_python + model: facebook-detr-resnet-101 + flavor: base + directory: object_detection_python + platforms: linux/amd64 + no_gpu: 1 runs-on: ubuntu-latest permissions: contents: read diff --git a/model_servers/object_detection_python/Makefile b/model_servers/object_detection_python/Makefile index 22bc7c04a..9f41c5947 100644 --- a/model_servers/object_detection_python/Makefile +++ b/model_servers/object_detection_python/Makefile @@ -10,13 +10,12 @@ VULKAN_IMAGE := $(REGISTRY)/$(REGISTRY_ORG)/$(COMPONENT)/$(APP)_vulkan:latest MODEL_NAME ?= facebook/detr-resnet-101 -MODELS_DIR := /models +MODELS_DIR := /app/models .PHONY: run run: cd ../../models && \ - podman run -it -d -p $(PORT):$(PORT) -v ./$(MODEL_NAME):$(MODELS_DIR)/$(MODEL_NAME):$(BIND_MOUNT_OPTIONS) -e MODEL_PATH=$(MODELS_DIR)/$(MODEL_NAME) -e HOST=0.0.0.0 -e PORT=$(PORT) $(IMAGE) - + podman run -it -d -p $(PORT):$(PORT) -v ./$(MODEL_NAME)/:$(MODELS_DIR)/$(MODEL_NAME):$(BIND_MOUNT_OPTIONS) -e MODEL_PATH=$(MODELS_DIR)/$(MODEL_NAME) -e HOST=0.0.0.0 -e PORT=$(PORT) $(IMAGE) .PHONY: all all: build download-model-facebook-detr-resnet-101 run @@ -25,11 +24,11 @@ all: build download-model-facebook-detr-resnet-101 run download-model-facebook-detr-resnet-101: pip install -r ../../convert_models/requirements.txt cd ../../convert_models/ && \ - python3.11 download_huggingface.py -m facebook/detr-resnet-101 + python3 download_huggingface.py -m facebook/detr-resnet-101 cp -r ../../convert_models/converted_models/facebook ../../models/ .PHONY: test test: $(MAKE) download-model-facebook-detr-resnet-101 - ln -s ../../models/detr-resnet-101 ./ - PORT=$(PORT) MODEL_NAME=$(MODEL_NAME) MODELS_PATH=$(MODELS_PATH) IMAGE=$(IMAGE) PULL_ALWAYS=0 pytest -s -vvv + cp -r ../../models/facebook ./ + REGISTRY=$(REGISTRY) MODEL_NAME=$(MODEL_NAME) MODEL_PATH=$(MODELS_DIR)/$(MODEL_NAME) IMAGE=$(IMAGE) PORT=$(PORT) pytest -s -vvv diff --git a/model_servers/object_detection_python/base/Containerfile b/model_servers/object_detection_python/base/Containerfile index c788aa14d..eead8fd3a 100644 --- a/model_servers/object_detection_python/base/Containerfile +++ b/model_servers/object_detection_python/base/Containerfile @@ -1,9 +1,8 @@ FROM registry.access.redhat.com/ubi9/python-311:1-52.1712567218 ARG PORT=8000 WORKDIR /app -COPY src/requirements.txt . +COPY src . RUN pip install --upgrade pip && \ pip install --no-cache-dir --upgrade -r requirements.txt -COPY src/object_detection_server.py . EXPOSE $PORT ENTRYPOINT [ "uvicorn", "object_detection_server:app", "--host", "0.0.0.0" ] \ No newline at end of file diff --git a/model_servers/object_detection_python/src/object_detection_server.py b/model_servers/object_detection_python/src/object_detection_server.py index a0e339d54..7e71ee913 100644 --- a/model_servers/object_detection_python/src/object_detection_server.py +++ b/model_servers/object_detection_python/src/object_detection_server.py @@ -11,7 +11,7 @@ app = FastAPI() -model = os.getenv("MODEL_PATH", default="facebook/detr-resnet-101") +model = os.getenv("MODEL_PATH", default="/app/models/facebook/detr-resnet-101") revision = os.getenv("MODEL_REVISION", default="no_timm") if os.path.isfile(model): @@ -30,6 +30,10 @@ class Item(BaseModel): image: bytes +@app.get("/test-alive") +def tests_alive(): + return {"alive": True} + @app.post("/detection") def detection(item: Item): b64_image = item.image diff --git a/model_servers/object_detection_python/src/requirements.txt b/model_servers/object_detection_python/src/requirements.txt index af3b17fed..c74101262 100644 --- a/model_servers/object_detection_python/src/requirements.txt +++ b/model_servers/object_detection_python/src/requirements.txt @@ -34,3 +34,12 @@ transformers==4.39.3 typing_extensions==4.11.0 urllib3==2.2.1 uvicorn==0.29.0 + +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/model_servers/object_detection_python/tests/__init__.py b/model_servers/object_detection_python/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/model_servers/object_detection_python/tests/conftest.py b/model_servers/object_detection_python/tests/conftest.py new file mode 100644 index 000000000..a73c5e290 --- /dev/null +++ b/model_servers/object_detection_python/tests/conftest.py @@ -0,0 +1,57 @@ +import pytest_container +import os +import platform + +if not 'IMAGE' in os.environ: + IMAGE = 'ghcr.io/containers/model_servers/object_detection_python:latest' +else: + IMAGE = os.environ['IMAGE'] + +if not 'MODEL_NAME' in os.environ: + MODEL_NAME = 'facebook/detr-resnet-101' +else: + MODEL_NAME = os.environ['MODEL_NAME'] + +if not 'MODELS_DIR' in os.environ: + MODELS_DIR = "/app/models" +else: + MODELS_DIR = os.environ['MODELS_DIR'] + +MODEL_PATH = f"{MODELS_DIR}/{MODEL_NAME}" + +if not 'PORT' in os.environ: + PORT = 8000 +else: + PORT = os.environ['PORT'] + try: + PORT = int(PORT) + except: + PORT = 8000 + +MS = pytest_container.Container( + url=f"containers-storage:{IMAGE}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"{MODEL_PATH}", + host_path=f"./{MODEL_NAME}", + flags=["ro"] + ) + ], + extra_environment_variables={ + "MODEL_PATH": f"{MODEL_PATH}", + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) diff --git a/model_servers/object_detection_python/tests/requirements.txt b/model_servers/object_detection_python/tests/requirements.txt new file mode 100644 index 000000000..22fc97f27 --- /dev/null +++ b/model_servers/object_detection_python/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/model_servers/object_detection_python/tests/test_alive.py b/model_servers/object_detection_python/tests/test_alive.py new file mode 100644 index 000000000..32ef2736f --- /dev/null +++ b/model_servers/object_detection_python/tests/test_alive.py @@ -0,0 +1,13 @@ +import pytest_container +from .conftest import MS +import tenacity +import os + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl -X GET http://localhost:{auto_container.forwarded_ports[0].host_port}/test-alive").stdout.strip() diff --git a/recipes/computer_vision/object_detection/client/Containerfile b/recipes/computer_vision/object_detection/app/Containerfile similarity index 100% rename from recipes/computer_vision/object_detection/client/Containerfile rename to recipes/computer_vision/object_detection/app/Containerfile diff --git a/recipes/computer_vision/object_detection/client/object_detection_client.py b/recipes/computer_vision/object_detection/app/object_detection_client.py similarity index 92% rename from recipes/computer_vision/object_detection/client/object_detection_client.py rename to recipes/computer_vision/object_detection/app/object_detection_client.py index e843145f2..5f718604b 100644 --- a/recipes/computer_vision/object_detection/client/object_detection_client.py +++ b/recipes/computer_vision/object_detection/app/object_detection_client.py @@ -6,7 +6,7 @@ import io st.title("🕵️‍♀️ Object Detection") -endpoint =os.getenv("MODEL_ENDPOINT", default = "http://0.0.0.0:8000") +endpoint =os.getenv("MODEL_ENDPOINT", default = "http://0.0.0.0:8000/detection") headers = {"accept": "application/json", "Content-Type": "application/json"} image = st.file_uploader("Upload Image") @@ -27,7 +27,7 @@ img_bytes = bytes_io.getvalue() b64_image = base64.b64encode(img_bytes).decode('utf-8') data = {'image': b64_image} - response = requests.post(f'{endpoint}/detection', headers=headers,json=data, verify=False) + response = requests.post(f'{endpoint}', headers=headers,json=data, verify=False) # parse response and display outputs response_json = response.json() image = response_json["image"] diff --git a/recipes/computer_vision/object_detection/app/requirements.txt b/recipes/computer_vision/object_detection/app/requirements.txt new file mode 100644 index 000000000..f49b86687 --- /dev/null +++ b/recipes/computer_vision/object_detection/app/requirements.txt @@ -0,0 +1,40 @@ +altair==5.3.0 +attrs==23.2.0 +blinker==1.7.0 +cachetools==5.3.3 +certifi==2024.2.2 +charset-normalizer==3.3.2 +click==8.1.7 +gitdb==4.0.11 +GitPython==3.1.43 +idna==3.7 +Jinja2==3.1.3 +jsonschema==4.21.1 +jsonschema-specifications==2023.12.1 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +mdurl==0.1.2 +numpy==1.26.4 +packaging==24.0 +pandas==2.2.2 +pillow==10.3.0 +protobuf==4.25.3 +pyarrow==15.0.2 +pydeck==0.8.1b0 +Pygments==2.17.2 +python-dateutil==2.9.0.post0 +pytz==2024.1 +referencing==0.34.0 +requests==2.31.0 +rich==13.7.1 +rpds-py==0.18.0 +six==1.16.0 +smmap==5.0.1 +streamlit==1.33.0 +tenacity==8.2.3 +toml==0.10.2 +toolz==0.12.1 +tornado==6.4 +typing_extensions==4.11.0 +tzdata==2024.1 +urllib3==2.2.1 diff --git a/recipes/computer_vision/object_detection/client/requirements.txt b/recipes/computer_vision/object_detection/client/requirements.txt deleted file mode 100644 index 7b2195a0d..000000000 --- a/recipes/computer_vision/object_detection/client/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -streamlit -requests -pillow \ No newline at end of file diff --git a/recipes/computer_vision/object_detection/tests/__init__.py b/recipes/computer_vision/object_detection/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/recipes/computer_vision/object_detection/tests/conftest.py b/recipes/computer_vision/object_detection/tests/conftest.py new file mode 100644 index 000000000..eebb15a97 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/conftest.py @@ -0,0 +1,54 @@ +import pytest_container +import pytest +import os +import logging +import platform + +if 'PORT' not in os.environ: + PORT = 8000 +else: + PORT = os.environ['PORT'] + try: + PORT = int(PORT) + except: + PORT = 8000 + +if 'IMAGE' not in os.environ: + IMAGE = 'ghcr.io/containers/model_servers/object_detection_python:latest' +else: + IMAGE = os.environ['IMAGE'] + +MODEL_NAME=os.environ['MODEL_NAME'] +MODEL_PATH=os.environ['MODEL_PATH'] + +BIND_MOUNT_OPTIONS = 'ro' + +MS = pytest_container.Container( + url=f"containers-storage:{IMAGE}", + volume_mounts=[ + pytest_container.container.BindMount( + container_path=f"{MODEL_PATH}", + host_path=f"./{MODEL_NAME}", + flags=[BIND_MOUNT_OPTIONS] + ) + ], + extra_environment_variables={ + "MODEL_NAME": f"{MODEL_NAME}", + "MODEL_PATH": f"{MODEL_PATH}", + "HOST": "0.0.0.0", + "PORT": f"{PORT}" + }, + forwarded_ports=[ + pytest_container.PortForwarding( + container_port=PORT, + host_port=PORT + ) + ], + ) + +def pytest_addoption(parser): + pytest_container.add_logging_level_options(parser) + + +def pytest_generate_tests(metafunc): + pytest_container.auto_container_parametrize(metafunc) diff --git a/recipes/computer_vision/object_detection/tests/requirements.txt b/recipes/computer_vision/object_detection/tests/requirements.txt new file mode 100644 index 000000000..22fc97f27 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/requirements.txt @@ -0,0 +1,8 @@ +pip==24.0 +pytest-container==0.4.0 +pytest-selenium==4.1.0 +pytest-testinfra==10.1.0 +pytest==8.1.1 +requests==2.31.0 +selenium==4.19.0 +tenacity==8.2.3 diff --git a/recipes/computer_vision/object_detection/tests/test_alive.py b/recipes/computer_vision/object_detection/tests/test_alive.py new file mode 100644 index 000000000..226aac1c0 --- /dev/null +++ b/recipes/computer_vision/object_detection/tests/test_alive.py @@ -0,0 +1,12 @@ +import pytest_container +from .conftest import MS +import tenacity + +CONTAINER_IMAGES = [MS] + +def test_etc_os_release_present(auto_container: pytest_container.container.ContainerData): + assert auto_container.connection.file("/etc/os-release").exists + +@tenacity.retry(stop=tenacity.stop_after_attempt(5), wait=tenacity.wait_exponential()) +def test_alive(auto_container: pytest_container.container.ContainerData, host): + host.run_expect([0],f"curl http://localhost:{auto_container.forwarded_ports[0].host_port}",).stdout.strip()