Spaces:
Runtime error
Runtime error
Soutrik
commited on
Commit
•
1b0bd15
1
Parent(s):
6053557
added: client server on docker compose cpu tested
Browse files- Dockerfile +7 -0
- artifacts/image_prediction.png +0 -0
- docker-compose-old.yaml +74 -0
- docker-compose.yaml +29 -10
- run_client.sh +5 -0
- src/client.py +46 -36
- src/server.py +2 -2
Dockerfile
CHANGED
@@ -29,6 +29,10 @@ RUN --mount=type=cache,target=/tmp/poetry_cache poetry install --only main --no-
|
|
29 |
# Stage 2: Runtime environment
|
30 |
FROM python:3.10.15-slim as runner
|
31 |
|
|
|
|
|
|
|
|
|
32 |
# Copy application source code and necessary files
|
33 |
COPY src /app/src
|
34 |
COPY configs /app/configs
|
@@ -38,6 +42,9 @@ COPY main.py /app/main.py
|
|
38 |
# Copy virtual environment from the builder stage
|
39 |
COPY --from=builder /app/.venv /app/.venv
|
40 |
|
|
|
|
|
|
|
41 |
# Set the working directory to /app
|
42 |
WORKDIR /app
|
43 |
|
|
|
29 |
# Stage 2: Runtime environment
|
30 |
FROM python:3.10.15-slim as runner
|
31 |
|
32 |
+
# Install curl for health check script
|
33 |
+
RUN apt-get update && apt-get install -y --no-install-recommends curl && \
|
34 |
+
apt-get clean && rm -rf /var/lib/apt/lists/*
|
35 |
+
|
36 |
# Copy application source code and necessary files
|
37 |
COPY src /app/src
|
38 |
COPY configs /app/configs
|
|
|
42 |
# Copy virtual environment from the builder stage
|
43 |
COPY --from=builder /app/.venv /app/.venv
|
44 |
|
45 |
+
# Copy the client files
|
46 |
+
COPY run_client.sh /app/run_client.sh
|
47 |
+
|
48 |
# Set the working directory to /app
|
49 |
WORKDIR /app
|
50 |
|
artifacts/image_prediction.png
DELETED
Binary file (390 kB)
|
|
docker-compose-old.yaml
ADDED
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
version: '3.8'
|
2 |
+
|
3 |
+
services:
|
4 |
+
train:
|
5 |
+
build:
|
6 |
+
context: .
|
7 |
+
command: |
|
8 |
+
python -m src.train_optuna_callbacks experiment=catdog_experiment ++task_name=train ++train=True ++test=False && \
|
9 |
+
python -m src.create_artifacts && \
|
10 |
+
touch ./checkpoints/train_done.flag
|
11 |
+
volumes:
|
12 |
+
- ./data:/app/data
|
13 |
+
- ./checkpoints:/app/checkpoints
|
14 |
+
- ./artifacts:/app/artifacts
|
15 |
+
- ./logs:/app/logs
|
16 |
+
environment:
|
17 |
+
- PYTHONUNBUFFERED=1
|
18 |
+
- PYTHONPATH=/app
|
19 |
+
shm_size: '4g'
|
20 |
+
networks:
|
21 |
+
- default
|
22 |
+
env_file:
|
23 |
+
- .env
|
24 |
+
|
25 |
+
eval:
|
26 |
+
build:
|
27 |
+
context: .
|
28 |
+
command: |
|
29 |
+
sh -c 'while [ ! -f /app/checkpoints/train_done.flag ]; do sleep 10; done && python -m src.train_optuna_callbacks experiment=catdog_experiment ++task_name=test ++train=False ++test=True'
|
30 |
+
volumes:
|
31 |
+
- ./data:/app/data
|
32 |
+
- ./checkpoints:/app/checkpoints
|
33 |
+
- ./artifacts:/app/artifacts
|
34 |
+
- ./logs:/app/logs
|
35 |
+
environment:
|
36 |
+
- PYTHONUNBUFFERED=1
|
37 |
+
- PYTHONPATH=/app
|
38 |
+
shm_size: '4g'
|
39 |
+
networks:
|
40 |
+
- default
|
41 |
+
env_file:
|
42 |
+
- .env
|
43 |
+
depends_on:
|
44 |
+
- train
|
45 |
+
|
46 |
+
inference:
|
47 |
+
build:
|
48 |
+
context: .
|
49 |
+
command: |
|
50 |
+
sh -c 'while [ ! -f /app/checkpoints/train_done.flag ]; do sleep 10; done && python -m src.infer experiment=catdog_experiment'
|
51 |
+
volumes:
|
52 |
+
- ./data:/app/data
|
53 |
+
- ./checkpoints:/app/checkpoints
|
54 |
+
- ./artifacts:/app/artifacts
|
55 |
+
- ./logs:/app/logs
|
56 |
+
environment:
|
57 |
+
- PYTHONUNBUFFERED=1
|
58 |
+
- PYTHONPATH=/app
|
59 |
+
shm_size: '4g'
|
60 |
+
networks:
|
61 |
+
- default
|
62 |
+
env_file:
|
63 |
+
- .env
|
64 |
+
depends_on:
|
65 |
+
- train
|
66 |
+
|
67 |
+
volumes:
|
68 |
+
data:
|
69 |
+
checkpoints:
|
70 |
+
artifacts:
|
71 |
+
logs:
|
72 |
+
|
73 |
+
networks:
|
74 |
+
default:
|
docker-compose.yaml
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
version: '3.8'
|
2 |
-
|
3 |
services:
|
4 |
train:
|
5 |
build:
|
@@ -21,7 +19,7 @@ services:
|
|
21 |
- default
|
22 |
env_file:
|
23 |
- .env
|
24 |
-
|
25 |
eval:
|
26 |
build:
|
27 |
context: .
|
@@ -40,14 +38,13 @@ services:
|
|
40 |
- default
|
41 |
env_file:
|
42 |
- .env
|
43 |
-
|
44 |
-
- train
|
45 |
|
46 |
-
|
47 |
build:
|
48 |
context: .
|
49 |
command: |
|
50 |
-
sh -c 'while [ ! -f /app/checkpoints/train_done.flag ]; do sleep 10; done && python -m src.
|
51 |
volumes:
|
52 |
- ./data:/app/data
|
53 |
- ./checkpoints:/app/checkpoints
|
@@ -56,14 +53,36 @@ services:
|
|
56 |
environment:
|
57 |
- PYTHONUNBUFFERED=1
|
58 |
- PYTHONPATH=/app
|
|
|
59 |
shm_size: '4g'
|
60 |
networks:
|
61 |
- default
|
62 |
env_file:
|
63 |
-
- .env
|
64 |
-
|
65 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
|
|
|
67 |
volumes:
|
68 |
data:
|
69 |
checkpoints:
|
|
|
|
|
|
|
1 |
services:
|
2 |
train:
|
3 |
build:
|
|
|
19 |
- default
|
20 |
env_file:
|
21 |
- .env
|
22 |
+
|
23 |
eval:
|
24 |
build:
|
25 |
context: .
|
|
|
38 |
- default
|
39 |
env_file:
|
40 |
- .env
|
41 |
+
|
|
|
42 |
|
43 |
+
server:
|
44 |
build:
|
45 |
context: .
|
46 |
command: |
|
47 |
+
sh -c 'while [ ! -f /app/checkpoints/train_done.flag ]; do sleep 10; done && python -m src.server'
|
48 |
volumes:
|
49 |
- ./data:/app/data
|
50 |
- ./checkpoints:/app/checkpoints
|
|
|
53 |
environment:
|
54 |
- PYTHONUNBUFFERED=1
|
55 |
- PYTHONPATH=/app
|
56 |
+
- SERVER_URL=http://localhost:8080
|
57 |
shm_size: '4g'
|
58 |
networks:
|
59 |
- default
|
60 |
env_file:
|
61 |
+
- .env
|
62 |
+
ports:
|
63 |
+
- "8080:8080"
|
64 |
+
|
65 |
+
client:
|
66 |
+
build:
|
67 |
+
context: .
|
68 |
+
command: |
|
69 |
+
sh -c 'while [ ! -f /app/checkpoints/train_done.flag ]; do sleep 10; done && ./run_client.sh'
|
70 |
+
volumes:
|
71 |
+
- ./data:/app/data
|
72 |
+
- ./checkpoints:/app/checkpoints
|
73 |
+
- ./artifacts:/app/artifacts
|
74 |
+
- ./logs:/app/logs
|
75 |
+
environment:
|
76 |
+
- PYTHONUNBUFFERED=1
|
77 |
+
- PYTHONPATH=/app
|
78 |
+
- SERVER_URL=http://server:8080
|
79 |
+
shm_size: '4g'
|
80 |
+
networks:
|
81 |
+
- default
|
82 |
+
env_file:
|
83 |
+
- .env
|
84 |
|
85 |
+
|
86 |
volumes:
|
87 |
data:
|
88 |
checkpoints:
|
run_client.sh
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Run the client script
|
4 |
+
echo "Running the client script..."
|
5 |
+
python -m src.client
|
src/client.py
CHANGED
@@ -1,3 +1,4 @@
|
|
|
|
1 |
import requests
|
2 |
from urllib.request import urlopen
|
3 |
import base64
|
@@ -8,45 +9,52 @@ def fetch_image(url):
|
|
8 |
"""
|
9 |
Fetch image data from a URL.
|
10 |
"""
|
11 |
-
|
|
|
|
|
|
|
|
|
12 |
|
13 |
|
14 |
def encode_image_to_base64(img_data):
|
15 |
"""
|
16 |
Encode image bytes to a base64 string.
|
17 |
"""
|
18 |
-
return base64.b64encode(img_data).decode("utf-8")
|
19 |
-
|
20 |
-
|
21 |
-
def send_prediction_request(base64_image, server_url):
|
22 |
-
"""
|
23 |
-
Send a single base64 image to the prediction API and retrieve predictions.
|
24 |
-
"""
|
25 |
try:
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
return None
|
31 |
|
32 |
|
33 |
-
def
|
34 |
"""
|
35 |
-
Send a
|
|
|
36 |
"""
|
37 |
-
|
38 |
-
|
39 |
-
f"
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
|
46 |
|
47 |
def main():
|
48 |
-
# Server
|
49 |
-
|
|
|
50 |
|
51 |
# Example URLs for testing
|
52 |
image_urls = [
|
@@ -55,30 +63,32 @@ def main():
|
|
55 |
|
56 |
# Fetch and encode images
|
57 |
try:
|
58 |
-
|
59 |
base64_images = [encode_image_to_base64(fetch_image(url)) for url in image_urls]
|
60 |
-
|
61 |
except Exception as e:
|
62 |
-
|
63 |
return
|
64 |
|
65 |
# Test single image prediction
|
66 |
try:
|
67 |
-
|
68 |
-
single_response = send_prediction_request(base64_images[0],
|
69 |
if single_response and single_response.status_code == 200:
|
70 |
predictions = single_response.json().get("predictions", [])
|
71 |
if predictions:
|
72 |
-
|
73 |
for pred in predictions:
|
74 |
-
|
75 |
else:
|
76 |
-
|
77 |
elif single_response:
|
78 |
-
|
79 |
-
|
|
|
|
|
80 |
except Exception as e:
|
81 |
-
|
82 |
|
83 |
|
84 |
if __name__ == "__main__":
|
|
|
1 |
+
from loguru import logger
|
2 |
import requests
|
3 |
from urllib.request import urlopen
|
4 |
import base64
|
|
|
9 |
"""
|
10 |
Fetch image data from a URL.
|
11 |
"""
|
12 |
+
try:
|
13 |
+
return urlopen(url).read()
|
14 |
+
except Exception as e:
|
15 |
+
logger.error(f"Failed to fetch image from {url}: {e}")
|
16 |
+
raise
|
17 |
|
18 |
|
19 |
def encode_image_to_base64(img_data):
|
20 |
"""
|
21 |
Encode image bytes to a base64 string.
|
22 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
try:
|
24 |
+
return base64.b64encode(img_data).decode("utf-8")
|
25 |
+
except Exception as e:
|
26 |
+
logger.error(f"Failed to encode image to base64: {e}")
|
27 |
+
raise
|
|
|
28 |
|
29 |
|
30 |
+
def send_prediction_request(base64_image, server_urls):
|
31 |
"""
|
32 |
+
Send a single base64 image to the prediction API and retrieve predictions.
|
33 |
+
Tries multiple server URLs in order.
|
34 |
"""
|
35 |
+
for server_url in server_urls:
|
36 |
+
try:
|
37 |
+
logger.info(f"Attempting to send prediction request to {server_url}...")
|
38 |
+
response = requests.post(
|
39 |
+
f"{server_url}/predict", json={"image": base64_image}
|
40 |
+
)
|
41 |
+
if response.status_code == 200:
|
42 |
+
logger.info(f"Successfully connected to {server_url}")
|
43 |
+
return response
|
44 |
+
else:
|
45 |
+
logger.warning(
|
46 |
+
f"Server at {server_url} returned status code {response.status_code}"
|
47 |
+
)
|
48 |
+
except requests.exceptions.RequestException as e:
|
49 |
+
logger.error(f"Error connecting to the server at {server_url}: {e}")
|
50 |
+
logger.error("Failed to connect to any server.")
|
51 |
+
return None
|
52 |
|
53 |
|
54 |
def main():
|
55 |
+
# Server URLs to try
|
56 |
+
server_url_env = os.getenv("SERVER_URL", "http://localhost:8080")
|
57 |
+
server_urls = [server_url_env]
|
58 |
|
59 |
# Example URLs for testing
|
60 |
image_urls = [
|
|
|
63 |
|
64 |
# Fetch and encode images
|
65 |
try:
|
66 |
+
logger.info("Fetching and encoding images...")
|
67 |
base64_images = [encode_image_to_base64(fetch_image(url)) for url in image_urls]
|
68 |
+
logger.info("Images fetched and encoded successfully.")
|
69 |
except Exception as e:
|
70 |
+
logger.error(f"Error fetching or encoding images: {e}")
|
71 |
return
|
72 |
|
73 |
# Test single image prediction
|
74 |
try:
|
75 |
+
logger.info("--- Single Image Prediction ---")
|
76 |
+
single_response = send_prediction_request(base64_images[0], server_urls)
|
77 |
if single_response and single_response.status_code == 200:
|
78 |
predictions = single_response.json().get("predictions", [])
|
79 |
if predictions:
|
80 |
+
logger.info("Top Predictions:")
|
81 |
for pred in predictions:
|
82 |
+
logger.info(f"{pred['label']}: {pred['probability']:.2%}")
|
83 |
else:
|
84 |
+
logger.warning("No predictions returned.")
|
85 |
elif single_response:
|
86 |
+
logger.error(f"Error: {single_response.status_code}")
|
87 |
+
logger.error(single_response.text)
|
88 |
+
else:
|
89 |
+
logger.error("Failed to get a response from any server.")
|
90 |
except Exception as e:
|
91 |
+
logger.error(f"Error sending single prediction request: {e}")
|
92 |
|
93 |
|
94 |
if __name__ == "__main__":
|
src/server.py
CHANGED
@@ -82,7 +82,7 @@ class ImageClassifierAPI(lit.LitAPI):
|
|
82 |
|
83 |
def decode_request(self, request):
|
84 |
"""Handle both single and batch inputs."""
|
85 |
-
logger.info(f"decode_request received: {request}")
|
86 |
if not isinstance(request, dict) or "image" not in request:
|
87 |
logger.error(
|
88 |
"Invalid request format. Expected a dictionary with key 'image'."
|
@@ -94,7 +94,7 @@ class ImageClassifierAPI(lit.LitAPI):
|
|
94 |
|
95 |
def batch(self, inputs):
|
96 |
"""Batch process images."""
|
97 |
-
logger.info(f"batch received inputs: {inputs}")
|
98 |
if not isinstance(inputs, list):
|
99 |
raise ValueError("Input to batch must be a list.")
|
100 |
|
|
|
82 |
|
83 |
def decode_request(self, request):
|
84 |
"""Handle both single and batch inputs."""
|
85 |
+
# logger.info(f"decode_request received: {request}")
|
86 |
if not isinstance(request, dict) or "image" not in request:
|
87 |
logger.error(
|
88 |
"Invalid request format. Expected a dictionary with key 'image'."
|
|
|
94 |
|
95 |
def batch(self, inputs):
|
96 |
"""Batch process images."""
|
97 |
+
# logger.info(f"batch received inputs: {inputs}")
|
98 |
if not isinstance(inputs, list):
|
99 |
raise ValueError("Input to batch must be a list.")
|
100 |
|