jon-fernandes commited on
Commit
90f7e3f
·
verified ·
1 Parent(s): d8445b2

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +25 -138
app.py CHANGED
@@ -1,11 +1,10 @@
1
  import os
2
  import base64
3
  import inspect
4
- import tempfile
5
  from queue import Queue
6
  import threading
7
  import torch
8
- from PIL import Image, ImageOps
9
  from transformers import AutoProcessor, MiniCPMV4_6ForConditionalGeneration
10
  import gradio as gr
11
 
@@ -22,7 +21,6 @@ GPT_FALLBACK_MODEL_ID = "gpt-5.4-mini"
22
  GPT_MAX_COMPLETION_TOKENS = 4096
23
  GPT_REASONING_EFFORT = "none"
24
  NOTES_PROMPT = "Transcribe the musical notes in this image. Return only the transcription."
25
- MAX_PREPROCESSED_SIDE = 1800
26
 
27
  CAMERA_CAPTURE_JS = """
28
  function () {
@@ -100,86 +98,6 @@ def supports_keyword(callable_obj, keyword):
100
  return keyword in signature.parameters
101
 
102
 
103
- def _ensure_horizontal_music(image: Image.Image) -> Image.Image:
104
- width, height = image.size
105
- if height > width:
106
- return image.rotate(90, expand=True)
107
- return image
108
-
109
-
110
- def _score_training_orientation(image: Image.Image):
111
- try:
112
- import cv2
113
- import numpy as np
114
- except ImportError:
115
- width, height = image.size
116
- return width / max(height, 1)
117
-
118
- gray = np.array(image.convert("L"))
119
- height, width = gray.shape[:2]
120
- if width == 0 or height == 0:
121
- return -1
122
-
123
- # Normalize out uneven camera lighting before comparing densities.
124
- blur_size = min(max(min(width, height) // 6, 15) | 1, 201)
125
- background = cv2.medianBlur(gray, blur_size)
126
- normalized = cv2.divide(gray, background, scale=255)
127
- ink = normalized < 128
128
-
129
- band = max(width // 5, 1)
130
- left_density = float(ink[:, :band].mean())
131
- right_density = float(ink[:, -band:].mean())
132
- # Treble clef + key sig always sit on the left, making that band denser.
133
- return (width / max(height, 1)) * 2.0 + (left_density - right_density) * 15.0
134
-
135
-
136
- def _normalize_to_training_orientation(image: Image.Image):
137
- candidates = []
138
- for rotation in (0, 90, 180, 270):
139
- candidate = image.rotate(rotation, expand=True) if rotation else image.copy()
140
- candidate = _ensure_horizontal_music(candidate)
141
- score = _score_training_orientation(candidate)
142
- candidates.append((score, rotation, candidate))
143
-
144
- score, rotation, candidate = max(candidates, key=lambda item: item[0])
145
- return candidate, rotation, score
146
-
147
-
148
- def _limit_image_size(image: Image.Image) -> Image.Image:
149
- width, height = image.size
150
- max_side = max(width, height)
151
- if max_side <= MAX_PREPROCESSED_SIDE:
152
- return image
153
-
154
- scale = MAX_PREPROCESSED_SIDE / max_side
155
- return image.resize(
156
- (int(width * scale), int(height * scale)),
157
- Image.Resampling.LANCZOS,
158
- )
159
-
160
-
161
- def preprocess_sheet_music_image(image_path):
162
- original = ImageOps.exif_transpose(Image.open(image_path)).convert("RGB")
163
- original_size = original.size
164
- image, rotation, orientation_score = _normalize_to_training_orientation(original)
165
- image = _limit_image_size(image)
166
- processed_size = image.size
167
-
168
- temp_file = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
169
- temp_path = temp_file.name
170
- temp_file.close()
171
- image.save(temp_path, format="PNG", optimize=True)
172
- changed = processed_size != original_size
173
- report = (
174
- f"Preprocessing complete. Original: {original_size[0]}x{original_size[1]}; "
175
- f"sent to models: {processed_size[0]}x{processed_size[1]}; "
176
- f"selected rotation: {rotation} degrees; "
177
- f"orientation score: {orientation_score:.2f}; "
178
- f"changed: {'yes' if changed else 'no'}. "
179
- "Treble clef/key-signature content is expected at the top-left of the preview."
180
- )
181
- return image, temp_path, report
182
-
183
  print("Loading processor...")
184
  processor = AutoProcessor.from_pretrained(ORIGINAL_MODEL_ID, trust_remote_code=True)
185
 
@@ -343,26 +261,20 @@ def warmup_models():
343
  return
344
 
345
  print("Warming up local models...")
346
- image, processed_image_path, _ = preprocess_sheet_music_image(warmup_path)
347
- try:
348
- for name, model in (
349
- ("fine-tuned", finetuned_model),
350
- ("original", original_model),
351
- ):
352
- if model is None:
353
- print(f" Skipping {name} warmup; model failed to load.")
354
- continue
355
- print(f" Warming {name} model...")
356
- try:
357
- generate_model_text(model, image, max_new_tokens=8)
358
- except Exception as e:
359
- print(f" Warmup failed for {name} model: {type(e).__name__}: {e}")
360
- print("Model warmup complete.")
361
- finally:
362
  try:
363
- os.unlink(processed_image_path)
364
- except OSError:
365
- pass
 
366
 
367
 
368
  warmup_models()
@@ -483,18 +395,18 @@ def _run_stream(index, stream, updates):
483
  def predict_all(image_path):
484
  if image_path is None:
485
  message = "Please upload an image."
486
- yield None, "", message, message, message
487
  return
488
 
489
- image, processed_image_path, preprocessing_report = preprocess_sheet_music_image(image_path)
490
  updates = Queue()
491
  outputs = ["", "", ""]
492
- yield image, preprocessing_report, outputs[0], outputs[1], outputs[2]
493
 
494
  streams = [
495
  stream_model_text(finetuned_model, image.copy(), "fine-tuned"),
496
  stream_model_text(original_model, image.copy(), "original"),
497
- stream_gpt_text(processed_image_path),
498
  ]
499
 
500
  threads = [
@@ -517,16 +429,10 @@ def predict_all(image_path):
517
  continue
518
 
519
  outputs[index] = text
520
- yield (image, preprocessing_report, *outputs)
521
 
522
- try:
523
- for thread in threads:
524
- thread.join()
525
- finally:
526
- try:
527
- os.unlink(processed_image_path)
528
- except OSError:
529
- pass
530
 
531
 
532
  if HAS_SPACES:
@@ -550,30 +456,17 @@ with gr.Blocks(**blocks_kwargs) as demo:
550
  """
