Skip to content

Commit 3e78106

Browse files
committed
remove empty measure in svg
1 parent 6124e4b commit 3e78106

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

training/omr_datasets/convert_lieder.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,18 @@ 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+
# lc6196804: lc6196804-3-4.tokens has a strange `arpeggiate_breathMark`,
131+
# which will cause error in training, skip for now
132+
files_with_known_issues = [
133+
"sq8940236",
134+
"lc5001945",
135+
"lc6162644",
136+
"lc6209608",
137+
"lc6236149",
138+
"lc6420897",
139+
"lc6196804",
140+
]
130141
if any(issue in source_file for issue in files_with_known_issues):
131142
return jobs
132143
for target_format in formats:

training/omr_datasets/musescore_svg.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
# flake8: noqa: S101, B011
2+
13
import glob
24
import math
5+
import re
36
from xml.dom import minidom
47

58
from homr import constants
@@ -82,6 +85,9 @@ def add_bar_line(self, bar_line: BarLine, is_first: bool) -> None:
8285
if not already_present:
8386
self.bar_line_x_positions.add(bar_line.x)
8487

88+
def remove_bar_line(self, x: int) -> None:
89+
self.bar_line_x_positions.discard(x)
90+
8591
def merge_staff(self, other: "SvgStaff") -> "SvgStaff":
8692
if self.number_of_measures != other.number_of_measures:
8793
raise ValueError("Can't merge staffs with a different number of measures")
@@ -186,6 +192,27 @@ def get_position_from_multiple_svg_files(musicxml_file: str) -> list[SvgMusicFil
186192
return result
187193

188194

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

287314

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

381+
notes: list[tuple[float, float]] = []
382+
for path in doc.getElementsByTagName("path"):
383+
class_name = path.getAttribute("class")
384+
if class_name in ("Note", "Rest"):
385+
notes.append(_parse_note_position(path))
386+
313387
combined = _combine_staff_lines_and_bar_lines(staff_lines, bar_lines)
314388

315389
# Extend staffs using stem information
316390
_extend_staffs_with_stems(combined, stems)
317391

392+
_remove_empty_measures(svg_file, combined, notes)
393+
318394
return SvgMusicFile(svg_file, width, height, combined)
319395
finally:
320396
doc.unlink()

0 commit comments

Comments
 (0)