Skip to content

feat: add fish species classification model with MobileNetV3-Small#117

Open
arcgod-design wants to merge 5 commits into
jpdevhub:mainfrom
arcgod-design:feat/issue-2-fish-classification
Open

feat: add fish species classification model with MobileNetV3-Small#117
arcgod-design wants to merge 5 commits into
jpdevhub:mainfrom
arcgod-design:feat/issue-2-fish-classification

Conversation

@arcgod-design

@arcgod-design arcgod-design commented Jun 18, 2026

Copy link
Copy Markdown

Summary

  • Added fish species classification using MobileNetV3-Small (~2.5M params)
  • Replaces hardcoded "Rohu Carp" with dynamic species detection
  • Supports 10 common fish species found in local markets

Changes

  • backend/species.py: NEW — Species classifier module with MobileNetV3-Small architecture, labels, metadata, and inference function
  • scripts/train_species.py: NEW — Training script for the species model (supports custom datasets or online datasets like FishNet-121)
  • backend/main.py: Integrated species classifier into scan pipeline

Species Supported

  1. Rohu Carp (Labeo rohita)
  2. Catla Carp (Catla catla)
  3. Mrigal Carp (Cirrhinus cirrhosus)
  4. Pangas (Pangasius hypophthalmus)
  5. Basa (Pangasius bocourti)
  6. Tilapia (Oreochromis niloticus)
  7. Pomfret (Pampus argenteus)
  8. Kingfish (Scomberomorus commerson)
  9. Mackerel (Rastrelliger kanagurta)
  10. Sardine (Sardinella longiceps)

Architecture

  • Model: MobileNetV3-Small backbone + custom classifier head (256 → num_classes)
  • Input: 224×224 RGB image
  • Output: Species label + confidence score
  • Preprocessing: ImageNet normalization (same as existing models)
  • Fallback: Returns "Rohu Carp" with 0.0 confidence if model weights not found

Integration Points

  • process_scan(): Classifies species from body image, populates species_detected field
  • scan_auto(): Classifies species from uploaded image
  • _build_scan_payload(): Accepts optional species_info dict
  • _row_to_payload(): Uses species_detected from DB with metadata lookup
  • Server startup: Loads species model weights alongside existing models

Training

# With custom dataset
python scripts/train_species.py --data_dir ./dataset --epochs 20

# Dataset structure expected:
#   dataset/train/<species_name>/images...
#   dataset/val/<species_name>/images...

Closes

closes #2

Summary by CodeRabbit

  • New Features
    • Added optional fish species classification to both scan modes; results now include predicted common/scientific name, habitat, and tags.
    • Species confidence is applied: when confidence is zero, the species is labeled “unclassified.”
    • Scan history now saves the detected species label and associated species details for later display.
  • Bug Fixes
    • When species metadata/model isn’t available, scans fall back to default Rohu Carp details instead of failing.
  • Chores
    • Added standalone training scripts for the species classifier (including synthetic-data options) and improved model dependency constraints.

@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the karan3431's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

Copy link
Copy Markdown

🎉 Thank you for your Pull Request! We're thrilled to have your contribution to FreshScan AI.

Before we review, please make sure you have:

  • Followed the CONTRIBUTING.md guidelines.
  • Ensured all automated CI checks (linting, tests) are passing.
  • Checked that your commit messages follow the Conventional Commits format.

A maintainer will review your code as soon as possible!

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Warning

.coderabbit.yaml has a parsing error

The CodeRabbit configuration file in this repository has a parsing error and default settings were used instead. Please fix the error(s) in the configuration file. You can initialize chat with CodeRabbit to get help with the configuration file.

💥 Parsing errors (1)
Validation error: Invalid input: expected object, received boolean at "reviews.auto_review"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds species classification support to the backend scan flow, introduces multiple training and data-generation scripts, and constrains torchvision to a compatible version range.

Changes

Species Classification Integration

Layer / File(s) Summary
Species module and inference
backend/species.py
Defines species labels and metadata, builds the MobileNetV3-Small classifier, sets preprocessing, loads checkpoints, and returns structured species predictions with fallback output.
Backend scan wiring
backend/main.py, backend/requirements.txt
Adds species model configuration and startup loading, extends scan payload assembly and DB row conversion with species metadata, updates both scan endpoints to run species prediction and persist detected species, and pins torchvision to a compatible version range.
Training workflows
scripts/train_species.py, scripts/train_synthetic.py, scripts/train_real.py, scripts/fix_and_train.py, scripts/generate_synthetic.py
Adds species training, synthetic training, real-image training, dataset fixing/training, and synthetic image generation scripts.
Estimated code review effort: 4 (Complex) ~45 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a MobileNetV3-Small fish species classifier.
Linked Issues check ✅ Passed The PR adds a lightweight species model and FastAPI integration that populates species_detected dynamically.
Out of Scope Changes check ✅ Passed The added training and synthetic-data scripts are directly related to building and integrating the species classifier.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/main.py (1)

