Spaces:
Running
on
Zero
Running
on
Zero
Bug fixes
Browse files- app.py +16 -11
- utils/file_utils.py +17 -1
- utils/hex_grid.py +3 -3
- utils/image_utils.py +211 -41
app.py
CHANGED
@@ -43,7 +43,7 @@ from utils.misc import (
|
|
43 |
convert_ratio_to_dimensions,
|
44 |
update_dimensions_on_ratio,
|
45 |
get_seed,
|
46 |
-
get_output_name
|
47 |
) #install_cuda_toolkit,install_torch, _get_output, setup_runtime_env)
|
48 |
|
49 |
from utils.image_utils import (
|
@@ -62,7 +62,8 @@ from utils.image_utils import (
|
|
62 |
resize_image_with_aspect_ratio,
|
63 |
build_prerendered_images_by_quality,
|
64 |
get_image_from_dict,
|
65 |
-
calculate_optimal_fill_dimensions
|
|
|
66 |
)
|
67 |
|
68 |
from utils.hex_grid import (
|
@@ -717,8 +718,7 @@ def on_input_image_change(image_path):
|
|
717 |
gr.Warning("Please upload an Input Image to get started.")
|
718 |
return None, gr.update()
|
719 |
img, img_path = convert_to_rgba_png(image_path)
|
720 |
-
|
721 |
-
width, height = pil_img.size
|
722 |
return [img_path, gr.update(width=width, height=height)]
|
723 |
|
724 |
def update_sketch_dimensions(input_image, sketch_image):
|
@@ -773,10 +773,12 @@ def unload_3d_models(is_open: bool = False) -> bool:
|
|
773 |
if not is_open:
|
774 |
gr.Info("Unloading 3D models...")
|
775 |
global image_processor, depth_model, TRELLIS_PIPELINE
|
776 |
-
TRELLIS_PIPELINE
|
777 |
-
|
778 |
-
|
779 |
-
|
|
|
|
|
780 |
#torch.cuda.empty_cache()
|
781 |
#torch.cuda.ipc_collect()
|
782 |
gc.collect()
|
@@ -890,7 +892,7 @@ def depth_process_image(image_path, resized_width=800, z_scale=208):
|
|
890 |
torch.cuda.ipc_collect()
|
891 |
return img
|
892 |
|
893 |
-
def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_image, output_image, overlay_image, bordered_image_output, progress=gr.Progress(track_tqdm=True)):
|
894 |
# Choose the image based on source
|
895 |
if depth_image_source == "Input Image":
|
896 |
image_path = input_image
|
@@ -912,6 +914,9 @@ def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_imag
|
|
912 |
# Process the image for depth estimation
|
913 |
depth_img = depth_process_image(image_path, resized_width=1536, z_scale=336)
|
914 |
depth_img = resize_image_with_aspect_ratio(depth_img, 1536, 1536)
|
|
|
|
|
|
|
915 |
|
916 |
return depth_img, image_path, output_name, final_seed
|
917 |
|
@@ -1200,7 +1205,7 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
1200 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
1201 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
1202 |
apply_lut_button.click(
|
1203 |
-
lambda lut_filename, input_image: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image)[
|
1204 |
inputs=[lut_filename, input_image],
|
1205 |
outputs=[input_image],
|
1206 |
scroll_to_output=True
|
@@ -1321,7 +1326,7 @@ with gr.Blocks(css_paths="style_20250314.css", title=title, theme='Surn/beeuty',
|
|
1321 |
y_spacing = gr.Number(label="Adjust Vertical spacing", value=3, minimum=-200, maximum=200, precision=1)
|
1322 |
with gr.Row():
|
1323 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
1324 |
-
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Row
|
1325 |
with gr.Row():
|
1326 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
1327 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
|
|
43 |
convert_ratio_to_dimensions,
|
44 |
update_dimensions_on_ratio,
|
45 |
get_seed,
|
46 |
+
get_output_name
|
47 |
) #install_cuda_toolkit,install_torch, _get_output, setup_runtime_env)
|
48 |
|
49 |
from utils.image_utils import (
|
|
|
62 |
resize_image_with_aspect_ratio,
|
63 |
build_prerendered_images_by_quality,
|
64 |
get_image_from_dict,
|
65 |
+
calculate_optimal_fill_dimensions,
|
66 |
+
save_image_to_temp_png
|
67 |
)
|
68 |
|
69 |
from utils.hex_grid import (
|
|
|
718 |
gr.Warning("Please upload an Input Image to get started.")
|
719 |
return None, gr.update()
|
720 |
img, img_path = convert_to_rgba_png(image_path)
|
721 |
+
width, height = img.size
|
|
|
722 |
return [img_path, gr.update(width=width, height=height)]
|
723 |
|
724 |
def update_sketch_dimensions(input_image, sketch_image):
|
|
|
773 |
if not is_open:
|
774 |
gr.Info("Unloading 3D models...")
|
775 |
global image_processor, depth_model, TRELLIS_PIPELINE
|
776 |
+
if TRELLIS_PIPELINE:
|
777 |
+
TRELLIS_PIPELINE.to("cpu")
|
778 |
+
del TRELLIS_PIPELINE
|
779 |
+
if depth_model:
|
780 |
+
del image_processor
|
781 |
+
del depth_model
|
782 |
#torch.cuda.empty_cache()
|
783 |
#torch.cuda.ipc_collect()
|
784 |
gc.collect()
|
|
|
892 |
torch.cuda.ipc_collect()
|
893 |
return img
|
894 |
|
895 |
+
def generate_3d_asset_part1(depth_image_source, randomize_seed, seed, input_image, output_image, overlay_image, bordered_image_output, req: gr.Request, progress=gr.Progress(track_tqdm=True)):
|
896 |
# Choose the image based on source
|
897 |
if depth_image_source == "Input Image":
|
898 |
image_path = input_image
|
|
|
914 |
# Process the image for depth estimation
|
915 |
depth_img = depth_process_image(image_path, resized_width=1536, z_scale=336)
|
916 |
depth_img = resize_image_with_aspect_ratio(depth_img, 1536, 1536)
|
917 |
+
|
918 |
+
user_dir = os.path.join(constants.TMPDIR, str(req.session_hash))
|
919 |
+
depth_img = save_image_to_temp_png(depth_img, user_dir, f"{output_name}_depth")
|
920 |
|
921 |
return depth_img, image_path, output_name, final_seed
|
922 |
|
|
|
1205 |
lut_file.change(get_filename, inputs=[lut_file], outputs=[lut_filename])
|
1206 |
lut_filename.change(show_lut, inputs=[lut_filename, lut_example_image], outputs=[lut_example_image])
|
1207 |
apply_lut_button.click(
|
1208 |
+
lambda lut_filename, input_image: gr.Warning("Please upload an Input Image to get started.") if input_image is None else apply_lut_to_image_path(lut_filename, input_image)[1],
|
1209 |
inputs=[lut_filename, input_image],
|
1210 |
outputs=[input_image],
|
1211 |
scroll_to_output=True
|
|
|
1326 |
y_spacing = gr.Number(label="Adjust Vertical spacing", value=3, minimum=-200, maximum=200, precision=1)
|
1327 |
with gr.Row():
|
1328 |
rotation = gr.Slider(-90, 180, 0.0, 0.1, label="Hexagon Rotation (degree)")
|
1329 |
+
add_hex_text = gr.Dropdown(label="Add Text to Hexagons", choices=[None, "Column-Row Coordinates", "Column(Letter)-Row Coordinates", "Column-Row(Letter) Coordinates", "Sequential Numbers", "Playing Cards Sequential", "Playing Cards Alternate Red and Black", "Custom List"], value=None)
|
1330 |
with gr.Row():
|
1331 |
custom_text_list = gr.TextArea(label="Custom Text List", value=constants.cards_alternating, visible=False,)
|
1332 |
custom_text_color_list = gr.TextArea(label="Custom Text Color List", value=constants.card_colors_alternating, visible=False)
|
utils/file_utils.py
CHANGED
@@ -38,7 +38,23 @@ def rename_file_to_lowercase_extension(file_path: str) -> str:
|
|
38 |
if ext != new_ext:
|
39 |
new_filename = name + new_ext
|
40 |
new_file_path = os.path.join(directory, new_filename)
|
41 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
return new_file_path
|
43 |
else:
|
44 |
return file_path
|
|
|
38 |
if ext != new_ext:
|
39 |
new_filename = name + new_ext
|
40 |
new_file_path = os.path.join(directory, new_filename)
|
41 |
+
try:
|
42 |
+
os.rename(file_path, new_file_path)
|
43 |
+
print(f"Rename {file_path} to {new_file_path}\n")
|
44 |
+
except Exception as e:
|
45 |
+
print(f"os.rename failed: {e}. Falling back to binary copy operation.")
|
46 |
+
try:
|
47 |
+
# Read the file in binary mode and write it to new_file_path
|
48 |
+
with open(file_path, 'rb') as f:
|
49 |
+
data = f.read()
|
50 |
+
with open(new_file_path, 'wb') as f:
|
51 |
+
f.write(data)
|
52 |
+
print(f"Rename {file_path} to {new_file_path}\n")
|
53 |
+
# Optionally, remove the original file after copying
|
54 |
+
#os.remove(file_path)
|
55 |
+
except Exception as inner_e:
|
56 |
+
print(f"Failed to copy file from {file_path} to {new_file_path}: {inner_e}")
|
57 |
+
raise inner_e
|
58 |
return new_file_path
|
59 |
else:
|
60 |
return file_path
|
utils/hex_grid.py
CHANGED
@@ -274,13 +274,13 @@ def generate_hexagon_grid_with_text(hex_size, border_size, input_image=None, ima
|
|
274 |
if font_size:
|
275 |
font = ImageFont.truetype(font_path, font_size)
|
276 |
# Determine the text to draw
|
277 |
-
if add_hex_text_option == "Row
|
278 |
text = f"{col},{row}"
|
279 |
elif add_hex_text_option == "Sequential Numbers":
|
280 |
text = f"{hex_index}"
|
281 |
-
elif add_hex_text_option == "Column
|
282 |
text = f"{number_to_letter(col)}{row}"
|
283 |
-
elif add_hex_text_option == "Column
|
284 |
text = f"{col}{number_to_letter(row)}"
|
285 |
elif text_list:
|
286 |
text = text_list[hex_index % len(text_list)]
|
|
|
274 |
if font_size:
|
275 |
font = ImageFont.truetype(font_path, font_size)
|
276 |
# Determine the text to draw
|
277 |
+
if add_hex_text_option == "Column-Row Coordinates":
|
278 |
text = f"{col},{row}"
|
279 |
elif add_hex_text_option == "Sequential Numbers":
|
280 |
text = f"{hex_index}"
|
281 |
+
elif add_hex_text_option == "Column(Letter)-Row Coordinates":
|
282 |
text = f"{number_to_letter(col)}{row}"
|
283 |
+
elif add_hex_text_option == "Column-Row(Letter) Coordinates":
|
284 |
text = f"{col}{number_to_letter(row)}"
|
285 |
elif text_list:
|
286 |
text = text_list[hex_index % len(text_list)]
|
utils/image_utils.py
CHANGED
@@ -17,6 +17,44 @@ from utils.color_utils import (
|
|
17 |
)
|
18 |
from utils.file_utils import rename_file_to_lowercase_extension
|
19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
def get_image_from_dict(image_path):
|
21 |
if isinstance(image_path, dict) :
|
22 |
if 'composite' in image_path:
|
@@ -44,14 +82,16 @@ def open_image(image_path):
|
|
44 |
Raises:
|
45 |
Exception: If there is an error opening the image.
|
46 |
"""
|
|
|
47 |
if isinstance(image_path, Image.Image):
|
48 |
return image_path
|
49 |
-
|
50 |
-
image_path =
|
|
|
|
|
51 |
|
52 |
import requests
|
53 |
try:
|
54 |
-
image_path, is_dict = get_image_from_dict(image_path)
|
55 |
# Strip leading and trailing double quotation marks, if present
|
56 |
image_path = image_path.strip('"')
|
57 |
if image_path.startswith('http'):
|
@@ -488,6 +528,24 @@ def resize_and_crop_image(image: Image, new_width: int = 512, new_height: int =
|
|
488 |
|
489 |
##################################################### LUTs ############################################################
|
490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
491 |
def is_3dlut_row(row: List[str]) -> bool:
|
492 |
"""
|
493 |
Check if one line in the file has exactly 3 numeric values.
|
@@ -504,8 +562,68 @@ def is_3dlut_row(row: List[str]) -> bool:
|
|
504 |
except ValueError:
|
505 |
return False
|
506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
507 |
|
508 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
509 |
"""
|
510 |
Read LUT from a raw file.
|
511 |
|
@@ -562,33 +680,33 @@ def show_lut(lut_filename: str, lut_example_image: Image = default_lut_example_i
|
|
562 |
lut_example_image = open_image(default_lut_example_img)
|
563 |
return lut_example_image
|
564 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
565 |
|
566 |
|
567 |
-
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
568 |
-
"""
|
569 |
-
Converts an RGB image to RGBA by adding an alpha channel.
|
570 |
-
Ensures that the original image remains unaltered.
|
571 |
|
572 |
-
Parameters:
|
573 |
-
image (PIL.Image.Image): The RGB image to convert.
|
574 |
|
575 |
-
Returns:
|
576 |
-
PIL.Image.Image: The converted RGBA image.
|
577 |
-
"""
|
578 |
-
if image.mode != 'RGB':
|
579 |
-
if image.mode == 'RGBA':
|
580 |
-
return image
|
581 |
-
elif image.mode == 'P':
|
582 |
-
# Convert palette image to RGBA
|
583 |
-
image = image.convert('RGB')
|
584 |
-
else:
|
585 |
-
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
586 |
-
# Create a copy of the image to avoid modifying the original
|
587 |
-
rgba_image = image.copy()
|
588 |
-
# Optionally, set a default alpha value (e.g., fully opaque)
|
589 |
-
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
590 |
-
rgba_image.putalpha(alpha)
|
591 |
-
return rgba_image
|
592 |
|
593 |
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image, str]:
|
594 |
"""
|
@@ -602,9 +720,24 @@ def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image,
|
|
602 |
Returns:
|
603 |
tuple: A tuple containing the PIL Image object with the LUT applied and the new image path as a string.
|
604 |
"""
|
|
|
|
|
|
|
605 |
if image_path is None:
|
606 |
raise UserWarning("No image provided.")
|
607 |
return None, None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
608 |
path = Path(image_path)
|
609 |
img = open_image(image_path)
|
610 |
if not ((path.suffix.lower() == '.png' and img.mode == 'RGBA')):
|
@@ -619,16 +752,50 @@ def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image,
|
|
619 |
if image_path != new_image_path:
|
620 |
delete_image(image_path)
|
621 |
else:
|
622 |
-
|
623 |
-
|
|
|
|
|
|
|
624 |
try:
|
625 |
-
|
626 |
except Exception as e:
|
627 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
628 |
-
|
629 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
630 |
|
631 |
############################################# RGBA ###########################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
632 |
|
633 |
# Example usage
|
634 |
# convert_jpg_to_rgba('input.jpg', 'output.png')
|
@@ -654,12 +821,15 @@ def convert_jpg_to_rgba(input_path) -> tuple[Image, str]:
|
|
654 |
|
655 |
# Check if the input file exists
|
656 |
if not input_path.exists():
|
657 |
-
|
|
|
|
|
|
|
658 |
|
659 |
# Check file extension first to skip unnecessary processing
|
660 |
if input_path.suffix.lower() not in ('.jpg', '.jpeg'):
|
661 |
print(f"Skipping conversion: {input_path} is not a JPG or JPEG file.")
|
662 |
-
return
|
663 |
|
664 |
print(f"Converting to PNG: {input_path} is a JPG or JPEG file.")
|
665 |
|
@@ -779,22 +949,22 @@ def resize_all_images_in_folder(target_width: int, output_folder: str = "resized
|
|
779 |
with Image.open(file_path) as img:
|
780 |
# Convert to RGB if needed (handles RGBA, CMYK, etc.)
|
781 |
if img.mode != 'RGB':
|
782 |
-
img = img.convert('RGB')
|
783 |
# Calculate target height maintaining aspect ratio
|
784 |
original_width, original_height = img.size
|
785 |
aspect_ratio = original_height / original_width
|
786 |
-
target_height = int(target_width * aspect_ratio)
|
787 |
# Resize using the reference function
|
788 |
-
resized_img = resize_image_with_aspect_ratio(img, target_width, target_height)
|
789 |
# Create output filename
|
790 |
-
output_filename = output_path / f"{file_prefix}{file_path.name}"
|
791 |
# Save the resized image
|
792 |
-
resized_img.save(output_filename, quality=95)
|
793 |
successful += 1
|
794 |
-
print(f"Successfully resized: {file_path.name}")
|
795 |
except Exception as e:
|
796 |
failed += 1
|
797 |
-
print(f"Failed to resize {file_path.name}: {str(e)}")
|
798 |
|
799 |
print(f"\nResizing complete. Successfully processed: {successful}, Failed: {failed}")
|
800 |
return successful, failed
|
|
|
17 |
)
|
18 |
from utils.file_utils import rename_file_to_lowercase_extension
|
19 |
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
def save_image_to_temp_png(image_source, user_dir: str = None, file_name: str = None):
|
24 |
+
"""
|
25 |
+
Opens an image from a file path, URL, or DataURL and saves it as a PNG in the user's temporary directory.
|
26 |
+
|
27 |
+
Parameters:
|
28 |
+
image_source (str, dict or PIL.Image.Image): The source of the image to open.
|
29 |
+
|
30 |
+
Returns:
|
31 |
+
str: The file path of the saved PNG image in the temporary directory.
|
32 |
+
"""
|
33 |
+
import tempfile
|
34 |
+
import uuid
|
35 |
+
|
36 |
+
# Open the image using the existing utility function
|
37 |
+
img = open_image(image_source)
|
38 |
+
|
39 |
+
# Ensure the image is in a format that supports PNG (convert if necessary)
|
40 |
+
if img.mode not in ("RGB", "RGBA"):
|
41 |
+
img = img.convert("RGBA")
|
42 |
+
|
43 |
+
# Generate a unique filename in the system temporary directory
|
44 |
+
if user_dir is None:
|
45 |
+
user_dir = tempfile.gettempdir()
|
46 |
+
|
47 |
+
if file_name is None:
|
48 |
+
file_name = f"{uuid.uuid4()}.png"
|
49 |
+
|
50 |
+
temp_filepath = os.path.join(user_dir, file_name.lower())
|
51 |
+
os.makedirs(user_dir, exist_ok=True)
|
52 |
+
|
53 |
+
# Save the image as PNG
|
54 |
+
img.save(temp_filepath, format="PNG")
|
55 |
+
|
56 |
+
return temp_filepath
|
57 |
+
|
58 |
def get_image_from_dict(image_path):
|
59 |
if isinstance(image_path, dict) :
|
60 |
if 'composite' in image_path:
|
|
|
82 |
Raises:
|
83 |
Exception: If there is an error opening the image.
|
84 |
"""
|
85 |
+
|
86 |
if isinstance(image_path, Image.Image):
|
87 |
return image_path
|
88 |
+
elif isinstance(image_path, dict):
|
89 |
+
image_path, is_dict = get_image_from_dict(image_path)
|
90 |
+
|
91 |
+
image_path = rename_file_to_lowercase_extension(image_path)
|
92 |
|
93 |
import requests
|
94 |
try:
|
|
|
95 |
# Strip leading and trailing double quotation marks, if present
|
96 |
image_path = image_path.strip('"')
|
97 |
if image_path.startswith('http'):
|
|
|
528 |
|
529 |
##################################################### LUTs ############################################################
|
530 |
|
531 |
+
class Color1DLUT(ImageFilter.Filter):
|
532 |
+
"""Custom filter to apply a 1D LUT to an RGB image."""
|
533 |
+
def __init__(self, table, size):
|
534 |
+
self.table = table
|
535 |
+
self.size = size
|
536 |
+
if size != 256:
|
537 |
+
raise ValueError("Only 1D LUTs with size 256 are supported")
|
538 |
+
# Create a 768-entry LUT (256 for R, G, B) scaled to 0-255
|
539 |
+
lut_r = [int(table[i][0] * 255) for i in range(256)]
|
540 |
+
lut_g = [int(table[i][1] * 255) for i in range(256)]
|
541 |
+
lut_b = [int(table[i][2] * 255) for i in range(256)]
|
542 |
+
self.lut = lut_r + lut_g + lut_b
|
543 |
+
|
544 |
+
def filter(self, image):
|
545 |
+
if image.mode != 'RGB':
|
546 |
+
image = image.convert('RGB')
|
547 |
+
return image.point(self.lut)
|
548 |
+
|
549 |
def is_3dlut_row(row: List[str]) -> bool:
|
550 |
"""
|
551 |
Check if one line in the file has exactly 3 numeric values.
|
|
|
562 |
except ValueError:
|
563 |
return False
|
564 |
|
565 |
+
def read_lut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> Union[ImageFilter.Color3DLUT, Color1DLUT]:
|
566 |
+
"""
|
567 |
+
Read a LUT from a .cube file and return a filter object.
|
568 |
+
|
569 |
+
Detects whether the file contains a 1D or 3D LUT based on keywords
|
570 |
+
"LUT_1D_SIZE" or "LUT_3D_SIZE". Initially assumes a 3D LUT if no size
|
571 |
+
keyword is specified.
|
572 |
|
573 |
+
Args:
|
574 |
+
path_lut: Path to the LUT file (string or os.PathLike).
|
575 |
+
num_channels: Number of color channels in the LUT (default is 3).
|
576 |
+
|
577 |
+
Returns:
|
578 |
+
ImageFilter.Color3DLUT for 3D LUTs or Color1DLUT for 1D LUTs.
|
579 |
+
|
580 |
+
Raises:
|
581 |
+
FileNotFoundError: If the file does not exist.
|
582 |
+
ValueError: If the LUT data is invalid or size mismatches.
|
583 |
+
"""
|
584 |
+
with open(path_lut) as f:
|
585 |
+
lines = f.read().splitlines()
|
586 |
+
|
587 |
+
lut_type = "3D" # Initially assume 3D LUT
|
588 |
+
size = None
|
589 |
+
table = []
|
590 |
+
|
591 |
+
# Parse the file
|
592 |
+
for line in lines:
|
593 |
+
line = line.strip()
|
594 |
+
if line.startswith("#") or not line:
|
595 |
+
continue # Skip comments and empty lines
|
596 |
+
parts = line.split()
|
597 |
+
if parts[0] == "LUT_3D_SIZE":
|
598 |
+
size = int(parts[1])
|
599 |
+
lut_type = "3D"
|
600 |
+
elif parts[0] == "LUT_1D_SIZE":
|
601 |
+
size = int(parts[1])
|
602 |
+
lut_type = "1D"
|
603 |
+
elif is_3dlut_row(parts):
|
604 |
+
table.append(tuple(float(val) for val in parts))
|
605 |
+
|
606 |
+
# Process based on LUT type
|
607 |
+
if lut_type == "3D":
|
608 |
+
if size is None:
|
609 |
+
# Calculate size assuming 3D LUT
|
610 |
+
len_table = len(table)
|
611 |
+
if len_table == 0:
|
612 |
+
raise ValueError("No valid LUT data found")
|
613 |
+
size = round(len_table ** (1 / 3))
|
614 |
+
if size ** 3 != len_table:
|
615 |
+
raise ValueError(f"Number of table entries {len_table} is not a perfect cube")
|
616 |
+
elif len(table) != size ** 3:
|
617 |
+
raise ValueError(f"Expected {size**3} entries for 3D LUT, got {len(table)}")
|
618 |
+
return ImageFilter.Color3DLUT(size, table, channels=num_channels)
|
619 |
+
else: # lut_type == "1D"
|
620 |
+
if size is None:
|
621 |
+
raise ValueError("LUT_1D_SIZE not specified for 1D LUT")
|
622 |
+
if len(table) != size:
|
623 |
+
raise ValueError(f"Expected {size} entries for 1D LUT, got {len(table)}")
|
624 |
+
return Color1DLUT(table, size)
|
625 |
+
|
626 |
+
def read_3Dlut(path_lut: Union[str, os.PathLike], num_channels: int = 3) -> ImageFilter.Color3DLUT:
|
627 |
"""
|
628 |
Read LUT from a raw file.
|
629 |
|
|
|
680 |
lut_example_image = open_image(default_lut_example_img)
|
681 |
return lut_example_image
|
682 |
|
683 |
+
def apply_1d_lut(image, lut_file):
|
684 |
+
# Read the 1D LUT
|
685 |
+
with open(lut_file) as f:
|
686 |
+
lines = f.read().splitlines()
|
687 |
+
table = []
|
688 |
+
for line in lines:
|
689 |
+
if not line.startswith(("#", "LUT", "TITLE", "DOMAIN")) and line.strip():
|
690 |
+
values = [float(v) for v in line.split()]
|
691 |
+
table.append(tuple(values))
|
692 |
+
|
693 |
+
# Convert image to grayscale
|
694 |
+
if image.mode != 'L':
|
695 |
+
image = image.convert('L')
|
696 |
+
img_array = np.array(image) / 255.0 # Normalize to [0, 1]
|
697 |
+
|
698 |
+
# Map grayscale values to colors
|
699 |
+
lut_size = len(table)
|
700 |
+
indices = (img_array * (lut_size - 1)).astype(int)
|
701 |
+
colors = np.array(table)[indices]
|
702 |
+
|
703 |
+
# Create RGB image
|
704 |
+
rgb_image = Image.fromarray((colors * 255).astype(np.uint8), mode='RGB')
|
705 |
+
return rgb_image
|
706 |
|
707 |
|
|
|
|
|
|
|
|
|
708 |
|
|
|
|
|
709 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
710 |
|
711 |
def apply_lut_to_image_path(lut_filename: str, image_path: str) -> tuple[Image, str]:
|
712 |
"""
|
|
|
720 |
Returns:
|
721 |
tuple: A tuple containing the PIL Image object with the LUT applied and the new image path as a string.
|
722 |
"""
|
723 |
+
import gradio as gr
|
724 |
+
|
725 |
+
img_lut = None
|
726 |
if image_path is None:
|
727 |
raise UserWarning("No image provided.")
|
728 |
return None, None
|
729 |
+
|
730 |
+
# Split the path into directory and filename
|
731 |
+
directory, file_name = os.path.split(image_path)
|
732 |
+
lut_directory, lut_file_name = os.path.split(lut_filename)
|
733 |
+
|
734 |
+
# Split the filename into name and extension
|
735 |
+
name, ext = os.path.splitext(file_name)
|
736 |
+
lut_name, lut_ext = os.path.splitext(lut_file_name)
|
737 |
+
|
738 |
+
# Convert the extension to lowercase
|
739 |
+
new_ext = ext.lower()
|
740 |
+
|
741 |
path = Path(image_path)
|
742 |
img = open_image(image_path)
|
743 |
if not ((path.suffix.lower() == '.png' and img.mode == 'RGBA')):
|
|
|
752 |
if image_path != new_image_path:
|
753 |
delete_image(image_path)
|
754 |
else:
|
755 |
+
# ensure the file extension is lower_case, otherwise leave as is
|
756 |
+
new_filename = name + new_ext
|
757 |
+
new_image_path = os.path.join(directory, new_filename)
|
758 |
+
# Apply the LUT to the image
|
759 |
+
if (lut_filename is not None and img is not None):
|
760 |
try:
|
761 |
+
img_lut = apply_lut(img, lut_filename)
|
762 |
except Exception as e:
|
763 |
print(f"BAD LUT: Error applying LUT {str(e)}.")
|
764 |
+
if img_lut is not None:
|
765 |
+
new_filename = name + "_"+ lut_name + new_ext
|
766 |
+
new_image_path = os.path.join(directory, new_filename)
|
767 |
+
delete_image(image_path)
|
768 |
+
img = img_lut
|
769 |
+
img.save(new_image_path, format='PNG')
|
770 |
+
print(f"Image with LUT saved as {new_image_path}")
|
771 |
+
return img, gr.update(value=str(new_image_path))
|
772 |
|
773 |
############################################# RGBA ###########################################################
|
774 |
+
def convert_rgb_to_rgba_safe(image: Image) -> Image:
|
775 |
+
"""
|
776 |
+
Converts an RGB image to RGBA by adding an alpha channel.
|
777 |
+
Ensures that the original image remains unaltered.
|
778 |
+
|
779 |
+
Parameters:
|
780 |
+
image (PIL.Image.Image): The RGB image to convert.
|
781 |
+
|
782 |
+
Returns:
|
783 |
+
PIL.Image.Image: The converted RGBA image.
|
784 |
+
"""
|
785 |
+
if image.mode != 'RGB':
|
786 |
+
if image.mode == 'RGBA':
|
787 |
+
return image
|
788 |
+
elif image.mode == 'P':
|
789 |
+
# Convert palette image to RGBA
|
790 |
+
image = image.convert('RGB')
|
791 |
+
else:
|
792 |
+
raise ValueError("Unsupported image mode for conversion to RGBA.")
|
793 |
+
# Create a copy of the image to avoid modifying the original
|
794 |
+
rgba_image = image.copy()
|
795 |
+
# Optionally, set a default alpha value (e.g., fully opaque)
|
796 |
+
alpha = Image.new('L', rgba_image.size, 255) # 255 for full opacity
|
797 |
+
rgba_image.putalpha(alpha)
|
798 |
+
return rgba_image
|
799 |
|
800 |
# Example usage
|
801 |
# convert_jpg_to_rgba('input.jpg', 'output.png')
|
|
|
821 |
|
822 |
# Check if the input file exists
|
823 |
if not input_path.exists():
|
824 |
+
#if file was renamed to lower case, update the input path
|
825 |
+
input_path = output_path
|
826 |
+
if not input_path.exists():
|
827 |
+
raise FileNotFoundError(f"The file {input_path} does not exist.")
|
828 |
|
829 |
# Check file extension first to skip unnecessary processing
|
830 |
if input_path.suffix.lower() not in ('.jpg', '.jpeg'):
|
831 |
print(f"Skipping conversion: {input_path} is not a JPG or JPEG file.")
|
832 |
+
return None, None
|
833 |
|
834 |
print(f"Converting to PNG: {input_path} is a JPG or JPEG file.")
|
835 |
|
|
|
949 |
with Image.open(file_path) as img:
|
950 |
# Convert to RGB if needed (handles RGBA, CMYK, etc.)
|
951 |
if img.mode != 'RGB':
|
952 |
+
img = img.convert('RGB')
|
953 |
# Calculate target height maintaining aspect ratio
|
954 |
original_width, original_height = img.size
|
955 |
aspect_ratio = original_height / original_width
|
956 |
+
target_height = int(target_width * aspect_ratio)
|
957 |
# Resize using the reference function
|
958 |
+
resized_img = resize_image_with_aspect_ratio(img, target_width, target_height)
|
959 |
# Create output filename
|
960 |
+
output_filename = output_path / f"{file_prefix}{file_path.name.lower()}"
|
961 |
# Save the resized image
|
962 |
+
resized_img.save(output_filename, quality=95)
|
963 |
successful += 1
|
964 |
+
print(f"Successfully resized: {file_path.name.lower()}")
|
965 |
except Exception as e:
|
966 |
failed += 1
|
967 |
+
print(f"Failed to resize {file_path.name.lower()}: {str(e)}")
|
968 |
|
969 |
print(f"\nResizing complete. Successfully processed: {successful}, Failed: {failed}")
|
970 |
return successful, failed
|