kamau1 commited on
Commit
a78c902
·
verified ·
1 Parent(s): adf4a4e

horizontal image slider added: smooth scroll, nav buttons, responsive tiles

Browse files
Dockerfile CHANGED
@@ -20,10 +20,19 @@ RUN mkdir -p /models
20
  # Download models and sample images from seamo-ai/marina-species-v1
21
  RUN python -c "\
22
  from huggingface_hub import hf_hub_download; \
 
 
23
  hf_hub_download('seamo-ai/marina-species-v1', 'marina-benthic-33k.pt', local_dir='/models', local_dir_use_symlinks=False); \
24
  hf_hub_download('seamo-ai/marina-species-v1', 'marina-benthic-33k.names', local_dir='/models', local_dir_use_symlinks=False); \
25
- hf_hub_download('seamo-ai/marina-species-v1', 'images/red_fish.png', local_dir='/models', local_dir_use_symlinks=False); \
26
- hf_hub_download('seamo-ai/marina-species-v1', 'images/red_fish_2.png', local_dir='/models', local_dir_use_symlinks=False)"
 
 
 
 
 
 
 
27
 
28
  # Stage 2: Build the application
29
  FROM python:3.10-slim
@@ -75,8 +84,7 @@ COPY --chown=user ./static static
75
 
76
  # Copy sample images from models to static directory for web access
77
  RUN mkdir -p static/images/samples
78
- RUN cp $HOME/app/models/images/red_fish.png static/images/samples/
79
- RUN cp $HOME/app/models/images/red_fish_2.png static/images/samples/
80
 
81
  # Create necessary directories
82
  RUN mkdir -p $HOME/.cache/huggingface $HOME/.cache/torch
 
20
  # Download models and sample images from seamo-ai/marina-species-v1
21
  RUN python -c "\
22
  from huggingface_hub import hf_hub_download; \
23
+ import os; \
24
+ # Download model files \
25
  hf_hub_download('seamo-ai/marina-species-v1', 'marina-benthic-33k.pt', local_dir='/models', local_dir_use_symlinks=False); \
26
  hf_hub_download('seamo-ai/marina-species-v1', 'marina-benthic-33k.names', local_dir='/models', local_dir_use_symlinks=False); \
27
+ # Download all sample images \
28
+ sample_images = ['crab.png', 'fish.png', 'fish_2.png', 'fish_3.png', 'fish_4.png', 'fish_5.png', 'flat_fish.png', 'flat_red_fish.png', 'jelly.png', 'jelly_2.png', 'jelly_3.png', 'puff.png', 'red_fish.png', 'red_fish_2.png', 'scene.png', 'scene_2.png', 'scene_3.png', 'scene_4.png', 'scene_5.png', 'scene_6.png', 'soft_coral.png', 'starfish.png', 'starfish_2.png']; \
29
+ for img in sample_images: \
30
+ try: \
31
+ hf_hub_download('seamo-ai/marina-species-v1', f'images/{img}', local_dir='/models', local_dir_use_symlinks=False); \
32
+ print(f'Downloaded {img}'); \
33
+ except Exception as e: \
34
+ print(f'Failed to download {img}: {e}'); \
35
+ "
36
 
37
  # Stage 2: Build the application
38
  FROM python:3.10-slim
 
84
 
85
  # Copy sample images from models to static directory for web access
86
  RUN mkdir -p static/images/samples