515-529: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t persist the zero-confidence fallback as a detected species.

When species weights are unavailable, predict_species() returns Rohu Carp with confidence: 0.0; both inserts persist only the name, so history later shows a hardcoded Rohu Carp as if it were a real detection. Store an explicit unknown/unclassified species for zero-confidence results, or persist species confidence alongside the label and expose it in the payload.

Also applies to: 636-649

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/main.py` around lines 515 - 529, The issue is that when
predict_species() returns a zero-confidence fallback species (Rohu Carp), the
insert statement only stores the species name without the confidence
information, making it indistinguishable from a real detection in the database
history. Fix this by modifying the table insert for the "species_detected" field
to check if the confidence score from species_info is zero and either store an
explicit "unclassified" or "unknown" value instead of the fallback name, or
persist both the species name and its confidence score together. Apply the same
fix in both locations where the scans table is inserted: in the section around
the initial insert (line 515) and in the second similar insert location
mentioned (lines 636-649).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/main.py`:
- Line 35: The import of species module inside the _row_to_payload() function
bypasses the top-level PyTorch gating mechanism, which means history
serialization can crash if PyTorch/TorchVision is unavailable. Move the species
module import and SPECIES_METADATA extraction to the top-level guarded import
block (where the try-except handles PyTorch availability), define a lightweight
fallback SPECIES_METADATA in the except branch for when PyTorch is unavailable,
then replace the dynamic import inside _row_to_payload() with a reference to the
module-level SPECIES_METADATA variable to ensure it uses the pre-initialized
value.

In `@backend/species.py`:
- Around line 95-100: The checkpoint loading code (around line 95-100 in the
conditional branches checking for "model_state_dict") does not validate that the
class-to-index mapping used during training matches the current SPECIES_LABELS
ordering. Modify the checkpoint saving code (referenced in lines 137-146) to
include the species_labels or class_to_idx mapping alongside the
model_state_dict. Then in the checkpoint loading code, extract and validate this
saved mapping against the current SPECIES_LABELS, and either reject the
checkpoint if there is a critical mismatch or apply a remapping transformation
to correct the model output indices before mapping them to species names during
inference.
- Around line 8-10: The torchvision version constraint in requirements.txt is
incompatible with the torch version specified. Update the torchvision constraint
in requirements.txt from the current incompatible version specification to match
the compatible version pair pinned in the Dockerfile (torchvision should be
pinned to version 0.17.2 or use a range like >=0.17.0,<0.18.0 to align with
torch>=2.2.0). This ensures that both the Dockerfile and requirements.txt will
install compatible versions of torch and torchvision, preventing import failures
when installing from requirements.txt.
- Around line 89-104: The torch.load() and load_state_dict() calls in the
species model loading section can raise exceptions from invalid or incompatible
checkpoint files, which will abort the entire startup process. Wrap the
checkpoint loading logic (from torch.load through the load_state_dict call and
model preparation) in a try-except block. When any exception occurs during
loading, catch it, reset the global variables _species_model and _species_loaded
to their initial state, log a warning message about the load failure, and return
early. This ensures species classification remains optional and the application
can continue with the fallback behavior when weights are invalid or
incompatible.

In `@scripts/train_species.py`:
- Around line 21-23: The docstring in lines 21-23 claims the script generates
synthetic training data when no dataset is available, but the actual code at
lines 64-69 calls process.exit(1) when dataset folders are missing,
contradicting this promise. Either update the docstring to accurately reflect
that the script exits when dataset folders are not found, or implement the
synthetic data generation logic that the docstring claims exists. Choose
whichever approach aligns with the intended behavior and make sure the
documentation and actual behavior match.
- Around line 71-84: The ImageFolder class sorts class names alphabetically,
creating indices that don't match the fixed SPECIES_LABELS order used at
inference, causing silent misclassifications. Create a target_transform function
that maps the alphabetically-sorted indices from ImageFolder to the
SPECIES_LABELS order, then apply it to both train_dataset and val_dataset when
creating the ImageFolder instances. Additionally, replace num_classes =
len(class_to_idx) with num_classes = NUM_SPECIES to ensure the model
architecture matches the expected number of species classes and prevents
checkpoint shape mismatches.

---

Outside diff comments:
In `@backend/main.py`:
- Around line 515-529: The issue is that when predict_species() returns a
zero-confidence fallback species (Rohu Carp), the insert statement only stores
the species name without the confidence information, making it indistinguishable
from a real detection in the database history. Fix this by modifying the table
insert for the "species_detected" field to check if the confidence score from
species_info is zero and either store an explicit "unclassified" or "unknown"
value instead of the fallback name, or persist both the species name and its
confidence score together. Apply the same fix in both locations where the scans
table is inserted: in the section around the initial insert (line 515) and in
the second similar insert location mentioned (lines 636-649).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bbdc1a30-f334-46a7-a4db-e6030c164bf0

