Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added camera style presets and controls #1797

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions camera_presets/angles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[
{
"name": "Overhead Shot",
"prompt": "{prompt}, (overhead shot:{weight})",
"default": "1.5"
},
{
"name": "Top Down",
"prompt": "{prompt}, (top down view:{weight})",
"default": "1.5"
},
{
"name": "Bird's Eye",
"prompt": "{prompt}, (bird's eye view:{weight})",
"default": "1.5"
},
{
"name": "High Angle",
"prompt": "{prompt}, (high angle:{weight})",
"default": "1.5"
},
{
"name": "Slightly Above",
"prompt": "{prompt}, (slightly above:{weight})",
"default": "1.5"
},
{
"name": "Straight On",
"prompt": "{prompt}, (straight on:{weight})",
"default": "1.5"
},
{
"name": "Hero View",
"prompt": "{prompt}, (hero view:{weight})",
"default": "1.5"
},
{
"name": "Low View",
"prompt": "{prompt}, (low view:{weight})",
"default": "1.5"
},
{
"name": "Worm Eye View",
"prompt": "{prompt}, (worm's eye view:{weight})",
"default": "1.5"
},
{
"name": "Selfie",
"prompt": "{prompt}, (selfie:{weight})",
"default": "1.5"
}
]
62 changes: 62 additions & 0 deletions camera_presets/distances.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
[
{
"name": "Extreme Close-up",
"prompt": "{prompt}, (extreme close-up:{weight})",
"default": "1.5"
},
{
"name": "Close-up",
"prompt": "{prompt}, (close-up:{weight})",
"default": "1.5"
},
{
"name": "Medium Close-up",
"prompt": "{prompt}, (medium close-up:{weight})",
"default": "1.5"
},
{
"name": "Medium Shot",
"prompt": "{prompt}, (medium shot:{weight})",
"default": "1.5"
},
{
"name": "Long Shot",
"prompt": "{prompt}, (long shot:{weight})",
"default": "1.5"
},
{
"name": "Full Shot",
"prompt": "{prompt}, (full shot:{weight})",
"default": "1.5"
},
{
"name": "Medium Full Shot",
"prompt": "{prompt}, (medium full shot:{weight})",
"default": "1.5"
},
{
"name": "Establishing Shot",
"prompt": "{prompt}, (establishing shot:{weight})",
"default": "1.5"
},
{
"name": "Point of View",
"prompt": "{prompt}, (point-of-view:{weight})",
"default": "1.5"
},
{
"name": "Cowboy Shot",
"prompt": "{prompt}, (cowboy shot:{weight})",
"default": "1.5"
},
{
"name": "Upper Body",
"prompt": "{prompt}, (upper body:{weight})",
"default": "1.5"
},
{
"name": "Full Body",
"prompt": "{prompt}, (full body:{weight})",
"default": "1.5"
}
]
18 changes: 17 additions & 1 deletion modules/async_worker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import threading

from modules.camera import apply_camera_preset


class AsyncTask:
def __init__(self, args):
Expand Down Expand Up @@ -122,6 +124,10 @@ def handler(async_task):
prompt = args.pop()
negative_prompt = args.pop()
style_selections = args.pop()
camera_angle = args.pop()
camera_angle_weight = args.pop()
camera_distance = args.pop()
camera_distance_weight = args.pop()
performance_selection = args.pop()
aspect_ratios_selection = args.pop()
image_number = args.pop()
Expand Down Expand Up @@ -162,6 +168,8 @@ def handler(async_task):
use_expansion = False

use_style = len(style_selections) > 0
use_camera_angle = len(camera_angle) > 0
use_camera_distance = len(camera_distance) > 0

if base_model_name == refiner_model_name:
print(f'Refiner disabled because base model and refiner are same.')
Expand Down Expand Up @@ -388,6 +396,12 @@ def handler(async_task):
positive_basic_workloads = []
negative_basic_workloads = []

if use_camera_angle:
task_prompt = apply_camera_preset(preset='angle', name=camera_angle, weight=camera_angle_weight, positive=task_prompt)

if use_camera_distance:
task_prompt = apply_camera_preset(preset='distance', name=camera_distance, weight=camera_distance_weight, positive=task_prompt)

