Source code for mutwo.abjad_utilities.tests

import functools
import operator
import os
import math
import typing
import unittest

try:
    from PIL import Image  # type: ignore
    from PIL import ImageChops  # type: ignore
except ImportError:
    pass

import abjad

from mutwo import abjad_converters
from mutwo import core_events
from mutwo import music_events


__all__ = ("AbjadTestCase", "run_if_ekmelily_available")


[docs]class AbjadTestCase(unittest.TestCase): base_path = f"tests{os.sep}img"
[docs] @staticmethod def t(reset_tests: bool = False, remove_ly: bool = True): def t(test_method: typing.Callable): return lambda self: self._test( test_method.__name__, reset_tests, remove_ly, **test_method(self) ) return t
[docs] def assertImagesEqual(self, path0: str, path1: str): image0, image1 = (Image.open(path) for path in (path0, path1)) difference = ImageChops.difference(image1, image0) self.assertTrue(difference.getbbox() is None, "Images differ!")
[docs] def assertImagesAlmostEqual( self, image0: Image, image1: Image, tolerance: float = 0.01 ): d = root_mean_square_difference(image0, image1) self.assertTrue( d < tolerance, f"Images differ above tolerance: '{d} >= '{tolerance}'!" )
[docs] def setUp(self): header_block = abjad.Block(name="header") header_block.items.append('tagline = "---integration-test---"') self.lilypond_file = abjad.LilyPondFile() self.score_block = abjad.Block(name="score") self.lilypond_file.items.append( "\n".join( ( r'\include "lilypond-book-preamble.ly"', r"#(ly:set-option 'tall-page-formats 'png)", ) ) ) self.lilypond_file.items.extend([header_block, self.score_block])
def _test( self, name: str, reset_tests: bool = False, remove_ly: bool = True, converter: abjad_converters.ConsecutionToAbjadVoice = abjad_converters.ConsecutionToAbjadVoice(), ev: core_events.abc.Event = core_events.Consecution( [core_events.SimpleEvent(1)] ), tolerance: float = 0.1, ): converted_event = converter.convert(_parse_event(ev)) base_p = self.base_path file_ok_path = f"{base_p}{os.sep}{name}_ok.png" file_test_path = f"{base_p}{os.sep}{name}_test.png" if reset_tests: file_test_path = file_ok_path match converted_event: case abjad.Voice(): item = abjad.Staff([converted_event]) case abjad.Score(): item = converted_event case _: raise NotImplementedError(converted_event) self.score_block.items.append(item) abjad.persist.as_png( self.lilypond_file, png_file_path=file_test_path, remove_ly=remove_ly ) image_ok, image_test = (Image.open(p) for p in (file_ok_path, file_test_path)) if tolerance > 0: self.assertImagesAlmostEqual(image_ok, image_test, tolerance) else: self.assertImagesEqual(image_ok, image_test) if not reset_tests: os.remove(file_test_path)
def _parse_event(ev): match ev: case music_events.NoteLike(): ev = core_events.Consecution([ev]) case core_events.Consecution(): ev = ev case core_events.Concurrence(): ev = ev case _: raise NotImplementedError(type(ev)) return ev def root_mean_square_difference(image0: Image, image1: Image): """Calculate root-mean-square difference between two images. Taken from: http://snipplr.com/view/757/compare-two-pil-images-in-python/ """ h0, h1 = (i.histogram() for i in (image0, image1)) def mean_square(a: float, b: float) -> float: if not a: a = 0.0 if not b: b = 0.0 return (a - b) ** 2 return math.sqrt( functools.reduce(operator.add, map(mean_square, h0, h1)) / (image0.size[0] * image0.size[1]) )
[docs]def run_if_ekmelily_available(method_to_wrap: typing.Callable): """Decorator which only runs test if ekmelily is found""" try: from mutwo import ekmelily_converters # type: ignore ekmelily_found = True except ImportError: ekmelily_found = False def test(*args, **kwargs): if ekmelily_found: return method_to_wrap(*args, **kwargs) return test