Klasta commited on
Commit
5c6e546
·
1 Parent(s): 431eba4

First commit

Browse files
Files changed (10) hide show
  1. .dockerignore +9 -0
  2. .gitattributes +1 -0
  3. Dockerfile +21 -0
  4. README.md +25 -3
  5. app/__init__.py +0 -0
  6. app/cd.pt +3 -0
  7. app/main.py +26 -0
  8. app/model.py +22 -0
  9. app/predict.py +34 -0
  10. requirements.txt +9 -0
.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ mlops
2
+ model_funcs
3
+ __pycache__
4
+ *.pyc
5
+ *.log
6
+ .git
7
+ .venv
8
+ *.swp
9
+ *.swo
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ app/cd.pt filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12.3
2
+
3
+ # Create non-root user with UID 1000
4
+ RUN useradd -m -u 1000 user
5
+ USER user
6
+ ENV PATH="/home/user/.local/bin:$PATH"
7
+
8
+ WORKDIR /app
9
+
10
+ # Copy requirements and install
11
+ COPY --chown=user requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy app code
15
+ COPY --chown=user app/ app/
16
+
17
+ # Expose port 7860 (used by HF Spaces)
18
+ EXPOSE 7860
19
+
20
+ # Run FastAPI with uvicorn on the required port
21
+ CMD ["python3", "-m", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,32 @@
1
  ---
2
- title: Testing Spaces
3
- emoji: 👀
4
  colorFrom: gray
5
  colorTo: red
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Cat vs Dog Classifier 🐶🐱
3
+ emoji: 🐾
4
  colorFrom: gray
5
  colorTo: red
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
+ # 🐾 Cat vs Dog Classifier - FastAPI + PyTorch
11
+
12
+ This Space hosts a FastAPI-based image classification API that distinguishes between **cats** and **dogs** using a fine-tuned **ResNet50** model.
13
+
14
+ ## 🚀 How It Works
15
+
16
+ - Accepts an uploaded image via a POST request.
17
+ - Processes the image using `albumentations` and a pre-trained PyTorch model.
18
+ - Returns `"cat"` or `"dog"` based on prediction confidence.
19
+
20
+ ## 🧪 Try It Out
21
+
22
+ You can interact with the `/predict-image` endpoint using tools like:
23
+ - `curl`
24
+ - Postman
25
+ - Any frontend that supports `multipart/form-data` file uploads.
26
+
27
+ ### Example:
28
+
29
+ ```bash
30
+ curl -X POST \
31
+ -F "file=@your_image.jpg" \
32
+ https://Klasta-testing_spaces.hf.space/predict-image
app/__init__.py ADDED
File without changes
app/cd.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba6880fed2ae00203357b8de47b92f31d1d7ad4fb6b5c45b79968e47328aa583
3
+ size 94343841
app/main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fastapi
2
+ import io
3
+ from fastapi import UploadFile, File
4
+ from PIL import Image
5
+ from .predict import predict_image
6
+
7
+ app = fastapi.FastAPI()
8
+
9
+ @app.get("/")
10
+ def root():
11
+ return {"message": "API is working!"}
12
+
13
+ @app.post("/predict-image")
14
+ async def upload_image(file: UploadFile = File(...)):
15
+ if not file.content_type.startswith("image/"):
16
+ return {"error": "Please upload an image to proceed"}
17
+
18
+ contents = await file.read()
19
+
20
+ try:
21
+ image = Image.open(io.BytesIO(contents))
22
+ except Exception as e:
23
+ return {"error": f"Failed to open image: {str(e)}"}
24
+
25
+ pred = predict_image(image)
26
+ return {"prediction": pred}
app/model.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torchvision.models as models
4
+
5
+
6
+ class CatvsDogResNet50(nn.Module):
7
+ def __init__(self, freeze_backbone: bool = True):
8
+ super().__init__()
9
+ self.backbone = models.resnet50(pretrained=True)
10
+
11
+ if freeze_backbone:
12
+ for param in self.backbone.parameters():
13
+ param.requires_grad = False
14
+
15
+ num_ftrs = self.backbone.fc.in_features
16
+ self.backbone.fc = nn.Sequential(
17
+ nn.Dropout(0.5),
18
+ nn.Linear(num_ftrs, 1),
19
+ )
20
+
21
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
22
+ return self.backbone(x)
app/predict.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app/predict.py
2
+
3
+ import torch
4
+ import numpy as np
5
+ from PIL import Image
6
+ import albumentations as A
7
+ from albumentations.pytorch import ToTensorV2
8
+ from .model import CatvsDogResNet50
9
+
10
+ transform = A.Compose(
11
+ [
12
+ A.Resize(224, 224),
13
+ A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),
14
+ ToTensorV2(),
15
+ ]
16
+ )
17
+
18
+ device = "cuda" if torch.cuda.is_available() else "cpu" # Fallback to CPU if no GPU
19
+ model = CatvsDogResNet50(freeze_backbone=True)
20
+ model.load_state_dict(torch.load(r"app/cd.pt", map_location=device))
21
+ model = model.to(device).eval()
22
+
23
+
24
+ def predict_image(image: Image.Image) -> str:
25
+ img = image.convert("RGB")
26
+ img = np.array(img)
27
+ img = transform(image=img)["image"]
28
+ img = img.unsqueeze(0).to(device)
29
+
30
+ with torch.inference_mode():
31
+ out = model(img)
32
+ prob = torch.sigmoid(out).item()
33
+ pred = "dog" if prob > 0.5 else "cat"
34
+ return pred
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.115.12
2
+ uvicorn==0.34.3
3
+ torch==2.7.1
4
+ torchvision==0.22.1
5
+ albumentations==2.0.8
6
+ pillow==11.2.1
7
+ numpy==2.1.3
8
+ python-multipart==0.0.20
9
+ mlflow==3.1.0