A ResNet-50 image classification pipeline for identifying biological specimens (built and tested on beetle family classification) from photographs. Built for usability in pathology and ecology labs: train a model on your own labelled photos, evaluate it against held-out data, and hand a trained model to user through a point-and-click desktop UI — no code required to run it day-to-day.
The interactive classifier is internally named EcoVision; you'll see that name in its window title and in some file paths.
- Overview
- Repository structure
- Data format
- Trained model format
- Getting started
- Usage
- Troubleshooting
- Contributors
- License
| Notebook / script | Purpose |
|---|---|
Code/Split_Dataset.ipynb |
Randomly splits a folder of images sorted by category into the training/testing layout the other notebooks expect, at a ratio you choose. |
Code/Train_Model.ipynb |
Trains a transfer-learned ResNet-50 classifier on your own image folders and saves the resulting model, metrics, and metadata. |
Code/Compare_Models_on_Dataset.ipynb |
Runs one or more trained models against a shared evaluation dataset and produces ROC/PR curves and confusion matrices for side-by-side comparison. |
Code/Model_UI.ipynb |
Launches a desktop GUI (no coding required) for classifying a single image or a whole folder with a trained model, including a Grad-CAM heatmap of what the model focused on. |
All three read from and write to two shared top-level folders: Data/ (your images) and Models/ (trained weights and their metrics).
Specimen_Classifier/
├── Code/
│ ├── Split_Dataset.ipynb Randomly split a category-sorted folder into training/testing
│ ├── Train_Model.ipynb Train a new ResNet-50 classifier
│ ├── Model_UI.ipynb Desktop GUI for running inference with a trained model
│ ├── Compare_Models_on_Dataset.ipynb Batch-evaluate & plot ROC/PR curves and confusion matrices
│ └── Python-Requirements.txt Pinned dependency versions
│
├── Data/ ⚠ not tracked in git — populate this folder locally
│ └── <dataset name>/
│ ├── training/
│ │ └── <class name>/*.jpg
│ └── testing/
│ └── <class name>/*.jpg
│
├── Models/
│ └── <model_run_name>/
│ ├── best_weights_ResNet50.h5 Trained weights (tracked via Git LFS)
│ ├── model_info.json Architecture, source dataset, per-class training counts
│ ├── confusion_matrix.csv
│ ├── classification_report.csv
│ └── ResNet-Predictions.csv Raw per-class prediction probabilities on the test set
│
├── Presentation_Plots/ ⚠ not tracked in git — regenerated by Compare_Models_on_Dataset.ipynb
│ └── cache/ Cached predictions, keyed by model/dataset/class-scheme hash
│
├── .gitattributes Marks *.h5 / *.zip for Git LFS
├── .gitignore
└── README.md
Data/ and Presentation_Plots/ are intentionally excluded from version control (see .gitignore) — raw datasets are too large to commit, and plots are cheap to regenerate. Everything under Models/ is committed, with the large .h5 weight files handled by Git LFS.
Both Train_Model.ipynb and Compare_Models_on_Dataset.ipynb expect images sorted into one subfolder per class. For training, that structure is duplicated under separate training/ and testing/ roots so the split is explicit:
Data/InHouse_July/
├── training/
│ ├── Carabidae/
│ │ ├── img001.jpg
│ │ └── ...
│ ├── Chrysomelidae/
│ ├── Other/
│ └── Staphylinidae/
└── testing/
├── Carabidae/
├── Chrysomelidae/
├── Other/
└── Staphylinidae/
The training/ and testing/ trees must contain the same set of class subfolders — Keras infers class labels (and their order) alphabetically from these folder names. Compare_Models_on_Dataset.ipynb instead expects a single flat folder of class subfolders (no train/test split), since it's evaluating rather than training.
Each subfolder of Models/ is one training run, named after that run's model_run_name. Train_Model.ipynb creates it automatically and writes five files into it:
| File | Contents |
|---|---|
best_weights_ResNet50.h5 |
The model checkpoint with the lowest validation loss (via Keras ModelCheckpoint + EarlyStopping). |
model_info.json |
Base architecture, source dataset name, and per-class training image counts. |
confusion_matrix.csv |
Predicted vs. true class counts on the test set. |
classification_report.csv |
Per-class precision / recall / F1 / support, plus macro and accuracy rows. |
ResNet-Predictions.csv |
Raw softmax output for every test image (one row per image, one column per class). |
Model_UI.ipynb and Compare_Models_on_Dataset.ipynb both read this folder structure directly, so a model isn't usable by either until all five files are present.
| Tool | Why |
|---|---|
| Git | Clone this repository. |
| Git LFS | Required before cloning — the .h5 model weights in Models/ are stored via Git LFS. Without it you'll get small placeholder pointer files instead of usable weights. |
| Anaconda | Supplies Python plus numpy, pandas, matplotlib, scikit-learn, Pillow, and Jupyter out of the box. |
| An IDE with Jupyter support, e.g. VS Code with the Python and Jupyter extensions | To open and run the .ipynb notebooks. |
Windows: run the git-related commands below in Git Bash (installed alongside Git for Windows) — they won't work as-is in Command Prompt or PowerShell. Run the pip install command in Anaconda Prompt specifically, not Git Bash — Git Bash can silently resolve python/pip to the wrong install (e.g. the Windows Store stub) instead of Anaconda's.
Mac: Terminal handles every step below, no switching required.
# 1. Install Git LFS once per machine, before cloning
git lfs install
# 2. Clone the repository
git clone https://github.com/timlee-bioinf/Specimen_Classifier.git
cd Specimen_Classifier
# 3. Confirm the model weights actually downloaded (should be ~100-130MB each,
# not a few bytes — if they're tiny, Git LFS wasn't active during clone)
ls -lh Models/*/*.h5# 4. Install the one dependency Anaconda doesn't ship with by default
# (In Anaconda Prompt, `base` is already active, so this is all you need.)
pip install tensorflowUsing a plain PowerShell window instead of Anaconda Prompt (e.g. VS Code's default integrated terminal)? Activate the environment explicitly first, since PowerShell doesn't always do this automatically:
conda activate base
pip install tensorflowThis project intentionally runs on Anaconda's plain base environment — no custom virtual environment is required. See Code/Python-Requirements.txt for the exact versions this codebase is tested against.
Place your own image data under Data/ following the structure above before running any of the notebooks.
If your images are sorted into one flat folder per category (e.g. Data/Collection/Carabidae/, Data/Collection/Chrysomelidae/, ...) rather than already split into training//testing/, open Code/Split_Dataset.ipynb and run it top to bottom. It walks you through picking that source folder, picking an output folder, and choosing a training/testing percentage split (80/20 by default) — no coding required, just two folder-picker pop-ups and one number to edit. It copies images (your originals are left untouched) into the same training//testing/ layout the other notebooks expect.
Open Code/Train_Model.ipynb and run it top to bottom. Before running, edit these two variables near the top to name the run and point at your data:
model_run_name = "InHouse_0603_ResNet50_1" # change this for every new training run
dataset_dir = os.path.join(REPO_ROOT, "Data", "<dataset>") # change this to point at a different Data/ subfolderREPO_ROOT is found automatically (it walks up from wherever the notebook is running until it finds this repo's Models/ folder), so these paths work regardless of whose machine or which folder the repo is cloned into — no need to edit REPO_ROOT, train_data_dir, test_data_dir, or models_dir directly.
The notebook trains a ResNet-50 (ImageNet weights, frozen base layers) with early stopping, then writes the five output files into Models/<model_run_name>/.
Code/Compare_Models_on_Dataset.ipynb scores one or more trained models against a shared dataset and plots per-class and combined ROC/PR curves plus a confusion matrix per model. Open it, select the Anaconda base kernel, and edit only its Config cell (DATASET_DIR, EVAL_CLASSES, MODELS, TARGET_CLASS, AVERAGING) to point it at the dataset and models you want to compare — every markdown cell above it explains what each setting does in plain language, so no Python knowledge is needed to change what gets compared. Nothing past the Config cell needs to change. It also supports mapping a model's finer-grained classes down onto a coarser evaluation scheme (e.g. collapsing extra classes into "Other").
Then choose Run All. Every plot renders inline in the notebook and is saved to Presentation_Plots/, with intermediate predictions cached under Presentation_Plots/cache/ so re-running doesn't re-run inference unless the config actually changed.
Open Code/Model_UI.ipynb in VS Code (or Jupyter), select the Anaconda base kernel, and choose Run All. This opens a separate desktop window — titled EcoVision Beetle Classifier — that may appear behind your other windows the first time.
From that window you can:
- Pick any trained model from
Models/via the dropdown. - Classify a single image, with a Grad-CAM heatmap showing what the model focused on.
- Classify a whole folder of images at once and see a live confusion matrix if the folder is organized into class subfolders.
Model weight files are a few bytes instead of ~100MB
Git LFS wasn't active when you cloned. Install it (git lfs install), then run git lfs pull from inside the repository to fetch the real files.
No "base" kernel available when opening a notebook
Make sure Anaconda is installed and your IDE has been restarted since. In VS Code, confirm the Python and Jupyter extensions are installed, then use "Select Kernel" → Python Environments → the environment labeled base.
The classifier window never appears after "Run All"
Check your taskbar/dock — it's a separate desktop window (built with Tkinter) and can open behind your editor rather than in front of it.
- Tim Lee (@timlee-bioinf)
- Jarrett Blair (@Jarrett-Blair)
- Preston Johnstone (@AhoyCapn)
No license has been specified for this repository yet. Until one is added, please contact the maintainers before reusing or redistributing this code.