Spaces:
Sleeping
Sleeping
File size: 6,213 Bytes
a22e992 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
import cv2
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import os
from typing import Dict, Any
import base64
import io
class ImageAnalysisTool:
"""Standalone image analysis tool for SEM images"""
def __init__(self):
self.name = "SEM Image Analysis Tool"
self.description = "Analyzes SEM images to extract microstructural information about soil cemented materials"
def _run(self, image_path: str) -> Dict[str, Any]:
"""
Analyze SEM image and extract relevant features
"""
try:
# Load and process image
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
return {"error": f"Could not load image from {image_path}"}
# Basic image properties
height, width = image.shape
mean_intensity = np.mean(image)
std_intensity = np.std(image)
# Convert to PIL for additional analysis
pil_image = Image.fromarray(image)
# Encode image for vision model
image_base64 = self._encode_image_to_base64(pil_image)
# Basic texture analysis
texture_features = self._analyze_texture(image)
# Porosity estimation (simple threshold-based)
porosity_info = self._estimate_porosity(image)
# Particle analysis
particle_info = self._analyze_particles(image)
analysis_results = {
"image_properties": {
"width": int(width),
"height": int(height),
"mean_intensity": float(mean_intensity),
"std_intensity": float(std_intensity)
},
"texture_features": texture_features,
"porosity_analysis": porosity_info,
"particle_analysis": particle_info,
"image_base64": image_base64,
"image_path": image_path
}
return analysis_results
except Exception as e:
return {"error": f"Error analyzing image: {str(e)}"}
def _encode_image_to_base64(self, image: Image.Image) -> str:
"""Convert PIL image to base64 string"""
buffered = io.BytesIO()
image.save(buffered, format="PNG")
img_str = base64.b64encode(buffered.getvalue()).decode()
return img_str
def _analyze_texture(self, image: np.ndarray) -> Dict[str, float]:
"""Analyze texture properties of the image"""
# Calculate local standard deviation (texture measure)
kernel = np.ones((9, 9), np.float32) / 81
mean_filtered = cv2.filter2D(image.astype(np.float32), -1, kernel)
sqr_diff = (image.astype(np.float32) - mean_filtered) ** 2
texture_map = cv2.filter2D(sqr_diff, -1, kernel)
return {
"texture_variance": float(np.mean(texture_map)),
"texture_uniformity": float(np.std(texture_map)),
"contrast": float(np.max(image) - np.min(image))
}
def _estimate_porosity(self, image: np.ndarray) -> Dict[str, Any]:
"""Estimate porosity using threshold-based segmentation"""
# Use Otsu's thresholding for automatic threshold selection
_, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# Calculate porosity (assuming dark regions are pores)
total_pixels = image.shape[0] * image.shape[1]
pore_pixels = np.sum(binary == 0)
porosity_percentage = (pore_pixels / total_pixels) * 100
# Analyze pore size distribution
contours, _ = cv2.findContours(255 - binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
pore_areas = [cv2.contourArea(cnt) for cnt in contours if cv2.contourArea(cnt) > 10]
return {
"estimated_porosity_percent": float(porosity_percentage),
"number_of_pores": len(pore_areas),
"average_pore_area": float(np.mean(pore_areas)) if pore_areas else 0,
"max_pore_area": float(np.max(pore_areas)) if pore_areas else 0,
"min_pore_area": float(np.min(pore_areas)) if pore_areas else 0
}
def _analyze_particles(self, image: np.ndarray) -> Dict[str, Any]:
"""Analyze particle characteristics"""
# Edge detection for particle boundaries
edges = cv2.Canny(image, 50, 150)
# Find contours (potential particles)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter contours by area to remove noise
min_area = 50 # minimum particle area
particles = [cnt for cnt in contours if cv2.contourArea(cnt) > min_area]
if not particles:
return {"number_of_particles": 0}
# Calculate particle properties
areas = [cv2.contourArea(cnt) for cnt in particles]
perimeters = [cv2.arcLength(cnt, True) for cnt in particles]
# Calculate equivalent diameters
equivalent_diameters = [2 * np.sqrt(area / np.pi) for area in areas]
# Calculate circularity (roundness measure)
circularities = []
for i, cnt in enumerate(particles):
if perimeters[i] > 0:
circularity = 4 * np.pi * areas[i] / (perimeters[i] ** 2)
circularities.append(circularity)
return {
"number_of_particles": len(particles),
"average_particle_area": float(np.mean(areas)),
"particle_area_std": float(np.std(areas)),
"average_equivalent_diameter": float(np.mean(equivalent_diameters)),
"diameter_range": {
"min": float(np.min(equivalent_diameters)),
"max": float(np.max(equivalent_diameters))
},
"average_circularity": float(np.mean(circularities)) if circularities else 0,
"circularity_std": float(np.std(circularities)) if circularities else 0
} |