if use_style:
for s in style_selections:
p, n = apply_style(s, positive=task_prompt)
Expand Down Expand Up @@ -775,11 +789,13 @@ def callback(step, x0, x, total_steps, y):
imgs = [inpaint_worker.current_task.post_process(x) for x in imgs]

for x in imgs:
camera_settings = str([x for x in [camera_angle, camera_distance] if len(x) > 0])
camera_settings = len(camera_settings) > 0 and " | " + camera_settings or ''
d = [
('Prompt', task['log_positive_prompt']),
('Negative Prompt', task['log_negative_prompt']),
('Fooocus V2 Expansion', task['expansion']),
('Styles', str(raw_style_selections)),
('Styles', str(raw_style_selections) + camera_settings),
('Performance', performance_selection),
('Resolution', str((width, height))),
('Sharpness', sharpness),
Expand Down
98 changes: 98 additions & 0 deletions modules/camera.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import json
import os

from modules.util import normalize_key


def load_presets(file_path):
"""
This function loads presets from a given file path. It reads the file, parses the JSON content, and stores the presets in a dictionary.
Each preset is a tuple containing a prompt and a weight.

Args:
file_path (str): The path to the file containing the presets.

Returns:
dict: A dictionary where the keys are the normalized names of the presets and the values are tuples containing the prompt and the weight of each preset.
"""
presets = {}
try:
with open(file_path, encoding='utf-8') as f:
for preset in json.load(f):
key = normalize_key(preset['name'])
prompt = preset.get('prompt', '')
weight = preset.get('default', 1)
presets[key] = (prompt, weight)
except Exception as e:
print(str(e))
print(f'Failed to load camera presets file {file_path}')
return presets


# Paths to the files containing the camera angles and distances presets
angles_path = os.path.join(os.path.dirname(__file__), '../camera_presets/angles.json')
distances_path = os.path.join(os.path.dirname(__file__), '../camera_presets/distances.json')

# Load the camera angles and distances presets
camera_angles = load_presets(angles_path)
camera_distances = load_presets(distances_path)

# Get the names of the camera angles and distances presets for use in the UI
camera_angle_names = list(camera_angles.keys())
camera_distance_names = list(camera_distances.keys())


def get_preset_weight(preset, name):
"""
This function retrieves the weight of a given preset.

Args:
preset (str): The type of the preset ('angle' or 'distance').
name (str): The name of the preset.

Returns:
str: The weight of the preset.
"""
_, w = _get_preset(preset, name)
return w


def apply_camera_preset(preset, name, weight, positive):
"""
This function applies a camera preset. It replaces the placeholders in the presets' prompt with the given weight and positive value.

Args:
preset (str): The type of the preset ('angle' or 'distance').
name (str): The name of the preset.
weight (str): The weight to replace the '{weight}' placeholder in the presets' prompt.
positive (str): The value to replace the '{prompt}' placeholder in the presets' prompt.

Returns:
str: The presets' prompt with the placeholders replaced by the given weight and positive value.
"""
p, _ = _get_preset(preset, name)
return p.replace('{weight}', str(weight)).replace('{prompt}', positive)


def _get_preset(preset, name):
"""
This function retrieves a preset from the appropriate dictionary based on the preset type.

Args:
preset (str): The type of the preset ('angle' or 'distance').
name (str): The name of the preset.

Returns:
tuple: The preset (a tuple containing the prompt and the weight).

Raises:
ValueError: If the preset type is not 'angle' or 'distance'.
"""
if preset == 'angle':
camera_presets = camera_angles
elif preset == 'distance':
camera_presets = camera_distances
else:
raise ValueError(f'Unknown preset type : {preset}')

return camera_presets[name]
11 changes: 4 additions & 7 deletions modules/sdxl_styles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import re
import json

from modules.util import get_files_from_folder
from modules.util import get_files_from_folder, normalize_key


# cannot use modules.config - validators causing circular imports
Expand All @@ -11,11 +11,8 @@
wildcards_max_bfs_depth = 64


def normalize_key(k):
k = k.replace('-', ' ')
words = k.split(' ')
words = [w[:1].upper() + w[1:].lower() for w in words]
k = ' '.join(words)
def normalize_style_key(k):
k = normalize_key(k)
k = k.replace('3d', '3D')
k = k.replace('Sai', 'SAI')
k = k.replace('Mre', 'MRE')
Expand All @@ -41,7 +38,7 @@ def normalize_key(k):
try:
with open(os.path.join(styles_path, styles_file), encoding='utf-8') as f:
for entry in json.load(f):
name = normalize_key(entry['name'])
name = normalize_style_key(entry['name'])
prompt = entry['prompt'] if 'prompt' in entry else ''
negative_prompt = entry['negative_prompt'] if 'negative_prompt' in entry else ''
styles[name] = (prompt, negative_prompt)
Expand Down
18 changes: 18 additions & 0 deletions modules/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,21 @@ def get_files_from_folder(folder_path, exensions=None, name_filter=None):
filenames.append(path)

return sorted(filenames, key=lambda x: -1 if os.sep in x else 1)


def normalize_key(k):
"""
This function takes a string key as input and transforms it to be more human.

Args:
k (str): The string to be normalized.

Returns:
str: The normalized string.
"""
# Replace hyphens with spaces
# Split the string into words
# Capitalize the first letter of each word and make the rest of the letters lowercase
# Join the words back together with spaces in between

return ' '.join(w.capitalize() for w in k.replace('-', ' ').split())
27 changes: 27 additions & 0 deletions webui.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import copy

from modules.sdxl_styles import legal_style_names
from modules.camera import camera_angle_names, camera_distance_names, get_preset_weight
from modules.private_logger import get_current_html_path
from modules.ui_gradio_extensions import reload_javascript
from modules.auth import auth_enabled, check_auth
Expand Down Expand Up @@ -230,6 +231,26 @@ def ip_advance_checked(x):
value=modules.config.default_aspect_ratio, info='width × height',
elem_classes='aspect_ratios')
image_number = gr.Slider(label='Image Number', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number)

