Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e0939d3
Clean out otolith ageing notebooks
MattGrossi-NOAA Jul 30, 2025
ff0a750
Containerization tools
MattGrossi-NOAA Jul 30, 2025
b625abf
Update with SAM
MattGrossi-NOAA Jul 30, 2025
512b5f7
Add local execution settings
MattGrossi-NOAA Jul 30, 2025
0402651
Test SAM
MattGrossi-NOAA Jul 30, 2025
f6aa034
Merge pull request #1 from SEFSC/main
MattGrossi-NOAA Jul 31, 2025
bbf05d9
Add paths to new models
MattGrossi-NOAA Jul 31, 2025
f42ab50
Add paths to 2024 test data
MattGrossi-NOAA Jul 31, 2025
8adc150
Merge branch 'main' into scales-dev-mattgfe
MattGrossi-NOAA Aug 1, 2025
42f7973
Debugged version from Aotian
MattGrossi-NOAA Aug 1, 2025
e6a46e6
Test histogram equalization
MattGrossi-NOAA Aug 1, 2025
12199fa
Add function and class docstrings and code comments, rename some vars…
MattGrossi-NOAA Aug 4, 2025
3a00008
Add comments, rename some vars for clarity
MattGrossi-NOAA Aug 4, 2025
331acd2
Add -c flag
MattGrossi-NOAA Aug 4, 2025
ac18a7b
Change dirs for testing
MattGrossi-NOAA Aug 5, 2025
2b6f17d
Add Quarto site folders
MattGrossi-NOAA Aug 5, 2025
1f6c832
Add Dockerfile
MattGrossi-NOAA Aug 5, 2025
8ab1c07
Merge branch 'scales-dev-athpc' into scales
MattGrossi-NOAA Aug 5, 2025
ec00a0b
Add shebang and header
MattGrossi-NOAA Aug 6, 2025
351a1e5
Merge pull request #4 from SEFSC/scales-dev-mattgfe
MattGrossi-NOAA Aug 6, 2025
c79394f
Add pip to conda env
MattGrossi-NOAA Aug 20, 2025
a6ff5ac
Add defaults for normalization and segmentation
MattGrossi-NOAA Sep 8, 2025
ac52641
Inversion default False
MattGrossi-NOAA Sep 8, 2025
1d94894
Add more explanatory comments
MattGrossi-NOAA Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ Menhaden Scales Aging/Inference Script/sam_vit_b_01ec64.pth
Menhaden Scales Aging/Chapter 4 - Multimodal Train and Inference/best_model.pth
Menhaden Scales Aging/Chapter 4 - Multimodal Train and Inference/final_model.pth
Menhaden Scales Aging/Chapter 1 - Data Preprocessing/Raw Image/27061.tif

.quarto/
_site/

Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
#!/usr/bin/env python

# -----------------------------------------------------------------------------
# Title: Scale_Aging_Inference_Script_Image_Only.py
#
# Description: This script predicts the age of fish scale images using a pre-
# trained ResNet18 model. Arguments, hyperparameters, and other
# settings are included in a configs.yml file. Predicted ages are
# written to a CSV file.
#
# Author: aotian.zheng@noaa.gov
# Release Date: July 2025
# Last Updated: August 2025
#
# Usage: python Scale_Aging_Inference_Script_Image_Only.py -c path/to/configs.yml
# -----------------------------------------------------------------------------

import argparse
import yaml
import cv2 as cv
from matplotlib import pyplot as plt
import numpy as np
import os
from os import listdir
from os.path import isfile, join
import numpy as np
from PIL import Image
import torch
from torchvision import transforms
from torchvision.io import read_image
from torch.utils.data.dataset import Dataset # For custom datasets
Expand All @@ -15,48 +32,68 @@
from torch.utils.data import DataLoader
from torchvision.models import resnet18, ResNet18_Weights
from tqdm import tqdm
import torch
import yaml

class FishTestDataset(Dataset):
"""Custom Dataset for loading fish scale images for age inference.

Attributes
----------
image_dir : str
Path to the directory containing images.
image_name : list
List of image filenames in the directory.
transforms : callable, optional
A function/transform that takes in a PIL image and returns a transformed version.

Methods
-------
__len__ : returns the number of images in the dataset.
__getitem__(index) : returns the image and its filename at the specified index.
"""
def __init__(self, image_dir, transform=None):

# Get the directory dataset images
"""
Parameters
----------
image_dir : str
Path to the directory containing images.
transform : callable, optional
A function/transform that takes in a PIL image and returns a transformed version.
"""

# Get the directory of the images to age
self.image_dir = image_dir

# Get the transform methods
self.transforms = transform


# Image Name
self.image_name = [f for f in listdir(image_dir) if isfile(join(image_dir, f))]


def __len__(self):
"""Returns the number of images in the dataset."""
return len(self.image_name)

def __getitem__(self, index):
"""Returns the image and its filename at the specified index."""
# Open the specified image
img_path = os.path.join(self.image_dir, str(self.image_name[index]))
image = Image.open(img_path)

# Transform the image, if transforms are provided
if self.transforms:
image = self.transforms(image)

return image, self.image_name[index]


# Parse command line arguments. Currently only requires a path to a configuration yaml file.
parser = argparse.ArgumentParser()
parser.add_argument("--config_path", help="path to configuration yaml file")

parser.add_argument("-c", "--config_path", help="path to configuration yaml file")
args = parser.parse_args()

# Open the configuration file and read in the parameters
with open(args.config_path, 'r') as file:
config = yaml.safe_load(file)


data_dir = config["img_path"]

# Image transformations: resizing, cropping, normalization
data_transforms = transforms.Compose(
[
transforms.Resize(224),
Expand All @@ -65,24 +102,22 @@ def __getitem__(self, index):
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
]
)
test_dataset = FishTestDataset( data_dir, data_transforms)
test_dataset = FishTestDataset(image_dir=config["image_path"], transform=data_transforms)
test_loader = DataLoader(test_dataset, batch_size=24, shuffle=False, drop_last=False)

# Load the model using GPU, if available, in evaluation mode.
# Number of classes corresponds to the number of age classes.
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model = resnet18(num_classes = 5)

# Load model - TODO
model = resnet18(num_classes=5)
model.load_state_dict(torch.load(config["model_path"]))

model.eval()
model.to(device)

import torch

output_path = "inference_results.csv"
# Create output file and write header
file = open(config["out_path"], 'w')
file.write("Image Name, Predicted Age\n")

# Loop through the dataset and make predictions
for images, img_path in tqdm(test_loader):
images = images.to(device)
outputs = model(images)
Expand All @@ -91,7 +126,8 @@ def __getitem__(self, index):
preds = preds.cpu().detach().numpy()
for i in range(preds.shape[0]):
age = str(preds[i])
if(preds[i] ==4):
# Change the maximum age class to "4+"
if(preds[i] == 4):
age = "4+"
file.write("%s,%s\n"%(img_path[i],age))
file.write("%s,%s\n" % (img_path[i], age))
file.close()
Loading
Loading