Skip to content

Commit

Permalink
Added Save Annotations as JSON.
Browse files Browse the repository at this point in the history
Added ComfyUI annotations image.
  • Loading branch information
ggarra13 committed Sep 25, 2024
1 parent 95d9a7f commit 3cb2852
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 9 deletions.
7 changes: 3 additions & 4 deletions cmake/Modules/BuildFLTK.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ include( ExternalProject )
# The cutting EDGE!
#set( FLTK_GIT_TAG master )

# Adds label_image_spacing().
#set(FLTK_GIT_TAG 05c91b287f16d87f335a8cc375074d712cb8511a)
# Wayland fix. (latest stable)
#set(FLTK_GIT_TAG 382d6b2fbdb441ef8cbbe52a7dc07032aa733188)

# Wayland fix.
set(FLTK_GIT_TAG 382d6b2fbdb441ef8cbbe52a7dc07032aa733188)
set(FLTK_GIT_TAG a333817f4178d944685fc787bd389097e8d85aca)

if(MRV2_PYFLTK OR FLTK_BUILD_SHARED)
# If we are building pyFLTK compile shared
Expand Down
145 changes: 145 additions & 0 deletions src/ComfyUI/custom_nodes/mrv2/draw/mrv2_annotations_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
from PIL import Image, ImageDraw
import json

import folder_paths
import logging
import numpy as np
import os
import time
import torch

# Function to scale points
def _scale_points(points, scale_x, scale_y):
return [(x * scale_x, y * scale_y) for x, y in points]

# Function to draw a circle with a specific width
def _draw_circle(draw, center, radius, width, color):
outer_radius = radius
inner_radius = radius - width / 2

# Draw the outer circle
draw.ellipse(
(center[0] - outer_radius, center[1] - outer_radius, center[0] + outer_radius, center[1] + outer_radius),
outline=255,
width=width
)

class mrv2AnnotationsImageNode:
@classmethod
def INPUT_TYPES(s):
input_directory = folder_paths.get_input_directory()
files = [f for f in os.listdir(input_directory) \
if os.path.isfile(os.path.join(input_directory, f))]
return {"required":
{ "annotations": (sorted(files), {"image_upload": True})},
}


RETURN_TYPES = ("IMAGE", "MASK")
OUTPUT_TOOLTIPS = ("The decoded image and mask.",)

FUNCTION = "create_mask"

CATEGORY = "mrv2/mask"
DESCRIPTION = "Loads a mrv2 annotation .json file and creates a mask for it."

@classmethod
def VALIDATE_INPUTS(s, annotations):
if not folder_paths.exists_annotated_filepath(annotations):
return "Invalid image file: {}".format(annotations)

return True

@classmethod
def IS_CHANGED(s, annotations):
return time.time()

def create_mask(self, annotations):
input_directory = folder_paths.get_input_directory()
annotations_path = os.path.join(input_directory, annotations)

logging.debug(f"Reading annotation {annotations_path}")
# Load the JSON file
with open(annotations_path) as f:
data = json.load(f)

# Outputs
output_images = []
output_masks = []

# Image size
image_width, image_height = data['render_size']

scale_factor = 4 # Drawing on a canvas 4x larger for antialiasing
high_res_width = image_width * scale_factor
high_res_height = image_height * scale_factor

# Create a high-resolution black image
image = Image.new('L', (high_res_width, high_res_height), 0)
draw = ImageDraw.Draw(image)

# Process the paths from the JSON
for annotation in data['annotations']:
for shape in annotation['shapes']:
shape_type = shape['type']
pen_size = int(shape.get('pen_size', 1.0) * scale_factor)
if shape.get('pts', None):
points = [(point['x'], image_height - point['y'])
for point in shape['pts']]

# Scale points to the higher-resolution canvas
points = _scale_points(points, scale_factor, scale_factor)