with gr.Group():
# TODO: refactor UI
with gr.Row():
camera_angle = gr.Dropdown(label=f'Camera Angle',
choices=camera_angle_names, value=None, scale=2)
camera_angle_weight = gr.Number(label='Weight', minimum=0, maximum=2, step=0.01, value=0, scale=1)

camera_angle.change(lambda x: camera_angle_weight.update(value=get_preset_weight('angle', x)),
inputs=[camera_angle], outputs=[camera_angle_weight],
queue=False, show_progress=False)

with gr.Row():
camera_distance = gr.Dropdown(label=f'Camera Distance', choices=camera_distance_names, value=None, scale=2)
camera_distance_weight = gr.Number(label='Weight', minimum=0, maximum=2, step=0.01, value=0, scale=1)

camera_distance.change(lambda x: camera_distance_weight.update(value=get_preset_weight('distance', x)),
inputs=[camera_distance], outputs=[camera_distance_weight],
queue=False, show_progress=False)

negative_prompt = gr.Textbox(label='Negative Prompt', show_label=True, placeholder="Type prompt here.",
info='Describing what you do not want to see.', lines=2,
elem_id='negative_prompt',
Expand Down Expand Up @@ -321,6 +342,7 @@ def refresh_seed(r, seed_string):

with gr.Row():
model_refresh = gr.Button(label='Refresh', value='\U0001f504 Refresh All Files', variant='secondary', elem_classes='refresh_button')

with gr.Tab(label='Advanced'):
guidance_scale = gr.Slider(label='Guidance Scale', minimum=1.0, maximum=30.0, step=0.01,
value=modules.config.default_cfg_scale,
Expand Down Expand Up @@ -521,6 +543,7 @@ def inpaint_mode_change(mode):

ctrls = [
prompt, negative_prompt, style_selections,
camera_angle, camera_angle_weight, camera_distance, camera_distance_weight,
performance_selection, aspect_ratios_selection, image_number, image_seed, sharpness, guidance_scale
]

Expand Down Expand Up @@ -559,6 +582,10 @@ def parse_meta(raw_prompt_txt, is_generating):
prompt,
negative_prompt,
style_selections,
camera_angle,
camera_angle_weight,
camera_distance,
camera_distance_weight,
performance_selection,
aspect_ratios_selection,
overwrite_width,
Expand Down