def render_masks_of_nodes_into_image(self, nodes: List[Node],
                                         destination_directory: str):
        for node in tqdm(nodes,
                         desc="Generating images from node masks",
                         smoothing=0.1):  # type: Node
            symbol_class = node.class_name
            # Make a copy of the mask to not temper with the original data
            mask = node.mask.copy()
            # We want to draw black symbols on white canvas. The mask encodes foreground pixels
            # that we are interested in with a 1 and background pixels with a 0 and stores those values in
            # an uint8 numpy array. To use Image.fromarray, we have to generate a greyscale mask, where
            # white pixels have the value 255 and black pixels have the value 0. To achieve this, we simply
            # subtract one from each uint, and by exploiting the underflow of the uint we get the following mapping:
            # 0 (background) => 255 (white) and 1 (foreground) => 0 (black) which is exactly what we wanted.
            mask -= 1
            image = Image.fromarray(mask, mode="L")

            target_directory = os.path.join(destination_directory,
                                            symbol_class)
            os.makedirs(target_directory, exist_ok=True)

            export_path = ExportPath(destination_directory, symbol_class,
                                     node.unique_id)
            image.save(export_path.get_full_path())
    def draw_capitan_stroke_images(self, symbols: List[CapitanSymbol],
                                   destination_directory: str,
                                   stroke_thicknesses: List[int]) -> None:
        """
        Creates a visual representation of the Capitan strokes by drawing lines that connect the points
        from each stroke of each symbol.

        :param symbols: The list of parsed Capitan-symbols
        :param destination_directory: The directory, in which the symbols should be generated into. One sub-folder per
                                      symbol category will be generated automatically
        :param stroke_thicknesses: The thickness of the pen, used for drawing the lines in pixels. If multiple are
                                   specified, multiple images will be generated that have a different suffix, e.g.
                                   1-16-3.png for the 3-px version and 1-16-2.png for the 2-px version of the image 1-16
        """

        total_number_of_symbols = len(symbols) * len(stroke_thicknesses)
        output = "Generating {0} images with {1} symbols in {2} different stroke thicknesses ({3})".format(
            total_number_of_symbols, len(symbols), len(stroke_thicknesses), stroke_thicknesses)

        print(output)
        print("In directory {0}".format(os.path.abspath(destination_directory)), flush=True)

        progress_bar = tqdm(total=total_number_of_symbols, mininterval=0.25, desc="Rendering strokes")
        capitan_file_name_counter = 0
        for symbol in symbols:
            capitan_file_name_counter += 1
            target_directory = os.path.join(destination_directory, symbol.symbol_class)
            os.makedirs(target_directory, exist_ok=True)

            raw_file_name_without_extension = "capitan-{0}-{1}-stroke".format(symbol.symbol_class,
                                                                              capitan_file_name_counter)

            for stroke_thickness in stroke_thicknesses:
                export_path = ExportPath(destination_directory, symbol.symbol_class, raw_file_name_without_extension,
                                         'png', stroke_thickness)
                symbol.draw_capitan_stroke_onto_canvas(export_path, stroke_thickness, 0)
                progress_bar.update(1)

        progress_bar.close()
    def draw_onto_canvas(self,
                         export_path: ExportPath,
                         stroke_thickness: int,
                         margin: int,
                         destination_width: int,
                         destination_height: int,
                         staff_line_spacing: int = 14,
                         staff_line_vertical_offsets: List[int] = None,
                         bounding_boxes: dict = None,
                         random_position_on_canvas: bool = False) -> None:
        """
        Draws the symbol onto a canvas with a fixed size

        :param bounding_boxes: The dictionary into which the bounding-boxes will be added of each generated image
        :param export_path: The path, where the symbols should be created on disk
        :param stroke_thickness:
        :param margin:
        :param destination_width:
        :param destination_height:
        :param staff_line_spacing:
        :param staff_line_vertical_offsets: Offsets used for drawing staff-lines. If None provided, no staff-lines will be drawn if multiple integers are provided, multiple images will be generated
        """
        width = self.dimensions.width + 2 * margin
        height = self.dimensions.height + 2 * margin
        if random_position_on_canvas:
            # max is required for elements that are larger than the canvas,
            # where the possible range for the random value would be negative
            random_horizontal_offset = random.randint(
                0, max(0, destination_width - width))
            random_vertical_offset = random.randint(
                0, max(0, destination_height - height))
            offset = Point2D(
                self.dimensions.origin.x - margin - random_horizontal_offset,
                self.dimensions.origin.y - margin - random_vertical_offset)
        else:
            width_offset_for_centering = (destination_width - width) / 2
            height_offset_for_centering = (destination_height - height) / 2
            offset = Point2D(
                self.dimensions.origin.x - margin - width_offset_for_centering,
                self.dimensions.origin.y - margin -
                height_offset_for_centering)

        image_without_staff_lines = Image.new(
            'RGB', (destination_width, destination_height),
            "white")  # create a new white image
        draw = ImageDraw.Draw(image_without_staff_lines)
        black = (0, 0, 0)

        for stroke in self.strokes:
            for i in range(0, len(stroke) - 1):
                start_point = self.__subtract_offset(stroke[i], offset)
                end_point = self.__subtract_offset(stroke[i + 1], offset)
                draw.line(
                    (start_point.x, start_point.y, end_point.x, end_point.y),
                    black, stroke_thickness)

        location = self.__subtract_offset(self.dimensions.origin, offset)
        bounding_box_in_image = Rectangle(location, self.dimensions.width,
                                          self.dimensions.height)
        # self.draw_bounding_box(draw, location)

        del draw

        if staff_line_vertical_offsets is not None and staff_line_vertical_offsets:
            for staff_line_vertical_offset in staff_line_vertical_offsets:
                image_with_staff_lines = image_without_staff_lines.copy()
                self.__draw_staff_lines_into_image(image_with_staff_lines,
                                                   stroke_thickness,
                                                   staff_line_spacing,
                                                   staff_line_vertical_offset)
                file_name_with_offset = export_path.get_full_path(
                    staff_line_vertical_offset)
                image_with_staff_lines.save(file_name_with_offset)
                image_with_staff_lines.close()

                if bounding_boxes is not None:
                    # Note that the ImageDatasetGenerator does not yield the full path, but only the class_name and
                    # the file_name, e.g. '3-4-Time\\1-13_3_offset_74.png', so we store only that part in the dictionary
                    class_and_file_name = export_path.get_class_name_and_file_path(
                        staff_line_vertical_offset)
                    bounding_boxes[class_and_file_name] = bounding_box_in_image
        else:
            image_without_staff_lines.save(export_path.get_full_path())
            if bounding_boxes is not None:
                # Note that the ImageDatasetGenerator does not yield the full path, but only the class_name and
                # the file_name, e.g. '3-4-Time\\1-13_3_offset_74.png', so we store only that part in the dictionary
                class_and_file_name = export_path.get_class_name_and_file_path(
                )
                bounding_boxes[class_and_file_name] = bounding_box_in_image

        image_without_staff_lines.close()
    def create_images(raw_data_directory: str,
                      destination_directory: str,
                      stroke_thicknesses: List[int],
                      canvas_width: int = None,
                      canvas_height: int = None,
                      staff_line_spacing: int = 14,
                      staff_line_vertical_offsets: List[int] = None,
                      random_position_on_canvas: bool = False) -> dict:
        """
        Creates a visual representation of the Homus Dataset by parsing all text-files and the symbols as specified
        by the parameters by drawing lines that connect the points from each stroke of each symbol.

        Each symbol will be drawn in the center of a fixed canvas, specified by width and height.

        :param raw_data_directory: The directory, that contains the text-files that contain the textual representation
                                    of the music symbols
        :param destination_directory: The directory, in which the symbols should be generated into. One sub-folder per
                                      symbol category will be generated automatically
        :param stroke_thicknesses: The thickness of the pen, used for drawing the lines in pixels. If multiple are
                                   specified, multiple images will be generated that have a different suffix, e.g.
                                   1-16-3.png for the 3-px version and 1-16-2.png for the 2-px version of the image 1-16
        :param canvas_width: The width of the canvas, that each image will be drawn upon, regardless of the original size of
                      the symbol. Larger symbols will be cropped. If the original size of the symbol should be used,
                      provided None here.
        :param canvas_height: The height of the canvas, that each image will be drawn upon, regardless of the original size of
                       the symbol. Larger symbols will be cropped. If the original size of the symbol should be used,
                       provided None here
        :param staff_line_spacing: Number of pixels spacing between each of the five staff-lines
        :param staff_line_vertical_offsets: List of vertical offsets, where the staff-lines will be superimposed over
                                            the drawn images. If None is provided, no staff-lines will be superimposed.
                                            If multiple values are provided, multiple versions of each symbol will be
                                            generated with the appropriate staff-lines, e.g. 1-5_3_offset_70.png and
                                            1-5_3_offset_77.png for two versions of the symbol 1-5 with stroke thickness
                                            3 and staff-line offsets 70 and 77 pixels from the top.
        :param random_position_on_canvas: True, if the symbols should be randomly placed on the fixed canvas.
                                          False, if the symbols should be centered in the fixed canvas.
                                          Note that this flag only has an effect, if fixed canvas sizes are used.
        :return: A dictionary that contains the file-names of all generated symbols and the respective bounding-boxes
                 of each symbol.
        """
        all_symbol_files = [
            y for x in os.walk(raw_data_directory)
            for y in glob(os.path.join(x[0], '*.txt'))
        ]

        staff_line_multiplier = 1
        if staff_line_vertical_offsets is not None and staff_line_vertical_offsets:
            staff_line_multiplier = len(staff_line_vertical_offsets)

        total_number_of_symbols = len(all_symbol_files) * len(
            stroke_thicknesses) * staff_line_multiplier
        output = "Generating {0} images with {1} symbols in {2} different stroke thicknesses ({3})".format(
            total_number_of_symbols, len(all_symbol_files),
            len(stroke_thicknesses), stroke_thicknesses)

        if staff_line_vertical_offsets is not None:
            output += " and with staff-lines with {0} different offsets from the top ({1})".format(
                staff_line_multiplier, staff_line_vertical_offsets)

        if canvas_width is not None and canvas_height is not None:
            if random_position_on_canvas is False:
                output += "\nRandomly drawn on a fixed canvas of size {0}x{1} (Width x Height)".format(
                    canvas_width, canvas_height)
            else:
                output += "\nCentrally drawn on a fixed canvas of size {0}x{1} (Width x Height)".format(
                    canvas_width, canvas_height)

        print(output)
        print("In directory {0}".format(
            os.path.abspath(destination_directory)),
              flush=True)

        bounding_boxes = dict()

        progress_bar = tqdm(total=total_number_of_symbols, mininterval=0.25)
        for symbol_file in all_symbol_files:
            with open(symbol_file) as file:
                content = file.read()

            symbol = HomusSymbol.initialize_from_string(content)

            target_directory = os.path.join(destination_directory,
                                            symbol.symbol_class)
            os.makedirs(target_directory, exist_ok=True)

            raw_file_name_without_extension = os.path.splitext(
                os.path.basename(symbol_file))[0]

            for stroke_thickness in stroke_thicknesses:
                export_path = ExportPath(destination_directory,
                                         symbol.symbol_class,
                                         raw_file_name_without_extension,
                                         'png', stroke_thickness)
                if canvas_width is None and canvas_height is None:
                    symbol.draw_into_bitmap(export_path,
                                            stroke_thickness,
                                            margin=2)
                else:
                    symbol.draw_onto_canvas(export_path, stroke_thickness, 0,
                                            canvas_width, canvas_height,
                                            staff_line_spacing,
                                            staff_line_vertical_offsets,
                                            bounding_boxes,
                                            random_position_on_canvas)

                progress_bar.update(1 * staff_line_multiplier)

        progress_bar.close()
        return bounding_boxes