From 09775767605536ffd19e83453be3e4159d4c82b9 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Tue, 25 Feb 2025 10:40:52 -0600 Subject: [PATCH 1/7] Add weights_only(false) to torch.load false used to be the default, it now has to be set manually since we have custom code in the model checkpoint --- .../SuperpixelClassification/SuperpixelClassificationTorch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py index 672584c..d193c07 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py @@ -410,7 +410,7 @@ def cacheOptimalBatchSize(self, batchSize: int, model: torch.nn.Module, training return batchSize def loadModel(self, modelPath): - model = torch.load(modelPath) + model = torch.load(modelPath, weights_only=False) model.eval() return model From 0120b77e15921eb65ec72d28742a956aee620471 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Tue, 25 Feb 2025 10:42:14 -0600 Subject: [PATCH 2/7] Add try-catch to model load This is to print out the filename if anything fails for further inspection --- .../SuperpixelClassificationTorch.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py index d193c07..f83c382 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py @@ -410,9 +410,15 @@ def cacheOptimalBatchSize(self, batchSize: int, model: torch.nn.Module, training return batchSize def loadModel(self, modelPath): - model = torch.load(modelPath, weights_only=False) - model.eval() - return model + self.add_safe_globals() + try: + model = torch.load(modelPath, weights_only=False) + model.eval() + return model + except Exception as e: + print(f"Unable to load {modelPath}") + raise + def saveModel(self, model, modelPath): torch.save(model, modelPath) From fba1521665772bfce77f8ef3801d77d9c214edd0 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Wed, 28 May 2025 15:19:55 -0500 Subject: [PATCH 3/7] Add missing import --- .../SuperpixelClassificationTensorflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTensorflow.py b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTensorflow.py index 0af02d8..958ab42 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTensorflow.py +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTensorflow.py @@ -3,6 +3,7 @@ from typing import Optional import h5py +import numpy as np import tensorflow as tf from SuperpixelClassificationBase import SuperpixelClassificationBase From 57365eda9984863a9ec177d59b710d1165498340 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Tue, 15 Apr 2025 13:27:17 -0500 Subject: [PATCH 4/7] Fix spelling --- .../SuperpixelClassification/SuperpixelClassification.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassification.xml b/superpixel_classification/SuperpixelClassification/SuperpixelClassification.xml index ea0c301..b5c570c 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassification.xml +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassification.xml @@ -61,7 +61,7 @@ Superpixel parameters gensuperpixels - generate-superpxiels + generate-superpixels If an image does not have an annotation with superpixels, generate one true From e081ffb9c2286c51e815d5ae95e1ea05b87b16c9 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Tue, 15 Apr 2025 13:27:45 -0500 Subject: [PATCH 5/7] Ignore any temporary files (generated during training in git --- .dockerignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.dockerignore b/.dockerignore index 7797741..96a401f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ +**/tmp* +test_data .ruff_cache .tox *.egg-info From 4ef743bc2d5a809037ec8661c2f78e0d967a4f22 Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Wed, 28 May 2025 10:35:28 -0500 Subject: [PATCH 6/7] Add print statements for tracking --- .../SuperpixelClassificationBase.py | 16 ++++++++++++---- .../SuperpixelClassificationTorch.py | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationBase.py b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationBase.py index a694d59..f708aab 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationBase.py +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationBase.py @@ -416,12 +416,14 @@ def trainModelAddItem(self, gc, record, item, annotrec, elem, feature, item['name'], annotrec['annotation']['name'], annotrec['_id'], annotrec['_version'])) featurePath = os.path.join(record['tempdir'], feature['name']) gc.downloadFile(feature['_id'], featurePath) + print(f"Downloaded '{feature['_id']}' to '{featurePath}'") with h5py.File(featurePath, 'r') as ffptr: fds = ffptr['images'] for idx, labelnum in enumerate(elem['values']): if labelnum and labelnum < len(elem['categories']): labelname = elem['categories'][labelnum]['label'] if labelname in excludeLabelList: + skipped_excluded += 1 continue if labelname not in record['groups']: record['groups'][labelname] = elem['categories'][labelnum] @@ -449,6 +451,7 @@ def trainModelAddItem(self, gc, record, item, annotrec, elem, feature, record['lastlog'] = time.time() print(record['ds'].shape, record['counts'], '%5.3f' % (time.time() - record['starttime'])) + print(f"Skipped {skipped_excluded} samples with labels that were excluded") def trainModel(self, gc, folderId, annotationName, features, modelFolderId, batchSize, epochs, trainingSplit, randomInput, labelList, @@ -506,11 +509,11 @@ def trainModel(self, gc, folderId, annotationName, features, modelFolderId, for attempt in Retrying(stop=stop_after_attempt(self.uploadRetries)): with attempt: modelFile = gc.uploadFileToFolder(modelFolderId, modelPath) - print('Saved model') + print(f'Saved model to {modelFolderId}') for attempt in Retrying(stop=stop_after_attempt(self.uploadRetries)): with attempt: modTrainingFile = gc.uploadFileToFolder(modelFolderId, modTrainingPath) - print('Saved modTraining') + print(f'Saved modTraining to {modelFolderId}') return modelFile, modTrainingFile def predictLabelsForItem(self, gc, annotationName, annotationFolderId, tempdir, model, item, @@ -734,7 +737,7 @@ def predictLabels(self, gc, folderId, annotationName, features, modelFolderId, modelFile = next(gc.listFile(item['_id'], limit=1)) break if not modelFile: - print('No model file found') + print(f'No model file found in {modelFolderId}') return print(modelFile['name'], item) modelPath = os.path.join(tempdir, modelFile['name']) @@ -747,7 +750,7 @@ def predictLabels(self, gc, folderId, annotationName, features, modelFolderId, modTrainingFile = next(gc.listFile(item['_id'], limit=1)) break if not modTrainingFile: - print('No modTraining file found') + print(f'No modTraining file found in {modelFolderId}') return print(modTrainingFile['name'], item) modTrainingPath = os.path.join(tempdir, modTrainingFile['name']) @@ -797,16 +800,21 @@ def main(self, args): gc, args.images, args.annotationName, args.radius, args.magnification, args.annotationDir, args.numWorkers, prog) + print("Creating features...") features = self.createFeatures( gc, args.images, args.annotationName, args.features, args.patchSize, args.numWorkers, prog) + print("Done creating features...") if args.train: self.trainModel( gc, args.images, args.annotationName, features, args.modeldir, args.batchSize, args.epochs, args.split, args.randominput, args.labels, args.exclude, prog) + print("Done training...") self.predictLabels( gc, args.images, args.annotationName, features, args.modeldir, args.annotationDir, args.heatmaps, args.radius, args.magnification, args.certainty, args.batchSize, prog) + print("Done predicting labels...") + print("Done, exiting") diff --git a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py index f83c382..1bcf297 100644 --- a/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py +++ b/superpixel_classification/SuperpixelClassification/SuperpixelClassificationTorch.py @@ -171,6 +171,7 @@ def trainModelDetails( val_ds: torch.utils.data.TensorDataset train_dl: torch.utils.data.DataLoader val_dl: torch.utils.data.DataLoader + prog.message('Loading features for model training') train_arg1 = torch.from_numpy(record['ds'][train_indices].transpose((0, 3, 2, 1))) train_arg2 = torch.from_numpy(record['labelds'][train_indices]) val_arg1 = torch.from_numpy(record['ds'][val_indices].transpose((0, 3, 2, 1))) From e1dc0913c3764c6e905c2e3b8b02a38331b4805a Mon Sep 17 00:00:00 2001 From: Anders Sildnes Date: Tue, 3 Jun 2025 09:10:53 -0500 Subject: [PATCH 7/7] Add simple script to inspect feature files --- tools/inspect_image_feature_file.py | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tools/inspect_image_feature_file.py diff --git a/tools/inspect_image_feature_file.py b/tools/inspect_image_feature_file.py new file mode 100644 index 0000000..a93d911 --- /dev/null +++ b/tools/inspect_image_feature_file.py @@ -0,0 +1,37 @@ +''' +This script will open a feature file (.h5) and show a 3x3 grid of images. +This tool is useful if you suspect that features are not extracted properly, for example due to erroneous mask values/indexing. +''' + +import h5py +import matplotlib.pyplot as plt +import numpy as np +import sys + +if len(sys.argv) > 0: + feature_file = sys.argv[1] +else: + feature_file = "features.h5" + +# open the file +with h5py.File(feature_file, "r") as f: + # get the images dataset + images = f["images"] + # get the first 9 images + images = images[:9] + # reshape the images to 3x3 + #images = np.reshape(images, (3,3,100,100,3)) + # transpose the images to 3x3 + #images = np.transpose(images, (0,2,1,3,4)) + # flatten the images to 9x100x100x3 + #images = np.reshape(images, (9,100,100,3)) + + # hide axis from pyplot + plt.axis('off') + + # plot the images + for i in range(9): + plt.subplot(3,3,i+1) + plt.imshow(images[i]) + plt.show() + print(f"Image {i+1} is {images[i].shape}")