87
+ RUN if [ -d "$HOME/app/models/images" ]; then cp $HOME/app/models/images/*.png static/images/samples/ 2>/dev/null || true; fi
 
88
 
89
  # Create necessary directories
90
  RUN mkdir -p $HOME/.cache/huggingface $HOME/.cache/torch
static/css/dashboard-simple.css CHANGED
@@ -380,33 +380,95 @@
380
  border-top: 1px solid #e5e7eb;
381
  }
382
 
383
- .sample-images-section h4 {
 
 
 
384
  margin-bottom: 15px;
 
 
 
 
385
  color: #374151;
386
  font-size: 1rem;
387
  font-weight: 600;
388
  }
389
 
390
- .sample-images-grid {
391
- display: grid;
392
- grid-template-columns: 1fr 1fr;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
  gap: 12px;
 
 
 
 
 
 
 
 
 
394
  }
395
 
396
  .sample-image-item {
397
  position: relative;
398
- aspect-ratio: 1;
 
399
  border-radius: 8px;
400
  overflow: hidden;
401
  cursor: pointer;
402
  transition: all 0.2s ease;
403
  border: 2px solid transparent;
 
404
  }
405
 
406
  .sample-image-item:hover {
407
  border-color: #2563eb;
408
- transform: scale(1.02);
409
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
410
  }
411
 
412
  .sample-image-item img {
@@ -420,10 +482,24 @@
420
  bottom: 0;
421
  left: 0;
422
  right: 0;
423
- background: linear-gradient(transparent, rgba(0,0,0,0.7));
424
  color: white;
425
- padding: 8px;
426
- font-size: 0.75rem;
427
  font-weight: 500;
428
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  }
 
380
  border-top: 1px solid #e5e7eb;
381
  }
382
 
383
+ .sample-images-header {
384
+ display: flex;
385
+ justify-content: space-between;
386
+ align-items: center;
387
  margin-bottom: 15px;
388
+ }
389
+
390
+ .sample-images-header h4 {
391
+ margin: 0;
392
  color: #374151;
393
  font-size: 1rem;
394
  font-weight: 600;
395
  }
396
 
397
+ .slider-controls {
398
+ display: flex;
399
+ gap: 8px;
400
+ }
401
+
402
+ .slider-btn {
403
+ width: 32px;
404
+ height: 32px;
405
+ border: 1px solid #d1d5db;
406
+ background: white;
407
+ border-radius: 6px;
408
+ display: flex;
409
+ align-items: center;
410
+ justify-content: center;
411
+ cursor: pointer;
412
+ font-size: 18px;
413
+ font-weight: bold;
414
+ color: #6b7280;
415
+ transition: all 0.2s ease;
416
+ user-select: none;
417
+ }
418
+
419
+ .slider-btn:hover {
420
+ border-color: #2563eb;
421
+ color: #2563eb;
422
+ background: #f8fafc;
423
+ }
424
+
425
+ .slider-btn:disabled {
426
+ opacity: 0.5;
427
+ cursor: not-allowed;
428
+ border-color: #e5e7eb;
429
+ color: #9ca3af;
430
+ }
431
+
432
+ .slider-btn:disabled:hover {
433
+ border-color: #e5e7eb;
434
+ color: #9ca3af;
435
+ background: white;
436
+ }
437
+
438
+ /* Horizontal Slider */
439
+ .sample-images-slider {
440
+ display: flex;
441
+ overflow-x: auto;
442
+ scroll-behavior: smooth;
443
+ scroll-snap-type: x mandatory;
444
  gap: 12px;
445
+ padding: 4px 0 8px 0;
446
+
447
+ /* Hide scrollbar */
448
+ scrollbar-width: none;
449
+ -ms-overflow-style: none;
450
+ }
451
+
452
+ .sample-images-slider::-webkit-scrollbar {
453
+ display: none;
454
  }
455
 
456
  .sample-image-item {
457
  position: relative;
458
+ flex: 0 0 120px;
459
+ height: 120px;
460
  border-radius: 8px;
461
  overflow: hidden;
462
  cursor: pointer;
463
  transition: all 0.2s ease;
464
  border: 2px solid transparent;
465
+ scroll-snap-align: start;
466
  }
467
 
468
  .sample-image-item:hover {
469
  border-color: #2563eb;
470
+ transform: scale(1.05);
471
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2);
472
  }
473
 
474
  .sample-image-item img {
 
482
  bottom: 0;
483
  left: 0;
484
  right: 0;
485
+ background: linear-gradient(transparent, rgba(0,0,0,0.8));
486
  color: white;
487
+ padding: 6px 8px;
488
+ font-size: 0.7rem;
489
  font-weight: 500;
490
  text-align: center;
491
+ line-height: 1.2;
492
+ }
493
+
494
+ /* Responsive adjustments */
495
+ @media (max-width: 768px) {
496
+ .sample-image-item {
497
+ flex: 0 0 100px;
498
+ height: 100px;
499
+ }
500
+
501
+ .sample-image-overlay {
502
+ font-size: 0.65rem;
503
+ padding: 4px 6px;
504
+ }
505
  }
static/js/dashboard.js CHANGED
@@ -45,7 +45,9 @@ class MarineDashboard {
45
  this.deviceInfo = document.getElementById('deviceInfo');
46
 
47
  // Sample images
48
- this.sampleImagesGrid = document.getElementById('sampleImagesGrid');
 
 
49
  }
50
 
