1313import patchcore .common
1414import patchcore .patchcore
1515from torchvision import transforms
16+ from skimage import io # For TIFF saving
1617
1718def 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
119142def 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"\n Heatmaps saved in: { output_dir } /" )
189+ print (f"\n Visualization heatmaps saved in: { output_dir } /" )
190+ print (f"Raw anomaly masks saved in: { raw_mask_dir } /" )
191+ print ("\n Raw 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
167197if __name__ == "__main__" :
0 commit comments