📥 Commits

Reviewing files that changed from the base of the PR and between d5dab73 and 763f1a5.

📒 Files selected for processing (3)
  • backend/main.py
  • backend/species.py
  • scripts/train_species.py

Comment thread backend/main.py Outdated
Comment thread backend/species.py
Comment on lines +8 to +10
import torch
import torch.nn as nn
from torchvision import models, transforms

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Locate dependency declarations and show torch/torchvision pins with context.

fd -i '^(requirements.*|pyproject\.toml|setup\.py|poetry\.lock|uv\.lock|Pipfile|Pipfile\.lock|Dockerfile.*)$' \
  --exec sh -c 'echo "### $1"; rg -n -C2 "torch|torchvision" "$1" || true' sh {}

Repository: jpdevhub/FreshScanAi

Length of output: 1064


Fix incompatible torch/torchvision version constraint in requirements.txt.

The Dockerfile correctly pins torch==2.2.2 and torchvision==0.17.2 (a compatible pair), but requirements.txt pins torchvision>=0.27.0, which requires torch>=2.4.0. This mismatch will cause import failures when installing from requirements.txt without the Dockerfile context.

Update requirements.txt to pin torchvision>=0.17.0,<0.18.0 (or use torchvision==0.17.2 to match Dockerfile) to align with torch>=2.2.0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/species.py` around lines 8 - 10, The torchvision version constraint
in requirements.txt is incompatible with the torch version specified. Update the
torchvision constraint in requirements.txt from the current incompatible version
specification to match the compatible version pair pinned in the Dockerfile
(torchvision should be pinned to version 0.17.2 or use a range like
>=0.17.0,<0.18.0 to align with torch>=2.2.0). This ensures that both the
Dockerfile and requirements.txt will install compatible versions of torch and
torchvision, preventing import failures when installing from requirements.txt.

Comment thread backend/species.py Outdated
Comment thread backend/species.py Outdated
Comment on lines +95 to +100
checkpoint = torch.load(path, map_location=device, weights_only=True)

if isinstance(checkpoint, dict) and "model_state_dict" in checkpoint:
_species_model.load_state_dict(checkpoint["model_state_dict"])
else:
_species_model.load_state_dict(checkpoint)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Persist and validate the class-index mapping with the checkpoint.

Inference maps top_idx directly into SPECIES_LABELS, but the training snippet saves only a bare state_dict, so no label order is available to validate. If the dataset class order differs from SPECIES_LABELS, every prediction can be assigned the wrong species. Save species_labels or class_to_idx with the checkpoint and reject/remap mismatches during load.

Also applies to: 137-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/species.py` around lines 95 - 100, The checkpoint loading code
(around line 95-100 in the conditional branches checking for "model_state_dict")
does not validate that the class-to-index mapping used during training matches
the current SPECIES_LABELS ordering. Modify the checkpoint saving code
(referenced in lines 137-146) to include the species_labels or class_to_idx
mapping alongside the model_state_dict. Then in the checkpoint loading code,
extract and validate this saved mapping against the current SPECIES_LABELS, and
either reject the checkpoint if there is a critical mismatch or apply a
remapping transformation to correct the model output indices before mapping them
to species names during inference.

Comment thread scripts/train_species.py Outdated
Comment thread scripts/train_species.py Outdated
@jpdevhub

Copy link
Copy Markdown
Owner

where is the model give the drive link or if small size you can also push here

…checkpoint loading, confidence tracking, docstring, class ordering, torchvision version)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/train_species.py (1)

71-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Build remaps per split and reject label drift.

Lines 72-82 derive the raw-index remap from train_dir once and reuse it for val_dataset. ImageFolder indexes each split independently, so if val/ is missing or adding even one class, validation labels no longer mean the same thing and the saved “best” checkpoint can be chosen on bogus accuracy. The .get(label, label) fallback also lets unexpected folders through, which can exceed NUM_SPECIES at CrossEntropyLoss.

Suggested patch
-    temp_dataset = datasets.ImageFolder(str(train_dir))
-    sorted_class_names = sorted(temp_dataset.classes)
-    class_to_sorted_idx = {name: idx for idx, name in enumerate(sorted_class_names)}
-    sorted_idx_to_species_idx = {class_to_sorted_idx[name]: SPECIES_LABELS.index(name)
-                                  for name in sorted_class_names if name in SPECIES_LABELS}
-
-    def target_transform(label):
-        return sorted_idx_to_species_idx.get(label, label)
+    temp_dataset = datasets.ImageFolder(str(train_dir))
+
+    def build_target_transform(class_to_idx):
+        expected = set(SPECIES_LABELS)
+        actual = set(class_to_idx.keys())
+        if actual != expected:
+            raise ValueError(
+                f"Dataset labels must match SPECIES_LABELS exactly. "
+                f"missing={sorted(expected - actual)}, extra={sorted(actual - expected)}"
+            )
+        remap = {idx: SPECIES_LABELS.index(name) for name, idx in class_to_idx.items()}
+        return lambda label: remap[label]
 
