Upload app.py with huggingface_hub
Browse files
app.py
CHANGED
|
@@ -1074,268 +1074,155 @@ def save_to_html(content, source_type, filename=None):
|
|
| 1074 |
# Function to analyze images with a prompt for folder analysis
|
| 1075 |
def analyze_folder_images(folder_path, prompt):
|
| 1076 |
"""Analyze all images in a folder."""
|
| 1077 |
-
|
| 1078 |
-
|
| 1079 |
|
| 1080 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1081 |
image_files = []
|
| 1082 |
for ext in SUPPORTED_EXTENSIONS:
|
| 1083 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1084 |
|
| 1085 |
if not image_files:
|
| 1086 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1087 |
|
| 1088 |
-
# Sort files
|
| 1089 |
image_files.sort()
|
| 1090 |
|
| 1091 |
-
|
|
|
|
| 1092 |
|
| 1093 |
-
#
|
| 1094 |
-
for
|
| 1095 |
-
|
| 1096 |
-
|
| 1097 |
-
|
| 1098 |
-
|
| 1099 |
-
|
| 1100 |
-
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
result += "Error: Model failed to load for PDF analysis.\n"
|
| 1111 |
-
continue
|
| 1112 |
-
|
| 1113 |
-
# Try a completely different approach for PDFs to avoid tensor issues
|
| 1114 |
-
try:
|
| 1115 |
-
# Convert PDF to images with detailed logging
|
| 1116 |
-
logger.info(f"Starting PDF to image conversion for {file_name}")
|
| 1117 |
-
with open(image_file, 'rb') as pdf_file:
|
| 1118 |
-
pdf_data = pdf_file.read()
|
| 1119 |
-
logger.debug(f"Read {len(pdf_data)} bytes from PDF file")
|
| 1120 |
-
|
| 1121 |
-
# Try both methods
|
| 1122 |
-
try:
|
| 1123 |
-
logger.debug("Attempting convert_from_path...")
|
| 1124 |
-
pdf_images = convert_from_path(image_file)
|
| 1125 |
-
logger.info(f"convert_from_path successful: {len(pdf_images)} pages")
|
| 1126 |
-
except Exception as path_err:
|
| 1127 |
-
logger.error(f"convert_from_path failed: {str(path_err)}")
|
| 1128 |
-
logger.error(traceback.format_exc())
|
| 1129 |
-
|
| 1130 |
-
# Fall back to bytes method
|
| 1131 |
-
logger.debug("Falling back to convert_from_bytes...")
|
| 1132 |
-
pdf_images = convert_from_bytes(pdf_data)
|
| 1133 |
-
logger.info(f"convert_from_bytes successful: {len(pdf_images)} pages")
|
| 1134 |
-
|
| 1135 |
-
logger.info(f"PDF converted to {len(pdf_images)} pages")
|
| 1136 |
-
|
| 1137 |
-
if not pdf_images or len(pdf_images) == 0:
|
| 1138 |
-
logger.error("PDF converted but no pages were extracted")
|
| 1139 |
-
result += "PDF converted but no pages were extracted.\n"
|
| 1140 |
-
continue
|
| 1141 |
-
|
| 1142 |
-
# Process each page separately to avoid batch issues
|
| 1143 |
-
for i, img in enumerate(pdf_images):
|
| 1144 |
-
try:
|
| 1145 |
-
logger.info(f"Processing PDF page {i+1}/{len(pdf_images)}")
|
| 1146 |
-
logger.debug(f"Page {i+1} image: size={img.size}, mode={img.mode}")
|
| 1147 |
-
|
| 1148 |
-
# Manual preprocessing - don't use the typical image loading pipeline
|
| 1149 |
-
logger.debug("Converting image to RGB")
|
| 1150 |
-
img = img.convert('RGB')
|
| 1151 |
-
|
| 1152 |
-
# Log the image info for debugging
|
| 1153 |
-
logger.debug(f"After RGB conversion: size={img.size}, mode={img.mode}")
|
| 1154 |
-
|
| 1155 |
-
# Resize and transform manually
|
| 1156 |
-
logger.debug(f"Resizing image to {IMAGE_SIZE}x{IMAGE_SIZE}")
|
| 1157 |
-
img_resized = img.resize((IMAGE_SIZE, IMAGE_SIZE))
|
| 1158 |
-
logger.debug(f"After resize: size={img_resized.size}, mode={img_resized.mode}")
|
| 1159 |
-
|
| 1160 |
-
# Build transform and apply it
|
| 1161 |
-
logger.debug("Building and applying transform")
|
| 1162 |
-
transform = T.Compose([
|
| 1163 |
-
T.ToTensor(),
|
| 1164 |
-
T.Normalize(mean=IMAGENET_MEAN, std=IMAGENET_STD)
|
| 1165 |
-
])
|
| 1166 |
-
|
| 1167 |
-
# Transform to tensor with explicit error handling
|
| 1168 |
-
try:
|
| 1169 |
-
logger.debug("Converting image to tensor")
|
| 1170 |
-
tensor = transform(img_resized)
|
| 1171 |
-
logger.debug(f"Transform successful: tensor shape={tensor.shape}, dtype={tensor.dtype}")
|
| 1172 |
-
|
| 1173 |
-
# Log tensor stats for debugging numerical issues
|
| 1174 |
-
if tensor.numel() > 0:
|
| 1175 |
-
logger.debug(f"Tensor stats: min={tensor.min().item():.4f}, max={tensor.max().item():.4f}, "
|
| 1176 |
-
f"mean={tensor.mean().item():.4f}, std={tensor.std().item():.4f}")
|
| 1177 |
-
|
| 1178 |
-
# Add batch dimension with careful checking
|
| 1179 |
-
if isinstance(tensor, torch.Tensor):
|
| 1180 |
-
logger.debug("Adding batch dimension")
|
| 1181 |
-
tensor = tensor.unsqueeze(0)
|
| 1182 |
-
logger.debug(f"After unsqueeze: shape={tensor.shape}")
|
| 1183 |
-
else:
|
| 1184 |
-
logger.error(f"Expected tensor but got {type(tensor)}")
|
| 1185 |
-
raise TypeError(f"Transform returned {type(tensor)} instead of tensor")
|
| 1186 |
-
except Exception as tensor_err:
|
| 1187 |
-
logger.error(f"Error in tensor creation: {str(tensor_err)}")
|
| 1188 |
-
logger.error(traceback.format_exc())
|
| 1189 |
-
raise
|
| 1190 |
-
|
| 1191 |
-
# Move to device and set data type
|
| 1192 |
-
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 1193 |
-
logger.debug(f"Moving tensor to device: {device}")
|
| 1194 |
-
tensor = tensor.to(device)
|
| 1195 |
-
|
| 1196 |
-
if torch.cuda.is_available():
|
| 1197 |
-
logger.debug("Converting tensor to bfloat16")
|
| 1198 |
-
tensor = tensor.to(torch.bfloat16)
|
| 1199 |
-
|
| 1200 |
-
logger.info(f"Preprocessed tensor: shape={tensor.shape}, device={tensor.device}, dtype={tensor.dtype}")
|
| 1201 |
-
|
| 1202 |
-
# Use direct text generation
|
| 1203 |
-
page_prompt = f"PDF Page {i+1}: {prompt}"
|
| 1204 |
-
logger.debug(f"Preparing tokenization for prompt: {page_prompt}")
|
| 1205 |
-
input_tokens = tokenizer(page_prompt, return_tensors="pt").to(device)
|
| 1206 |
-
logger.debug(f"Tokenization complete: shape={input_tokens['input_ids'].shape}")
|
| 1207 |
-
|
| 1208 |
-
# Generate with proper error handling
|
| 1209 |
-
try:
|
| 1210 |
-
# Try direct generation first
|
| 1211 |
-
logger.info("Attempting direct generation")
|
| 1212 |
-
outputs = model.generate(
|
| 1213 |
-
input_tokens["input_ids"],
|
| 1214 |
-
pixel_values=tensor,
|
| 1215 |
-
max_new_tokens=512,
|
| 1216 |
-
do_sample=False
|
| 1217 |
-
)
|
| 1218 |
-
logger.info("Generation successful")
|
| 1219 |
-
response = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 1220 |
-
logger.debug(f"Response length: {len(response)} chars")
|
| 1221 |
-
except Exception as gen_err:
|
| 1222 |
-
logger.error(f"Error in direct generation: {str(gen_err)}")
|
| 1223 |
-
logger.error(traceback.format_exc())
|
| 1224 |
-
|
| 1225 |
-
# Fall back to chat method
|
| 1226 |
-
try:
|
| 1227 |
-
print("Trying chat method fallback")
|
| 1228 |
-
question = f"<image>\n{page_prompt}"
|
| 1229 |
-
# IMPORTANT: Ensure we're not passing a list here!
|
| 1230 |
-
if isinstance(tensor, list):
|
| 1231 |
-
print("WARNING: tensor is a list, converting...")
|
| 1232 |
-
if len(tensor) > 0:
|
| 1233 |
-
# Take the first item if it's a tensor
|
| 1234 |
-
if isinstance(tensor[0], torch.Tensor):
|
| 1235 |
-
tensor = tensor[0].unsqueeze(0)
|
| 1236 |
-
else:
|
| 1237 |
-
# Create a new tensor from scratch
|
| 1238 |
-
print("Creating new tensor from scratch")
|
| 1239 |
-
tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
|
| 1240 |
-
dtype=torch.float32).to(device)
|
| 1241 |
-
if torch.cuda.is_available():
|
| 1242 |
-
tensor = tensor.to(torch.bfloat16)
|
| 1243 |
-
else:
|
| 1244 |
-
# Create a dummy tensor
|
| 1245 |
-
tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
|
| 1246 |
-
dtype=torch.float32).to(device)
|
| 1247 |
-
if torch.cuda.is_available():
|
| 1248 |
-
tensor = tensor.to(torch.bfloat16)
|
| 1249 |
-
|
| 1250 |
-
# Verify tensor shape and type before passing to model
|
| 1251 |
-
print(f"Final tensor type: {type(tensor)}, shape: {tensor.shape if hasattr(tensor, 'shape') else 'unknown'}")
|
| 1252 |
-
|
| 1253 |
-
# Use the chat method with verified tensor
|
| 1254 |
-
response, _ = model.chat(
|
| 1255 |
-
tokenizer=tokenizer,
|
| 1256 |
-
pixel_values=tensor,
|
| 1257 |
-
question=question,
|
| 1258 |
-
generation_config={"max_new_tokens": 512, "do_sample": False},
|
| 1259 |
-
history=None,
|
| 1260 |
-
return_history=True
|
| 1261 |
-
)
|
| 1262 |
-
except Exception as chat_err:
|
| 1263 |
-
print(f"Chat fallback failed: {str(chat_err)}")
|
| 1264 |
-
import traceback
|
| 1265 |
-
print(traceback.format_exc())
|
| 1266 |
-
|
| 1267 |
-
# Last attempt - use direct model forward pass
|
| 1268 |
-
try:
|
| 1269 |
-
print("Attempting direct model forward pass")
|
| 1270 |
-
# Create inputs manually
|
| 1271 |
-
if hasattr(model, "forward"):
|
| 1272 |
-
# Create tensors from scratch if needed
|
| 1273 |
-
if not isinstance(tensor, torch.Tensor):
|
| 1274 |
-
tensor = torch.zeros((1, 3, IMAGE_SIZE, IMAGE_SIZE),
|
| 1275 |
-
dtype=torch.float32).to(device)
|
| 1276 |
-
if torch.cuda.is_available():
|
| 1277 |
-
tensor = tensor.to(torch.bfloat16)
|
| 1278 |
-
|
| 1279 |
-
# Get input tokens in the right format
|
| 1280 |
-
input_ids = input_tokens["input_ids"]
|
| 1281 |
-
if len(input_ids.shape) == 1:
|
| 1282 |
-
input_ids = input_ids.unsqueeze(0)
|
| 1283 |
-
|
| 1284 |
-
# Prepare inputs for direct call
|
| 1285 |
-
inputs = {
|
| 1286 |
-
"input_ids": input_ids,
|
| 1287 |
-
"pixel_values": tensor,
|
| 1288 |
-
"return_dict": True,
|
| 1289 |
-
}
|
| 1290 |
-
# Call model directly
|
| 1291 |
-
outputs = model(**inputs)
|
| 1292 |
-
# Try to get some output
|
| 1293 |
-
if hasattr(outputs, "logits") and outputs.logits is not None:
|
| 1294 |
-
pred_ids = torch.argmax(outputs.logits, dim=-1)
|
| 1295 |
-
response = tokenizer.decode(pred_ids[0], skip_special_tokens=True)
|
| 1296 |
-
else:
|
| 1297 |
-
response = "Failed to generate analysis - model output didn't contain usable data"
|
| 1298 |
-
else:
|
| 1299 |
-
response = "Failed to generate analysis - model doesn't support direct calling"
|
| 1300 |
-
except Exception as final_err:
|
| 1301 |
-
print(f"All attempts failed: {str(final_err)}")
|
| 1302 |
-
import traceback
|
| 1303 |
-
print(traceback.format_exc())
|
| 1304 |
-
response = f"Analysis failed due to model error: {str(final_err)}"
|
| 1305 |
-
|
| 1306 |
-
# Add to result
|
| 1307 |
-
result += f"\n-- PDF Page {i+1} --\n{response.strip()}\n"
|
| 1308 |
-
|
| 1309 |
-
except Exception as page_err:
|
| 1310 |
-
print(f"Error processing page {i+1}: {str(page_err)}")
|
| 1311 |
-
import traceback
|
| 1312 |
-
print(traceback.format_exc())
|
| 1313 |
-
result += f"\n-- PDF Page {i+1} --\nError: {str(page_err)}\n"
|
| 1314 |
-
|
| 1315 |
-
except Exception as pdf_err:
|
| 1316 |
-
print(f"PDF processing error: {str(pdf_err)}")
|
| 1317 |
-
import traceback
|
| 1318 |
-
print(traceback.format_exc())
|
| 1319 |
-
result += f"Failed to process PDF: {str(pdf_err)}\n"
|
| 1320 |
-
|
| 1321 |
-
except Exception as e:
|
| 1322 |
-
print(f"General exception in PDF processing: {str(e)}")
|
| 1323 |
-
import traceback
|
| 1324 |
-
print(traceback.format_exc())
|
| 1325 |
-
result += f"Failed to process PDF: {str(e)}\n"
|
| 1326 |
-
else:
|
| 1327 |
-
# Regular image processing
|
| 1328 |
try:
|
| 1329 |
-
image = Image.open(
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
| 1334 |
-
|
| 1335 |
-
|
| 1336 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1337 |
|
| 1338 |
-
|
|
|
|
|
|
|
| 1339 |
|
| 1340 |
# Function to process an image with text prompt
|
| 1341 |
def process_image_with_text(image, prompt):
|
|
@@ -1882,10 +1769,36 @@ def main():
|
|
| 1882 |
|
| 1883 |
# Tab for folder analysis
|
| 1884 |
with gr.Tab("Folder Analysis"):
|
| 1885 |
-
gr.Markdown("Analyze all images and PDFs in a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1886 |
|
| 1887 |
with gr.Row():
|
| 1888 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1889 |
|
| 1890 |
prompt_folder = gr.Dropdown(
|
| 1891 |
label="Analysis Prompt",
|
|
@@ -1893,23 +1806,142 @@ def main():
|
|
| 1893 |
value=prompts[0],
|
| 1894 |
allow_custom_value=True
|
| 1895 |
)
|
| 1896 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1897 |
output_folder = gr.Textbox(label="Analysis Result", lines=20)
|
| 1898 |
|
| 1899 |
-
#
|
| 1900 |
-
|
| 1901 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1902 |
|
| 1903 |
analyze_btn_folder.click(
|
| 1904 |
-
fn=
|
| 1905 |
inputs=[folder_path, prompt_folder],
|
| 1906 |
-
outputs=output_folder
|
| 1907 |
)
|
| 1908 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1909 |
save_btn_folder.click(
|
| 1910 |
fn=lambda text, prompt: save_to_file(text, "folder_analysis", prompt=prompt),
|
| 1911 |
inputs=[output_folder, prompt_folder],
|
| 1912 |
-
outputs=save_status_folder
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1913 |
)
|
| 1914 |
|
| 1915 |
# Add a new tab for logs
|
|
|
|
| 1074 |
# Function to analyze images with a prompt for folder analysis
|
| 1075 |
def analyze_folder_images(folder_path, prompt):
|
| 1076 |
"""Analyze all images in a folder."""
|
| 1077 |
+
# Add extensive logging to debug panel
|
| 1078 |
+
logger.info(f"analyze_folder_images called with path: '{folder_path}'")
|
| 1079 |
|
| 1080 |
+
# Increment operations counter
|
| 1081 |
+
gui_stats['operations_completed'] += 1
|
| 1082 |
+
|
| 1083 |
+
if not folder_path or folder_path.strip() == "":
|
| 1084 |
+
error_msg = "No folder path provided. Please enter a valid folder path."
|
| 1085 |
+
logger.error(error_msg)
|
| 1086 |
+
gui_stats['errors'] += 1
|
| 1087 |
+
gui_stats['last_error'] = error_msg
|
| 1088 |
+
gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1089 |
+
return error_msg
|
| 1090 |
+
|
| 1091 |
+
# Clean up the folder path
|
| 1092 |
+
folder_path = folder_path.strip()
|
| 1093 |
+
logger.debug(f"Cleaned folder path: '{folder_path}'")
|
| 1094 |
+
|
| 1095 |
+
# Try multiple path options
|
| 1096 |
+
potential_paths = [
|
| 1097 |
+
folder_path, # As provided
|
| 1098 |
+
os.path.join(os.getcwd(), folder_path), # Relative to cwd
|
| 1099 |
+
os.path.normpath(folder_path), # Normalized path
|
| 1100 |
+
os.path.abspath(folder_path), # Absolute path
|
| 1101 |
+
os.path.expanduser(folder_path) # Expand user directory (~)
|
| 1102 |
+
]
|
| 1103 |
+
|
| 1104 |
+
# If we're in a Hugging Face Space, try the /data path
|
| 1105 |
+
if os.path.exists("/data"):
|
| 1106 |
+
potential_paths.append(os.path.join("/data", folder_path))
|
| 1107 |
+
|
| 1108 |
+
# Debug information
|
| 1109 |
+
logger.debug(f"Current working directory: {os.getcwd()}")
|
| 1110 |
+
try:
|
| 1111 |
+
logger.debug(f"Directory contents of current directory: {os.listdir('.')}")
|
| 1112 |
+
except Exception as e:
|
| 1113 |
+
logger.warning(f"Could not list current directory: {str(e)}")
|
| 1114 |
+
|
| 1115 |
+
# Try each path
|
| 1116 |
+
valid_path = None
|
| 1117 |
+
for test_path in potential_paths:
|
| 1118 |
+
logger.debug(f"Testing path: '{test_path}'")
|
| 1119 |
+
if os.path.exists(test_path):
|
| 1120 |
+
logger.debug(f"Path exists: '{test_path}'")
|
| 1121 |
+
if os.path.isdir(test_path):
|
| 1122 |
+
valid_path = test_path
|
| 1123 |
+
logger.info(f"Found valid directory path: '{valid_path}'")
|
| 1124 |
+
break
|
| 1125 |
+
else:
|
| 1126 |
+
logger.debug(f"Path exists but is not a directory: '{test_path}'")
|
| 1127 |
+
|
| 1128 |
+
if not valid_path:
|
| 1129 |
+
error_msg = f"Could not find a valid directory at '{folder_path}'. Please provide a complete and valid folder path."
|
| 1130 |
+
logger.error(error_msg)
|
| 1131 |
+
gui_stats['errors'] += 1
|
| 1132 |
+
gui_stats['last_error'] = error_msg
|
| 1133 |
+
gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1134 |
+
|
| 1135 |
+
# Try to provide helpful information about available directories
|
| 1136 |
+
try:
|
| 1137 |
+
available_dirs = [d for d in os.listdir('.') if os.path.isdir(d)]
|
| 1138 |
+
if available_dirs:
|
| 1139 |
+
return f"Error: {error_msg}\n\nAvailable directories in current location: {', '.join(available_dirs)}"
|
| 1140 |
+
else:
|
| 1141 |
+
return f"Error: {error_msg}\n\nNo directories found in the current location."
|
| 1142 |
+
except:
|
| 1143 |
+
return f"Error: {error_msg}"
|
| 1144 |
+
|
| 1145 |
+
# Convert to Path object for easier handling
|
| 1146 |
+
folder_path = Path(valid_path)
|
| 1147 |
+
logger.debug(f"Using folder path: {folder_path}")
|
| 1148 |
+
|
| 1149 |
+
# Find all image files in the directory
|
| 1150 |
image_files = []
|
| 1151 |
for ext in SUPPORTED_EXTENSIONS:
|
| 1152 |
+
logger.debug(f"Searching for files with extension: {ext}")
|
| 1153 |
+
# Use glob patterns that are case-insensitive
|
| 1154 |
+
image_files.extend(list(folder_path.glob(f"*{ext.lower()}")))
|
| 1155 |
+
image_files.extend(list(folder_path.glob(f"*{ext.upper()}")))
|
| 1156 |
+
|
| 1157 |
+
logger.info(f"Found {len(image_files)} images in {folder_path}")
|
| 1158 |
|
| 1159 |
if not image_files:
|
| 1160 |
+
error_msg = f"No supported image files found in '{folder_path}'. Supported formats: {', '.join(SUPPORTED_EXTENSIONS)}"
|
| 1161 |
+
logger.warning(error_msg)
|
| 1162 |
+
gui_stats['warnings'] += 1
|
| 1163 |
+
gui_stats['last_warning'] = error_msg
|
| 1164 |
+
gui_stats['last_warning_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1165 |
+
return error_msg
|
| 1166 |
|
| 1167 |
+
# Sort the files for consistent output
|
| 1168 |
image_files.sort()
|
| 1169 |
|
| 1170 |
+
results = []
|
| 1171 |
+
results.append(f"Found {len(image_files)} images in {folder_path}\n")
|
| 1172 |
|
| 1173 |
+
# Load model once for all images
|
| 1174 |
+
logger.info("Loading model for folder analysis")
|
| 1175 |
+
model, tokenizer = load_model()
|
| 1176 |
+
if model is None or tokenizer is None:
|
| 1177 |
+
error_msg = "Error: Could not load model for image analysis"
|
| 1178 |
+
logger.error(error_msg)
|
| 1179 |
+
gui_stats['errors'] += 1
|
| 1180 |
+
gui_stats['last_error'] = error_msg
|
| 1181 |
+
gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1182 |
+
return error_msg
|
| 1183 |
+
|
| 1184 |
+
# Process each image
|
| 1185 |
+
for i, img_path in enumerate(image_files, 1):
|
| 1186 |
+
try:
|
| 1187 |
+
logger.info(f"Processing image {i}/{len(image_files)}: {img_path.name}")
|
| 1188 |
+
|
| 1189 |
+
# Open and process the image
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1190 |
try:
|
| 1191 |
+
image = Image.open(img_path).convert('RGB')
|
| 1192 |
+
logger.debug(f"Image loaded: size={image.size}, mode={image.mode}")
|
| 1193 |
+
except Exception as img_err:
|
| 1194 |
+
error_msg = f"Error opening image {img_path.name}: {str(img_err)}"
|
| 1195 |
+
logger.error(error_msg)
|
| 1196 |
+
logger.error(traceback.format_exc())
|
| 1197 |
+
gui_stats['errors'] += 1
|
| 1198 |
+
gui_stats['last_error'] = error_msg
|
| 1199 |
+
gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1200 |
+
results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\nError opening image: {str(img_err)}\n")
|
| 1201 |
+
continue
|
| 1202 |
+
|
| 1203 |
+
# Process image
|
| 1204 |
+
logger.debug(f"Processing image with prompt: {prompt}")
|
| 1205 |
+
# Use the more reliable process_image_with_text function
|
| 1206 |
+
image_result = process_image_with_text(image, f"Image {i}/{len(image_files)} - {img_path.name}: {prompt}")
|
| 1207 |
+
|
| 1208 |
+
# Add result with separator
|
| 1209 |
+
results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\n{image_result}\n")
|
| 1210 |
+
|
| 1211 |
+
# Log success
|
| 1212 |
+
logger.info(f"Successfully processed image {i}/{len(image_files)}: {img_path.name}")
|
| 1213 |
+
|
| 1214 |
+
except Exception as e:
|
| 1215 |
+
error_msg = f"Error processing image {img_path.name}: {str(e)}"
|
| 1216 |
+
logger.error(error_msg)
|
| 1217 |
+
logger.error(traceback.format_exc())
|
| 1218 |
+
gui_stats['errors'] += 1
|
| 1219 |
+
gui_stats['last_error'] = error_msg
|
| 1220 |
+
gui_stats['last_error_time'] = datetime.datetime.now().strftime("%H:%M:%S")
|
| 1221 |
+
results.append(f"---\nImage {i}/{len(image_files)}: {img_path.name}\nError: {str(e)}\n")
|
| 1222 |
|
| 1223 |
+
combined_result = "\n".join(results)
|
| 1224 |
+
logger.info(f"Folder analysis complete, processed {len(image_files)} images")
|
| 1225 |
+
return combined_result
|
| 1226 |
|
| 1227 |
# Function to process an image with text prompt
|
| 1228 |
def process_image_with_text(image, prompt):
|
|
|
|
| 1769 |
|
| 1770 |
# Tab for folder analysis
|
| 1771 |
with gr.Tab("Folder Analysis"):
|
| 1772 |
+
gr.Markdown("## Analyze all images and PDFs in a folder")
|
| 1773 |
+
gr.Markdown("""
|
| 1774 |
+
Please enter a complete folder path. You can try these options:
|
| 1775 |
+
- Absolute path (e.g., `/home/user/images`)
|
| 1776 |
+
- Relative path from current directory (e.g., `example_images`)
|
| 1777 |
+
- Path with ~ for home directory (e.g., `~/images`)
|
| 1778 |
+
""")
|
| 1779 |
|
| 1780 |
with gr.Row():
|
| 1781 |
+
with gr.Column(scale=4):
|
| 1782 |
+
folder_path = gr.Textbox(
|
| 1783 |
+
label="Folder Path",
|
| 1784 |
+
placeholder="Enter the complete path to the folder containing images",
|
| 1785 |
+
value="example_images" # Default to example folder
|
| 1786 |
+
)
|
| 1787 |
+
with gr.Column(scale=1):
|
| 1788 |
+
example_folders = gr.Dropdown(
|
| 1789 |
+
choices=["example_images", "example_images_2", "/data/images", "images"],
|
| 1790 |
+
label="Example Folders",
|
| 1791 |
+
value="example_images"
|
| 1792 |
+
)
|
| 1793 |
+
|
| 1794 |
+
def set_folder_path(folder):
|
| 1795 |
+
return folder
|
| 1796 |
+
|
| 1797 |
+
example_folders.change(
|
| 1798 |
+
fn=set_folder_path,
|
| 1799 |
+
inputs=[example_folders],
|
| 1800 |
+
outputs=[folder_path]
|
| 1801 |
+
)
|
| 1802 |
|
| 1803 |
prompt_folder = gr.Dropdown(
|
| 1804 |
label="Analysis Prompt",
|
|
|
|
| 1806 |
value=prompts[0],
|
| 1807 |
allow_custom_value=True
|
| 1808 |
)
|
| 1809 |
+
|
| 1810 |
+
# Show folder contents without analyzing
|
| 1811 |
+
view_folder_btn = gr.Button("View Folder Contents")
|
| 1812 |
+
folder_contents = gr.Markdown("Select a folder and click 'View Folder Contents' to see available images")
|
| 1813 |
+
|
| 1814 |
+
def view_folder_contents(folder_path):
|
| 1815 |
+
"""List all image files in the folder without analyzing them."""
|
| 1816 |
+
logger.info(f"Viewing contents of folder: '{folder_path}'")
|
| 1817 |
+
|
| 1818 |
+
if not folder_path or folder_path.strip() == "":
|
| 1819 |
+
return "Please enter a folder path."
|
| 1820 |
+
|
| 1821 |
+
# Clean up the folder path
|
| 1822 |
+
folder_path = folder_path.strip()
|
| 1823 |
+
|
| 1824 |
+
# Try multiple path options
|
| 1825 |
+
potential_paths = [
|
| 1826 |
+
folder_path,
|
| 1827 |
+
os.path.join(os.getcwd(), folder_path),
|
| 1828 |
+
os.path.normpath(folder_path),
|
| 1829 |
+
os.path.abspath(folder_path),
|
| 1830 |
+
os.path.expanduser(folder_path)
|
| 1831 |
+
]
|
| 1832 |
+
|
| 1833 |
+
# If we're in a Hugging Face Space, try the /data path
|
| 1834 |
+
if os.path.exists("/data"):
|
| 1835 |
+
potential_paths.append(os.path.join("/data", folder_path))
|
| 1836 |
+
|
| 1837 |
+
# Try each path
|
| 1838 |
+
valid_path = None
|
| 1839 |
+
for test_path in potential_paths:
|
| 1840 |
+
if os.path.exists(test_path) and os.path.isdir(test_path):
|
| 1841 |
+
valid_path = test_path
|
| 1842 |
+
break
|
| 1843 |
+
|
| 1844 |
+
if not valid_path:
|
| 1845 |
+
return f"Could not find a valid directory at '{folder_path}'.\n\nTried the following paths:\n" + "\n".join(f"- {p}" for p in potential_paths)
|
| 1846 |
+
|
| 1847 |
+
# List image files
|
| 1848 |
+
image_files = []
|
| 1849 |
+
for ext in SUPPORTED_EXTENSIONS:
|
| 1850 |
+
files = glob.glob(os.path.join(valid_path, f"*{ext}"))
|
| 1851 |
+
files.extend(glob.glob(os.path.join(valid_path, f"*{ext.upper()}")))
|
| 1852 |
+
image_files.extend(files)
|
| 1853 |
+
|
| 1854 |
+
# Sort
|
| 1855 |
+
image_files.sort()
|
| 1856 |
+
|
| 1857 |
+
if not image_files:
|
| 1858 |
+
return f"No supported image files found in '{valid_path}'.\n\nSupported formats: {', '.join(SUPPORTED_EXTENSIONS)}"
|
| 1859 |
+
|
| 1860 |
+
# Format as markdown
|
| 1861 |
+
output = f"### Found {len(image_files)} images in '{valid_path}'\n\n"
|
| 1862 |
+
for i, file in enumerate(image_files, 1):
|
| 1863 |
+
file_name = os.path.basename(file)
|
| 1864 |
+
file_size = os.path.getsize(file) / 1024 # KB
|
| 1865 |
+
output += f"{i}. **{file_name}** ({file_size:.1f} KB)\n"
|
| 1866 |
+
|
| 1867 |
+
output += f"\nPath used: `{valid_path}`"
|
| 1868 |
+
return output
|
| 1869 |
+
|
| 1870 |
+
view_folder_btn.click(
|
| 1871 |
+
fn=view_folder_contents,
|
| 1872 |
+
inputs=[folder_path],
|
| 1873 |
+
outputs=[folder_contents]
|
| 1874 |
+
)
|
| 1875 |
+
|
| 1876 |
+
gr.Markdown("---")
|
| 1877 |
+
analyze_btn_folder = gr.Button("Analyze All Images in Folder", variant="primary")
|
| 1878 |
output_folder = gr.Textbox(label="Analysis Result", lines=20)
|
| 1879 |
|
| 1880 |
+
# Status indicator
|
| 1881 |
+
with gr.Row():
|
| 1882 |
+
folder_status = gr.Markdown("Ready to analyze folder images")
|
| 1883 |
+
|
| 1884 |
+
# Define a function to update status while processing
|
| 1885 |
+
def analyze_with_status(folder_path, prompt):
|
| 1886 |
+
folder_status_msg = "Starting folder analysis..."
|
| 1887 |
+
yield folder_status_msg, ""
|
| 1888 |
+
|
| 1889 |
+
try:
|
| 1890 |
+
# Get number of potential images
|
| 1891 |
+
try:
|
| 1892 |
+
folder_path = folder_path.strip()
|
| 1893 |
+
folder_obj = Path(folder_path)
|
| 1894 |
+
if folder_obj.exists() and folder_obj.is_dir():
|
| 1895 |
+
image_count = sum(1 for _ in folder_obj.glob("*.*") if _.suffix.lower() in SUPPORTED_EXTENSIONS)
|
| 1896 |
+
folder_status_msg = f"Found {image_count} images to process. Starting analysis..."
|
| 1897 |
+
yield folder_status_msg, ""
|
| 1898 |
+
except:
|
| 1899 |
+
pass
|
| 1900 |
+
|
| 1901 |
+
# Run analysis
|
| 1902 |
+
folder_status_msg = "Processing images... (this may take several minutes)"
|
| 1903 |
+
yield folder_status_msg, ""
|
| 1904 |
+
|
| 1905 |
+
# Run the actual analysis
|
| 1906 |
+
result = analyze_folder_images(folder_path, prompt)
|
| 1907 |
+
|
| 1908 |
+
folder_status_msg = "Folder analysis complete!"
|
| 1909 |
+
yield folder_status_msg, result
|
| 1910 |
+
except Exception as e:
|
| 1911 |
+
error_msg = f"Error analyzing folder: {str(e)}"
|
| 1912 |
+
folder_status_msg = "Analysis failed! See error message in results."
|
| 1913 |
+
yield folder_status_msg, error_msg
|
| 1914 |
|
| 1915 |
analyze_btn_folder.click(
|
| 1916 |
+
fn=analyze_with_status,
|
| 1917 |
inputs=[folder_path, prompt_folder],
|
| 1918 |
+
outputs=[folder_status, output_folder]
|
| 1919 |
)
|
| 1920 |
|
| 1921 |
+
# Save button for folder analysis
|
| 1922 |
+
with gr.Row():
|
| 1923 |
+
save_btn_folder = gr.Button("Save Results to Text File")
|
| 1924 |
+
save_json_folder = gr.Button("Save Results as JSON")
|
| 1925 |
+
save_html_folder = gr.Button("Save Results as HTML")
|
| 1926 |
+
|
| 1927 |
+
save_status_folder = gr.Textbox(label="Save Status", lines=1)
|
| 1928 |
+
|
| 1929 |
save_btn_folder.click(
|
| 1930 |
fn=lambda text, prompt: save_to_file(text, "folder_analysis", prompt=prompt),
|
| 1931 |
inputs=[output_folder, prompt_folder],
|
| 1932 |
+
outputs=[save_status_folder]
|
| 1933 |
+
)
|
| 1934 |
+
|
| 1935 |
+
save_json_folder.click(
|
| 1936 |
+
fn=lambda content: save_to_json(content, "folder", "Folder analysis", None)[0],
|
| 1937 |
+
inputs=[output_folder],
|
| 1938 |
+
outputs=[save_status_folder]
|
| 1939 |
+
)
|
| 1940 |
+
|
| 1941 |
+
save_html_folder.click(
|
| 1942 |
+
fn=lambda content: save_to_html(content, "folder")[0],
|
| 1943 |
+
inputs=[output_folder],
|
| 1944 |
+
outputs=[save_status_folder]
|
| 1945 |
)
|
| 1946 |
|
| 1947 |
# Add a new tab for logs
|