51
  bindEvents() {
@@ -64,6 +66,10 @@ class MarineDashboard {
64
 
65
  // Identify button
66
  this.identifyBtn.addEventListener('click', this.identifySpecies.bind(this));
 
 
 
 
67
  }
68
 
69
  // API Status Check
@@ -246,19 +252,35 @@ class MarineDashboard {
246
  // Load Sample Images
247
  loadSampleImages() {
248
  const sampleImages = [
249
- {
250
- name: 'red_fish.png',
251
- url: 'https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/red_fish.png',
252
- description: 'Marine Fish Sample'
253
- },
254
- {
255
- name: 'red_fish_2.png',
256
- url: 'https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/red_fish_2.png',
257
- description: 'Deep Sea Sample'
258
- }
259
- ];
260
-
261
- this.sampleImagesGrid.innerHTML = sampleImages.map(image => `
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  <div class="sample-image-item" onclick="dashboard.loadSampleImage('${image.url}', '${image.name}')">
263
  <img src="${image.url}" alt="${image.description}" loading="lazy" />
264
  <div class="sample-image-overlay">${image.description}</div>
@@ -304,6 +326,23 @@ class MarineDashboard {
304
  window.MarineAPI.utils.showNotification('Failed to load sample image. Please try uploading your own image.', 'error');
305
  }
306
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  }
308
 
309
  // Make dashboard instance globally available for sample image clicks
 
45
  this.deviceInfo = document.getElementById('deviceInfo');
46
 
47
  // Sample images
48
+ this.sampleImagesSlider = document.getElementById('sampleImagesSlider');
49
+ this.sliderPrev = document.getElementById('sliderPrev');
50
+ this.sliderNext = document.getElementById('sliderNext');
51
  }
52
 
53
  bindEvents() {
 
66
 
67
  // Identify button
68
  this.identifyBtn.addEventListener('click', this.identifySpecies.bind(this));
69
+
70
+ // Slider controls
71
+ this.sliderPrev.addEventListener('click', this.scrollSliderLeft.bind(this));
72
+ this.sliderNext.addEventListener('click', this.scrollSliderRight.bind(this));
73
  }
74
 
75
  // API Status Check
 
252
  // Load Sample Images
253
  loadSampleImages() {
254
  const sampleImages = [
255
+ { name: 'crab.png', description: 'Crab Species' },
256
+ { name: 'fish.png', description: 'Fish Species' },
257
+ { name: 'fish_2.png', description: 'Fish Variety' },
258
+ { name: 'fish_3.png', description: 'Marine Fish' },
259
+ { name: 'fish_4.png', description: 'Ocean Fish' },
260
+ { name: 'fish_5.png', description: 'Deep Sea Fish' },
261
+ { name: 'flat_fish.png', description: 'Flatfish' },
262
+ { name: 'flat_red_fish.png', description: 'Red Flatfish' },
263
+ { name: 'jelly.png', description: 'Jellyfish' },
264
+ { name: 'jelly_2.png', description: 'Jellyfish Species' },
265
+ { name: 'jelly_3.png', description: 'Marine Jelly' },
266
+ { name: 'puff.png', description: 'Pufferfish' },
267
+ { name: 'red_fish.png', description: 'Red Fish' },
268
+ { name: 'red_fish_2.png', description: 'Red Fish Species' },
269
+ { name: 'scene.png', description: 'Marine Scene' },
270
+ { name: 'scene_2.png', description: 'Ocean Scene' },
271
+ { name: 'scene_3.png', description: 'Underwater Scene' },
272
+ { name: 'scene_4.png', description: 'Deep Sea Scene' },
273
+ { name: 'scene_5.png', description: 'Marine Habitat' },
274
+ { name: 'scene_6.png', description: 'Ocean Floor' },
275
+ { name: 'soft_coral.png', description: 'Soft Coral' },
276
+ { name: 'starfish.png', description: 'Starfish' },
277
+ { name: 'starfish_2.png', description: 'Sea Star' }
278
+ ].map(img => ({
279
+ ...img,
280
+ url: `https://huggingface.co/seamo-ai/marina-species-v1/resolve/main/images/${img.name}`
281
+ }));
282
+
283
+ this.sampleImagesSlider.innerHTML = sampleImages.map(image => `
284
  <div class="sample-image-item" onclick="dashboard.loadSampleImage('${image.url}', '${image.name}')">
285
  <img src="${image.url}" alt="${image.description}" loading="lazy" />
286
  <div class="sample-image-overlay">${image.description}</div>
 
326
  window.MarineAPI.utils.showNotification('Failed to load sample image. Please try uploading your own image.', 'error');
327
  }
328
  }
329
+
330
+ // Slider Control Functions
331
+ scrollSliderLeft() {
332
+ const scrollAmount = 240; // 2 items (120px each + 12px gap)
333
+ this.sampleImagesSlider.scrollBy({
334
+ left: -scrollAmount,
335
+ behavior: 'smooth'
336
+ });
337
+ }
338
+
339
+ scrollSliderRight() {
340
+ const scrollAmount = 240; // 2 items (120px each + 12px gap)
341
+ this.sampleImagesSlider.scrollBy({
342
+ left: scrollAmount,
343
+ behavior: 'smooth'
344
+ });
345
+ }
346
  }
347
 
348
  // Make dashboard instance globally available for sample image clicks
templates/dashboard.html CHANGED
@@ -102,8 +102,14 @@
102
 
103
  <!-- Sample Images -->
104
  <div class="sample-images-section">
105
- <h4>Try Sample Images</h4>
106
- <div class="sample-images-grid" id="sampleImagesGrid">
 
 
 
 
 
 
107
  <!-- Sample images will be populated here -->
108
  </div>
109
  </div>
 
102
 
103
  <!-- Sample Images -->
104
  <div class="sample-images-section">
105
+ <div class="sample-images-header">
106
+ <h4>Try Sample Images</h4>
107
+ <div class="slider-controls">
108
+ <button class="slider-btn slider-btn-prev" id="sliderPrev" aria-label="Previous images">‹</button>
109
+ <button class="slider-btn slider-btn-next" id="sliderNext" aria-label="Next images">›</button>
110
+ </div>
111
+ </div>
112
+ <div class="sample-images-slider" id="sampleImagesSlider">
113
  <!-- Sample images will be populated here -->
114
  </div>
115
  </div>