Kesheratmex commited on
Commit
af05f8c
·
1 Parent(s): de13b31

Add textwrap import to support multiline string handling

Browse files
Files changed (1) hide show
  1. app.py +101 -36
app.py CHANGED
@@ -8,6 +8,7 @@ import numpy as np
8
  import torch
9
  import importlib
10
  import requests
 
11
 
12
  # Optional PDF reporting: import reportlab safely and set a flag.
13
  # REPORTLAB_AVAILABLE will be used by _write_pdf to select the PDF code path.
@@ -250,73 +251,137 @@ def _write_pdf(path: str, title: str, narrative: str, frames):
250
  if REPORTLAB_AVAILABLE:
251
  c = canvas.Canvas(path, pagesize=A4)
252
  width, height = A4
253
- margin = 40
 
254
  y = height - margin
255
- c.setFont("Helvetica-Bold", 16)
256
- c.drawString(margin, y, title)
257
- y -= 30
258
- c.setFont("Helvetica", 11)
259
- for line in (narrative or "").splitlines():
260
- if y < margin + 50:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  c.showPage()
262
  y = height - margin
263
- c.setFont("Helvetica", 11)
264
  c.drawString(margin, y, line)
265
- y -= 16
266
- y -= 10
 
267
  c.setFont("Helvetica-Bold", 12)
 
 
 
 
268
  c.drawString(margin, y, "Per-frame detections:")
269
- y -= 18
270
- c.setFont("Helvetica", 10)
271
 
272
  # Iterate frames and list detections; include cropped images where available.
273
  for f in frames:
274
- if y < margin + 120:
275
  c.showPage()
276
  y = height - margin
277
- c.setFont("Helvetica", 10)
278
  c.drawString(margin, y, f"Frame {f.get('frame_index')}:")
279
- y -= 14
280
  dets = f.get("detections", [])
281
  if not dets:
 
 
 
 
282
  c.drawString(margin + 12, y, "No detections")
283
- y -= 12
284
  else:
285
  for d in dets:
286
- line = f"- {d.get('label')} | conf={d.get('confidence')} | bbox={d.get('bbox')}"
287
- c.drawString(margin + 12, y, line)
288
- y -= 12
289
-
290
- # If there is a cropped image for this detection, embed it in the PDF.
 
 
 
 
 
 
 
291
  try:
292
  img_path = d.get("image")
293
  if img_path and os.path.exists(img_path):
294
- # Desired image size in PDF points
295
- img_w = 140
296
- img_h = 80
297
- # If not enough vertical space, start a new page
298
  if y < margin + img_h + 20:
299
  c.showPage()
300
  y = height - margin
301
- c.setFont("Helvetica", 10)
302
- # Draw image at the right side of the page
303
  x_img = width - margin - img_w
304
  y_img = y - img_h + 6
305
  c.drawImage(img_path, x_img, y_img, width=img_w, height=img_h, preserveAspectRatio=True, mask='auto')
306
- # Optionally add a short caption / crop description under the image if available
307
  crop_desc = None
308
  if isinstance(d.get("crop_visual"), dict):
309
  crop_desc = d["crop_visual"].get("description")
310
  if crop_desc:
311
- # Split long lines
312
- for line_part in (crop_desc or "").splitlines():
313
- if y_img - 12 < margin:
 
314
  c.showPage()
315
- y_img = height - margin - img_h - 12
316
- c.setFont("Helvetica", 10)
317
- c.drawString(x_img, y_img - 12, line_part[:200])
318
- y_img -= 12
319
- # Move vertical cursor below the image area
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  y = y - img_h - 10
321
  except Exception:
322
  # If embedding fails, continue without stopping report generation
 
8
  import torch
9
  import importlib
10
  import requests
11
+ import textwrap
12
 
13
  # Optional PDF reporting: import reportlab safely and set a flag.
14
  # REPORTLAB_AVAILABLE will be used by _write_pdf to select the PDF code path.
 
251
  if REPORTLAB_AVAILABLE:
252
  c = canvas.Canvas(path, pagesize=A4)
253
  width, height = A4
254
+ # Increase margins and reduce font sizes to fit more content per page
255
+ margin = 50
256
  y = height - margin