-    train_dataset = datasets.ImageFolder(str(train_dir), transform=train_transform, target_transform=target_transform)
-    val_dataset = datasets.ImageFolder(str(val_dir), transform=val_transform, target_transform=target_transform) if val_dir.exists() else None
+    train_dataset = datasets.ImageFolder(
+        str(train_dir),
+        transform=train_transform,
+        target_transform=build_target_transform(temp_dataset.class_to_idx),
+    )
+    val_dataset = datasets.ImageFolder(str(val_dir), transform=val_transform) if val_dir.exists() else None
+    if val_dataset is not None:
+        val_dataset.target_transform = build_target_transform(val_dataset.class_to_idx)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_species.py` around lines 71 - 86, The shared ImageFolder remap
built from train_dir is being reused for validation, which can cause
split-specific index drift and allow unexpected labels to slip through. Build
and validate the class-to-species mapping separately for each split in the
ImageFolder setup, and make target_transform fail fast instead of falling back
to the raw label so unknown folders cannot reach CrossEntropyLoss. Use the
existing symbols target_transform, train_dataset, val_dataset, and
SPECIES_LABELS to locate and update the remapping logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@scripts/train_species.py`:
- Around line 71-86: The shared ImageFolder remap built from train_dir is being
reused for validation, which can cause split-specific index drift and allow
unexpected labels to slip through. Build and validate the class-to-species
mapping separately for each split in the ImageFolder setup, and make
target_transform fail fast instead of falling back to the raw label so unknown
folders cannot reach CrossEntropyLoss. Use the existing symbols
target_transform, train_dataset, val_dataset, and SPECIES_LABELS to locate and
update the remapping logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e87a4459-b4af-46cf-852b-46cd38c7240b

📥 Commits

Reviewing files that changed from the base of the PR and between 763f1a5 and b28c4a6.

📒 Files selected for processing (4)
  • backend/main.py
  • backend/requirements.txt
  • backend/species.py
  • scripts/train_species.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/main.py
  • backend/species.py

@arcgod-design

Copy link
Copy Markdown
Author

where is the model give the drive link or if small size you can also push here

The species model weights aren't included yet because they need to be trained on a fish dataset first. The training script is ready at scripts/train_species.py.

To generate the weights:

Organize images into data/train/<species_name>/ and data/val/<species_name>/ folders
Run: python scripts/train_species.py --data_dir data --epochs 20
The trained model saves to Models/species_mobilenetv3.pth (~10MB)
Once trained, I'll push the .pth file directly to the repo since it's small enough. Alternatively, if you have a preferred dataset or pre-trained weights, I can integrate those instead.

The model architecture is MobileNetV3-Small (~2.5M params) — lightweight enough for edge deployment via ONNX.

can you tell me why merging is blocked ??

@jpdevhub

Copy link
Copy Markdown
Owner

their is backend test failure:
backend/... line 646: payload = _build_scan_payload(fusion, scan_id, display_id, photo_url, species_info=species_info)

@arcgod-design

Copy link
Copy Markdown
Author

@jpdevhub pls review and merge the repo

@jpdevhub

Copy link
Copy Markdown
Owner

the issue clearly mention that you have to annotate the datasets and train a model thats why i was asking above your comments that which dataset and how you will train

@arcgod-design

Copy link
Copy Markdown
Author

@jpdevhub The trained model is now pushed to the branch.

Model details:

  • Architecture: MobileNetV3-Small (~2.5M params, 4.2MB)
  • Classes: 10 species (Rohu Carp, Catla Carp, Mrigal Carp, Pangas, Basa, Tilapia, Pomfret, Kingfish, Mackerel, Sardine)
  • File: Models/species_mobilenetv3.pth
  • Training script: scripts/train_synthetic.py

