mirror of
https://github.com/vladmandic/automatic
synced 2026-09-19 17:24:32 +02:00
add xyz and script support to control api
Signed-off-by: vladmandic <mandic00@live.com>
This commit is contained in:
+8
-3
@@ -1,12 +1,12 @@
|
||||
# Change Log for SD.Next
|
||||
|
||||
## Update for 2025-11-22
|
||||
## Update for 2025-11-23
|
||||
|
||||
### TBD
|
||||
|
||||
Merge commit: `f903a36d9`
|
||||
|
||||
### Highlights for 2025-11-22
|
||||
### Highlights for 2025-11-23
|
||||
|
||||
New native [kanvas](https://vladmandic.github.io/sdnext-docs/Kanvas/) module for image manipulation that fully replaces img2img, inpaint and outpaint controls
|
||||
And a first cloud model with **Google's Nano Banana** *2.5 Flash and 3.0 Pro* plus new **Photoroom PRX** model
|
||||
@@ -15,7 +15,7 @@ And a first cloud model with **Google's Nano Banana** *2.5 Flash and 3.0 Pro* pl
|
||||
|
||||
[ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) | [Sponsor](https://github.com/sponsors/vladmandic)
|
||||
|
||||
### Details for 2025-11-22
|
||||
### Details for 2025-11-23
|
||||
|
||||
- **Models**
|
||||
- **Google Gemini Nano Banana** [2.5 Flash](https://blog.google/products/gemini/gemini-nano-banana-examples/) and [3.0 Pro](https://deepmind.google/models/gemini-image/pro/)
|
||||
@@ -36,6 +36,10 @@ And a first cloud model with **Google's Nano Banana** *2.5 Flash and 3.0 Pro* pl
|
||||
- **amdgpu**: prefer rocm-on-windows over zluda
|
||||
- **amdgpu**: improve rocm-on-windows installer
|
||||
- **sdnq**: improve dequant logic
|
||||
- **API**
|
||||
- `/control` endpoint is now fully compatible with scripts
|
||||
- `/control` additional params to to control *xyz grid*
|
||||
see `cli/api-xyz.py` for simple example
|
||||
- **Internal**
|
||||
- sdnq: multiple improvements to quantization and dequantization logic
|
||||
- torch: update to `torch==2.9.1` for *cuda, ipex, openvino, rocm* backends
|
||||
@@ -51,6 +55,7 @@ And a first cloud model with **Google's Nano Banana** *2.5 Flash and 3.0 Pro* pl
|
||||
- sdnq: unconditional register on startup
|
||||
- python: start work on future-proofing for modern python versions, thanks @awsr
|
||||
- nunchaku: update to `1.0.2`
|
||||
- lint: add rules for run-on-windows
|
||||
- **Fixes**
|
||||
- xyz-grid: improve parsing of axis lists, thanks @awsr
|
||||
- hires: strength save/load in metadata, thanks @awsr
|
||||
|
||||
@@ -101,7 +101,6 @@ Anything marked with **(!!!)** means a change *will* eventually be required.
|
||||
|
||||
> npm run todo
|
||||
|
||||
- control: support scripts via api
|
||||
- fc: autodetect distilled based on model
|
||||
- fc: autodetect tensor format based on model
|
||||
- hypertile: vae breaks when using non-standard sizes
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python
|
||||
# example: api-control.py --prompt "anime girl" --control "Canny:Canny:1.0:0.1:0.9:/home/vlado/generative/Samples/anime1.jpg,None:Depth:0.9:0.0:1.0:/home/vlado/generative/Samples/anime1.jpg" --hires --detailer --output /tmp/anime.jpg
|
||||
import os
|
||||
import io
|
||||
import time
|
||||
import base64
|
||||
import logging
|
||||
import argparse
|
||||
import requests
|
||||
import urllib3
|
||||
from PIL import Image
|
||||
|
||||
sd_url = os.environ.get('SDAPI_URL', "http://127.0.0.1:7860")
|
||||
sd_username = os.environ.get('SDAPI_USR', None)
|
||||
sd_password = os.environ.get('SDAPI_PWD', None)
|
||||
|
||||
logging.basicConfig(level = logging.INFO, format = '%(asctime)s %(levelname)s: %(message)s')
|
||||
log = logging.getLogger(__name__)
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
options = {
|
||||
"save_images": False,
|
||||
"send_images": True,
|
||||
}
|
||||
|
||||
|
||||
def auth():
|
||||
if sd_username is not None and sd_password is not None:
|
||||
return requests.auth.HTTPBasicAuth(sd_username, sd_password)
|
||||
return None
|
||||
|
||||
|
||||
def post(endpoint: str, dct: dict = None):
|
||||
req = requests.post(f'{sd_url}{endpoint}', json = dct, timeout=300, verify=False, auth=auth())
|
||||
if req.status_code != 200:
|
||||
return { 'error': req.status_code, 'reason': req.reason, 'url': req.url }
|
||||
else:
|
||||
return req.json()
|
||||
|
||||
|
||||
def generate(args): # pylint: disable=redefined-outer-name
|
||||
t0 = time.time()
|
||||
options['prompt'] = args.prompt
|
||||
options['negative_prompt'] = args.negative
|
||||
|
||||
options['xyz'] = {
|
||||
'draw_legend': args.legend,
|
||||
'include_grid': args.grid,
|
||||
'include_subgrids': args.subgrids,
|
||||
'include_images': args.images,
|
||||
'include_time': args.time,
|
||||
'include_text': args.text,
|
||||
}
|
||||
if args.x_type and args.x_values:
|
||||
options['xyz']['x_type'] = args.x_type
|
||||
options['xyz']['x_values'] = args.x_values
|
||||
|
||||
if args.y_type and args.y_values:
|
||||
options['xyz']['y_type'] = args.y_type
|
||||
options['xyz']['y_values'] = args.y_values
|
||||
|
||||
if args.z_type and args.z_values:
|
||||
options['xyz']['z_type'] = args.z_type
|
||||
options['xyz']['z_values'] = args.z_values
|
||||
|
||||
data = post('/sdapi/v1/control', options)
|
||||
t1 = time.time()
|
||||
if 'info' in data:
|
||||
log.info(f'info: {data["info"]}')
|
||||
|
||||
def get_image(encoded, output):
|
||||
if not isinstance(encoded, list):
|
||||
return
|
||||
for i in range(len(encoded)):
|
||||
b64 = encoded[i].split(',',1)[0]
|
||||
info = data['info']
|
||||
image = Image.open(io.BytesIO(base64.b64decode(b64)))
|
||||
log.info(f'received image: size={image.size} time={t1-t0:.2f} info="{info}"')
|
||||
if output:
|
||||
image.save(output)
|
||||
log.info(f'image saved: size={image.size} filename={output}')
|
||||
|
||||
if 'images' in data:
|
||||
get_image(data['images'], args.output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description = 'api-control')
|
||||
parser.add_argument('--output', required=False, default=None, help='output filename')
|
||||
parser.add_argument('--prompt', required=False, default='', help='prompt text')
|
||||
parser.add_argument('--negative', required=False, default='', help='negative prompt text')
|
||||
parser.add_argument('--x-type', required=False, default=None, help='x axis type')
|
||||
parser.add_argument('--y-type', required=False, default=None, help='y axis type')
|
||||
parser.add_argument('--z-type', required=False, default=None, help='z axis type')
|
||||
parser.add_argument('--x-values', required=False, default=None, help='x axis values')
|
||||
parser.add_argument('--y-values', required=False, default=None, help='y axis values')
|
||||
parser.add_argument('--z-values', required=False, default=None, help='z axis values')
|
||||
parser.add_argument('--legend', required=False, default=True, help='Draw legend')
|
||||
parser.add_argument('--grid', required=False, default=True, help='Include grid')
|
||||
parser.add_argument('--subgrids', required=False, default=False, help='Include subgrids')
|
||||
parser.add_argument('--images', required=False, default=True, help='Include images')
|
||||
parser.add_argument('--time', required=False, default=True, help='Include time')
|
||||
parser.add_argument('--text', required=False, default=True, help='Include text')
|
||||
args = parser.parse_args()
|
||||
log.info(f'api-control: {args}')
|
||||
generate(args)
|
||||
+1
-1
@@ -755,7 +755,7 @@ def install_rocm_zluda():
|
||||
zluda_installer.load()
|
||||
except Exception as e:
|
||||
log.warning(f'Failed to load ZLUDA: {e}')
|
||||
else: # TODO install: switch to pytorch source when it becomes available
|
||||
else: # TODO rocm: switch to pytorch source when it becomes available
|
||||
if device is None:
|
||||
log.warning('No ROCm agent was found. Please make sure that graphics driver is installed and up to date.')
|
||||
if isinstance(rocm.environment, rocm.PythonPackageEnvironment):
|
||||
|
||||
+47
-7
@@ -18,6 +18,21 @@ class ItemControl(BaseModel):
|
||||
override: str = Field(title="Override image", default=None, description="")
|
||||
|
||||
|
||||
class ItemXYZ(BaseModel):
|
||||
x_type: str = Field(title="X axis values", default='')
|
||||
x_values: str = Field(title="X axis values", default='')
|
||||
y_type: str = Field(title="Y axis values", default='')
|
||||
y_values: str = Field(title="Y axis values", default='')
|
||||
z_type: str = Field(title="Z axis values", default='')
|
||||
z_values: str = Field(title="Z axis values", default='')
|
||||
draw_legend: bool = Field(title="Draw legend", default=True)
|
||||
include_grid: bool = Field(title="Include grid", default=True)
|
||||
include_subgrids: bool = Field(title="Include subgrids", default=False)
|
||||
include_images: bool = Field(title="Include images", default=False)
|
||||
include_time: bool = Field(title="Include time", default=False)
|
||||
include_text: bool = Field(title="Include text", default=False)
|
||||
|
||||
|
||||
ReqControl = models.create_model_from_signature(
|
||||
func = run.control_run,
|
||||
model_name = "StableDiffusionProcessingControl",
|
||||
@@ -31,6 +46,7 @@ ReqControl = models.create_model_from_signature(
|
||||
{"key": "ip_adapter", "type": Optional[List[models.ItemIPAdapter]], "default": None, "exclude": True},
|
||||
{"key": "face", "type": Optional[models.ItemFace], "default": None, "exclude": True},
|
||||
{"key": "control", "type": Optional[List[ItemControl]], "default": [], "exclude": True},
|
||||
{"key": "xyz", "type": Optional[ItemXYZ], "default": None, "exclude": True},
|
||||
# {"key": "extra", "type": Optional[dict], "default": {}, "exclude": True},
|
||||
]
|
||||
)
|
||||
@@ -54,13 +70,13 @@ class APIControl():
|
||||
def sanitize_args(self, args: dict):
|
||||
args = vars(args)
|
||||
args.pop('sampler_name', None)
|
||||
args.pop('script_name', None)
|
||||
args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them
|
||||
args.pop('alwayson_scripts', None)
|
||||
args.pop('face', None)
|
||||
args.pop('face_id', None)
|
||||
args.pop('ip_adapter', None)
|
||||
args.pop('save_images', None)
|
||||
args['override_script_name'] = args.pop('script_name', None)
|
||||
args['override_script_args'] = args.pop('script_args', None)
|
||||
return args
|
||||
|
||||
def sanitize_b64(self, request):
|
||||
@@ -75,6 +91,8 @@ class APIControl():
|
||||
sanitize_str(script_obj["args"])
|
||||
if hasattr(request, "script_args") and request.script_args:
|
||||
sanitize_str(request.script_args)
|
||||
if hasattr(request, 'override_script_args') and request.override_script_args:
|
||||
request.pop('override_script_args', None)
|
||||
|
||||
def prepare_face_module(self, req):
|
||||
if hasattr(req, "face") and req.face and not req.script_name and (not req.alwayson_scripts or "face" not in req.alwayson_scripts.keys()):
|
||||
@@ -97,6 +115,21 @@ class APIControl():
|
||||
]
|
||||
del req.face
|
||||
|
||||
def prepare_xyz_grid(self, req):
|
||||
if hasattr(req, "xyz") and req.xyz:
|
||||
req.script_name = "xyz grid"
|
||||
req.script_args = [
|
||||
req.xyz.x_type, req.xyz.x_values, '',
|
||||
req.xyz.y_type, req.xyz.y_values, '',
|
||||
req.xyz.z_type, req.xyz.z_values, '',
|
||||
False, # csv_mode
|
||||
req.xyz.draw_legend,
|
||||
False, # no_fixed_seeds
|
||||
req.xyz.include_grid, req.xyz.include_subgrids, req.xyz.include_images,
|
||||
req.xyz.include_time, req.xyz.include_text,
|
||||
]
|
||||
del req.xyz
|
||||
|
||||
def prepare_ip_adapter(self, request):
|
||||
if hasattr(request, "ip_adapter") and request.ip_adapter:
|
||||
args = { 'ip_adapter_names': [], 'ip_adapter_scales': [], 'ip_adapter_crops': [], 'ip_adapter_starts': [], 'ip_adapter_ends': [], 'ip_adapter_images': [], 'ip_adapter_masks': [] }
|
||||
@@ -150,18 +183,22 @@ class APIControl():
|
||||
del req.control
|
||||
|
||||
def post_control(self, req: ReqControl):
|
||||
self.prepare_face_module(req)
|
||||
requested = req.control
|
||||
self.prepare_face_module(req)
|
||||
self.prepare_control(req)
|
||||
self.prepare_xyz_grid(req)
|
||||
|
||||
# prepare scripts
|
||||
|
||||
# prepare args
|
||||
args = req.copy(update={ # Override __init__ params
|
||||
args = req.copy(update={ # Override __init__ params
|
||||
"sampler_index": processing_helpers.get_sampler_index(req.sampler_name),
|
||||
"is_generator": True,
|
||||
"inputs": [helpers.decode_base64_to_image(x) for x in req.inputs] if req.inputs else None,
|
||||
"inits": [helpers.decode_base64_to_image(x) for x in req.inits] if req.inits else None,
|
||||
"mask": helpers.decode_base64_to_image(req.mask) if req.mask else None,
|
||||
})
|
||||
|
||||
args = self.sanitize_args(args)
|
||||
send_images = args.pop('send_images', True)
|
||||
|
||||
@@ -171,10 +208,13 @@ class APIControl():
|
||||
output_images = []
|
||||
output_processed = []
|
||||
output_info = ''
|
||||
# TODO control: support scripts via api
|
||||
# init script args, call scripts.script_control.run, call scripts.script_control.after
|
||||
run.control_set({ 'do_not_save_grid': not req.save_images, 'do_not_save_samples': not req.save_images, **self.prepare_ip_adapter(req) })
|
||||
run.control_set({
|
||||
'do_not_save_grid': not req.save_images,
|
||||
'do_not_save_samples': not req.save_images,
|
||||
**self.prepare_ip_adapter(req),
|
||||
})
|
||||
run.control_set(getattr(req, "extra", {}))
|
||||
# run
|
||||
res = run.control_run(**args)
|
||||
for item in res:
|
||||
if len(item) > 0 and (isinstance(item[0], list) or item[0] is None): # output_images
|
||||
|
||||
@@ -70,6 +70,8 @@ def save_image(image, fn, ext):
|
||||
image = image.convert("RGB")
|
||||
elif image.mode == 'I;16':
|
||||
image = image.point(lambda p: p * 0.0038910505836576).convert("L")
|
||||
elif image.mode == 'P':
|
||||
image = image.convert("RGB")
|
||||
exif_bytes = piexif.dump({ "Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(parameters or "", encoding="unicode") } })
|
||||
image.save(fn, format=image_format, quality=shared.opts.jpeg_quality, exif=exif_bytes)
|
||||
elif image_format == 'WEBP':
|
||||
|
||||
@@ -442,7 +442,7 @@ class ResGPU(BaseModel): # definition of http response
|
||||
|
||||
# helper function
|
||||
|
||||
def create_model_from_signature(func: Callable, model_name: str, base_model: Type[BaseModel] = BaseModel, additional_fields: List = [], exclude_fields: List[str] = []):
|
||||
def create_model_from_signature(func: Callable, model_name: str, base_model: Type[BaseModel] = BaseModel, additional_fields: List = [], exclude_fields: List[str] = []) -> type[BaseModel]:
|
||||
from PIL import Image
|
||||
|
||||
class Config:
|
||||
|
||||
+13
-4
@@ -289,6 +289,8 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg
|
||||
hr_scale: float = 1.0, hr_resize_x: int = 0, hr_resize_y: int = 0, refiner_steps: int = 5, refiner_start: float = 0.0, refiner_prompt: str = '', refiner_negative: str = '',
|
||||
video_skip_frames: int = 0, video_type: str = 'None', video_duration: float = 2.0, video_loop: bool = False, video_pad: int = 0, video_interpolate: int = 0,
|
||||
extra: dict = {},
|
||||
override_script_name: str = None,
|
||||
override_script_args = [],
|
||||
*input_script_args,
|
||||
):
|
||||
global pipe, original_pipeline # pylint: disable=global-statement
|
||||
@@ -589,10 +591,17 @@ def control_run(state: str = '', # pylint: disable=keyword-arg-before-vararg
|
||||
p.scripts = scripts_manager.scripts_control
|
||||
p.script_args = input_script_args or []
|
||||
if len(p.script_args) == 0:
|
||||
script_runner = scripts_manager.scripts_control
|
||||
if not script_runner.scripts:
|
||||
script_runner.initialize_scripts(False)
|
||||
p.script_args = script.init_default_script_args(script_runner)
|
||||
if not p.scripts:
|
||||
p.scripts.initialize_scripts(False)
|
||||
p.script_args = script.init_default_script_args(p.scripts)
|
||||
|
||||
# init override scripts
|
||||
if override_script_name and override_script_args:
|
||||
selectable_scripts, selectable_script_idx = script.get_selectable_script(override_script_name, p.scripts)
|
||||
if selectable_scripts:
|
||||
for idx in range(len(override_script_args)):
|
||||
p.script_args[selectable_scripts.args_from + idx] = override_script_args[idx]
|
||||
p.script_args[0] = selectable_script_idx + 1
|
||||
|
||||
# actual processing
|
||||
processed: processing.Processed = None
|
||||
|
||||
@@ -73,9 +73,9 @@ class FilenameGenerator:
|
||||
if seed is not None and int(seed) > 0:
|
||||
self.seed = seed
|
||||
elif p is not None and getattr(p, 'all_seeds', None) is not None and len(p.all_seeds) > 0:
|
||||
self.seed = p.all_seeds[0] if int(p.all_seeds[0]) > 0 else 0
|
||||
self.seed = p.all_seeds[0] if p.all_seeds[0] is not None and int(p.all_seeds[0]) > 0 else 0
|
||||
elif p is not None and getattr(p, 'seeds', None) is not None and len(p.seeds) > 0:
|
||||
self.seed = p.seeds[0] if int(p.seeds[0]) > 0 else 0
|
||||
self.seed = p.seeds[0] if p.seeds[0] is not None and int(p.seeds[0]) > 0 else 0
|
||||
else:
|
||||
self.seed = p.seed if p is not None and getattr(p, 'seed', 0) > 0 else 0
|
||||
if prompt is not None:
|
||||
|
||||
@@ -178,6 +178,7 @@ def process_base(p: processing.StableDiffusionProcessing):
|
||||
else:
|
||||
taskid = shared.state.begin('Inference')
|
||||
output = shared.sd_model(**base_args)
|
||||
output = None
|
||||
shared.state.end(taskid)
|
||||
if isinstance(output, dict):
|
||||
output = SimpleNamespace(**output)
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"format": ". venv/bin/activate && pre-commit run --all-files",
|
||||
"lint": "npm run eslint && npm run format && npm run ruff && npm run pylint | grep -v TODO",
|
||||
"todo": "npm run pylint | grep W0511 | awk -F'TODO ' '{print \"- \"$NF}' | sed 's/ (fixme)//g' | sort",
|
||||
"eslint-win": "eslint . javascript/ --rule \"linebreak-style: off\"",
|
||||
"ruff-win": "venv\\scripts\\activate && ruff check",
|
||||
"pylint-win": "venv\\scripts\\activate && pylint *.py modules/ pipelines/ scripts/ extensions-builtin/",
|
||||
"format-win": "venv\\scripts\\activate && pre-commit run --all-files",
|
||||
"lint-win": "npm run eslint-win && npm run format-win && npm run ruff-win && npm run pylint-win",
|
||||
"test": ". venv/bin/activate; python launch.py --debug --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
+23
-13
@@ -218,21 +218,31 @@ class Script(scripts_manager.Script):
|
||||
opt.confirm(p, valslist)
|
||||
return valslist
|
||||
|
||||
def parse_axis(x_type, x_values, x_values_dropdown):
|
||||
x_opt = None
|
||||
if isinstance(x_type, str):
|
||||
x_opt = [o for o in self.current_axis_options if o.label.lower() == x_type.lower()]
|
||||
if len(x_opt) == 0:
|
||||
x_opt = [o for o in self.current_axis_options if x_type.lower() in o.label.lower()]
|
||||
if len(x_opt) > 0:
|
||||
x_opt = x_opt[0]
|
||||
else:
|
||||
x_opt = self.current_axis_options[x_type]
|
||||
if x_opt:
|
||||
if x_opt.choices is not None and not csv_mode:
|
||||
x_values = list_to_csv_string(x_values_dropdown)
|
||||
xs = process_axis(x_opt, x_values, x_values_dropdown)
|
||||
else:
|
||||
xs = []
|
||||
return x_opt, xs
|
||||
|
||||
try:
|
||||
x_opt = self.current_axis_options[x_type]
|
||||
if x_opt.choices is not None and not csv_mode:
|
||||
x_values = list_to_csv_string(x_values_dropdown)
|
||||
xs = process_axis(x_opt, x_values, x_values_dropdown)
|
||||
y_opt = self.current_axis_options[y_type]
|
||||
if y_opt.choices is not None and not csv_mode:
|
||||
y_values = list_to_csv_string(y_values_dropdown)
|
||||
ys = process_axis(y_opt, y_values, y_values_dropdown)
|
||||
z_opt = self.current_axis_options[z_type]
|
||||
if z_opt.choices is not None and not csv_mode:
|
||||
z_values = list_to_csv_string(z_values_dropdown)
|
||||
zs = process_axis(z_opt, z_values, z_values_dropdown)
|
||||
x_opt, xs = parse_axis(x_type, x_values, x_values_dropdown)
|
||||
y_opt, ys = parse_axis(y_type, y_values, y_values_dropdown)
|
||||
z_opt, zs = parse_axis(z_type, z_values, z_values_dropdown)
|
||||
except Exception as e:
|
||||
shared.log.error(f"XYZ grid: invalid axis values {e}")
|
||||
errors.display(e, 'xyz')
|
||||
return None
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes
|
||||
@@ -274,7 +284,7 @@ class Script(scripts_manager.Script):
|
||||
shared.state.update('Grid', total_steps, total_jobs * p.n_iter)
|
||||
|
||||
image_cell_count = p.n_iter * p.batch_size
|
||||
shared.log.info(f"XYZ grid: images={len(xs)*len(ys)*len(zs)*image_cell_count} grid={len(zs)} shape={len(xs)}x{len(ys)} cells={len(zs)} steps={total_steps}")
|
||||
shared.log.info(f"XYZ grid start: images={len(xs)*len(ys)*len(zs)*image_cell_count} grid={len(zs)} shape={len(xs)}x{len(ys)} cells={len(zs)} steps={total_steps} csv={csv_mode} legend={draw_legend} grid={include_grid} subgrid={include_subgrids} images={include_images} time={include_time} text={include_text}")
|
||||
AxisInfo = namedtuple('AxisInfo', ['axis', 'values'])
|
||||
shared.state.xyz_plot_x = AxisInfo(x_opt, xs)
|
||||
shared.state.xyz_plot_y = AxisInfo(y_opt, ys)
|
||||
|
||||
+23
-14
@@ -233,22 +233,31 @@ class Script(scripts_manager.Script):
|
||||
opt.confirm(p, valslist)
|
||||
return valslist
|
||||
|
||||
def parse_axis(x_type, x_values, x_values_dropdown):
|
||||
x_opt = None
|
||||
if isinstance(x_type, str):
|
||||
x_opt = [o for o in self.current_axis_options if o.label.lower() == x_type.lower()]
|
||||
if len(x_opt) == 0:
|
||||
x_opt = [o for o in self.current_axis_options if x_type.lower() in o.label.lower()]
|
||||
if len(x_opt) > 0:
|
||||
x_opt = x_opt[0]
|
||||
else:
|
||||
x_opt = self.current_axis_options[x_type]
|
||||
if x_opt:
|
||||
if x_opt.choices is not None and not csv_mode:
|
||||
x_values = list_to_csv_string(x_values_dropdown)
|
||||
xs = process_axis(x_opt, x_values, x_values_dropdown)
|
||||
else:
|
||||
xs = []
|
||||
return x_opt, xs
|
||||
|
||||
try:
|
||||
x_opt = self.current_axis_options[x_type]
|
||||
if x_opt.choices is not None and not csv_mode:
|
||||
x_values = list_to_csv_string(x_values_dropdown)
|
||||
xs = process_axis(x_opt, x_values, x_values_dropdown)
|
||||
y_opt = self.current_axis_options[y_type]
|
||||
if y_opt.choices is not None and not csv_mode:
|
||||
y_values = list_to_csv_string(y_values_dropdown)
|
||||
ys = process_axis(y_opt, y_values, y_values_dropdown)
|
||||
z_opt = self.current_axis_options[z_type]
|
||||
if z_opt.choices is not None and not csv_mode:
|
||||
z_values = list_to_csv_string(z_values_dropdown)
|
||||
zs = process_axis(z_opt, z_values, z_values_dropdown)
|
||||
x_opt, xs = parse_axis(x_type, x_values, x_values_dropdown)
|
||||
y_opt, ys = parse_axis(y_type, y_values, y_values_dropdown)
|
||||
z_opt, zs = parse_axis(z_type, z_values, z_values_dropdown)
|
||||
except Exception as e:
|
||||
shared.log.error(f"XYZ grid: invalid axis values {e}")
|
||||
active = False
|
||||
errors.display(e, 'xyz')
|
||||
return None
|
||||
|
||||
Image.MAX_IMAGE_PIXELS = None # disable check in Pillow and rely on check below to allow large custom image sizes
|
||||
@@ -292,7 +301,7 @@ class Script(scripts_manager.Script):
|
||||
shared.state.update('Grid', total_steps, total_jobs)
|
||||
|
||||
image_cell_count = p.n_iter * p.batch_size
|
||||
shared.log.info(f"XYZ grid start: images={len(xs)*len(ys)*len(zs)*image_cell_count} grid={len(zs)} shape={len(xs)}x{len(ys)} cells={len(zs)} steps={total_steps}")
|
||||
shared.log.info(f"XYZ grid start: images={len(xs)*len(ys)*len(zs)*image_cell_count} grid={len(zs)} shape={len(xs)}x{len(ys)} cells={len(zs)} steps={total_steps} csv={csv_mode} legend={draw_legend} grid={include_grid} subgrid={include_subgrids} images={include_images} time={include_time} text={include_text}")
|
||||
AxisInfo = namedtuple('AxisInfo', ['axis', 'values'])
|
||||
shared.state.xyz_plot_x = AxisInfo(x_opt, xs)
|
||||
shared.state.xyz_plot_y = AxisInfo(y_opt, ys)
|
||||
|
||||
Reference in New Issue
Block a user