Tzetha commited on
Commit
81e78bd
·
verified ·
1 Parent(s): 608e9bd

Uploaded Complete App

Browse files
app.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import models, transforms
5
+ from PIL import Image
6
+ import os
7
+
8
+ # Load class names from your training dataset
9
+ CLASS_NAMES = sorted(os.listdir("oxford_pet_dataset/train")) # ensure these match training classes
10
+
11
+ # Load the model
12
+ @st.cache_resource
13
+ def load_model():
14
+ model = models.resnet18(pretrained=False)
15
+ model.fc = nn.Linear(model.fc.in_features, len(CLASS_NAMES))
16
+ model.load_state_dict(torch.load("pet_classifier.pth", map_location=torch.device("cpu")))
17
+ model.eval()
18
+ return model
19
+
20
+ model = load_model()
21
+
22
+ # Image transform (should match training)
23
+ transform = transforms.Compose([
24
+ transforms.Resize((224, 224)),
25
+ transforms.ToTensor(),
26
+ transforms.Normalize([0.5]*3, [0.5]*3)
27
+ ])
28
+
29
+ # Streamlit UI
30
+ st.title("🐾 Oxford Pet Classifier")
31
+ st.write("Upload a photo of a cat or dog and I’ll try to guess the breed!")
32
+
33
+ uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
34
+
35
+ if uploaded_file is not None:
36
+ image = Image.open(uploaded_file).convert("RGB")
37
+ st.image(image, caption="Uploaded Image", use_column_width=True)
38
+
39
+ # Preprocess
40
+ input_tensor = transform(image).unsqueeze(0) # (1, 3, 224, 224)
41
+
42
+ # Predict
43
+ with torch.no_grad():
44
+ outputs = model(input_tensor)
45
+ _, predicted = torch.max(outputs, 1)
46
+ predicted_label = CLASS_NAMES[predicted.item()]
47
+
48
+ st.markdown(f"### 🐕 Prediction: **{predicted_label.title()}**")
models/notebook/data_results.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
models/python/dataset_dl.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tarfile
3
+ import urllib.request
4
+ from pathlib import Path
5
+ from sklearn.model_selection import train_test_split
6
+ import shutil
7
+ from collections import defaultdict
8
+
9
+ # URLs
10
+ images_url = "https://www.robots.ox.ac.uk/~vgg/data/pets/data/images.tar.gz"
11
+ annotations_url = "https://www.robots.ox.ac.uk/~vgg/data/pets/data/annotations.tar.gz"
12
+
13
+ # Paths
14
+ root_dir = Path("oxford_pet_dataset")
15
+ images_tar = root_dir / "images.tar.gz"
16
+ annotations_tar = root_dir / "annotations.tar.gz"
17
+ images_dir = root_dir / "images"
18
+ annotations_dir = root_dir / "annotations"
19
+
20
+ # Create directory
21
+ root_dir.mkdir(exist_ok=True)
22
+
23
+ # Download function
24
+ def download(url, path):
25
+ if not path.exists():
26
+ print(f"Downloading {url}...")
27
+ urllib.request.urlretrieve(url, path)
28
+ print(f"Downloaded to {path}")
29
+ else:
30
+ print(f"{path.name} already exists.")
31
+
32
+ # Extract function
33
+ def extract(tar_path, extract_to):
34
+ if not extract_to.exists():
35
+ print(f"Extracting {tar_path.name}...")
36
+ with tarfile.open(tar_path) as tar:
37
+ tar.extractall(path=extract_to.parent)
38
+ print(f"Extracted to {extract_to}")
39
+ else:
40
+ print(f"{extract_to.name} already extracted.")
41
+
42
+ # Download and extract
43
+ download(images_url, images_tar)
44
+ download(annotations_url, annotations_tar)
45
+ extract(images_tar, images_dir)
46
+ extract(annotations_tar, annotations_dir)
47
+
48
+ # Function to extract class name from filename
49
+ def get_class_name(filename):
50
+ # Format: 'Abyssinian_123.jpg' → 'abyssinian'
51
+ return filename.name.split("_")[0].lower()
52
+
53
+ # Group image files by class
54
+ class_to_files = defaultdict(list)
55
+ for img_path in images_dir.glob("*.jpg"):
56
+ cls = get_class_name(img_path)
57
+ class_to_files[cls].append(img_path)
58
+
59
+ # Split each class into train/val/test and copy
60
+ for cls, files in class_to_files.items():
61
+ train_cls, testval_cls = train_test_split(files, test_size=0.2, random_state=42)
62
+ val_cls, test_cls = train_test_split(testval_cls, test_size=0.5, random_state=42)
63
+
64
+ for split_name, split_data in zip(["train", "val", "test"], [train_cls, val_cls, test_cls]):
65
+ split_cls_dir = root_dir / split_name / cls
66
+ split_cls_dir.mkdir(parents=True, exist_ok=True)
67
+ for file in split_data:
68
+ shutil.copy(file, split_cls_dir / file.name)
69
+
70
+ print("✅ Dataset is now organized by class for ImageFolder.")
models/python/train.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torchvision import datasets, transforms, models
6
+ from torch.utils.data import DataLoader
7
+ from tqdm import tqdm
8
+ from pathlib import Path
9
+
10
+ # Set device
11
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
12
+
13
+ # Paths
14
+ root_dir = Path("/oxford_pet_dataset")
15
+ train_dir = root_dir / "train"
16
+ val_dir = root_dir / "val"
17
+
18
+ # Parameters
19
+ BATCH_SIZE = 32
20
+ EPOCHS = 10
21
+ NUM_CLASSES = len(os.listdir(train_dir)) # Assumes one folder per class
22
+
23
+ # Transforms
24
+ train_transforms = transforms.Compose([
25
+ transforms.Resize((224, 224)),
26
+ transforms.RandomHorizontalFlip(),
27
+ transforms.ToTensor(),
28
+ transforms.Normalize([0.5]*3, [0.5]*3)
29
+ ])
30
+
31
+ val_transforms = transforms.Compose([
32
+ transforms.Resize((224, 224)),
33
+ transforms.ToTensor(),
34
+ transforms.Normalize([0.5]*3, [0.5]*3)
35
+ ])
36
+
37
+ # Datasets
38
+ train_dataset = datasets.ImageFolder(train_dir, transform=train_transforms)
39
+ val_dataset = datasets.ImageFolder(val_dir, transform=val_transforms)
40
+
41
+ # DataLoaders
42
+ train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
43
+ val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE)
44
+
45
+ # Model
46
+ model = models.resnet18(pretrained=True)
47
+ model.fc = nn.Linear(model.fc.in_features, NUM_CLASSES)
48
+ model = model.to(device)
49
+
50
+ # Loss and optimizer
51
+ criterion = nn.CrossEntropyLoss()
52
+ optimizer = optim.Adam(model.parameters(), lr=1e-4)
53
+
54
+ # Training loop
55
+ for epoch in range(EPOCHS):
56
+ model.train()
57
+ train_loss, train_correct = 0.0, 0
58
+
59
+ for inputs, labels in tqdm(train_loader, desc=f"Epoch {epoch+1}/{EPOCHS} [Train]"):
60
+ inputs, labels = inputs.to(device), labels.to(device)
61
+
62
+ optimizer.zero_grad()
63
+ outputs = model(inputs)
64
+ loss = criterion(outputs, labels)
65
+ loss.backward()
66
+ optimizer.step()
67
+
68
+ train_loss += loss.item() * inputs.size(0)
69
+ train_correct += (outputs.argmax(1) == labels).sum().item()
70
+
71
+ train_acc = train_correct / len(train_dataset)
72
+
73
+ # Validation
74
+ model.eval()
75
+ val_loss, val_correct = 0.0, 0
76
+
77
+ with torch.no_grad():
78
+ for inputs, labels in tqdm(val_loader, desc=f"Epoch {epoch+1}/{EPOCHS} [Val]"):
79
+ inputs, labels = inputs.to(device), labels.to(device)
80
+ outputs = model(inputs)
81
+ loss = criterion(outputs, labels)
82
+
83
+ val_loss += loss.item() * inputs.size(0)
84
+ val_correct += (outputs.argmax(1) == labels).sum().item()
85
+
86
+ val_acc = val_correct / len(val_dataset)
87
+
88
+ print(f"Epoch {epoch+1}: Train Acc: {train_acc:.4f}, Val Acc: {val_acc:.4f}")
89
+
90
+ # Save model
91
+ torch.save(model.state_dict(), "pet_classifier.pth")
92
+ print("Model saved as pet_classifier.pth")
pet_classifier.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:617c30e1fffc5689181299f3c6d7778ca5503b765de987779993776821fd3636
3
+ size 44857584