NEMOtools / app /static /index.html
AndrewKof's picture
Add OOD Detector logic and artifacts
16da67b
<!doctype html>
<html lang="en">
<head>
<meta name="description" content="Free AI-powered tool for identifying Mediterranean fish species. Upload a photo to classify 101 species using DINOv2 and YOLOv11 models.">
<script async src="https://www.googletagmanager.com/gtag/js?id=G-QFG67BD3FB"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
// ✅ UPDATED CONFIG: Enables tracking inside Hugging Face iframes
gtag('config', 'G-QFG67BD3FB', {
'cookie_flags': 'SameSite=None;Secure',
'cookie_domain': 'auto'
});
</script>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>NEMO Tools</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
.toggle-wrapper { display:flex; align-items:center; gap:10px; cursor:pointer; }
.toggle { width:44px; height:24px; background:#d1d5db; border-radius:9999px; position:relative; transition:0.2s; }
.toggle-circle { width:20px; height:20px; background:white; border-radius:50%; position:absolute; top:2px; left:2px; transition:0.2s; }
.toggle.active { background:#7c3aed; }
.toggle.active .toggle-circle { transform:translateX(20px); }
body {
background-image: url('/static/background.jpg');
background-size: cover;
background-position: center;
background-attachment: fixed;
background-repeat: no-repeat;
color: #f9fafb;
}
body::before {
content: "";
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0, 10, 20, 0.3);
z-index: -1;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col font-sans">
<header class="sticky top-0 z-50 backdrop-blur-md bg-white/70 border-b border-white/20 shadow-sm transition-all duration-300">
<div class="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
<div class="flex items-center gap-4 group cursor-pointer">
<div class="relative">
<div class="absolute -inset-1 bg-gradient-to-r from-blue-400 to-indigo-500 rounded-full blur opacity-25 group-hover:opacity-50 transition duration-500"></div>
<img src="/static/assets/logo.png"
alt="NEMO logo"
class="relative h-16 w-16 rounded-full shadow-md border-2 border-white object-cover transform group-hover:scale-105 transition duration-300" />
</div>
<div>
<h1 class="text-2xl font-extrabold text-indigo-900 tracking-tight leading-none group-hover:text-indigo-700 transition">NEMO tools</h1>
<p class="text-xs font-semibold text-indigo-500 uppercase tracking-wide mt-1">Mediterranean Fish Species Classification Tool</p>
</div>
</div>
<nav class="flex gap-2 md:gap-3 bg-white/50 p-1.5 rounded-full border border-white/40 shadow-inner">
<button id="tab-research"
class="tab-btn px-5 py-2 rounded-full text-sm font-semibold text-slate-600 hover:text-indigo-700 hover:bg-white/80 transition-all duration-200 focus:outline-none"
onclick="showTab('research')">
Research
</button>
<button id="tab-people"
class="tab-btn px-5 py-2 rounded-full text-sm font-semibold text-slate-600 hover:text-indigo-700 hover:bg-white/80 transition-all duration-200 focus:outline-none"
onclick="showTab('people')">
People
</button>
<button id="tab-tools"
class="tab-btn px-6 py-2 rounded-full text-sm font-bold text-white bg-indigo-600 shadow-md hover:bg-indigo-700 hover:shadow-lg transition-all duration-200 transform hover:-translate-y-0.5 focus:outline-none"
onclick="showTab('tools')">
Tools
</button>
</nav>
</div>
</header>
<main class="max-w-6xl mx-auto px-4 py-8 w-full flex-grow">
<section id="page-people" class="hidden max-w-6xl mx-auto mb-12">
<div class="text-center mb-12">
<h2 class="text-3xl font-bold text-indigo-900">The Team</h2>
<p class="text-lg text-gray-800 mt-2">
Our interdisciplinary team combines expertise in ecology, marine biology, and artificial intelligence.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/kofidis.jpg" alt="Andreas Kofidis" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Andreas Kofidis</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Data Scientist, IACM-FORTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
His research focuses on deep learning and computer vision, specifically in the development of robust algorithms for object detection and classification in challenging environments.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/dretaki.jpg" alt="Eleni Dretaki" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Eleni Dretaki</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Data Scientist, IACM-FORTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
A data scientist specializing in applied mathematics and statistical analysis. Her work involves processing complex datasets and developing computational methodologies for scientific applications.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/zotou.jpg" alt="Maria Zotou" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Maria Zotou</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">PhD candidate, UAegean</span>
<p class="text-gray-600 text-sm leading-relaxed">
Her research interests lie in marine biodiversity assessment and ecosystem monitoring. She specializes in the taxonomy of Mediterranean marine fauna and field survey methodologies.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/poursanidis.jpg" alt="Dimitris Poursanidis" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Dr. Dimitris Poursanidis</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Technical Researcher, IACM-FORTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
An expert in remote sensing and coastal habitat mapping. His work utilizes satellite and drone imagery to study spatial patterns in marine environments and support conservation planning.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/doxa.jpg" alt="Aggeliki Doxa" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Dr. Aggeliki Doxa</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Associate Prof. of Biology, IACM-FORTH & UoC</span>
<p class="text-gray-600 text-sm leading-relaxed">
Her research focuses on ecological modeling, biodiversity dynamics, and the impact of environmental changes on species distributions and ecosystem services.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/mazaris.jpg" alt="Antonios Mazaris" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Prof. Antonios D. Mazaris</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Professor of Ecology, AUTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
His work addresses large-scale conservation challenges, focusing on global biodiversity patterns, extinction risk assessments, and the effectiveness of protected area networks.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/katsanevakis.jpg" alt="Stelios Katsanevakis" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Prof. Stelios Katsanevakis</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Marine Ecologist, UAegean</span>
<p class="text-gray-600 text-sm leading-relaxed">
His research investigates marine ecosystem dynamics, biological invasions, and marine conservation planning, aiming to support ecosystem-based management strategies.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/kamarianakis.jpg" alt="Yiannis Kamarianakis" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Dr. Yiannis Kamarianakis</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Research Director, IACM-FORTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
An expert in applied mathematics and statistics. He specializes in time-series analysis, spatial statistics, and the development of predictive models for complex systems.
</p>
</div>
<div class="bg-white rounded-2xl shadow-lg p-6 flex flex-col items-center text-center hover:shadow-xl transition">
<img src="/static/team/pantazis.jpg" alt="Yannis Pantazis" class="w-24 h-24 rounded-full mb-4 object-cover border-4 border-indigo-50 shadow-sm">
<h3 class="text-lg font-bold text-gray-800">Dr. Yannis Pantazis</h3>
<span class="text-indigo-600 font-medium text-sm mb-3">Principal Researcher, IACM-FORTH</span>
<p class="text-gray-600 text-sm leading-relaxed">
His research spans machine learning, signal processing, and data-driven discovery, focusing on developing novel AI architectures and mathematical frameworks for scientific applications.
</p>
</div>
</div>
</section>
<section id="page-research" class="hidden max-w-5xl mx-auto mb-12">
<div class="bg-white shadow-lg rounded-2xl p-8">
<div class="border-b pb-6 mb-6">
<h2 class="text-3xl font-bold text-indigo-900">NEMO-Tools Project</h2>
<p class="text-lg text-indigo-600 font-medium mt-2">
<span class="text-indigo-900 font-bold">NE</span>xt-generation
<span class="text-indigo-900 font-bold">MO</span>nitoring and mapping
<span class="text-indigo-900 font-bold">Tools</span> to assess marine ecosystems and biodiversity
</p>
</div>
<div class="prose max-w-none text-gray-700 leading-relaxed space-y-4">
<p>
<strong>NEMO-Tools</strong> is an ambitious research initiative designed to develop affordable, user-friendly, and smart tools for marine biodiversity monitoring. Its primary goal is to support the <strong>EU Biodiversity Strategy for 2030</strong> by providing high-precision data to measure ecosystem health and recovery.
</p>
<p>
While this web interface focuses on <strong>Artificial Intelligence (AI)</strong> for species identification, the full NEMO-Tools project integrates a hierarchical approach combining multiple cutting-edge technologies:
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 my-8">
<div class="bg-indigo-50 p-5 rounded-xl border border-indigo-100">
<h3 class="font-bold text-indigo-800 mb-2 flex items-center gap-2">🤖 Artificial Intelligence</h3>
<p class="text-sm">
Utilizing Deep Learning (DINOv2, YOLOv11) to classify species from underwater imagery. This tool specifically utilizes the <strong>MEDFISH101</strong> dataset to identify 101 Mediterranean fish species.
</p>
</div>
<div class="bg-indigo-50 p-5 rounded-xl border border-indigo-100">
<h3 class="font-bold text-indigo-800 mb-2 flex items-center gap-2">🛰️ Remote Sensing</h3>
<p class="text-sm">
Employing satellite (Planet SuperDove) and drone (UAV) imagery to map macroalgae forests and detect marine megafauna in shallow coastal waters.
</p>
</div>
<div class="bg-indigo-50 p-5 rounded-xl border border-indigo-100">
<h3 class="font-bold text-indigo-800 mb-2 flex items-center gap-2">🧬 Environmental DNA (eDNA)</h3>
<p class="text-sm">
Developing cost-effective sampling protocols and low-cost 3D-printed probes to detect rare or elusive species through genetic material released in the water.
</p>
</div>
<div class="bg-indigo-50 p-5 rounded-xl border border-indigo-100">
<h3 class="font-bold text-indigo-800 mb-2 flex items-center gap-2">⚓ Robotics & Acoustics</h3>
<p class="text-sm">
Deploying upgraded ROVs for quantitative benthic assessments and Ground Unmanned Vehicles (GUVs) for sandy shore monitoring. Includes low-cost hydrophones for citizen-science acoustic monitoring.
</p>
</div>
</div>
<p>
The identification tool provided here addresses the challenge of monitoring visually similar species.
It leverages the <strong>MEDFISH101</strong> dataset, a rigorously curated collection of approximately
<strong>70,000 expert-validated images</strong>. This dataset represents 101 fish species
inhabiting the Hellenic and Mediterranean seas, spanning major families such as <em>Sparidae</em>,
<em>Serranidae</em>, and <em>Labridae</em>.
</p>
<p>
By encompassing commercial, protected, and invasive species—including the Indo-Pacific Lionfish (<em>Pterois miles</em>)—the tool facilitates near real-time ecological monitoring and early detection
of biological threats. Powered by state-of-the-art Vision Foundation Models achieving up to
<strong>94,12% accuracy</strong> using DINOv2-Giant, it empowers citizen scientists to contribute reliable data for
evidence-based marine management.
</p>
</div>
<div class="mt-10">
<h3 class="text-xl font-bold text-indigo-700 mb-4 flex items-center gap-2">
🐟 MEDFISH101 Species List
</h3>
<p class="text-sm text-gray-500 mb-4">
The following 101 species are currently supported by the model classification engine:
</p>
<div class="bg-gray-50 rounded-xl p-6 border border-gray-200 h-[500px] overflow-y-auto">
<ul class="grid grid-cols-1 md:grid-cols-3 gap-y-2 gap-x-4 text-sm text-gray-700 font-medium">
<li><i>Aidablennius sphynx</i></li>
<li><i>Apogon imberbis</i></li>
<li><i>Arnoglossus laterna</i></li>
<li><i>Atherina boyeri</i></li>
<li><i>Atherina hepsetus</i></li>
<li><i>Boops boops</i></li>
<li><i>Bothus podas</i></li>
<li><i>Callionymus pusillus</i></li>
<li><i>Callionymus risso</i></li>
<li><i>Caranx crysos</i></li>
<li><i>Chelidonichthys lucerna</i></li>
<li><i>Chromis chromis</i></li>
<li><i>Coris julis</i></li>
<li><i>Dactylopterus volitans</i></li>
<li><i>Dasyatis pastinaca</i></li>
<li><i>Diplodus annularis</i></li>
<li><i>Diplodus puntazzo</i></li>
<li><i>Diplodus sargus</i></li>
<li><i>Diplodus vulgaris</i></li>
<li><i>Echiichthys vipera</i></li>
<li><i>Epinephelus costae</i></li>
<li><i>Epinephelus marginatus</i></li>
<li><i>Fistularia commersonii</i></li>
<li><i>Gobius auratus</i></li>
<li><i>Gobius bucchichi</i></li>
<li><i>Gobius cobitis</i></li>
<li><i>Gobius cruentatus</i></li>
<li><i>Gobius geniporus</i></li>
<li><i>Gobius incognitus</i></li>
<li><i>Gobius niger</i></li>
<li><i>Gobius paganellus</i></li>
<li><i>Gobius vittatus</i></li>
<li><i>Hippocampus guttulatus</i></li>
<li><i>Hippocampus hippocampus</i></li>
<li><i>Labrus bergylta</i></li>
<li><i>Labrus merula</i></li>
<li><i>Labrus mixtus</i></li>
<li><i>Labrus viridis</i></li>
<li><i>Lagocephalus sceleratus</i></li>
<li><i>Lithognathus mormyrus</i></li>
<li><i>Microlipophrys nigriceps</i></li>
<li><i>Mugil auratus</i></li>
<li><i>Mugil cephalus</i></li>
<li><i>Mullus barbatus</i></li>
<li><i>Mullus surmuletus</i></li>
<li><i>Muraena helena</i></li>
<li><i>Oblada melanura</i></li>
<li><i>Pagrus pagrus</i></li>
<li><i>Parablennius gattorugine</i></li>
<li><i>Parablennius incognitus</i></li>
<li><i>Parablennius pilicornis</i></li>
<li><i>Parablennius rouxi</i></li>
<li><i>Parablennius sanguinolentus</i></li>
<li><i>Parablennius tentacularis</i></li>
<li><i>Parablennius zvonimiri</i></li>
<li><i>Parupeneus forsskali</i></li>
<li><i>Phycis phycis</i></li>
<li><i>Plotosus lineatus</i></li>
<li><i>Pomatoschistus marmoratus</i></li>
<li><i>Pterois miles</i></li>
<li><i>Salaria pavo</i></li>
<li><i>Sargocentron rubrum</i></li>
<li><i>Sarpa salpa</i></li>
<li><i>Sciaena umbra</i></li>
<li><i>Scorpaena maderensis</i></li>
<li><i>Scorpaena notata</i></li>
<li><i>Scorpaena scrofa</i></li>
<li><i>Serranus cabrilla</i></li>
<li><i>Serranus hepatus</i></li>
<li><i>Serranus scriba</i></li>
<li><i>Siganus luridus</i></li>
<li><i>Siganus rivulatus</i></li>
<li><i>Solea solea</i></li>
<li><i>Sparisoma cretense</i></li>
<li><i>Sparus aurata</i></li>
<li><i>Spicara maena</i></li>
<li><i>Spicara smaris</i></li>
<li><i>Spondyliosoma cantharus</i></li>
<li><i>Stephanolepis diaspros</i></li>
<li><i>Symphodus cinereus</i></li>
<li><i>Symphodus mediterraneus</i></li>
<li><i>Symphodus melanocercus</i></li>
<li><i>Symphodus ocellatus</i></li>
<li><i>Symphodus roissali</i></li>
<li><i>Symphodus rostratus</i></li>
<li><i>Symphodus tinca</i></li>
<li><i>Syngnathus abaster</i></li>
<li><i>Syngnathus tenuirostris</i></li>
<li><i>Syngnathus typhle</i></li>
<li><i>Thalassoma pavo</i></li>
<li><i>Thorogobius ephippiatus</i></li>
<li><i>Torpedo marmorata</i></li>
<li><i>Torquigener flavimaculosus</i></li>
<li><i>Trachinus araneus</i></li>
<li><i>Trachinus draco</i></li>
<li><i>Trachinus radiatus</i></li>
<li><i>Tripterygion delaisi</i></li>
<li><i>Tripterygion melanurus</i></li>
<li><i>Tripterygion tripteronotus</i></li>
<li><i>Uranoscopus scaber</i></li>
<li><i>Xyrichtys novacula</i></li>
</ul>
</div>
</div>
</div>
</section>
<section id="page-tools">
<div class="bg-white shadow-lg rounded-2xl p-8 w-full">
<h2 class="text-2xl font-bold text-indigo-600 mb-6 flex items-center gap-2">🧰 Tools</h2>
<div class="flex gap-3 mb-6 border-b pb-2">
<button id="sub-classification" class="subtab-btn text-indigo-600 font-medium border-b-2 border-indigo-600 pb-1" onclick="showSubTool('classification')">🔍 Classification</button>
<button id="sub-attention" class="subtab-btn text-gray-500 hover:text-indigo-600 pb-1" onclick="showSubTool('attention')">🧠 Visualization</button>
</div>
<div id="tool-attention" class="flex flex-col items-center w-full mt-6">
<div class="flex flex-col md:flex-row w-full items-stretch justify-center gap-8 px-4">
<div class="flex-1 flex flex-col justify-center items-start gap-4 min-w-[250px]">
<p class="text-gray-600 font-semibold mb-1 text-sm">Choose your model:</p>
<div class="toggle-wrapper group" onclick="setModel('dino')">
<div id="toggle-dino-vis" class="toggle active group-hover:ring-2 group-hover:ring-green-300">
<div class="toggle-circle shadow-sm"></div>
</div>
<div class="flex flex-col">
<span class="text-gray-800 font-bold text-sm">DINOv2-Giant</span>
<span class="text-gray-400 text-xs">High accuracy, slower</span>
</div>
</div>
<div class="toggle-wrapper group" onclick="setModel('yolo')">
<div id="toggle-yolo-vis" class="toggle group-hover:ring-2 group-hover:ring-green-300">
<div class="toggle-circle shadow-sm"></div>
</div>
<div class="flex flex-col">
<span class="text-gray-800 font-bold text-sm">YOLOv11-X</span>
<span class="text-gray-400 text-xs">Lower accuracy, faster</span>
</div>
</div>
</div>
<div class="flex-1 flex flex-col justify-center items-center text-center border-2 border-dashed border-gray-300 rounded-xl p-6 bg-gray-50 hover:bg-gray-100 transition min-w-[300px]">
<p class="text-gray-700 font-bold mb-2">Upload Image</p>
<p class="text-gray-400 text-xs mb-5">Supported: JPG, PNG</p>
<label for="vis-file"
class="inline-flex items-center justify-center gap-2 px-8 py-3 bg-green-600 hover:bg-green-700 text-white font-bold rounded-full cursor-pointer shadow-md transition-all transform hover:scale-105">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
Choose File
</label>
<input id="vis-file" type="file" accept="image/*" class="hidden" onchange="onVisFileSelected()" />
<p id="vis-filename" class="text-gray-500 mt-3 text-xs italic font-medium"></p>
</div>
<div class="flex-1 min-w-[250px] hidden md:block"></div>
</div>
<div class="w-full flex justify-center mt-6">
<button id="vis-run" onclick="runVisualization()"
style="display:none;"
class="px-8 py-3 bg-indigo-600 text-white text-lg font-semibold rounded-full shadow-md hover:bg-indigo-700 transition transform hover:scale-105">
▶️ Run Visualization
</button>
</div>
<div id="attention-extra" class="hidden flex flex-col items-center gap-6 w-full mt-8">
<div class="flex flex-col md:flex-row justify-center items-start gap-6">
<div class="flex flex-col items-center">
<h4 class="text-gray-600 mb-2 font-medium">Original Image</h4>
<img id="original" class="rounded-lg shadow-md max-w-[200px] hidden" />
</div>
<div class="flex flex-col items-center">
<h4 class="text-gray-600 mb-2 font-medium">PCA Attention</h4>
<img id="output" class="rounded-lg shadow-md max-w-[200px] hidden" />
</div>
</div>
<div id="headsContainer" class="hidden mt-8 w-full">
<h4 class="text-gray-600 mb-3 font-medium text-center">All Attention Heads</h4>
<div id="headsGrid" class="flex flex-wrap justify-center gap-3"></div>
</div>
<p id="status" class="text-center text-gray-500 mt-2 text-sm"></p>
</div>
</div>
<div id="tool-classification" class="hidden flex flex-col items-start mt-6 w-full">
<div class="flex flex-col md:flex-row w-full items-stretch justify-center gap-8 px-4">
<div class="flex-1 flex flex-col justify-center items-start gap-4 min-w-[250px]">
<p class="text-gray-600 font-semibold mb-1 text-sm">Choose your model:</p>
<div class="toggle-wrapper group" onclick="setModel('dino')">
<div id="toggle-dino-cls" class="toggle active group-hover:ring-2 group-hover:ring-green-300">
<div class="toggle-circle shadow-sm"></div>
</div>
<div class="flex flex-col">
<span class="text-gray-800 font-bold text-sm">DINOv2-Giant</span>
<span class="text-gray-400 text-xs">High accuracy, slower</span>
</div>
</div>
<div class="toggle-wrapper group" onclick="setModel('yolo')">
<div id="toggle-yolo-cls" class="toggle group-hover:ring-2 group-hover:ring-green-300">
<div class="toggle-circle shadow-sm"></div>
</div>
<div class="flex flex-col">
<span class="text-gray-800 font-bold text-sm">YOLOv11-X</span>
<span class="text-gray-400 text-xs">Lower accuracy, faster</span>
</div>
</div>
</div>
<div class="flex-1 flex flex-col justify-center items-center text-center border-2 border-dashed border-gray-300 rounded-xl p-6 bg-gray-50 hover:bg-gray-100 transition min-w-[300px]">
<p class="text-gray-700 font-bold mb-2">Upload Image</p>
<p class="text-gray-400 text-xs mb-5">Supported: JPG, PNG</p>
<label for="cls-file"
class="inline-flex items-center justify-center gap-2 px-8 py-3 bg-green-600 hover:bg-green-700 text-white font-bold rounded-full cursor-pointer shadow-md transition-all transform hover:scale-105">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
Choose File
</label>
<input id="cls-file" type="file" accept="image/*" class="hidden" onchange="onClsFileSelected()" />
<p id="cls-filename" class="text-gray-500 mt-3 text-xs italic"></p>
</div>
<div class="flex-1 flex flex-col justify-center gap-4 min-w-[250px] pl-12">
<div>
<label class="block text-gray-700 font-bold mb-1 text-sm">Location of observation:</label>
<input id="obs-location" type="text" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm text-gray-900 focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all focus:outline-none shadow-sm" placeholder="e.g. Aegean Sea" />
</div>
<div>
<label class="block text-gray-700 font-bold mb-1 text-sm">Potential species name:</label>
<input id="obs-species" type="text" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm text-gray-900 focus:ring-2 focus:ring-green-500 focus:border-green-500 transition-all focus:outline-none shadow-sm" placeholder="e.g. Sparus aurata" />
</div>
</div>
</div>
<div class="w-full flex justify-center mt-6">
<button id="cls-run" onclick="runClassification()" style="display:none;" class="px-8 py-3 bg-green-600 text-white text-lg font-semibold rounded-full shadow-md hover:bg-green-700 transition">
▶️ Run Classification
</button>
</div>
<div id="cls-result" class="text-center text-gray-700 mt-6 text-lg font-medium w-full"></div>
</div>
</div>
</section>
</main>
<script>
let selectedModel = "dino"; // default model
function setModel(name) {
selectedModel = name;
// Update Visualization Toggles
document.getElementById("toggle-dino-vis")?.classList.toggle("active", name === "dino");
document.getElementById("toggle-yolo-vis")?.classList.toggle("active", name === "yolo");
// Update Classification Toggles
document.getElementById("toggle-dino-cls")?.classList.toggle("active", name === "dino");
document.getElementById("toggle-yolo-cls")?.classList.toggle("active", name === "yolo");
console.log("MODEL SELECTED:", selectedModel);
}
let activeTool = "attention";
function showSubTool(name) {
const subs = ["attention", "classification"];
subs.forEach(s => {
document.getElementById("tool-" + s).classList.add("hidden");
document.getElementById("sub-" + s).classList.remove("text-indigo-600", "font-medium", "border-b-2", "border-indigo-600");
document.getElementById("sub-" + s).classList.add("text-gray-500");
});
document.getElementById("tool-" + name).classList.remove("hidden");
document.getElementById("sub-" + name).classList.add("text-indigo-600", "font-medium", "border-b-2", "border-indigo-600");
document.getElementById("sub-" + name).classList.remove("text-gray-500");
activeTool = name;
}
// ──────────────────────────────────────────────
// 🧠 VISUALIZATION LOGIC
// ──────────────────────────────────────────────
function onVisFileSelected() {
const fileInput = document.getElementById("vis-file");
const nameEl = document.getElementById("vis-filename");
const runBtn = document.getElementById("vis-run");
const extra = document.getElementById("attention-extra");
const original = document.getElementById("original");
// Reset previous results
extra.classList.add("hidden");
if (fileInput.files.length > 0) {
const file = fileInput.files[0];
nameEl.textContent = file.name; // Show filename
runBtn.style.display = "block"; // Show Run button
// Pre-load original image for display later
const reader = new FileReader();
reader.onload = e => {
original.src = e.target.result;
original.classList.remove("hidden");
};
reader.readAsDataURL(file);
} else {
nameEl.textContent = "";
runBtn.style.display = "none";
}
}
async function runVisualization() {
const file = document.getElementById("vis-file").files[0];
const output = document.getElementById("output");
const status = document.getElementById("status");
const headsContainer = document.getElementById("headsContainer");
const headsGrid = document.getElementById("headsGrid");
const btn = document.getElementById("vis-run");
const extra = document.getElementById("attention-extra");
if (!file) return alert("Please choose an image first!");
// 1. Change Button State to Loading
btn.disabled = true;
btn.classList.add("opacity-80", "cursor-not-allowed");
// Replace text with spinner
btn.innerHTML = `
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing...
`;
// Setup UI for results
extra.classList.remove("hidden");
output.classList.add("hidden");
headsContainer.classList.add("hidden");
headsGrid.innerHTML = "";
status.textContent = "Running model inference...";
// Google Analytics
if (typeof gtag === 'function') {
gtag('event', 'run_visualization', { 'event_category': 'Model', 'event_label': 'DINOv2_Hybrid' });
}
const fd = new FormData();
fd.append("file", file);
try {
const res = await fetch("/attention", { method: "POST", body: fd });
const json = await res.json();
// 1. Show PCA Image
if (json.pca_image) {
output.src = "data:image/png;base64," + json.pca_image;
output.classList.remove("hidden");
}
// 2. Show 16 Attention Heads
if (json.head_attention_maps) {
json.head_attention_maps.forEach((b64, i) => {
const img = document.createElement("img");
img.src = "data:image/png;base64," + b64;
img.className = "rounded-md shadow-sm w-[120px] transition hover:scale-110";
img.title = `Head ${i+1}`;
headsGrid.appendChild(img);
});
headsContainer.classList.remove("hidden");
}
status.textContent = "✅ Visualization Complete!";
} catch (err) {
status.textContent = "❌ Error: " + err.message;
} finally {
// 2. Reset Button State
btn.disabled = false;
btn.innerHTML = "▶️ Run Visualization";
btn.classList.remove("opacity-80", "cursor-not-allowed");
}
}
// ──────────────────────────────────────────────
// 🔍 CLASSIFICATION LOGIC
// ──────────────────────────────────────────────
function onClsFileSelected() {
const fileInput = document.getElementById("cls-file");
const fileName = document.getElementById("cls-filename");
const runBtn = document.getElementById("cls-run");
// Clear previous results
document.getElementById("cls-result").innerHTML = "";
if (fileInput.files.length > 0) {
fileName.textContent = fileInput.files[0].name;
runBtn.style.display = "block";
} else {
fileName.textContent = "";
runBtn.style.display = "none";
}
}
async function runClassification() {
const file = document.getElementById("cls-file").files[0];
const result = document.getElementById("cls-result");
const btn = document.getElementById("cls-run");
if (!file) return alert("Please choose an image to classify!");
// 1. Loading State
btn.disabled = true;
btn.classList.add("opacity-80", "cursor-not-allowed");
btn.innerHTML = `
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline-block" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing...
`;
// Analytics
if (typeof gtag === 'function') {
gtag('event', 'run_classification', { 'event_category': 'Model', 'event_label': selectedModel });
}
const fd = new FormData();
fd.append("file", file);
fd.append("model", selectedModel);
try {
const res = await fetch("/classify", { method: "POST", body: fd });
const json = await res.json();
result.innerHTML = "";
// --- Inside runClassification() after receiving json ---
if (json.ood_metadata && json.ood_metadata.is_ood) {
const warningHtml = `
<div class="bg-amber-50 border-l-4 border-amber-400 p-4 mb-4 mx-auto max-w-4xl rounded shadow-sm">
<div class="flex">
<div class="ml-3">
<p class="text-sm text-amber-700">
<strong>⚠️ Out-of-Distribution Warning:</strong>
This image does not look like a known fish species from our database (OOD Score: ${json.ood_metadata.distance.toFixed(2)}).
Results below may be inaccurate.
(<a href="javascript:void(0)" onclick="showTab('research')" class="text-indigo-600 font-bold hover:underline">View supported species list</a>)
</p>
</div>
</div>
</div>
`;
result.insertAdjacentHTML('afterbegin', warningHtml);
}
// 2. Display Plot
if (json.plot) {
const plotImg = document.createElement("img");
plotImg.src = json.plot;
plotImg.className = "block mx-auto mt-6 max-w-4xl rounded-lg shadow-md";
result.appendChild(plotImg);
} else {
result.textContent = "No predictions returned.";
}
// 3. Social Share Buttons (WhatsApp, Instagram, LinkedIn, X, Facebook)
if (json.top5 && json.top5.length > 0) {
const topResult = json.top5[0];
const species = topResult.label;
const confidence = (topResult.score * 100).toFixed(1);
const shareUrl = "https://huggingface.co/spaces/AndrewKof/NEMOtools";
const shareText = `I just identified a ${species} with ${confidence}% confidence using NEMO-Tools! 🐟`;
// Link Generators
const twitterLink = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}&hashtags=MarineBiology,AI,NEMOTools`;
const fbLink = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}&quote=${encodeURIComponent(shareText)}`;
const waLink = `https://api.whatsapp.com/send?text=${encodeURIComponent(shareText + " " + shareUrl)}`;
const liLink = `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}`;
// HTML Block
const shareHtml = `
<div class="mt-8 flex flex-col items-center w-full">
<p class="text-sm text-gray-500 mb-4 font-medium uppercase tracking-wide">Share your discovery</p>
<div class="flex flex-wrap justify-center gap-3 w-full max-w-2xl">
<a href="${twitterLink}" target="_blank" rel="noopener noreferrer" class="flex items-center justify-center gap-2 w-12 h-12 bg-black text-white rounded-full hover:bg-gray-800 transition shadow-sm hover:-translate-y-1 hover:shadow-md" title="Share on X">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
<a href="${fbLink}" target="_blank" rel="noopener noreferrer" class="flex items-center justify-center gap-2 w-12 h-12 bg-[#1877F2] text-white rounded-full hover:bg-blue-700 transition shadow-sm hover:-translate-y-1 hover:shadow-md" title="Share on Facebook">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036c-2.648 0-2.928 1.67-2.928 4.353v1.618h2.391l-.928 3.667h-1.463v7.98H9.101Z"/></svg>
</a>
<a href="${waLink}" target="_blank" rel="noopener noreferrer" class="flex items-center justify-center gap-2 w-12 h-12 bg-[#25D366] text-white rounded-full hover:bg-green-600 transition shadow-sm hover:-translate-y-1 hover:shadow-md" title="Share on WhatsApp">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z"/></svg>
</a>
<a href="${liLink}" target="_blank" rel="noopener noreferrer" class="flex items-center justify-center gap-2 w-12 h-12 bg-[#0077B5] text-white rounded-full hover:bg-blue-800 transition shadow-sm hover:-translate-y-1 hover:shadow-md" title="Share on LinkedIn">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
</a>
</div>
</div>
`;
result.insertAdjacentHTML('beforeend', shareHtml);
}
} catch (err) {
result.textContent = "❌ Error: " + err.message;
} finally {
btn.disabled = false;
btn.innerHTML = "▶️ Run Classification";
btn.classList.remove("opacity-80", "cursor-not-allowed");
}
}
// Helper for Instagram Copy
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
alert("Link copied! You can now open Instagram and paste it in a Story or Message.");
window.open("https://instagram.com", "_blank");
}, (err) => {
console.error('Could not copy text: ', err);
});
}
// Helper for "Run Active Tool" if needed
async function runActiveTool() {
if (activeTool === "attention") await runVisualization();
else await runClassification();
}
// TAB SWITCHING LOGIC
// TAB SWITCHING LOGIC (Fixed)
function showTab(tabName) {
// 1. Hide all pages
['tools', 'research', 'people'].forEach(page => {
const el = document.getElementById('page-' + page);
if(el) el.classList.add('hidden');
});
// 2. Show selected page
const selected = document.getElementById('page-' + tabName);
if(selected) selected.classList.remove('hidden');
// 3. Reset All Buttons to "Inactive" State
const allButtons = document.querySelectorAll('.tab-btn');
allButtons.forEach(btn => {
// Remove the "Active" styles (Purple/Indigo background & White text)
btn.classList.remove('bg-indigo-600', 'text-white', 'shadow-md');
// Add the "Inactive" styles (Transparent background & Gray text)
btn.classList.add('text-slate-600', 'hover:bg-white/80', 'hover:text-indigo-700');
});
// 4. Set Active Button Style
const activeBtn = document.getElementById('tab-' + tabName);
if(activeBtn) {
// Remove "Inactive" styles
activeBtn.classList.remove('text-slate-600', 'hover:bg-white/80', 'hover:text-indigo-700');
// Add "Active" styles (Purple/Indigo background & White text)
activeBtn.classList.add('bg-indigo-600', 'text-white', 'shadow-md');
}
}
// Initialize
showSubTool("classification");
</script>
<footer class="bg-slate-900 border-t border-slate-800 mt-20 pb-12 text-white">
<div class="max-w-7xl mx-auto px-6 pt-16">
<div class="text-center mb-10">
<h3 class="text-sm font-bold tracking-[0.2em] text-slate-400 uppercase">
Supported By
</h3>
<div class="w-16 h-1 bg-indigo-500 mx-auto mt-4 rounded-full shadow-lg shadow-indigo-500/50"></div>
</div>
<div class="bg-white rounded-3xl p-10 shadow-2xl mx-auto max-w-6xl">
<div class="flex flex-wrap justify-center items-center gap-x-16 gap-y-12">
<a href="https://www.iacm.forth.gr/" target="_blank" rel="noopener noreferrer">
<img src="/static/logos/forth.png" alt="IACM-FORTH" class="h-24 md:h-32 w-auto object-contain transition-transform duration-300 hover:scale-110 cursor-pointer">
</a>
<a href="https://www.aegean.gr/" target="_blank" rel="noopener noreferrer">
<img src="/static/logos/aegean.png" alt="University of the Aegean" class="h-24 md:h-32 w-auto object-contain transition-transform duration-300 hover:scale-110 cursor-pointer">
</a>
<a href="https://www.elidek.gr/en/homepage/" target="_blank" rel="noopener noreferrer">
<img src="/static/logos/hfri.png" alt="HFRI" class="h-24 md:h-32 w-auto object-contain transition-transform duration-300 hover:scale-110 cursor-pointer">
</a>
<a href="https://www.uoc.gr/en/home/" target="_blank" rel="noopener noreferrer">
<img src="/static/logos/uoc.png" alt="University of Crete" class="h-24 md:h-32 w-auto object-contain transition-transform duration-300 hover:scale-110 cursor-pointer">
</a>
<a href="https://www.auth.gr/en/" target="_blank" rel="noopener noreferrer">
<img src="/static/logos/auth.png" alt="Aristotle University of Thessaloniki" class="h-12 md:h-14 w-auto object-contain transition-transform duration-300 hover:scale-110 cursor-pointer">
</a>
</div>
</div>
<div class="mt-16 text-center border-t border-slate-800 pt-8">
<p class="text-slate-500 text-sm">
© 2025 NEMO-Tools. Mediterranean Fish Species Classification. All rights reserved.
</p>
</div>
</div>
</footer>
</body>
</html>