257
+
258
+ # Fonts and sizes
259
+ title_font = "Helvetica-Bold"
260
+ body_font = "Helvetica"
261
+ small_font = "Helvetica"
262
+ title_size = 14
263
+ body_size = 10
264
+ small_size = 9
265
+ line_height = body_size * 1.15
266
+ small_line_height = small_size * 1.12
267
+
268
+ # Helper: simple wrapper using textwrap with estimated chars per line
269
+ def wrap_text(text, font_size, max_width):
270
+ approx_char_width = font_size * 0.55
271
+ max_chars = max(40, int(max_width / approx_char_width))
272
+ out = []
273
+ for para in str(text or "").splitlines():
274
+ wrapped = textwrap.wrap(para, width=max_chars)
275
+ out.extend(wrapped if wrapped else [""])
276
+ return out
277
+
278
+ # Title
279
+ c.setFont(title_font, title_size)
280
+ for tline in wrap_text(title, title_size, width - 2 * margin):
281
+ if y < margin + title_size * 1.5:
282
+ c.showPage()
283
+ y = height - margin
284
+ c.setFont(title_font, title_size)
285
+ c.drawString(margin, y, tline)
286
+ y -= title_size * 1.3
287
+
288
+ y -= 6
289
+ # Narrative
290
+ c.setFont(body_font, body_size)
291
+ for line in wrap_text(narrative or "", body_size, width - 2 * margin):
292
+ if y < margin + line_height:
293
  c.showPage()
294
  y = height - margin
295
+ c.setFont(body_font, body_size)
296
  c.drawString(margin, y, line)
297
+ y -= line_height
298
+
299
+ y -= 8
300
  c.setFont("Helvetica-Bold", 12)
301
+ if y < margin + 30:
302
+ c.showPage()
303
+ y = height - margin
304
+ c.setFont("Helvetica-Bold", 12)
305
  c.drawString(margin, y, "Per-frame detections:")
306
+ y -= 14
307
+ c.setFont(small_font, small_size)
308
 
309
  # Iterate frames and list detections; include cropped images where available.
310
  for f in frames:
311
+ if y < margin + 100:
312
  c.showPage()
313
  y = height - margin
314
+ c.setFont(small_font, small_size)
315
  c.drawString(margin, y, f"Frame {f.get('frame_index')}:")
316
+ y -= small_line_height
317
  dets = f.get("detections", [])
318
  if not dets:
319
+ if y < margin + small_line_height:
320
+ c.showPage()
321
+ y = height - margin
322
+ c.setFont(small_font, small_size)
323
  c.drawString(margin + 12, y, "No detections")
324
+ y -= small_line_height
325
  else:
326
  for d in dets:
327
+ det_text = f"- {d.get('label')} | conf={d.get('confidence')} | bbox={d.get('bbox')}"
328
+ # Wrap det_text leaving room for images (if present)
329
+ text_max_width = width - 2 * margin - 150
330
+ for dl in wrap_text(det_text, small_size, text_max_width):
331
+ if y < margin + small_line_height:
332
+ c.showPage()
333
+ y = height - margin
334
+ c.setFont(small_font, small_size)
335
+ c.drawString(margin + 12, y, dl)
336
+ y -= small_line_height
337
+
338
+ # If there is a cropped image for this detection, embed it in the PDF (smaller)
339
  try:
340
  img_path = d.get("image")
341
  if img_path and os.path.exists(img_path):
342
+ img_w = 120
343
+ img_h = 70
 
 
344
  if y < margin + img_h + 20:
345
  c.showPage()
346
  y = height - margin
347
+ c.setFont(small_font, small_size)
 
348
  x_img = width - margin - img_w
349
  y_img = y - img_h + 6
350
  c.drawImage(img_path, x_img, y_img, width=img_w, height=img_h, preserveAspectRatio=True, mask='auto')
351
+ # Crop visual description under image
352
  crop_desc = None
353
  if isinstance(d.get("crop_visual"), dict):
354
  crop_desc = d["crop_visual"].get("description")
355
  if crop_desc:
356
+ cd_lines = wrap_text(crop_desc, small_size, img_w)
357
+ text_y = y_img - 12
358
+ for cd in cd_lines:
359
+ if text_y < margin + 20:
360
  c.showPage()
361
+ y = height - margin
362
+ text_y = y - img_h - 12
363
+ c.setFont(small_font, small_size)
364
+ c.drawString(x_img, text_y, cd)
365
+ text_y -= small_line_height
366
+ # Move cursor below image
367
+ y = y - img_h - 10
368
+ except Exception:
369
+ pass
370
+ c.save()
371
+ else:
372
+ # Fallback: plain text file (saved with .pdf extension)
373
+ with open(path, "w", encoding="utf-8") as f:
374
+ f.write(title + "\n\n")
375
+ f.write((narrative or "") + "\n\n")
376
+ f.write("Per-frame detections:\n")
377
+ for fr in frames:
378
+ f.write(f"Frame {fr.get('frame_index')}:\n")
379
+ dets = fr.get("detections", [])
380
+ if not dets:
381
+ f.write(" No detections\n")
382
+ else:
383
+ for d in dets:
384
+ f.write(f" - {d}\n")
385
  y = y - img_h - 10
386
  except Exception:
387
  # If embedding fails, continue without stopping report generation