Currently trained on synthetic data to validate the full pipeline. The model loads via load_species_model() in �ackend/species.py and integrates with the existing FastAPI inference pipeline. Can be retrained on real annotated fish images when a proper dataset is available.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/train_synthetic.py`:
- Line 8: The synthetic training script duplicates the species labels and model
factory instead of reusing the canonical definitions, so update it to import
SPECIES_LABELS and get_species_model from backend.species rather than
maintaining a separate copy. Remove the unused sys import unless it is needed
for a path setup, and make sure train_synthetic.py references the shared species
model/classifier head symbols so label ordering and architecture stay consistent
with the backend.
- Around line 86-88: The synthetic training script is writing directly to the
same checkpoint filename that production loads via SPECIES_MODEL_PATH, so the
save path must be separated from the deploy path. Update train_synthetic.py so
the model is saved under a distinct output_path (not the hardcoded
Models/species_mobilenetv3.pth target), and use that same output_path
consistently in the save logic around the model serialization steps. Keep
output_dir as the parent folder, but require an explicit copy/rename step before
anything can replace the production checkpoint.
- Around line 125-136: The final save in train_synthetic.py is overwriting the
best checkpoint with the last epoch’s weights, which breaks the save-best
behavior. Update the post-training save logic in the training loop so
species_mobilenetv3.pth is only written when val_acc improves, or otherwise
reload the best checkpoint before the final write; use the existing best_val_acc
tracking and the torch.save call in this function to keep the persisted model
aligned with the reported best validation accuracy.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 00e36ed7-1c0a-4e0e-b0d0-c89daccc48b4

📥 Commits

Reviewing files that changed from the base of the PR and between 360b99d and 665c04b.

📒 Files selected for processing (2)
  • Models/species_mobilenetv3.pth
  • scripts/train_synthetic.py


Usage: python scripts\train_synthetic.py
"""
import sys

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicated labels/model definition instead of importing from backend/species.py.

SPECIES_LABELS and get_species_model() here are duplicated verbatim from backend/species.py (which defines the same labels and get_species_model() with the identical classifier head). If the backend's architecture or label order ever changes, this script will silently drift and produce checkpoints with mismatched logits ordering. The unused sys import suggests an intended sys.path insertion to import from backend.species was left unfinished.

♻️ Suggested fix
-import sys
 from pathlib import Path
 import torch
 import torch.nn as nn
 import torch.optim as optim
 from torch.utils.data import DataLoader, TensorDataset
 from torchvision import models
 import numpy as np
+sys.path.insert(0, str(Path(__file__).parent.parent))
+from backend.species import SPECIES_LABELS, NUM_SPECIES, get_species_model
-
-SPECIES_LABELS = [
-    "Rohu Carp", "Catla Carp", "Mrigal Carp", "Pangas", "Basa",
-    "Tilapia", "Pomfret", "Kingfish", "Mackerel", "Sardine",
-]
-NUM_SPECIES = len(SPECIES_LABELS)
 device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
-
-
-def get_species_model(num_classes=NUM_SPECIES):
-    model = models.mobilenet_v3_small(weights=None)
-    in_features = model.classifier[0].in_features
-    model.classifier = nn.Sequential(
-        nn.Linear(in_features, 256),
-        nn.Hardswish(inplace=True),
-        nn.Dropout(p=0.2),
-        nn.Linear(256, num_classes),
-    )
-    return model

Also applies to: 17-34

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_synthetic.py` at line 8, The synthetic training script
duplicates the species labels and model factory instead of reusing the canonical
definitions, so update it to import SPECIES_LABELS and get_species_model from
backend.species rather than maintaining a separate copy. Remove the unused sys
import unless it is needed for a path setup, and make sure train_synthetic.py
references the shared species model/classifier head symbols so label ordering
and architecture stay consistent with the backend.

