mofeibai commited on
Commit
220e014
·
verified ·
1 Parent(s): e72e2d7

Upload 6 files

Browse files
Files changed (6) hide show
  1. Dockerfile +25 -0
  2. LICENSE +21 -0
  3. SECURITY.md +10 -0
  4. main.py +174 -0
  5. models.py +49 -0
  6. requirements.txt +10 -0
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.12-slim
3
+
4
+ # Set the working directory in the container
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ build-essential \
11
+ pkg-config \
12
+ libhdf5-dev \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ # Copy the current directory contents into the container at /app
16
+ COPY . /app
17
+
18
+ # Install any needed packages specified in requirements.txt
19
+ RUN pip install --no-cache-dir -r requirements.txt
20
+
21
+ # Make port 8000 available to the world outside this container
22
+ EXPOSE 8000
23
+
24
+ # Command to run the Uvicorn server
25
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Amir Boroumand
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
SECURITY.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ This is an open source project that is provided as-is without warranty or liability.
6
+ As such no supportability commitment. The maintainers will do the best they can to address any report promptly and responsibly.
7
+
8
+ ## Reporting a Vulnerability
9
+
10
+ Please use the "Private vulnerability reporting" feature in the GitHub repository (under the "Security" tab).
main.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module providing an API for NSFW image detection."""
2
+
3
+ import io
4
+ import hashlib
5
+ import logging
6
+ import aiohttp
7
+ from fastapi import FastAPI, File, UploadFile, HTTPException
8
+ from fastapi.responses import JSONResponse
9
+ from transformers import pipeline
10
+ from transformers.pipelines import PipelineException
11
+ from PIL import Image
12
+ from cachetools import Cache
13
+ import tensorflow as tf
14
+ from models import (
15
+ FileImageDetectionResponse,
16
+ UrlImageDetectionResponse,
17
+ ImageUrlsRequest,
18
+ )
19
+
20
+
21
+ app = FastAPI()
22
+
23
+ logging.basicConfig(
24
+ level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
25
+ )
26
+
27
+ # Initialize Cache with no TTL
28
+ cache = Cache(maxsize=1000)
29
+
30
+ # Load the model using the transformers pipeline
31
+ model = pipeline("image-classification", model="falconsai/nsfw_image_detection")
32
+
33
+ # Detect the device used by TensorFlow
34
+ DEVICE = "GPU" if tf.config.list_physical_devices("GPU") else "CPU"
35
+ logging.info("TensorFlow version: %s", tf.__version__)
36
+ logging.info("Model is using: %s", DEVICE)
37
+
38
+ if DEVICE == "GPU":
39
+ logging.info("GPUs available: %d", len(tf.config.list_physical_devices("GPU")))
40
+
41
+
42
+ async def download_image(image_url: str) -> bytes:
43
+ """Download an image from a URL."""
44
+ async with aiohttp.ClientSession() as session:
45
+ async with session.get(image_url) as response:
46
+ if response.status != 200:
47
+ raise HTTPException(
48
+ status_code=response.status, detail="Image could not be retrieved."
49
+ )
50
+ return await response.read()
51
+
52
+
53
+ def hash_data(data):
54
+ """Function for hashing image data."""
55
+ return hashlib.sha256(data).hexdigest()
56
+
57
+
58
+ @app.post("/v1/detect", response_model=FileImageDetectionResponse)
59
+ async def classify_image(file: UploadFile = File(None)):
60
+ """Function analyzing image."""
61
+ if file is None:
62
+ raise HTTPException(
63
+ status_code=400,
64
+ detail="An image file must be provided.",
65
+ )
66
+
67
+ try:
68
+ logging.info("Processing %s", file.filename)
69
+
70
+ # Read the image file
71
+ image_data = await file.read()
72
+ image_hash = hash_data(image_data)
73
+
74
+ if image_hash in cache:
75
+ # Return cached entry
76
+ logging.info("Returning cached entry for %s", file.filename)
77
+
78
+ cached_response = cache[image_hash]
79
+ response_data = {**cached_response, "file_name": file.filename}
80
+
81
+ return FileImageDetectionResponse(**response_data)
82
+
83
+ image = Image.open(io.BytesIO(image_data))
84
+
85
+ # Use the model to classify the image
86
+ results = model(image)
87
+
88
+ # Find the prediction with the highest confidence using the max() function
89
+ best_prediction = max(results, key=lambda x: x["score"])
90
+
91
+ # Calculate the confidence score, rounded to the nearest tenth and as a percentage
92
+ confidence_percentage = round(best_prediction["score"] * 100, 1)
93
+
94
+ # Prepare the custom response data
95
+ response_data = {
96
+ "is_nsfw": best_prediction["label"] == "nsfw",
97
+ "confidence_percentage": confidence_percentage,
98
+ }
99
+
100
+ # Populate hash
101
+ cache[image_hash] = response_data.copy()
102
+
103
+ # Add file_name to the API response
104
+ response_data["file_name"] = file.filename
105
+
106
+ return FileImageDetectionResponse(**response_data)
107
+
108
+ except PipelineException as e:
109
+ logging.error("Error processing image: %s", str(e))
110
+ raise HTTPException(
111
+ status_code=500, detail=f"Error processing image: {str(e)}"
112
+ ) from e
113
+
114
+
115
+ @app.post("/v1/detect/urls", response_model=list[UrlImageDetectionResponse])
116
+ async def classify_images(request: ImageUrlsRequest):
117
+ """Function analyzing images from URLs."""
118
+ response_data = []
119
+
120
+ for image_url in request.urls:
121
+ try:
122
+ logging.info("Downloading image from URL: %s", image_url)
123
+ image_data = await download_image(image_url)
124
+ image_hash = hash_data(image_data)
125
+
126
+ if image_hash in cache:
127
+ # Return cached entry
128
+ logging.info("Returning cached entry for %s", image_url)
129
+
130
+ cached_response = cache[image_hash]
131
+ response = {**cached_response, "url": image_url}
132
+
133
+ response_data.append(response)
134
+ continue
135
+
136
+ image = Image.open(io.BytesIO(image_data))
137
+
138
+ # Use the model to classify the image
139
+ results = model(image)
140
+
141
+ # Find the prediction with the highest confidence using the max() function
142
+ best_prediction = max(results, key=lambda x: x["score"])
143
+
144
+ # Calculate the confidence score, rounded to the nearest tenth and as a percentage
145
+ confidence_percentage = round(best_prediction["score"] * 100, 1)
146
+
147
+ # Prepare the custom response data
148
+ detection_result = {
149
+ "is_nsfw": best_prediction["label"] == "nsfw",
150
+ "confidence_percentage": confidence_percentage,
151
+ }
152
+
153
+ # Populate hash
154
+ cache[image_hash] = detection_result.copy()
155
+
156
+ # Add url to the API response
157
+ detection_result["url"] = image_url
158
+
159
+ response_data.append(detection_result)
160
+
161
+ except PipelineException as e:
162
+ logging.error("Error processing image from %s: %s", image_url, str(e))
163
+ raise HTTPException(
164
+ status_code=500,
165
+ detail=f"Error processing image from {image_url}: {str(e)}",
166
+ ) from e
167
+
168
+ return JSONResponse(status_code=200, content=response_data)
169
+
170
+
171
+ if __name__ == "__main__":
172
+ import uvicorn
173
+
174
+ uvicorn.run(app, host="127.0.0.1", port=8000)
models.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Module providing base models."""
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class ImageUrlsRequest(BaseModel):
7
+ """
8
+ Model representing the request body for the /v1/detect/urls endpoint.
9
+
10
+ Attributes:
11
+ urls (list[str]): List of image URLs to be processed.
12
+ """
13
+
14
+ urls: list[str]
15
+
16
+
17
+ class ImageDetectionResponse(BaseModel):
18
+ """
19
+ Base model representing the response body for image detection.
20
+
21
+ Attributes:
22
+ is_nsfw (bool): Whether the image is classified as NSFW.
23
+ confidence_percentage (float): Confidence level of the NSFW classification.
24
+ """
25
+
26
+ is_nsfw: bool
27
+ confidence_percentage: float
28
+
29
+
30
+ class FileImageDetectionResponse(ImageDetectionResponse):
31
+ """
32
+ Model extending ImageDetectionResponse with a file attribute.
33
+
34
+ Attributes:
35
+ file (str): The name of the file that was processed.
36
+ """
37
+
38
+ file_name: str
39
+
40
+
41
+ class UrlImageDetectionResponse(ImageDetectionResponse):
42
+ """
43
+ Model extending ImageDetectionResponse with a URL attribute.
44
+
45
+ Attributes:
46
+ url (str): The URL of the image that was processed.
47
+ """
48
+
49
+ url: str
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.110.2
2
+ uvicorn[standard]==0.29.0
3
+ transformers==4.40.0
4
+ aiohttp==3.9.5
5
+ pillow==10.3.0
6
+ python-multipart==0.0.9
7
+ tensorflow==2.16.1
8
+ tf-keras==2.16.0
9
+ cachetools===5.3.3
10
+ pydantic===2.7.2