From 8013bd4c13a424f0b7452dfc961c1c1677b93df3 Mon Sep 17 00:00:00 2001 From: Lucas Date: Fri, 28 Jun 2024 11:07:21 -0400 Subject: [PATCH] feat(jobs): add c clef extraction job --- .../rodan/jobs/extract_c_clefs/__init__.py | 4 + .../code/rodan/jobs/extract_c_clefs/base.py | 67 ++++++++++++++++ .../jobs/extract_c_clefs/extract_c_clefs.py | 80 +++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 rodan-main/code/rodan/jobs/extract_c_clefs/__init__.py create mode 100644 rodan-main/code/rodan/jobs/extract_c_clefs/base.py create mode 100644 rodan-main/code/rodan/jobs/extract_c_clefs/extract_c_clefs.py diff --git a/rodan-main/code/rodan/jobs/extract_c_clefs/__init__.py b/rodan-main/code/rodan/jobs/extract_c_clefs/__init__.py new file mode 100644 index 00000000..36e5d0bc --- /dev/null +++ b/rodan-main/code/rodan/jobs/extract_c_clefs/__init__.py @@ -0,0 +1,4 @@ +__version__ = "1.0.0" +from rodan.jobs import module_loader + +module_loader("rodan.jobs.extract_c_clefs.base") diff --git a/rodan-main/code/rodan/jobs/extract_c_clefs/base.py b/rodan-main/code/rodan/jobs/extract_c_clefs/base.py new file mode 100644 index 00000000..a05d0eb6 --- /dev/null +++ b/rodan-main/code/rodan/jobs/extract_c_clefs/base.py @@ -0,0 +1,67 @@ +from rodan.jobs.base import RodanTask +from .extract_c_clefs import * +import cv2 as cv +import os +import json +import logging + +logger = logging.getLogger("rodan") + + +class ExtractCClefs(RodanTask): + name = "Extract C Clefs" + author = "Lucas March" + description = "Finds the C clefs from a generated XML file from the interactive classifier and exports them to seperate images." + enabled = True + category = "Image Processing" + interactive = False + settings = { + "title": "Settings", + "type": "object", + "job_queue": "Python3", + } + input_port_types = ( + { + "name": "PNG Image", + "minimum": 1, + "maximum": 1, + "resource_types": ["image/rgba+png"], + }, + { + "name": "XML file", + "minimum": 1, + "maximum": 1, + "resource_types": ["application/gamera+xml"], + }, + ) + output_port_types = ( + { + "name": "C Clef", + "minimum": 1, + "maximum": 20, + "is_list": True, + "resource_types": ["image/rgba+png"], + }, + ) + + def run_my_task(self, inputs, settings, outputs): + logger.info("Running C Clef Extraction") + + image_path = inputs["PNG Image"][0]["resource_path"] + image_name = inputs["PNG Image"][0]["resource_name"] + xml_path = inputs["XML file"][0]["resource_path"] + + image = load_image(image_path) + xml = load_xml(xml_path) + coords = extract_coords(xml) + if not coords: + raise Exception("No C Clefs found in XML File.") + cropped_images = crop_images(image, coords) + output_base_path = outputs["C Clef"][0]["resource_folder"] + logger.info(f"output base path {output_base_path}") + for i, cropped_image in enumerate(cropped_images): + index = i + 1 # Start indexing from 1 + output_path = f"{output_base_path}{image_name}_{index}.png" + save_image(cropped_image, output_path) + + return True diff --git a/rodan-main/code/rodan/jobs/extract_c_clefs/extract_c_clefs.py b/rodan-main/code/rodan/jobs/extract_c_clefs/extract_c_clefs.py new file mode 100644 index 00000000..aef6c8a6 --- /dev/null +++ b/rodan-main/code/rodan/jobs/extract_c_clefs/extract_c_clefs.py @@ -0,0 +1,80 @@ +import argparse +import os +import cv2 +import numpy as np +import xml.etree.ElementTree as ET +from typing import List, Tuple, Optional + + +def crop_images( + image: np.ndarray, coords: List[Tuple[int, int, int, int]] +) -> List[np.ndarray]: + """ + Crop the image based on the coordinates. + Args: + image (np.ndarray): The image to crop. + coords (List[Tuple[int, int, int, int]]): List of tuples (ulx, uly, lrx, lry) for cropping. + Returns: + List[np.ndarray]: List of cropped image regions as numpy arrays. + """ + crops = [] + for ulx, uly, lrx, lry in coords: + crop = image[uly:lry, ulx:lrx] + crops.append(crop) + return crops + + +def save_image(image: np.ndarray, output_path: str) -> None: + """ + Save the image to the specified path. + Args: + image (np.ndarray): The image to save. + output_path (str): The path to save the image. + """ + cv2.imwrite(output_path, image) + print(f"Cropped image saved to {output_path}") + + +def load_image(path: str) -> np.ndarray: + """ + Load an image from the specified path. + Args: + path (str): Path to the image file. + Returns: + np.ndarray: The loaded image. + """ + return cv2.imread(path, cv2.IMREAD_COLOR) + + +def load_xml(path: str) -> ET.Element: + """ + Load an XML file from the specified path. + Args: + path (str): Path to the XML file. + Returns: + ET.Element: The root element of the parsed XML. + """ + tree = ET.parse(path) + return tree.getroot() + + +def extract_coords(xml: ET.Element) -> List[Optional[Tuple[int, int, int, int]]]: + """ + Extract bounding box coordinates from the XML element. + Args: + xml (ET.Element): The root element of the parsed XML. + Returns: + List[Optional[Tuple[int, int, int, int]]]: List of tuples (ulx, uly, lrx, lry) for cropping. + """ + coords_list = [] + for glyph in xml.findall(".//glyph"): + for id_tag in glyph.findall(".//id"): + if id_tag.attrib["name"] == "clef.c": + ulx = int(glyph.attrib["ulx"]) + uly = int(glyph.attrib["uly"]) + ncols = int(glyph.attrib["ncols"]) + nrows = int(glyph.attrib["nrows"]) + lrx = ulx + ncols + lry = uly + nrows + coords_list.append((ulx, uly, lrx, lry)) + return coords_list