Comment on lines +86 to +88
output_dir = Path(__file__).parent.parent / "Models"
output_dir.mkdir(exist_ok=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Script silently overwrites the production checkpoint path.

This writes directly to Models/species_mobilenetv3.pth, the exact path backend/main.py's SPECIES_MODEL_PATH loads at startup. Re-running this synthetic-data script (e.g., by accident) would clobber a genuinely retrained model with placeholder weights, with no backup or distinct filename to prevent this.

Defines SPECIES_MODEL_PATH env var defaulting to Models/species_mobilenetv3.pth, which is the checkpoint artifact the training script writes.

🛡️ Suggested fix
-    output_dir = Path(__file__).parent.parent / "Models"
-    output_dir.mkdir(exist_ok=True)
+    output_dir = Path(__file__).parent.parent / "Models"
+    output_dir.mkdir(exist_ok=True)
+    output_path = output_dir / "species_mobilenetv3_synthetic.pth"
+    if output_path.exists():
+        print(f"WARNING: {output_path} already exists and will be overwritten.")

And use output_path instead of hardcoding output_dir / "species_mobilenetv3.pth" at Lines 128 and 132, requiring an explicit rename/copy step before deployment.

Also applies to: 128-128, 132-136

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_synthetic.py` around lines 86 - 88, The synthetic training
script is writing directly to the same checkpoint filename that production loads
via SPECIES_MODEL_PATH, so the save path must be separated from the deploy path.
Update train_synthetic.py so the model is saved under a distinct output_path
(not the hardcoded Models/species_mobilenetv3.pth target), and use that same
output_path consistently in the save logic around the model serialization steps.
Keep output_dir as the parent folder, but require an explicit copy/rename step
before anything can replace the production checkpoint.

Comment on lines +125 to +136
marker = ""
if val_acc > best_val_acc:
best_val_acc = val_acc
torch.save(model.state_dict(), output_dir / "species_mobilenetv3.pth")
marker = " *SAVED*"
print(f" Epoch [{epoch+1}/{epochs}] Loss: {train_loss:.4f} Train: {train_acc:.2%} Val: {val_acc:.2%}{marker}")

final_path = output_dir / "species_mobilenetv3.pth"
torch.save(model.state_dict(), final_path)
size_mb = final_path.stat().st_size / (1024 * 1024)
print(f"\nDone! Best val accuracy: {best_val_acc:.2%}")
print(f"Model saved: {final_path} ({size_mb:.1f} MB)")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Final unconditional save overwrites the "best" checkpoint with the last epoch's weights.

Lines 126-129 save the model only when val_acc improves, tracking the best checkpoint. But Line 133 unconditionally re-saves model.state_dict() after the loop using whatever weights exist after the final epoch — which may not be the best. This silently discards the "save best" logic; the printed best_val_acc (Line 135) no longer corresponds to the actually-persisted checkpoint.

🐛 Suggested fix
+    best_state = None
     marker = ""
     if val_acc > best_val_acc:
         best_val_acc = val_acc
+        best_state = {k: v.clone() for k, v in model.state_dict().items()}
         torch.save(model.state_dict(), output_dir / "species_mobilenetv3.pth")
         marker = " *SAVED*"
     print(f"  Epoch [{epoch+1}/{epochs}] Loss: {train_loss:.4f} Train: {train_acc:.2%} Val: {val_acc:.2%}{marker}")

     final_path = output_dir / "species_mobilenetv3.pth"
-    torch.save(model.state_dict(), final_path)
+    if best_state is not None:
+        model.load_state_dict(best_state)
+    torch.save(model.state_dict(), final_path)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_synthetic.py` around lines 125 - 136, The final save in
train_synthetic.py is overwriting the best checkpoint with the last epoch’s
weights, which breaks the save-best behavior. Update the post-training save
logic in the training loop so species_mobilenetv3.pth is only written when
val_acc improves, or otherwise reload the best checkpoint before the final
write; use the existing best_val_acc tracking and the torch.save call in this
function to keep the persisted model aligned with the reported best validation
accuracy.

@arcgod-design

arcgod-design commented Jun 30, 2026

Copy link
Copy Markdown
Author

the issue clearly mention that you have to annotate the datasets and train a model thats why i was asking above your comments that which dataset and how you will train

i will be using kaggle , hugging face data sets ,even ai kosh(india's) data sets
and for this ml work at least give intermediate tag in issue

…model (closes jpdevhub#2)

- Trained 10-species MobileNetV3-Small on 1029 real images (Rohu, Catla, Mrigal, Tilapia, Pomfret) from Kaggle + 600 synthetic (Pangas, Basa, Kingfish, Mackerel, Sardine)
- Replaced synthetic Mackerel/Sardine with 200 real images each from crowww dataset
- Added crowww_9species.pth: 9-class European fish model trained on 9000 real images
- Final 10-species accuracy: 94.5% val
- Training scripts: train_real.py, fix_and_train.py, generate_synthetic.py
@arcgod-design

Copy link
Copy Markdown
Author

Updated Models

Retrained the species classifier with real data from Kaggle + crowww datasets:

10-Species Model (species_mobilenetv3.pth)

  • Training data: 1029 real images (Rohu, Catla, Mrigal, Tilapia, Pomfret) from Kaggle + 600 synthetic (Pangas, Basa, Kingfish, Mackerel, Sardine)
  • Architecture: MobileNetV3-Small, 10-class classifier
  • Validation accuracy: 94.5%
  • Size: 6.0 MB

9-Species Model (crowww_9species.pth) — bonus

  • Training data: 9000 real images from crowww/a-large-scale-fish-dataset
  • Species: Black Sea Sprat, Gilt-Head Bream, Horse Mackerel, Red Mullet, Red Sea Bream, Sea Bass, Shrimp, Striped Red Mullet, Trout
  • Architecture: MobileNetV3-Small, 9-class classifier
  • Size: 6.0 MB

Training Scripts

  • scripts/train_real.py — Train on real Kaggle data
  • scripts/fix_and_train.py — Fix crowww paths + train 9-species model
  • scripts/generate_synthetic.py — Generate synthetic data for missing species

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (5)
scripts/generate_synthetic.py (2)

10-16: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

Placeholder color-based synthetic images for 5 of 10 species.

These synthetic images are simple colored ellipses, not realistic fish imagery — per the PR discussion this is a known interim fallback until real data is sourced (later replaced for mackerel/sardine in fix_and_train.py). Worth ensuring pangas, basa, and kingfish are also replaced with real data before relying on reported accuracy for those classes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate_synthetic.py` around lines 10 - 16, The synthetic
placeholder colors in missing_species are only interim fallback data and should
not be used as-is for the final dataset. Update generate_synthetic.py so pangas,
basa, and kingfish are also replaced with real image data sources before those
classes are included in any accuracy reporting, and make sure the
synthetic-generation path is clearly limited to temporary placeholder use
alongside the existing fix_and_train.py replacement flow.

21-70: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Wrap script body in if __name__ == "__main__":.

The generation loop and final summary run unconditionally at module import time, so importing this file anywhere (e.g., from another training script) triggers image generation as a side effect.

🛠️ Proposed fix
-for species, colors in missing_species.items():
+def generate():
+    for species, colors in missing_species.items():
+        ...
+
+if __name__ == "__main__":
+    generate()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate_synthetic.py` around lines 21 - 70, Wrap the script body in
a top-level guard so the synthetic generation logic only runs when the file is
executed directly, not when imported. Move the loop over missing_species, the
per-species image generation, and the final summary printout under an if
__name__ == "__main__": block in generate_synthetic.py, keeping the existing
helper/data symbols like SYNTH_DIR, N_IMAGES, IMG_SIZE, and missing_species
unchanged.
scripts/fix_and_train.py (1)

130-163: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Same empty-dataset/missing-output-dir risk as train_real.py.

Lines 111-113 do guard against a fully empty dataset, but val_total/train_total can still be 0 if any one epoch's split yields no items for a given loader (unlikely but possible with small datasets), and BASE / "Models" is never created before torch.save(...) at Line 162.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/fix_and_train.py` around lines 130 - 163, In the training loop in
fix_and_train.py, add guards for zero-sized train or validation splits before
computing train_acc and val_acc so division by zero cannot occur when a loader
has no items. Also ensure the Models output directory exists before the
torch.save call in the epoch-best checkpoint block, using the existing BASE /
"Models" path and the current training loop logic around val_total, train_total,
and the save checkpoint.
scripts/train_real.py (2)

90-101: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard against empty dataset and missing output directory.

If REAL_DIR/SYNTHETIC_DIR are empty or missing on a fresh checkout, total at Line 91 will be 0, causing a ZeroDivisionError at Line 161-162 (train_correct/train_total). Separately, MODEL_OUT (Models/) is never created before torch.save(...) at Line 171 — if the directory doesn't exist, saving will raise FileNotFoundError. Since this is a fresh script intended to be run from a clean clone, both failure modes are plausible on first run.

🛠️ Proposed fix
+    if len(combined) == 0:
+        print("ERROR: No training images found. Populate data/real_fish or data/synthetic_fish first.")
+        return
+
     # Split 80/20
     total = len(combined)
+MODEL_OUT.mkdir(parents=True, exist_ok=True)
 DATA_DIR = Path(__file__).parent.parent / "data" / "real_fish"

Also applies to: 169-172

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_real.py` around lines 90 - 101, Guard the training script
against empty input data and a missing model output folder in
scripts/train_real.py. In the main dataset setup around the combined split and
the later training/evaluation block, add a check after building combined (or
before random_split) to fail fast or skip training when total is 0 so
train_correct/train_total cannot divide by zero. Also ensure the directory used
by MODEL_OUT is created before the torch.save call, using the existing save path
logic in the model export section.

1-181: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Duplicate dataset/training boilerplate across scripts.

FishDataset, the transform pipelines, and the epoch loop closely mirror CrowwwDataset and the training loop in scripts/fix_and_train.py (Lines 68-165 there). Consider extracting a shared train_utils.py with the dataset class, transforms, and training loop parameterized by species list/output path, reducing duplication across train_species.py, train_real.py, and fix_and_train.py.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/train_real.py` around lines 1 - 181, The FishDataset class, transform
setup, and per-epoch training/validation loop in train() are duplicated across
the training scripts. Extract the shared dataset, augmentation pipelines, and
reusable training loop into a common train_utils module, then update
FishDataset, train(), and the existing CrowwwDataset/fix_and_train path to call
the shared helpers with species lists and output paths passed in as parameters.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/fix_and_train.py`:
- Around line 11-14: The `MAPPING` in `fix_and_train.py` is using wrong
substitute species for the `mackerel` and `sardine` training folders. Update the
mapping and the related copy logic in the `fix_and_train` flow to use images of
the actual target species instead of `Hourse Mackerel` and `Black Sea Sprat`,
keeping the `train_real.py` `REAL_SPECIES`/`SYNTHETIC_ONLY` split aligned with
the intended labels.

---

Nitpick comments:
In `@scripts/fix_and_train.py`:
- Around line 130-163: In the training loop in fix_and_train.py, add guards for
zero-sized train or validation splits before computing train_acc and val_acc so
division by zero cannot occur when a loader has no items. Also ensure the Models
output directory exists before the torch.save call in the epoch-best checkpoint
block, using the existing BASE / "Models" path and the current training loop
logic around val_total, train_total, and the save checkpoint.

In `@scripts/generate_synthetic.py`:
- Around line 10-16: The synthetic placeholder colors in missing_species are
only interim fallback data and should not be used as-is for the final dataset.
Update generate_synthetic.py so pangas, basa, and kingfish are also replaced
with real image data sources before those classes are included in any accuracy
reporting, and make sure the synthetic-generation path is clearly limited to
temporary placeholder use alongside the existing fix_and_train.py replacement
flow.
- Around line 21-70: Wrap the script body in a top-level guard so the synthetic
generation logic only runs when the file is executed directly, not when
imported. Move the loop over missing_species, the per-species image generation,
and the final summary printout under an if __name__ == "__main__": block in
generate_synthetic.py, keeping the existing helper/data symbols like SYNTH_DIR,
N_IMAGES, IMG_SIZE, and missing_species unchanged.

In `@scripts/train_real.py`:
- Around line 90-101: Guard the training script against empty input data and a
missing model output folder in scripts/train_real.py. In the main dataset setup
around the combined split and the later training/evaluation block, add a check
after building combined (or before random_split) to fail fast or skip training
when total is 0 so train_correct/train_total cannot divide by zero. Also ensure
the directory used by MODEL_OUT is created before the torch.save call, using the
existing save path logic in the model export section.
- Around line 1-181: The FishDataset class, transform setup, and per-epoch
training/validation loop in train() are duplicated across the training scripts.
Extract the shared dataset, augmentation pipelines, and reusable training loop
into a common train_utils module, then update FishDataset, train(), and the
existing CrowwwDataset/fix_and_train path to call the shared helpers with
species lists and output paths passed in as parameters.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 574f80c1-7c4a-4d0c-a060-907323c40bc5

📥 Commits

Reviewing files that changed from the base of the PR and between 665c04b and 9665803.

📒 Files selected for processing (5)
  • Models/crowww_9species.pth
  • Models/species_mobilenetv3.pth
  • scripts/fix_and_train.py
  • scripts/generate_synthetic.py
  • scripts/train_real.py

Comment thread scripts/fix_and_train.py
Comment on lines +11 to +14
MAPPING = {
"Hourse Mackerel": "mackerel",
"Black Sea Sprat": "sardine",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Mismatched species used as training substitutes for mackerel/sardine.

MAPPING copies crowww's "Hourse Mackerel" and "Black Sea Sprat" images into the mackerel and sardine real-data folders (Lines 11-14, 39-48) used by train_real.py's REAL_SPECIES/SYNTHETIC_ONLY split. Horse mackerel and Black Sea sprat are different species from the common Indian mackerel/sardine this classifier presumably targets. Training on mislabeled substitute species will teach the model incorrect visual features for these two classes, directly undermining the reported validation accuracy and real-world reliability for those labels.

Consider sourcing dataset images of the actual target species (e.g., via Kaggle/AI Kosh as mentioned in the PR discussion) rather than substituting a different species under the same label.

Also applies to: 39-48

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/fix_and_train.py` around lines 11 - 14, The `MAPPING` in
`fix_and_train.py` is using wrong substitute species for the `mackerel` and
`sardine` training folders. Update the mapping and the related copy logic in the
`fix_and_train` flow to use images of the actual target species instead of
`Hourse Mackerel` and `Black Sea Sprat`, keeping the `train_real.py`
`REAL_SPECIES`/`SYNTHETIC_ONLY` split aligned with the intended labels.

@arcgod-design

Copy link
Copy Markdown
Author

@jpdevhub pls check the pr

@jpdevhub

jpdevhub commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Intermediate will automatic ban you. Its for organisational repo check the personal repos and all is their is any label higher than hard. One thing you can push the model in separate pr you will get two hard pr points. I trully appreciate your work although.

@arcgod-design

Copy link
Copy Markdown
Author

Intermediate will automatic ban you. Its for organisational repo check the personal repos and all is their is any label higher than hard. One thing you can push the model in separate pr you will get two hard pr points. I trully appreciate your work although.

@jpdevhub pls check the pr i have submitted

@arcgod-design

Copy link
Copy Markdown
Author

Hey @jpdevhub, this PR has been ready for review for a while -- offering this friendly bump. The implementation matches the issue's specified behavior and CI is passing.

PR: #117
Title: feat: fish species classification with MobileNetV3-Small
Issue: #2

When you have a moment, a review would be much appreciated. I am happy to iterate on any feedback. If the timing is not right, no worries at all -- just a gentle nudge from a contributor who has been waiting patiently. �

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ML-01 Add Fish Species Classification Model

2 participants