Skip to content

Commit 8bbd875

Browse files
authored
Merge pull request #1279 from microsoft/paullizer-content-understanding-extraction
Render EMF/WMF diagrams so Office figures are analyzed
2 parents 133e1bc + dbe9cfa commit 8bbd875

8 files changed

Lines changed: 1292 additions & 34 deletions

application/single_app/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
EXECUTOR_TYPE = 'thread'
9797
EXECUTOR_MAX_WORKERS = 30
9898
SESSION_TYPE = 'filesystem'
99-
VERSION = "0.250.222"
99+
VERSION = "0.250.223"
100100
IS_DEVELOPMENT = is_development_env_enabled()
101101

102102
SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')

application/single_app/functions_documents.py

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
)
2727
from functions_visio import build_visio_page_markdown, parse_vsdx_pages
2828
from functions_content import *
29+
from functions_office_media import extract_office_embedded_images_with_diagnostics
2930
from functions_content_understanding import analyze_image_with_content_understanding
3031
from functions_settings import *
3132
from functions_search import *
@@ -311,6 +312,29 @@ def _analyze_single_embedded_image(image_path, extraction_engine, image_extracti
311312
).strip()
312313

313314

315+
def _describe_embedded_image_skips(diagnostics):
316+
"""Render skip counts as a short, admin-readable explanation."""
317+
reasons = diagnostics.get('skipped_reasons') or {}
318+
if not reasons:
319+
return ''
320+
friendly = {
321+
'below_minimum_pixels': 'too small',
322+
'below_minimum_bytes': 'too small',
323+
'duplicate_image': 'duplicates',
324+
'unsupported_format': 'unsupported format',
325+
'unreadable_image': 'unreadable',
326+
'unreadable_or_oversized': 'unreadable or oversized',
327+
'per_document_cap_reached': 'over the per-document cap',
328+
'write_failed': 'could not be written',
329+
'unsafe_entry_name': 'unsafe file name',
330+
}
331+
parts = []
332+
for reason, count in sorted(reasons.items(), key=lambda item: -item[1]):
333+
label = friendly.get(reason, reason.replace('_', ' '))
334+
parts.append(f"{count} {label}")
335+
return ", ".join(parts)
336+
337+
314338
def _build_office_embedded_image_chunks(
315339
file_path,
316340
settings,
@@ -347,14 +371,39 @@ def _build_office_embedded_image_chunks(
347371

348372
try:
349373
temp_image_dir = tempfile.mkdtemp(prefix='office_images_')
350-
embedded_images = extract_office_embedded_images(
374+
embedded_images, image_diagnostics = extract_office_embedded_images_with_diagnostics(
351375
file_path,
352376
temp_image_dir,
353377
min_pixels=min_pixels,
354378
max_images=max_images,
355379
)
356380

381+
candidate_count = image_diagnostics.get('candidates', 0)
357382
if not embedded_images:
383+
# Say so explicitly. Otherwise "no images in this file" and "images found but all
384+
# skipped" look identical in the workspace log, which is the common confusion.
385+
if candidate_count:
386+
skip_summary = _describe_embedded_image_skips(image_diagnostics)
387+
update_callback(
388+
status=(
389+
f"Found {candidate_count} embedded image(s), none analyzable"
390+
+ (f" ({skip_summary})" if skip_summary else "")
391+
),
392+
office_embedded_image_count=0,
393+
office_embedded_image_candidates=candidate_count,
394+
office_embedded_image_skipped=image_diagnostics.get('skipped', 0),
395+
)
396+
log_event(
397+
f"[OFFICE_EMBEDDED_IMAGES] {os.path.basename(file_path)}: "
398+
f"{candidate_count} candidate(s), none analyzable. {image_diagnostics}",
399+
level=logging.WARNING,
400+
)
401+
else:
402+
update_callback(
403+
status="No embedded images found in this document",
404+
office_embedded_image_count=0,
405+
office_embedded_image_candidates=0,
406+
)
358407
return [], 0, extraction_engine
359408

360409
engine_label = (
@@ -364,10 +413,13 @@ def _build_office_embedded_image_chunks(
364413
)
365414
total_images = len(embedded_images)
366415
update_callback(
367-
status=f"Analyzing {total_images} embedded image(s) with {engine_label}..."
416+
status=f"Analyzing {total_images} of {candidate_count} embedded image(s) with {engine_label}..."
368417
)
369418

370419
for image_index, embedded_image in enumerate(embedded_images, start=1):
420+
update_callback(
421+
status=f"Analyzing embedded image {image_index} of {total_images} with {engine_label}..."
422+
)
371423
try:
372424
analysis_text = _analyze_single_embedded_image(
373425
embedded_image['path'],
@@ -380,9 +432,18 @@ def _build_office_embedded_image_chunks(
380432
f"[OFFICE_EMBEDDED_IMAGES] Failed to analyze {embedded_image.get('name')}: {image_error}",
381433
level=logging.WARNING,
382434
)
383-
continue
384-
385-
if not analysis_text:
435+
analysis_text = ''
436+
437+
# Text drawn inside a vector diagram is recovered during rasterization, so a figure
438+
# still contributes searchable labels even when the engine returns nothing.
439+
embedded_text = str(embedded_image.get('embedded_text') or '').strip()
440+
body_parts = []
441+
if analysis_text:
442+
body_parts.append(analysis_text)
443+
if embedded_text and embedded_text not in analysis_text:
444+
body_parts.append(f"Text labels in this figure:\n{embedded_text}")
445+
446+
if not body_parts:
386447
continue
387448

388449
location_label = ''
@@ -396,13 +457,19 @@ def _build_office_embedded_image_chunks(
396457
)
397458
chunks.append({
398459
'page_number': starting_page_number + len(chunks),
399-
'content': f"{heading}\n\n{analysis_text}",
460+
'content': f"{heading}\n\n" + "\n\n".join(body_parts),
400461
})
401462
analyzed_count += 1
402463

403464
if analyzed_count:
465+
skip_summary = _describe_embedded_image_skips(image_diagnostics)
404466
update_callback(
405-
status=f"Analyzed {analyzed_count} embedded image(s) with {engine_label}."
467+
status=(
468+
f"Analyzed {analyzed_count} of {candidate_count} embedded image(s) with {engine_label}."
469+
+ (f" Skipped: {skip_summary}." if skip_summary else "")
470+
),
471+
office_embedded_image_candidates=candidate_count,
472+
office_embedded_image_skipped=image_diagnostics.get('skipped', 0),
406473
)
407474
except Exception as embedded_image_error:
408475
log_event(

0 commit comments

Comments
 (0)