if shape_type == 'DrawPath' or shape_type == 'Rectangle':
# Draw the path (white color, width defined by 'pen_size')
draw.line(points, fill=255, width=int(pen_size))
elif shape_type == 'ErasePath':
# Draw the path (white color, width defined by 'pen_size')
draw.line(points, fill=0, width=int(pen_size * 1.5))
elif shape_type == 'Arrow':
left_side = [points[1], points[2]]
draw.line(left_side, fill=255, width=int(pen_size))
right_side = [points[1], points[4]]
draw.line(right_side, fill=255, width=int(pen_size))
root = [points[0], points[1]]
draw.line(root, fill=255, width=int(pen_size))
elif shape_type == 'Text':
continue
elif shape_type == 'Circle':
# Center and radius for the circle
center = shape['center'] # Center of the image
center[1] = image_height - center[1]
radius = int(shape['radius'] * scale_factor) # Outer radius

center = _scale_points([center], scale_factor, scale_factor)[0]

# Draw the circle with the specified stroke width
_draw_circle(draw, center, radius, pen_size, 255)


# Downscale to the target resolution
mask = image.resize((image_width, image_height),
Image.Resampling.LANCZOS)
image = mask.convert('RGB')

# Convert the PIL mask to a NumPy array
mask = np.array(mask).astype(np.float32) / 255.0
image = np.array(image).astype(np.float32) / 255.0

# Convert the NumPy array to torch data
image = torch.from_numpy(image)[None,]
mask = torch.from_numpy(mask).unsqueeze(0)

output_images.append(image)
output_masks.append(mask)

if len(output_images) > 1:
output_image = torch.cat(output_images, dim=0)
output_mask = torch.cat(output_masks, dim=0)
else:
output_image = output_images[0]
output_mask = output_masks[0]

print("Annotation Image", output_image.shape)
print("Annotation Mask", output_mask.shape)
return (output_image, output_mask)
5 changes: 4 additions & 1 deletion src/ComfyUI/custom_nodes/mrv2/nodes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@

from .save.mrv2_save_exr_image import mrv2SaveEXRImage
from .draw.mrv2_annotations_image import mrv2AnnotationsImageNode

NODE_CLASS_MAPPINGS = {
"mrv2SaveEXRImage": mrv2SaveEXRImage
"mrv2SaveEXRImage": mrv2SaveEXRImage,
"mrv2AnnotationsImageNode": mrv2AnnotationsImageNode,
}

NODE_DISPLAY_NAME_MAPPINGS = {
"mrv2SaveEXRImage": "mrv2 Save EXR Image",
"mrv2AnnotationsImageNode": "mrv2 Annotations Image Node",
}
1 change: 1 addition & 0 deletions src/ComfyUI/custom_nodes/mrv2/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
openexr
pillow
5 changes: 3 additions & 2 deletions src/ComfyUI/custom_nodes/mrv2/save/mrv2_save_exr_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def INPUT_TYPES(s):

CATEGORY = "mrv2/save"

@classmethod
def IS_CHANGED(s, images):
return time.time()

Expand Down Expand Up @@ -215,10 +216,10 @@ def save_images(self, images, masks = [],

# NOTE: names should be globally unique
NODE_CLASS_MAPPINGS = {
"mrv2SaveEXRImage": mrv2SaveEXRImage
"mrv2SaveEXRImage": mrv2SaveEXRImage,
}

# A dictionary that contains the friendly/humanly readable titles for the nodes
NODE_DISPLAY_NAME_MAPPINGS = {
"mrv2SaveEXRImage": "mrv2 Save EXR Node"
"mrv2SaveEXRImage": "mrv2 Save EXR Node",
}
5 changes: 4 additions & 1 deletion src/lib/mrvFl/mrvCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,8 @@ namespace mrv

void save_annotations_as_json_cb(Fl_Menu_* w, ViewerUI* ui)
{
auto player = ui->uiView->getTimelinePlayer();
auto view = ui->uiView;
auto player = view->getTimelinePlayer();
if (!player)
return;

Expand All @@ -679,6 +680,8 @@ namespace mrv
return;

Message j;
j["render_size"] = view->getRenderSize();

std::vector< draw::Annotation > flatAnnotations;
for (const auto& annotation : annotations)
{
Expand Down
2 changes: 1 addition & 1 deletion tlRender

0 comments on commit 3cb2852

Please sign in to comment.