Skip to content

Commit da1dc35

Browse files
committed
remove empty measure in svg
1 parent d3e0ffb commit da1dc35

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

training/omr_datasets/convert_lieder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ def create_formats(source_file: str, formats: list[str]) -> list[dict[str, str]]
126126
# lc5001945: nested tuplet, not good for training
127127
# lc6209608, lc6236149: empty&invisible staff in the very first system
128128
# lc6162644: irregular staff in the last svg page
129-
files_with_known_issues = ["sq8940236", "lc5001945", "lc6162644", "lc6209608", "lc6236149"]
129+
# lc6420897: page 9, measure 49 and 50 are hard to read
130+
files_with_known_issues = ["sq8940236", "lc5001945", "lc6162644", "lc6209608", "lc6236149", 'lc6420897']
130131
if any(issue in source_file for issue in files_with_known_issues):
131132
return jobs
132133
for target_format in formats:

training/omr_datasets/musescore_svg.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import glob
22
import math
3+
import re
34
from xml.dom import minidom
45

56
from homr import constants
@@ -81,6 +82,9 @@ def add_bar_line(self, bar_line: BarLine, is_first: bool) -> None:
8182
)
8283
if not already_present:
8384
self.bar_line_x_positions.add(bar_line.x)
85+
86+
def remove_bar_line(self, x: int) -> None:
87+
self.bar_line_x_positions.discard(x)
8488

8589
def merge_staff(self, other: "SvgStaff") -> "SvgStaff":
8690
if self.number_of_measures != other.number_of_measures:
@@ -186,6 +190,26 @@ def get_position_from_multiple_svg_files(musicxml_file: str) -> list[SvgMusicFil
186190
return result
187191

188192

193+
def _parse_note_position(note: minidom.Element) -> tuple[float, float]:
194+
# collect note/rest:
195+
# Older MuseScore: <path transform="matrix(a,b,c,d,e,f)"/> where (e, f) is the position.
196+
# Newer MuseScore: <path class="Note" d="M<x>,<y> ..."/> where d is the position.
197+
transform = note.getAttribute("transform")
198+
if transform:
199+
match = re.search(r"matrix\(([^)]*)\)", transform)
200+
assert match, f"Could not parse note transform: {transform}"
201+
values = [float(v) for v in match.group(1).split(",")]
202+
return values[4], values[5]
203+
204+
class_name = note.getAttribute("class")
205+
if class_name in ("Note", "Rest"):
206+
points = note.getAttribute("d")
207+
match = re.search(r"[Mm]\s*(-?[\d.]+)[\s,]+(-?[\d.]+)", points)
208+
assert match, f"Could not parse note position: {points}"
209+
return float(match.group(1)), float(match.group(2))
210+
211+
assert False, f"Unexpected element for note position: {class_name!r}"
212+
189213
def _parse_paths(points: str) -> SvgRectangle:
190214
[start, end] = points.split()
191215
[x1, y1] = start.split(",")
@@ -285,6 +309,47 @@ def _extend_staffs_with_stems(staffs: list[SvgStaff], stems: list[SvgRectangle])
285309
best_staff.extend_y_range(stem.y + stem.height)
286310

287311

312+
def _staff_has_content_between(
313+
staff: SvgStaff, notes: list[tuple[float, float]], start: float, end: float
314+
) -> bool:
315+
# there're 2 hard-coded tolerances to fit some corner cases.
316+
x_tolerance = 5.0
317+
y_tolerance = 60.0
318+
return any(
319+
staff.x <= mx <= staff.x + staff.width
320+
and (staff.y - y_tolerance) <= my <= (staff.y + staff.height + y_tolerance)
321+
and (start - x_tolerance) <= mx < end
322+
for mx, my in notes
323+
)
324+
325+
326+
def _remove_empty_measures(
327+
svg_file: str, staffs: list[SvgStaff], notes: list[tuple[float, float]]
328+
) -> None:
329+
# split `staffs` into several systems.
330+
# each system share the same barline x positions
331+
systems: dict[tuple[int, ...], list[SvgStaff]] = {}
332+
for staff in staffs:
333+
key = tuple(sorted(staff.bar_line_x_positions))
334+
systems.setdefault(key, []).append(staff)
335+
336+
for bar_line_x_positions, staffs_in_system in systems.items():
337+
for i in range(len(bar_line_x_positions) - 1):
338+
start = bar_line_x_positions[i]
339+
end = bar_line_x_positions[i + 1]
340+
has_content = any(
341+
_staff_has_content_between(staff, notes, start, end) for staff in staffs_in_system
342+
)
343+
if has_content:
344+
continue
345+
# Typically we drop the barline at the end, but if
346+
# this is the last measure, we drop barline at begin.
347+
is_trailing = i + 1 == len(bar_line_x_positions) - 1
348+
boundary = start if is_trailing else end
349+
for staff in staffs_in_system:
350+
staff.remove_bar_line(boundary)
351+
352+
288353
def get_position_information_from_svg(svg_file: str) -> SvgMusicFile:
289354
doc = minidom.parse(svg_file) # noqa: S318
290355
try:
@@ -310,11 +375,19 @@ def get_position_information_from_svg(svg_file: str) -> SvgMusicFile:
310375
if class_name == "Stem":
311376
stems.append(_parse_paths(line.getAttribute("points")))
312377

378+
notes: list[tuple[float, float]] = []
379+
for path in doc.getElementsByTagName("path"):
380+
class_name = path.getAttribute("class")
381+
if class_name in ("Note", "Rest"):
382+
notes.append(_parse_note_position(path))
383+
313384
combined = _combine_staff_lines_and_bar_lines(staff_lines, bar_lines)
314385

315386
# Extend staffs using stem information
316387
_extend_staffs_with_stems(combined, stems)
317388

389+
_remove_empty_measures(svg_file, combined, notes)
390+
318391
return SvgMusicFile(svg_file, width, height, combined)
319392
finally:
320393
doc.unlink()

0 commit comments

Comments
 (0)