551
  )
552
 
553
- gr.Markdown(
554
- "Camera: allow camera access, use the rear camera, frame the page normally with the top-left of the music at the top-left of the preview, then tap or click the preview to capture."
555
- )
556
  image_input = gr.Image(
557
  type="filepath",
558
- label="Sheet Music Image - capture the page in normal reading orientation",
559
  sources=["webcam", "upload", "clipboard"],
560
  webcam_options=gr.WebcamOptions(
561
  mirror=False,
562
- constraints={"facingMode": {"ideal": "environment"}},
563
  ),
564
- placeholder="Use Camera to frame the sheet music, then tap or click the preview.",
565
  elem_id="sheet-music-input",
566
  )
567
- processed_image_output = gr.Image(
568
- type="pil",
569
- label="Preprocessed Image Sent to All Models",
570
- interactive=False,
571
- )
572
- preprocessing_report_output = gr.Textbox(
573
- label="Preprocessing Confirmation",
574
- interactive=False,
575
- lines=2,
576
- )
577
  gr.Examples(
578
  examples=[
579
  ["examples/000100005-1_1_1.png"],
@@ -601,13 +494,7 @@ with gr.Blocks(**blocks_kwargs) as demo:
601
  notes_btn.click(
602
  fn=predict_all,
603
  inputs=[image_input],
604
- outputs=[
605
- processed_image_output,
606
- preprocessing_report_output,
607
- finetuned_output,
608
- original_output,
609
- gpt_output,
610
- ],
611
  )
612
 
613
  launch_kwargs = {
 
1
  import os
2
  import base64
3
  import inspect
 
4
  from queue import Queue
5
  import threading
6
  import torch
7
+ from PIL import Image
8
  from transformers import AutoProcessor, MiniCPMV4_6ForConditionalGeneration
9
  import gradio as gr
10
 
 
21
  GPT_MAX_COMPLETION_TOKENS = 4096
22
  GPT_REASONING_EFFORT = "none"
23
  NOTES_PROMPT = "Transcribe the musical notes in this image. Return only the transcription."
 
24
 
25
  CAMERA_CAPTURE_JS = """
26
  function () {
 
98
  return keyword in signature.parameters
99
 
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  print("Loading processor...")
102
  processor = AutoProcessor.from_pretrained(ORIGINAL_MODEL_ID, trust_remote_code=True)
103
 
 
261
  return
262
 
263
  print("Warming up local models...")
264
+ image = Image.open(warmup_path).convert("RGB")
265
+ for name, model in (
266
+ ("fine-tuned", finetuned_model),
267
+ ("original", original_model),
268
+ ):
269
+ if model is None:
270
+ print(f" Skipping {name} warmup; model failed to load.")
271
+ continue
272
+ print(f" Warming {name} model...")
 
 
 
 
 
 
 
273
  try:
274
+ generate_model_text(model, image, max_new_tokens=8)
275
+ except Exception as e:
276
+ print(f" Warmup failed for {name} model: {type(e).__name__}: {e}")
277
+ print("Model warmup complete.")
278
 
279
 
280
  warmup_models()
 
395
  def predict_all(image_path):
396
  if image_path is None:
397
  message = "Please upload an image."
398
+ yield message, message, message
399
  return
400
 
401
+ image = Image.open(image_path).convert("RGB")
402
  updates = Queue()
403
  outputs = ["", "", ""]
404
+ yield outputs[0], outputs[1], outputs[2]
405
 
406
  streams = [
407
  stream_model_text(finetuned_model, image.copy(), "fine-tuned"),
408
  stream_model_text(original_model, image.copy(), "original"),
409
+ stream_gpt_text(image_path),
410
  ]
411
 
412
  threads = [
 
429
  continue
430
 
431
  outputs[index] = text
432
+ yield (*outputs,)
433
 
434
+ for thread in threads:
435
+ thread.join()
 
 
 
 
 
 
436
 
437
 
438
  if HAS_SPACES:
 
456
  """
457
  )
458
 
 
 
 
459
  image_input = gr.Image(
460
  type="filepath",
461
+ label="Sheet Music Image",
462
  sources=["webcam", "upload", "clipboard"],
463
  webcam_options=gr.WebcamOptions(
464
  mirror=False,
465
+ constraints={"facingMode": "environment"},
466
  ),
467
+ placeholder="Use the camera to capture the sheet music, then click Transcribe Music.",
468
  elem_id="sheet-music-input",
469
  )
 
 
 
 
 
 
 
 
 
 
470
  gr.Examples(
471
  examples=[
472
  ["examples/000100005-1_1_1.png"],
 
494
  notes_btn.click(
495
  fn=predict_all,
496
  inputs=[image_input],
497
+ outputs=[finetuned_output, original_output, gpt_output],
 
 
 
 
 
 
498
  )
499
 
500
  launch_kwargs = {