Syzarseef commited on
Commit
552ee07
Β·
verified Β·
1 Parent(s): 6953c3b

Upload 9 files

Browse files
Files changed (9) hide show
  1. Dockerfile +34 -0
  2. LICENSE +21 -0
  3. aircraft_classifier.ipynb +0 -0
  4. app.py +240 -0
  5. config.py +39 -0
  6. model_utils.py +33 -0
  7. requirements.txt +12 -0
  8. setup.py +88 -0
  9. test_app.py +68 -0
Dockerfile ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.9 slim image
2
+ FROM python:3.9-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ g++ \
11
+ wget \
12
+ && rm -rf /var/lib/apt/lists/*
13
+
14
+ # Copy requirements first for better caching
15
+ COPY requirements.txt .
16
+
17
+ # Install Python dependencies
18
+ RUN pip install --no-cache-dir -r requirements.txt
19
+
20
+ # Copy application code
21
+ COPY . .
22
+
23
+ # Create models directory
24
+ RUN mkdir -p models
25
+
26
+ # Expose port
27
+ EXPOSE 7860
28
+
29
+ # Set environment variables
30
+ ENV GRADIO_SERVER_NAME=0.0.0.0
31
+ ENV GRADIO_SERVER_PORT=7860
32
+
33
+ # Command to run the application
34
+ CMD ["python", "app.py"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AhmedAl-Mahdi
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.
aircraft_classifier.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
app.py ADDED
@@ -0,0 +1,240 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import torch.nn as nn
4
+ import torchvision.transforms as transforms
5
+ from torchvision import models
6
+ import numpy as np
7
+ from PIL import Image
8
+ import os
9
+
10
+ # Aircraft class names (10 classes from the dataset)
11
+ CLASS_NAMES = [
12
+ '707-320', '737-400', '767-300', 'DC-9-30', 'DH-82',
13
+ 'Falcon_2000', 'Il-76', 'MD-11', 'Metroliner', 'PA-28'
14
+ ]
15
+
16
+ class AircraftClassifier(nn.Module):
17
+ """ResNet-18 based aircraft classifier"""
18
+ def __init__(self, num_classes=10):
19
+ super(AircraftClassifier, self).__init__()
20
+ # Load pre-trained ResNet-18
21
+ self.backbone = models.resnet18(pretrained=True)
22
+ # Replace the final fully connected layer
23
+ num_features = self.backbone.fc.in_features
24
+ self.backbone.fc = nn.Linear(num_features, num_classes)
25
+
26
+ def forward(self, x):
27
+ return self.backbone(x)
28
+
29
+ # Image preprocessing pipeline
30
+ def get_transforms():
31
+ """Get image preprocessing transforms"""
32
+ return transforms.Compose([
33
+ transforms.Resize((224, 224)),
34
+ transforms.ToTensor(),
35
+ transforms.Normalize(mean=[0.485, 0.456, 0.406],
36
+ std=[0.229, 0.224, 0.225])
37
+ ])
38
+
39
+ # Initialize model and device
40
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
41
+ model = AircraftClassifier(num_classes=len(CLASS_NAMES))
42
+
43
+ # Try to load trained model weights
44
+ model_path = 'models/aircraft_classifier.pth'
45
+ if os.path.exists(model_path):
46
+ try:
47
+ model.load_state_dict(torch.load(model_path, map_location=device))
48
+ print(f"βœ… Loaded trained model from {model_path}")
49
+ except Exception as e:
50
+ print(f"⚠️ Could not load trained model: {e}")
51
+ print("Using random weights - please train the model first!")
52
+ else:
53
+ print(f"⚠️ Model file not found at {model_path}")
54
+ print("Using random weights - please train the model first!")
55
+
56
+ model = model.to(device)
57
+ model.eval()
58
+
59
+ # Get image transforms
60
+ transform = get_transforms()
61
+
62
+ def classify_aircraft(image):
63
+ """
64
+ Classify an aircraft image
65
+
66
+ Args:
67
+ image: PIL Image or numpy array
68
+
69
+ Returns:
70
+ dict: Classification results with confidence scores
71
+ """
72
+ try:
73
+ # Convert to PIL Image if needed
74
+ if isinstance(image, np.ndarray):
75
+ image = Image.fromarray(image)
76
+
77
+ # Convert to RGB if needed
78
+ if image.mode != 'RGB':
79
+ image = image.convert('RGB')
80
+
81
+ # Apply transforms
82
+ input_tensor = transform(image).unsqueeze(0).to(device)
83
+
84
+ # Get prediction
85
+ with torch.no_grad():
86
+ outputs = model(input_tensor)
87
+ probabilities = torch.softmax(outputs, dim=1)
88
+
89
+ # Get top predictions
90
+ probs = probabilities.cpu().numpy()[0]
91
+
92
+ # Create results dictionary for Gradio
93
+ results = {}
94
+ for i, class_name in enumerate(CLASS_NAMES):
95
+ results[class_name] = float(probs[i])
96
+
97
+ return results
98
+
99
+ except Exception as e:
100
+ print(f"Error in classification: {e}")
101
+ # Return empty results in case of error
102
+ return {class_name: 0.0 for class_name in CLASS_NAMES}
103
+
104
+ def get_top_predictions(image):
105
+ """
106
+ Get top 3 predictions with confidence scores
107
+
108
+ Args:
109
+ image: PIL Image or numpy array
110
+
111
+ Returns:
112
+ str: Formatted string with top predictions
113
+ """
114
+ try:
115
+ results = classify_aircraft(image)
116
+
117
+ # Sort by confidence
118
+ sorted_results = sorted(results.items(), key=lambda x: x[1], reverse=True)
119
+
120
+ # Format top 3 predictions
121
+ output_text = "🎯 **Top Predictions:**\n\n"
122
+ for i, (class_name, confidence) in enumerate(sorted_results[:3]):
123
+ confidence_percent = confidence * 100
124
+ output_text += f"{i+1}. **{class_name}**: {confidence_percent:.2f}%\n"
125
+
126
+ return output_text
127
+
128
+ except Exception as e:
129
+ return f"❌ Error during classification: {str(e)}"
130
+
131
+ # Create Gradio interface
132
+ def create_interface():
133
+ """Create and configure the Gradio interface"""
134
+
135
+ # Custom CSS for better styling
136
+ css = """
137
+ .gradio-container {
138
+ max-width: 900px !important;
139
+ margin: auto !important;
140
+ }
141
+ .title {
142
+ text-align: center;
143
+ font-size: 2.5em;
144
+ font-weight: bold;
145
+ margin-bottom: 0.5em;
146
+ }
147
+ .description {
148
+ text-align: center;
149
+ font-size: 1.2em;
150
+ color: #666;
151
+ margin-bottom: 2em;
152
+ }
153
+ """
154
+
155
+ with gr.Blocks(css=css, title="Aircraft Classifier") as iface:
156
+ # Header
157
+ gr.HTML("""
158
+ <div class="title">πŸ›©οΈ Aircraft Classifier</div>
159
+ <div class="description">
160
+ Fine-grained aircraft classification using deep learning<br>
161
+ Upload an image to classify it into one of 10 aircraft types
162
+ </div>
163
+ """)
164
+
165
+ with gr.Row():
166
+ with gr.Column(scale=1):
167
+ # Input image
168
+ input_image = gr.Image(
169
+ type="pil",
170
+ label="Upload Aircraft Image",
171
+ height=400
172
+ )
173
+
174
+ # Example images section (commented out to avoid network issues)
175
+ # gr.HTML("### πŸ“Έ Try these example images:")
176
+ # gr.Examples(
177
+ # examples=[
178
+ # ["path/to/local/example1.jpg"],
179
+ # ["path/to/local/example2.jpg"],
180
+ # ],
181
+ # inputs=input_image,
182
+ # cache_examples=False
183
+ # )
184
+
185
+ with gr.Column(scale=1):
186
+ # Classification results
187
+ classification_output = gr.Label(
188
+ label="🎯 Classification Results",
189
+ num_top_classes=10
190
+ )
191
+
192
+ # Top predictions text
193
+ top_predictions = gr.Textbox(
194
+ label="πŸ“Š Detailed Results",
195
+ lines=6,
196
+ interactive=False
197
+ )
198
+
199
+ # Model information
200
+ gr.HTML("""
201
+ <div style="margin-top: 2em; padding: 1em; background-color: #f8f9fa; border-radius: 8px;">
202
+ <h3>πŸ”§ Model Information</h3>
203
+ <ul>
204
+ <li><b>Architecture:</b> ResNet-18 with transfer learning</li>
205
+ <li><b>Dataset:</b> FGVC-Aircraft (10 classes)</li>
206
+ <li><b>Accuracy:</b> 87.17% on test set</li>
207
+ <li><b>Classes:</b> 707-320, 737-400, 767-300, DC-9-30, DH-82, Falcon_2000, Il-76, MD-11, Metroliner, PA-28</li>
208
+ </ul>
209
+ </div>
210
+ """)
211
+
212
+ # Set up the prediction triggers
213
+ input_image.change(
214
+ fn=classify_aircraft,
215
+ inputs=[input_image],
216
+ outputs=[classification_output]
217
+ )
218
+
219
+ input_image.change(
220
+ fn=get_top_predictions,
221
+ inputs=[input_image],
222
+ outputs=[top_predictions]
223
+ )
224
+
225
+ return iface
226
+
227
+ # Launch the interface
228
+ if __name__ == "__main__":
229
+ print("πŸš€ Starting Aircraft Classifier Gradio Interface...")
230
+ print(f"πŸ“± Device: {device}")
231
+ print(f"🎯 Classes: {len(CLASS_NAMES)}")
232
+
233
+ # Create and launch interface
234
+ iface = create_interface()
235
+ iface.launch(
236
+ share=True, # Creates a public link
237
+ server_name="0.0.0.0", # Allow external connections
238
+ server_port=7860, # Default Gradio port
239
+ show_error=True
240
+ )
config.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Aircraft Classifier Configuration
2
+
3
+ # Model Configuration
4
+ MODEL_NAME = "ResNet-18"
5
+ NUM_CLASSES = 10
6
+ MODEL_PATH = "models/aircraft_classifier.pth"
7
+
8
+ # Class names for the 10 aircraft types
9
+ CLASS_NAMES = [
10
+ '707-320', # Boeing 707-320
11
+ '737-400', # Boeing 737-400
12
+ '767-300', # Boeing 767-300
13
+ 'DC-9-30', # McDonnell Douglas DC-9-30
14
+ 'DH-82', # de Havilland DH.82 Tiger Moth
15
+ 'Falcon_2000', # Dassault Falcon 2000
16
+ 'Il-76', # Ilyushin Il-76
17
+ 'MD-11', # McDonnell Douglas MD-11
18
+ 'Metroliner', # Fairchild Metroliner
19
+ 'PA-28' # Piper PA-28
20
+ ]
21
+
22
+ # Image preprocessing parameters
23
+ IMAGE_SIZE = (224, 224)
24
+ IMAGENET_MEAN = [0.485, 0.456, 0.406]
25
+ IMAGENET_STD = [0.229, 0.224, 0.225]
26
+
27
+ # Gradio interface settings
28
+ GRADIO_PORT = 7860
29
+ GRADIO_SHARE = True
30
+ ALLOW_FLAGGING = False
31
+
32
+ # Model performance metrics (from training)
33
+ MODEL_METRICS = {
34
+ "test_accuracy": 0.8717,
35
+ "f1_score": 0.8737,
36
+ "training_accuracy": 1.0000,
37
+ "validation_accuracy": 0.8559,
38
+ "epochs_trained": 17
39
+ }
model_utils.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from torchvision import models
4
+ import os
5
+
6
+ class AircraftClassifier(nn.Module):
7
+ """ResNet-18 based aircraft classifier"""
8
+ def __init__(self, num_classes=10):
9
+ super(AircraftClassifier, self).__init__()
10
+ # Load pre-trained ResNet-18
11
+ self.backbone = models.resnet18(pretrained=True)
12
+ # Replace the final fully connected layer
13
+ num_features = self.backbone.fc.in_features
14
+ self.backbone.fc = nn.Linear(num_features, num_classes)
15
+
16
+ def forward(self, x):
17
+ return self.backbone(x)
18
+
19
+ def save_model_checkpoint(model, filepath):
20
+ """Save model state dict to file"""
21
+ os.makedirs(os.path.dirname(filepath), exist_ok=True)
22
+ torch.save(model.state_dict(), filepath)
23
+ print(f"Model saved to {filepath}")
24
+
25
+ def load_model_checkpoint(filepath, num_classes=10, device='cpu'):
26
+ """Load model from checkpoint"""
27
+ model = AircraftClassifier(num_classes=num_classes)
28
+ if os.path.exists(filepath):
29
+ model.load_state_dict(torch.load(filepath, map_location=device))
30
+ print(f"Model loaded from {filepath}")
31
+ else:
32
+ print(f"Checkpoint file {filepath} not found")
33
+ return model
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ torch>=1.9.0
2
+ torchvision>=0.10.0
3
+ gradio>=3.0.0
4
+ numpy>=1.21.0
5
+ pillow>=8.3.0
6
+ matplotlib>=3.4.0
7
+ seaborn>=0.11.0
8
+ scikit-learn>=1.0.0
9
+ jupyter>=1.0.0
10
+ notebook>=6.4.0
11
+ tqdm>=4.62.0
12
+ pandas>=1.3.0
setup.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Setup script for Aircraft Classifier Gradio deployment
4
+ """
5
+ import os
6
+ import sys
7
+ import subprocess
8
+ import urllib.request
9
+ from pathlib import Path
10
+
11
+ def install_requirements():
12
+ """Install required packages"""
13
+ print("πŸ“¦ Installing requirements...")
14
+ try:
15
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
16
+ print("βœ… Requirements installed successfully")
17
+ except subprocess.CalledProcessError as e:
18
+ print(f"❌ Error installing requirements: {e}")
19
+ return False
20
+ return True
21
+
22
+ def create_models_directory():
23
+ """Create models directory if it doesn't exist"""
24
+ models_dir = Path("models")
25
+ models_dir.mkdir(exist_ok=True)
26
+ print(f"πŸ“ Models directory created: {models_dir}")
27
+
28
+ def download_sample_model():
29
+ """Download or create a placeholder model file"""
30
+ model_path = Path("models/aircraft_classifier.pth")
31
+
32
+ if not model_path.exists():
33
+ print("⚠️ No trained model found.")
34
+ print("πŸ’‘ To use the classifier, you need to:")
35
+ print(" 1. Run the training notebook: aircraft_classifier.ipynb")
36
+ print(" 2. Save the trained model to: models/aircraft_classifier.pth")
37
+ print(" 3. Or the app will use random weights (for demo purposes)")
38
+
39
+ def check_system():
40
+ """Check system requirements"""
41
+ print("πŸ” Checking system requirements...")
42
+
43
+ # Check Python version
44
+ if sys.version_info < (3, 8):
45
+ print("❌ Python 3.8+ is required")
46
+ return False
47
+
48
+ print(f"βœ… Python {sys.version.split()[0]}")
49
+
50
+ # Check if CUDA is available
51
+ try:
52
+ import torch
53
+ if torch.cuda.is_available():
54
+ print(f"βœ… CUDA available: {torch.cuda.get_device_name(0)}")
55
+ else:
56
+ print("⚠️ CUDA not available, using CPU")
57
+ except ImportError:
58
+ print("⚠️ PyTorch not installed yet")
59
+
60
+ return True
61
+
62
+ def main():
63
+ """Main setup function"""
64
+ print("πŸ›©οΈ Aircraft Classifier - Setup Script")
65
+ print("=" * 50)
66
+
67
+ # Check system requirements
68
+ if not check_system():
69
+ sys.exit(1)
70
+
71
+ # Install requirements
72
+ if not install_requirements():
73
+ sys.exit(1)
74
+
75
+ # Create necessary directories
76
+ create_models_directory()
77
+
78
+ # Check for model file
79
+ download_sample_model()
80
+
81
+ print("\nπŸŽ‰ Setup complete!")
82
+ print("\nπŸš€ To start the Gradio interface:")
83
+ print(" python app.py")
84
+ print("\nπŸ“š To train the model:")
85
+ print(" jupyter notebook aircraft_classifier.ipynb")
86
+
87
+ if __name__ == "__main__":
88
+ main()
test_app.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify the Aircraft Classifier Gradio app functionality
4
+ """
5
+ import torch
6
+ import numpy as np
7
+ from PIL import Image
8
+ import sys
9
+ import os
10
+
11
+ # Add current directory to path
12
+ sys.path.append('.')
13
+
14
+ def test_app_functionality():
15
+ """Test that the app components work correctly"""
16
+ print("πŸ§ͺ Testing Aircraft Classifier App Components")
17
+ print("=" * 50)
18
+
19
+ try:
20
+ # Import app components
21
+ from app import AircraftClassifier, classify_aircraft, get_top_predictions, CLASS_NAMES
22
+ from config import MODEL_METRICS
23
+
24
+ print("βœ… Successfully imported app components")
25
+
26
+ # Test model creation
27
+ model = AircraftClassifier(num_classes=len(CLASS_NAMES))
28
+ print(f"βœ… Model created: {model.__class__.__name__}")
29
+ print(f" Classes: {len(CLASS_NAMES)}")
30
+
31
+ # Create a dummy test image (random noise)
32
+ test_image = Image.fromarray(np.random.randint(0, 255, (224, 224, 3), dtype=np.uint8))
33
+ print("βœ… Created test image")
34
+
35
+ # Test classification function
36
+ results = classify_aircraft(test_image)
37
+ print("βœ… Classification function works")
38
+ print(f" Got {len(results)} class predictions")
39
+
40
+ # Test top predictions function
41
+ top_preds = get_top_predictions(test_image)
42
+ print("βœ… Top predictions function works")
43
+ print(" Sample output:")
44
+ print(f" {top_preds[:100]}...")
45
+
46
+ # Display model metrics
47
+ print(f"\nπŸ“Š Model Performance (from config):")
48
+ for metric, value in MODEL_METRICS.items():
49
+ print(f" {metric}: {value}")
50
+
51
+ print(f"\nπŸ›©οΈ Aircraft Classes:")
52
+ for i, class_name in enumerate(CLASS_NAMES):
53
+ print(f" {i+1:2d}. {class_name}")
54
+
55
+ print(f"\nπŸŽ‰ All tests passed! The Gradio app is ready to deploy.")
56
+ print(f"πŸ’‘ To launch the interface, run: python app.py")
57
+
58
+ return True
59
+
60
+ except Exception as e:
61
+ print(f"❌ Test failed: {e}")
62
+ import traceback
63
+ traceback.print_exc()
64
+ return False
65
+
66
+ if __name__ == "__main__":
67
+ success = test_app_functionality()
68
+ sys.exit(0 if success else 1)