Skip to content

Commit 029b85a

Browse files
committed
using results of patchcore + SAM to identify masks
1 parent 6c035c5 commit 029b85a

File tree

4 files changed

+304
-10
lines changed

4 files changed

+304
-10
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,8 @@ LabletAnomaly.egg-info/
66
results/
77
masonry_dataset/
88
anomaly_heatmaps/
9-
categorized_dataset/
9+
categorized_dataset/
10+
perspective_dataset/
11+
raw_anomaly_masks/
12+
13+
venv/

inference_masonry.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import patchcore.common
1414
import patchcore.patchcore
1515
from torchvision import transforms
16+
from skimage import io # For TIFF saving
1617

1718
def load_model(model_path, device):
1819
"""Load the trained PatchCore model"""
@@ -33,7 +34,25 @@ def load_model(model_path, device):
3334
print("Model loaded successfully!")
3435
return patchcore_model
3536

36-
def generate_heatmap(model, image_path, device, save_path):
37+
def save_raw_anomaly_mask(anomaly_mask_resized, save_dir, filename_base):
38+
"""Save raw anomaly mask in multiple formats for post-processing"""
39+
os.makedirs(save_dir, exist_ok=True)
40+
41+
# Save as NumPy array (best for numerical processing)
42+
npy_path = os.path.join(save_dir, f"{filename_base}_mask.npy")
43+
np.save(npy_path, anomaly_mask_resized)
44+
45+
# Save as 16-bit PNG (good compatibility, some precision loss)
46+
png_path = os.path.join(save_dir, f"{filename_base}_mask.png")
47+
# Normalize to 0-65535 range for 16-bit PNG
48+
mask_normalized = ((anomaly_mask_resized - anomaly_mask_resized.min()) /
49+
(anomaly_mask_resized.max() - anomaly_mask_resized.min()) * 65535).astype(np.uint16)
50+
Image.fromarray(mask_normalized).save(png_path)
51+
52+
print(f"Raw masks saved: {filename_base}_mask.[npy|png]")
53+
return npy_path, png_path
54+
55+
def generate_heatmap(model, image_path, device, save_path, raw_mask_dir):
3756
"""Generate anomaly heatmap for a single image"""
3857
# Create a minimal dataset instance just to access the transforms
3958
# We'll override the problematic methods to avoid file system checks
@@ -88,6 +107,10 @@ def _setup_transforms(self):
88107
original_size = original_image.size # (width, height)
89108
anomaly_mask_resized = np.array(Image.fromarray(anomaly_mask).resize(original_size, Image.BILINEAR))
90109

110+
# Save raw anomaly mask for post-processing
111+
filename_base = os.path.splitext(os.path.basename(image_path))[0]
112+
save_raw_anomaly_mask(anomaly_mask_resized, raw_mask_dir, filename_base)
113+
91114
# Create visualization with consistent 0-6 scale
92115
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
93116

@@ -114,7 +137,7 @@ def _setup_transforms(self):
114137
plt.close()
115138

116139
print(f"Heatmap saved: {save_path} (Score: {anomaly_score:.3f})")
117-
return anomaly_score, anomaly_mask
140+
return anomaly_score, anomaly_mask_resized
118141

119142
def main():
120143
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
@@ -123,14 +146,16 @@ def main():
123146
# Load model
124147
model = load_model(model_path, device)
125148

126-
# Create output directory
149+
# Create output directories
127150
output_dir = "anomaly_heatmaps"
151+
raw_mask_dir = "raw_anomaly_masks"
128152
os.makedirs(output_dir, exist_ok=True)
153+
os.makedirs(raw_mask_dir, exist_ok=True)
129154

130155
# Process test images
131156
test_dirs = [
132-
"masonry_dataset/wall/test/good",
133-
"masonry_dataset/wall/test/defect"
157+
"masonry_dataset/wall/test/defect",
158+
"masonry_dataset/wall/test/good"
134159
]
135160

136161
all_scores = []
@@ -148,20 +173,25 @@ def main():
148173
save_path = os.path.join(output_dir, f"{category}_{filename}_heatmap.png")
149174

150175
try:
151-
score, mask = generate_heatmap(model, image_path, device, save_path)
176+
score, mask = generate_heatmap(model, image_path, device, save_path, raw_mask_dir)
152177
all_scores.append((category, filename, score))
153178
except Exception as e:
154179
print(f"Error processing {filename}: {e}")
155180

156181
# Print summary
157-
print(f"\n{'='*50}")
182+
print(f"\n{'='*60}")
158183
print("ANOMALY DETECTION SUMMARY")
159-
print(f"{'='*50}")
184+
print(f"{'='*60}")
160185

161186
for category, filename, score in sorted(all_scores, key=lambda x: x[2], reverse=True):
162187
print(f"{category:10} | {filename:20} | Score: {score:.3f}")
163188

164-
print(f"\nHeatmaps saved in: {output_dir}/")
189+
print(f"\nVisualization heatmaps saved in: {output_dir}/")
190+
print(f"Raw anomaly masks saved in: {raw_mask_dir}/")
191+
print("\nRaw mask formats:")
192+
print("- .npy: NumPy array (best for numerical processing)")
193+
print("- .tiff: 32-bit TIFF (preserves float values)")
194+
print("- .png: 16-bit PNG (good compatibility)")
165195
print("Higher scores indicate more anomalous regions")
166196

167197
if __name__ == "__main__":

mask_selection.ipynb

Lines changed: 260 additions & 0 deletions
Large diffs are not rendered by default.

train_masonry.sh

100644100755
File mode changed.

0 commit comments

Comments
 (0)