From 498b02d20fb2e4d7f0021a2401ddf4c5fa30b45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 31 Oct 2022 09:52:21 +0100 Subject: [PATCH 1/7] add concatenate_images to solve issue #171 --- partitura/io/musescore.py | 53 +++++++++++++++----- partitura/utils/misc.py | 100 +++++++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 13 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index f5b2eb76..baf08598 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -7,6 +7,7 @@ import platform import warnings +import glob import os import shutil import subprocess @@ -16,12 +17,14 @@ from partitura.io.importmusicxml import load_musicxml from partitura.io.exportmusicxml import save_musicxml -from partitura.score import Score +from partitura.score import Score, ScoreLike from partitura.utils.misc import ( deprecated_alias, deprecated_parameter, PathLike, + concatenate_images, + PIL_EXISTS, ) @@ -137,16 +140,23 @@ def load_via_musescore( ) -def render_musescore(part, fmt, out_fn=None, dpi=90): - """Render a part using musescore. +@deprecated_alias(out_fn="out", part="score_data") +def render_musescore( + score_data: ScoreLike, + fmt: str, + out: Optional[PathLike] = None, + dpi: Optional[int] = 90, +) -> Optional[PathLike]: + """ + Render a part using musescore. Parameters ---------- - part : Part + score_data : ScoreLike Part to be rendered fmt : {'png', 'pdf'} Output image format - out_fn : str or None, optional + out : str or None, optional The path of the image output file, if not specified, the rendering will be saved to a temporary filename. Defaults to None. @@ -154,6 +164,10 @@ def render_musescore(part, fmt, out_fn=None, dpi=90): Image resolution. This option is ignored when `fmt` is 'pdf'. Defaults to 90. + Returns + ------- + out : Optional[PathLike] + Path to the output generated image (or None if no image was generated) """ mscore_exec = find_musescore3() @@ -172,14 +186,14 @@ def render_musescore(part, fmt, out_fn=None, dpi=90): xml_fh = Path(tmpdir) / "score.musicxml" img_fh = Path(tmpdir) / f"score.{fmt}" - save_musicxml(part, xml_fh) + save_musicxml(score_data, xml_fh) cmd = [ mscore_exec, "-T", "10", "-r", - "{}".format(dpi), + "{}".format(int(dpi)), "-o", os.fspath(img_fh), os.fspath(xml_fh), @@ -215,12 +229,27 @@ def render_musescore(part, fmt, out_fn=None, dpi=90): # ps.stderr.decode('UTF-8'))) if fmt == "png": - img_fh = (img_fh.parent / (img_fh.stem + "-1")).with_suffix(img_fh.suffix) + + if PIL_EXISTS: + # get all generated image files + img_files = glob.glob( + os.path.join(img_fh.parent, img_fh.stem + "-*.png") + ) + concatenate_images( + filenames=img_files, + out=img_fh, + concat_mode="vertical", + ) + else: + # The first image seems to be blank (MuseScore adds an empy page) + img_fh = (img_fh.parent / (img_fh.stem + "-2")).with_suffix( + img_fh.suffix + ) if img_fh.is_file(): - if out_fn is None: - out_fn = os.path.join(gettempdir(), "partitura_render_tmp.png") - shutil.copy(img_fh, out_fn) - return out_fn + if out is None: + out = os.path.join(gettempdir(), "partitura_render_tmp.png") + shutil.copy(img_fh, out) + return out return None diff --git a/partitura/utils/misc.py b/partitura/utils/misc.py index da02564d..262d8b15 100644 --- a/partitura/utils/misc.py +++ b/partitura/utils/misc.py @@ -7,7 +7,18 @@ import os import warnings -from typing import Union, Callable, Dict, Any, Iterable +from typing import Union, Callable, Dict, Any, Iterable, Optional + +import numpy as np + +try: + from PIL import Image + from PIL.ImageFile import ImageFile + PIL_EXISTS = True +except ImportError: + Image = None + ImageFile = Any + PIL_EXISTS = False # Recommended by PEP 519 PathLike = Union[str, bytes, os.PathLike] @@ -153,3 +164,90 @@ def to_be_deprecated( ) # Remove deprecated kwarg from kwargs kwargs.pop(deprecated_kwarg) + + +def concatenate_images( + filenames: Iterable[PathLike], + out: Optional[PathLike] = None, + concat_mode: str = "vertical", +) -> Optional[ImageFile]: + """ + Concatenate Images + + Parameters + ---------- + filenames: Iterable[PathLike] + A list of images to be concatenated. + out : Optional[PathLike] + The output file where the image will be saved. + concat_mode : {"vertical", "horizontal"} + Whether to concatenate the images vertically or horizontally. + Default is vertical. + + Returns + ------- + new_image : Optional[PIL.Image.Image] + The output image. This is only returned if `out` is not None. + + Notes + ----- + + If the Pillow library is not installed, this method will return None. + """ + if not PIL_EXISTS: + warnings.warn( + message=( + "The pillow library was not found. This method " + "will just return None. (You can install it with " + "`pip install pillow`)." + ), + category=ImportWarning, + ) + return None + # Check that concat mode is vertical or horizontal + if concat_mode not in ("vertical", "horizontal"): + raise ValueError( + f"`concat_mode` should be 'vertical' or 'horizontal' but is {concat_mode}" + ) + + # Load images + images = [Image.open(fn) for fn in filenames] + + # Get image sizes + image_sizes = np.array([img.size for img in images], dtype=int) + + # size of the output image according to the concatenation mode + if concat_mode == "vertical": + output_size = (image_sizes[:, 0].max(), image_sizes[:, 1].sum()) + elif concat_mode == "horizontal": + output_size = (image_sizes[:, 0].sum(), image_sizes[:, 1].max()) + + # Color mode (assume it is the same for all images) + mode = images[0].mode + + # DPI (assume that it is the same for all images) + info = images[0].info + + # Initialize new image + new_image = Image.new(mode=mode, size=output_size, color=0) + + # coordinates to place the image + anchor_x = 0 + anchor_y = 0 + for img, size in zip(images, image_sizes): + + new_image.paste(img, (anchor_x, anchor_y)) + + # update coordinates according to the concatenation mode + if concat_mode == "vertical": + anchor_y += size[1] + + elif concat_mode == "horizontal": + anchor_x += size[0] + + # save image file + if out is not None: + new_image.save(out, **info) + + else: + return new_image From 963b2188949f978e2ed3bf316b5706959ed520ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 31 Oct 2022 10:47:45 +0100 Subject: [PATCH 2/7] add test for concatenate images --- partitura/utils/misc.py | 7 +++- tests/__init__.py | 4 ++ tests/data/png/example_score.png | Bin 0 -> 4432 bytes tests/test_display.py | 68 +++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 tests/data/png/example_score.png create mode 100644 tests/test_display.py diff --git a/partitura/utils/misc.py b/partitura/utils/misc.py index 262d8b15..9ea64138 100644 --- a/partitura/utils/misc.py +++ b/partitura/utils/misc.py @@ -172,12 +172,15 @@ def concatenate_images( concat_mode: str = "vertical", ) -> Optional[ImageFile]: """ - Concatenate Images + Concatenate Images to form one single image. Parameters ---------- filenames: Iterable[PathLike] - A list of images to be concatenated. + A list of images to be concatenated. This method assumes + that all of the images have the same resolution and color mode + (that is the case for png files generated by MuseScore). + See `partitura.io.musescore.render_musescore`. out : Optional[PathLike] The output file where the image will be saved. concat_mode : {"vertical", "horizontal"} diff --git a/tests/__init__.py b/tests/__init__.py index ea98732e..7a6f0b4f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,7 @@ """ import os +import glob BASE_PATH = os.path.dirname(os.path.realpath(__file__)) DATA_PATH = os.path.join(BASE_PATH, "data") @@ -17,6 +18,7 @@ MIDI_PATH = os.path.join(DATA_PATH, "midi") PARANGONADA_PATH = os.path.join(DATA_PATH, "parangonada") WAV_PATH = os.path.join(DATA_PATH, "wav") +PNG_PATH = os.path.join(DATA_PATH, "png") # this is a list of files for which importing and subsequent exporting should # yield identical MusicXML @@ -189,3 +191,5 @@ "example_linear_equal_temperament_sr8000.wav", ] ] + +PNG_TESTFILES = glob.glob(os.path.join(PNG_PATH, '*.png')) diff --git a/tests/data/png/example_score.png b/tests/data/png/example_score.png new file mode 100644 index 0000000000000000000000000000000000000000..9f85fa61d4c49b6ceedbea9a6bb58d9e30622c74 GIT binary patch literal 4432 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 Date: Mon, 31 Oct 2022 11:24:37 +0100 Subject: [PATCH 3/7] update render_lilypond and update documentation --- partitura/display.py | 60 ++++++++++++++++++++++++++++++++------- partitura/io/musescore.py | 4 +-- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/partitura/display.py b/partitura/display.py index 31ece28a..085b5c19 100644 --- a/partitura/display.py +++ b/partitura/display.py @@ -10,10 +10,15 @@ import warnings import os import subprocess +import shutil from tempfile import NamedTemporaryFile, TemporaryFile +from typing import Optional from partitura import save_musicxml from partitura.io.musescore import render_musescore +from partitura.score import ScoreLike + +from partitura.utils.misc import PathLike, deprecated_alias __all__ = ["render"] @@ -36,7 +41,13 @@ # return s -def render(part, fmt="png", dpi=90, out_fn=None): +@deprecated_alias(out_fn="out", part="score_data") +def render( + score_data: ScoreLike, + fmt: str = "png", + dpi: int = 90, + out: Optional[PathLike] = None, +) -> None: """Create a rendering of one or more parts or partgroups. The function can save the rendered image to a file (when @@ -49,25 +60,23 @@ def render(part, fmt="png", dpi=90, out_fn=None): Parameters ---------- - part : :class:`partitura.score.Part` or :class:`partitura.score.PartGroup` - or a list of these + part : ScoreLike The score content to be displayed fmt : {'png', 'pdf'}, optional The image format of the rendered material out_fn : str or None, optional The path of the image output file. If None, the rendering will be displayed in a viewer. - """ - img_fn = render_musescore(part, fmt, out_fn, dpi) + img_fn = render_musescore(score_data, fmt, out, dpi) if img_fn is None or not os.path.exists(img_fn): - img_fn = render_lilypond(part, fmt) + img_fn = render_lilypond(score_data, fmt) if img_fn is None or not os.path.exists(img_fn): return - if not out_fn: + if not out: # NOTE: the temporary image file will not be deleted. if platform.system() == "Linux": subprocess.call(["xdg-open", img_fn]) @@ -77,9 +86,33 @@ def render(part, fmt="png", dpi=90, out_fn=None): os.startfile(img_fn) -def render_lilypond(part, fmt="png"): +@deprecated_alias(part="score_data") +def render_lilypond( + score_data, + fmt="png", + out=None, +) -> Optional[PathLike]: + """ + Render a score-like object using Lilypond + + Parameters + ---------- + score_data : ScoreLike + Score-like object to be rendered + fmt : {'png', 'pdf'} + Output image format + out : str or None, optional + The path of the image output file, if not specified, the + rendering will be saved to a temporary filename. Defaults to + None. + + Returns + ------- + out : PathLike + Path of the generated output image (or None if no image was generated). + """ if fmt not in ("png", "pdf"): - print("warning: unsupported output format") + warnings.warn("warning: unsupported output format") return None prvw_sfx = ".preview.{}".format(fmt) @@ -89,7 +122,7 @@ def render_lilypond(part, fmt="png"): ) as img_fh: # save part to musicxml in file handle xml_fh - save_musicxml(part, xml_fh) + save_musicxml(score_data, xml_fh) # rewind read pointer of file handle before we pass it to musicxml2ly xml_fh.seek(0) @@ -141,4 +174,9 @@ def render_lilypond(part, fmt="png"): ) return - return img_fh.name + if out is not None: + shutil.copy(img_fh.name, out) + else: + out = img_fh.name + + return out diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index baf08598..5182020d 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -148,12 +148,12 @@ def render_musescore( dpi: Optional[int] = 90, ) -> Optional[PathLike]: """ - Render a part using musescore. + Render a score-like object using musescore. Parameters ---------- score_data : ScoreLike - Part to be rendered + Score-like object to be rendered fmt : {'png', 'pdf'} Output image format out : str or None, optional From a9cf0911b7ddfcc7afaf873b81d43fb7903522aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 31 Oct 2022 11:31:10 +0100 Subject: [PATCH 4/7] fix typo --- partitura/display.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/display.py b/partitura/display.py index 085b5c19..7149a931 100644 --- a/partitura/display.py +++ b/partitura/display.py @@ -60,7 +60,7 @@ def render( Parameters ---------- - part : ScoreLike + score_data : ScoreLike The score content to be displayed fmt : {'png', 'pdf'}, optional The image format of the rendered material From 9ac166fab486b20ed81b8e89de5217fa8bb75360 Mon Sep 17 00:00:00 2001 From: sildater <41552783+sildater@users.noreply.github.com> Date: Thu, 3 Nov 2022 15:33:58 +0100 Subject: [PATCH 5/7] fix path for windows --- partitura/io/musescore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 5182020d..b802734c 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -64,7 +64,7 @@ def find_musescore3(): result = shutil.which("/Applications/MuseScore 3.app/Contents/MacOS/mscore") elif platform.system() == "Windows": - result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore.exe") + result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore3.exe") return result From 13bbbf8df12029c7f17145c683471e4ce67f0917 Mon Sep 17 00:00:00 2001 From: sildater <41552783+sildater@users.noreply.github.com> Date: Tue, 22 Nov 2022 16:46:52 +0100 Subject: [PATCH 6/7] fix musescore loading and executable (for windows) --- partitura/io/musescore.py | 42 +++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index f5b2eb76..70321b56 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -61,7 +61,7 @@ def find_musescore3(): result = shutil.which("/Applications/MuseScore 3.app/Contents/MacOS/mscore") elif platform.system() == "Windows": - result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore.exe") + result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore3.exe") return result @@ -108,34 +108,38 @@ def load_via_musescore( raise MuseScoreNotFoundException() - with NamedTemporaryFile(suffix=".musicxml") as xml_fh: + xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml" - cmd = [mscore_exec, "-o", xml_fh.name, filename] + cmd = [mscore_exec, "-o", xml_fh, filename] - try: - - ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) + try: - if ps.returncode != 0: + ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) - raise FileImportException( - ( - "Command {} failed with code {}. MuseScore " - "error messages:\n {}" - ).format(cmd, ps.returncode, ps.stderr.decode("UTF-8")) - ) - except FileNotFoundError as f: + if ps.returncode != 0: raise FileImportException( - 'Executing "{}" returned {}.'.format(" ".join(cmd), f) + ( + "Command {} failed with code {}. MuseScore " + "error messages:\n {}" + ).format(cmd, ps.returncode, ps.stderr.decode("UTF-8")) ) + except FileNotFoundError as f: - return load_musicxml( - filename=xml_fh.name, - validate=validate, - force_note_ids=force_note_ids, + raise FileImportException( + 'Executing "{}" returned {}.'.format(" ".join(cmd), f) ) + score = load_musicxml( + filename=xml_fh, + validate=validate, + force_note_ids=force_note_ids, + ) + + os.remove(xml_fh) + + return score + def render_musescore(part, fmt, out_fn=None, dpi=90): """Render a part using musescore. From 2d5b0117855d6d186426b3ee68bc390212c4e043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Sun, 4 Dec 2022 06:44:14 +0530 Subject: [PATCH 7/7] fix export parangonada csv --- partitura/io/exportparangonada.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/partitura/io/exportparangonada.py b/partitura/io/exportparangonada.py index 57ceef69..1aa31c82 100644 --- a/partitura/io/exportparangonada.py +++ b/partitura/io/exportparangonada.py @@ -147,10 +147,30 @@ def save_parangonada_csv( np.savetxt( os.path.join(outdir, "ppart.csv"), # outdir + os.path.sep + "perf_note_array.csv", - perf_note_array, + perf_note_array[ + [ + "onset_sec", + "duration_sec", + "pitch", + "velocity", + "track", + "channel", + "id", + ] + ], fmt="%.20s", delimiter=",", - header=",".join(perf_note_array.dtype.names), + header=",".join( + [ + "onset_sec", + "duration_sec", + "pitch", + "velocity", + "track", + "channel", + "id", + ] + ), comments="", ) np.savetxt(