mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
merge with main
This commit is contained in:
commit
87a5b771c4
@ -26,7 +26,7 @@ from invokeai.backend.model_manager.config import (
|
|||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
model_records_router = APIRouter(prefix="/v1/model/record", tags=["model_manager_v2"])
|
model_records_router = APIRouter(prefix="/v1/model/record", tags=["model_manager_v2_unstable"])
|
||||||
|
|
||||||
|
|
||||||
class ModelsList(BaseModel):
|
class ModelsList(BaseModel):
|
||||||
|
@ -77,7 +77,7 @@ class CalculateImageTilesInvocation(BaseInvocation):
|
|||||||
title="Calculate Image Tiles Even Split",
|
title="Calculate Image Tiles Even Split",
|
||||||
tags=["tiles"],
|
tags=["tiles"],
|
||||||
category="tiles",
|
category="tiles",
|
||||||
version="1.0.0",
|
version="1.1.0",
|
||||||
classification=Classification.Beta,
|
classification=Classification.Beta,
|
||||||
)
|
)
|
||||||
class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
|
class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
|
||||||
@ -97,11 +97,11 @@ class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
|
|||||||
ge=1,
|
ge=1,
|
||||||
description="Number of tiles to divide image into on the y axis",
|
description="Number of tiles to divide image into on the y axis",
|
||||||
)
|
)
|
||||||
overlap_fraction: float = InputField(
|
overlap: int = InputField(
|
||||||
default=0.25,
|
default=128,
|
||||||
ge=0,
|
ge=0,
|
||||||
lt=1,
|
multiple_of=8,
|
||||||
description="Overlap between adjacent tiles as a fraction of the tile's dimensions (0-1)",
|
description="The overlap, in pixels, between adjacent tiles.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput:
|
def invoke(self, context: InvocationContext) -> CalculateImageTilesOutput:
|
||||||
@ -110,7 +110,7 @@ class CalculateImageTilesEvenSplitInvocation(BaseInvocation):
|
|||||||
image_width=self.image_width,
|
image_width=self.image_width,
|
||||||
num_tiles_x=self.num_tiles_x,
|
num_tiles_x=self.num_tiles_x,
|
||||||
num_tiles_y=self.num_tiles_y,
|
num_tiles_y=self.num_tiles_y,
|
||||||
overlap_fraction=self.overlap_fraction,
|
overlap=self.overlap,
|
||||||
)
|
)
|
||||||
return CalculateImageTilesOutput(tiles=tiles)
|
return CalculateImageTilesOutput(tiles=tiles)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ from invokeai.app.services.image_files.image_files_base import ImageFileStorageB
|
|||||||
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
|
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_1 import build_migration_1
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_1 import build_migration_1
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_2 import build_migration_2
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_2 import build_migration_2
|
||||||
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_3 import build_migration_3
|
||||||
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_4 import build_migration_4
|
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_4 import build_migration_4
|
||||||
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_impl import SqliteMigrator
|
||||||
|
|
||||||
@ -28,6 +29,7 @@ def init_db(config: InvokeAIAppConfig, logger: Logger, image_files: ImageFileSto
|
|||||||
migrator = SqliteMigrator(db=db)
|
migrator = SqliteMigrator(db=db)
|
||||||
migrator.register_migration(build_migration_1())
|
migrator.register_migration(build_migration_1())
|
||||||
migrator.register_migration(build_migration_2(image_files=image_files, logger=logger))
|
migrator.register_migration(build_migration_2(image_files=image_files, logger=logger))
|
||||||
|
migrator.register_migration(build_migration_3())
|
||||||
migrator.register_migration(build_migration_4())
|
migrator.register_migration(build_migration_4())
|
||||||
migrator.run_migrations()
|
migrator.run_migrations()
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ from invokeai.app.services.workflow_records.workflow_records_common import (
|
|||||||
UnsafeWorkflowWithVersionValidator,
|
UnsafeWorkflowWithVersionValidator,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from .util.migrate_yaml_config_1 import MigrateModelYamlToDb1
|
||||||
|
|
||||||
|
|
||||||
class Migration2Callback:
|
class Migration2Callback:
|
||||||
def __init__(self, image_files: ImageFileStorageBase, logger: Logger):
|
def __init__(self, image_files: ImageFileStorageBase, logger: Logger):
|
||||||
@ -24,6 +26,7 @@ class Migration2Callback:
|
|||||||
self._add_workflow_library(cursor)
|
self._add_workflow_library(cursor)
|
||||||
self._drop_model_manager_metadata(cursor)
|
self._drop_model_manager_metadata(cursor)
|
||||||
self._recreate_model_config(cursor)
|
self._recreate_model_config(cursor)
|
||||||
|
self._migrate_model_config_records(cursor)
|
||||||
self._migrate_embedded_workflows(cursor)
|
self._migrate_embedded_workflows(cursor)
|
||||||
|
|
||||||
def _add_images_has_workflow(self, cursor: sqlite3.Cursor) -> None:
|
def _add_images_has_workflow(self, cursor: sqlite3.Cursor) -> None:
|
||||||
@ -131,6 +134,11 @@ class Migration2Callback:
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _migrate_model_config_records(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
"""After updating the model config table, we repopulate it."""
|
||||||
|
model_record_migrator = MigrateModelYamlToDb1(cursor)
|
||||||
|
model_record_migrator.migrate()
|
||||||
|
|
||||||
def _migrate_embedded_workflows(self, cursor: sqlite3.Cursor) -> None:
|
def _migrate_embedded_workflows(self, cursor: sqlite3.Cursor) -> None:
|
||||||
"""
|
"""
|
||||||
In the v3.5.0 release, InvokeAI changed how it handles embedded workflows. The `images` table in
|
In the v3.5.0 release, InvokeAI changed how it handles embedded workflows. The `images` table in
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
|
||||||
|
|
||||||
|
from .util.migrate_yaml_config_1 import MigrateModelYamlToDb1
|
||||||
|
|
||||||
|
|
||||||
|
class Migration3Callback:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __call__(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
self._drop_model_manager_metadata(cursor)
|
||||||
|
self._recreate_model_config(cursor)
|
||||||
|
self._migrate_model_config_records(cursor)
|
||||||
|
|
||||||
|
def _drop_model_manager_metadata(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
"""Drops the `model_manager_metadata` table."""
|
||||||
|
cursor.execute("DROP TABLE IF EXISTS model_manager_metadata;")
|
||||||
|
|
||||||
|
def _recreate_model_config(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
"""
|
||||||
|
Drops the `model_config` table, recreating it.
|
||||||
|
|
||||||
|
In 3.4.0, this table used explicit columns but was changed to use json_extract 3.5.0.
|
||||||
|
|
||||||
|
Because this table is not used in production, we are able to simply drop it and recreate it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute("DROP TABLE IF EXISTS model_config;")
|
||||||
|
|
||||||
|
cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
CREATE TABLE IF NOT EXISTS model_config (
|
||||||
|
id TEXT NOT NULL PRIMARY KEY,
|
||||||
|
-- The next 3 fields are enums in python, unrestricted string here
|
||||||
|
base TEXT GENERATED ALWAYS as (json_extract(config, '$.base')) VIRTUAL NOT NULL,
|
||||||
|
type TEXT GENERATED ALWAYS as (json_extract(config, '$.type')) VIRTUAL NOT NULL,
|
||||||
|
name TEXT GENERATED ALWAYS as (json_extract(config, '$.name')) VIRTUAL NOT NULL,
|
||||||
|
path TEXT GENERATED ALWAYS as (json_extract(config, '$.path')) VIRTUAL NOT NULL,
|
||||||
|
format TEXT GENERATED ALWAYS as (json_extract(config, '$.format')) VIRTUAL NOT NULL,
|
||||||
|
original_hash TEXT, -- could be null
|
||||||
|
-- Serialized JSON representation of the whole config object,
|
||||||
|
-- which will contain additional fields from subclasses
|
||||||
|
config TEXT NOT NULL,
|
||||||
|
created_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
|
||||||
|
-- Updated via trigger
|
||||||
|
updated_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
|
||||||
|
-- unique constraint on combo of name, base and type
|
||||||
|
UNIQUE(name, base, type)
|
||||||
|
);
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
def _migrate_model_config_records(self, cursor: sqlite3.Cursor) -> None:
|
||||||
|
"""After updating the model config table, we repopulate it."""
|
||||||
|
model_record_migrator = MigrateModelYamlToDb1(cursor)
|
||||||
|
model_record_migrator.migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def build_migration_3() -> Migration:
|
||||||
|
"""
|
||||||
|
Build the migration from database version 2 to 3.
|
||||||
|
|
||||||
|
This migration does the following:
|
||||||
|
- Drops the `model_config` table, recreating it
|
||||||
|
- Migrates data from `models.yaml` into the `model_config` table
|
||||||
|
"""
|
||||||
|
migration_3 = Migration(
|
||||||
|
from_version=2,
|
||||||
|
to_version=3,
|
||||||
|
callback=Migration3Callback(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return migration_3
|
@ -86,8 +86,8 @@ def build_migration_4() -> Migration:
|
|||||||
Adds the tables needed to store model metadata and tags.
|
Adds the tables needed to store model metadata and tags.
|
||||||
"""
|
"""
|
||||||
migration_4 = Migration(
|
migration_4 = Migration(
|
||||||
from_version=2, # until migration_3 is merged, pretend we are doing 2-3
|
from_version=3,
|
||||||
to_version=3,
|
to_version=4,
|
||||||
callback=Migration4Callback(),
|
callback=Migration4Callback(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
# Copyright (c) 2023 Lincoln D. Stein
|
# Copyright (c) 2023 Lincoln D. Stein
|
||||||
"""Migrate from the InvokeAI v2 models.yaml format to the v3 sqlite format."""
|
"""Migrate from the InvokeAI v2 models.yaml format to the v3 sqlite format."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sqlite3
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from omegaconf import DictConfig, OmegaConf
|
from omegaconf import DictConfig, OmegaConf
|
||||||
from pydantic import TypeAdapter
|
from pydantic import TypeAdapter
|
||||||
@ -10,13 +14,12 @@ from pydantic import TypeAdapter
|
|||||||
from invokeai.app.services.config import InvokeAIAppConfig
|
from invokeai.app.services.config import InvokeAIAppConfig
|
||||||
from invokeai.app.services.model_records import (
|
from invokeai.app.services.model_records import (
|
||||||
DuplicateModelException,
|
DuplicateModelException,
|
||||||
ModelRecordServiceSQL,
|
|
||||||
UnknownModelException,
|
UnknownModelException,
|
||||||
)
|
)
|
||||||
from invokeai.app.services.shared.sqlite.sqlite_database import SqliteDatabase
|
|
||||||
from invokeai.backend.model_manager.config import (
|
from invokeai.backend.model_manager.config import (
|
||||||
AnyModelConfig,
|
AnyModelConfig,
|
||||||
BaseModelType,
|
BaseModelType,
|
||||||
|
ModelConfigFactory,
|
||||||
ModelType,
|
ModelType,
|
||||||
)
|
)
|
||||||
from invokeai.backend.model_manager.hash import FastModelHash
|
from invokeai.backend.model_manager.hash import FastModelHash
|
||||||
@ -25,9 +28,9 @@ from invokeai.backend.util.logging import InvokeAILogger
|
|||||||
ModelsValidator = TypeAdapter(AnyModelConfig)
|
ModelsValidator = TypeAdapter(AnyModelConfig)
|
||||||
|
|
||||||
|
|
||||||
class MigrateModelYamlToDb:
|
class MigrateModelYamlToDb1:
|
||||||
"""
|
"""
|
||||||
Migrate the InvokeAI models.yaml format (VERSION 3.0.0) to SQL3 database format (VERSION 3.2.0)
|
Migrate the InvokeAI models.yaml format (VERSION 3.0.0) to SQL3 database format (VERSION 3.5.0).
|
||||||
|
|
||||||
The class has one externally useful method, migrate(), which scans the
|
The class has one externally useful method, migrate(), which scans the
|
||||||
currently models.yaml file and imports all its entries into invokeai.db.
|
currently models.yaml file and imports all its entries into invokeai.db.
|
||||||
@ -41,17 +44,13 @@ class MigrateModelYamlToDb:
|
|||||||
|
|
||||||
config: InvokeAIAppConfig
|
config: InvokeAIAppConfig
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
cursor: sqlite3.Cursor
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, cursor: sqlite3.Cursor = None) -> None:
|
||||||
self.config = InvokeAIAppConfig.get_config()
|
self.config = InvokeAIAppConfig.get_config()
|
||||||
self.config.parse_args()
|
self.config.parse_args()
|
||||||
self.logger = InvokeAILogger.get_logger()
|
self.logger = InvokeAILogger.get_logger()
|
||||||
|
self.cursor = cursor
|
||||||
def get_db(self) -> ModelRecordServiceSQL:
|
|
||||||
"""Fetch the sqlite3 database for this installation."""
|
|
||||||
db_path = None if self.config.use_memory_db else self.config.db_path
|
|
||||||
db = SqliteDatabase(db_path=db_path, logger=self.logger, verbose=self.config.log_sql)
|
|
||||||
return ModelRecordServiceSQL(db)
|
|
||||||
|
|
||||||
def get_yaml(self) -> DictConfig:
|
def get_yaml(self) -> DictConfig:
|
||||||
"""Fetch the models.yaml DictConfig for this installation."""
|
"""Fetch the models.yaml DictConfig for this installation."""
|
||||||
@ -62,8 +61,10 @@ class MigrateModelYamlToDb:
|
|||||||
|
|
||||||
def migrate(self) -> None:
|
def migrate(self) -> None:
|
||||||
"""Do the migration from models.yaml to invokeai.db."""
|
"""Do the migration from models.yaml to invokeai.db."""
|
||||||
db = self.get_db()
|
try:
|
||||||
yaml = self.get_yaml()
|
yaml = self.get_yaml()
|
||||||
|
except OSError:
|
||||||
|
return
|
||||||
|
|
||||||
for model_key, stanza in yaml.items():
|
for model_key, stanza in yaml.items():
|
||||||
if model_key == "__metadata__":
|
if model_key == "__metadata__":
|
||||||
@ -86,22 +87,62 @@ class MigrateModelYamlToDb:
|
|||||||
new_config: AnyModelConfig = ModelsValidator.validate_python(stanza) # type: ignore # see https://github.com/pydantic/pydantic/discussions/7094
|
new_config: AnyModelConfig = ModelsValidator.validate_python(stanza) # type: ignore # see https://github.com/pydantic/pydantic/discussions/7094
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if original_record := db.search_by_path(stanza.path):
|
if original_record := self._search_by_path(stanza.path):
|
||||||
key = original_record[0].key
|
key = original_record.key
|
||||||
self.logger.info(f"Updating model {model_name} with information from models.yaml using key {key}")
|
self.logger.info(f"Updating model {model_name} with information from models.yaml using key {key}")
|
||||||
db.update_model(key, new_config)
|
self._update_model(key, new_config)
|
||||||
else:
|
else:
|
||||||
self.logger.info(f"Adding model {model_name} with key {model_key}")
|
self.logger.info(f"Adding model {model_name} with key {model_key}")
|
||||||
db.add_model(new_key, new_config)
|
self._add_model(new_key, new_config)
|
||||||
except DuplicateModelException:
|
except DuplicateModelException:
|
||||||
self.logger.warning(f"Model {model_name} is already in the database")
|
self.logger.warning(f"Model {model_name} is already in the database")
|
||||||
except UnknownModelException:
|
except UnknownModelException:
|
||||||
self.logger.warning(f"Model at {stanza.path} could not be found in database")
|
self.logger.warning(f"Model at {stanza.path} could not be found in database")
|
||||||
|
|
||||||
|
def _search_by_path(self, path: Path) -> Optional[AnyModelConfig]:
|
||||||
|
self.cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
SELECT config FROM model_config
|
||||||
|
WHERE path=?;
|
||||||
|
""",
|
||||||
|
(str(path),),
|
||||||
|
)
|
||||||
|
results = [ModelConfigFactory.make_config(json.loads(x[0])) for x in self.cursor.fetchall()]
|
||||||
|
return results[0] if results else None
|
||||||
|
|
||||||
def main():
|
def _update_model(self, key: str, config: AnyModelConfig) -> None:
|
||||||
MigrateModelYamlToDb().migrate()
|
record = ModelConfigFactory.make_config(config, key=key) # ensure it is a valid config obect
|
||||||
|
json_serialized = record.model_dump_json() # and turn it into a json string.
|
||||||
|
self.cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
UPDATE model_config
|
||||||
|
SET
|
||||||
|
config=?
|
||||||
|
WHERE id=?;
|
||||||
|
""",
|
||||||
|
(json_serialized, key),
|
||||||
|
)
|
||||||
|
if self.cursor.rowcount == 0:
|
||||||
|
raise UnknownModelException("model not found")
|
||||||
|
|
||||||
|
def _add_model(self, key: str, config: AnyModelConfig) -> None:
|
||||||
if __name__ == "__main__":
|
record = ModelConfigFactory.make_config(config, key=key) # ensure it is a valid config obect.
|
||||||
main()
|
json_serialized = record.model_dump_json() # and turn it into a json string.
|
||||||
|
try:
|
||||||
|
self.cursor.execute(
|
||||||
|
"""--sql
|
||||||
|
INSERT INTO model_config (
|
||||||
|
id,
|
||||||
|
original_hash,
|
||||||
|
config
|
||||||
|
)
|
||||||
|
VALUES (?,?,?);
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
key,
|
||||||
|
record.original_hash,
|
||||||
|
json_serialized,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except sqlite3.IntegrityError as exc:
|
||||||
|
raise DuplicateModelException(f"{record.name}: model is already in database") from exc
|
@ -389,7 +389,7 @@ class TextualInversionCheckpointProbe(CheckpointProbeBase):
|
|||||||
elif "clip_g" in checkpoint:
|
elif "clip_g" in checkpoint:
|
||||||
token_dim = checkpoint["clip_g"].shape[-1]
|
token_dim = checkpoint["clip_g"].shape[-1]
|
||||||
else:
|
else:
|
||||||
token_dim = list(checkpoint.values())[0].shape[0]
|
token_dim = list(checkpoint.values())[0].shape[-1]
|
||||||
if token_dim == 768:
|
if token_dim == 768:
|
||||||
return BaseModelType.StableDiffusion1
|
return BaseModelType.StableDiffusion1
|
||||||
elif token_dim == 1024:
|
elif token_dim == 1024:
|
||||||
|
@ -102,7 +102,7 @@ def calc_tiles_with_overlap(
|
|||||||
|
|
||||||
|
|
||||||
def calc_tiles_even_split(
|
def calc_tiles_even_split(
|
||||||
image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap_fraction: float = 0
|
image_height: int, image_width: int, num_tiles_x: int, num_tiles_y: int, overlap: int = 0
|
||||||
) -> list[Tile]:
|
) -> list[Tile]:
|
||||||
"""Calculate the tile coordinates for a given image shape with the number of tiles requested.
|
"""Calculate the tile coordinates for a given image shape with the number of tiles requested.
|
||||||
|
|
||||||
@ -111,31 +111,35 @@ def calc_tiles_even_split(
|
|||||||
image_width (int): The image width in px.
|
image_width (int): The image width in px.
|
||||||
num_x_tiles (int): The number of tile to split the image into on the X-axis.
|
num_x_tiles (int): The number of tile to split the image into on the X-axis.
|
||||||
num_y_tiles (int): The number of tile to split the image into on the Y-axis.
|
num_y_tiles (int): The number of tile to split the image into on the Y-axis.
|
||||||
overlap_fraction (float, optional): The target overlap as fraction of the tiles size. Defaults to 0.
|
overlap (int, optional): The overlap between adjacent tiles in pixels. Defaults to 0.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[Tile]: A list of tiles that cover the image shape. Ordered from left-to-right, top-to-bottom.
|
list[Tile]: A list of tiles that cover the image shape. Ordered from left-to-right, top-to-bottom.
|
||||||
"""
|
"""
|
||||||
|
# Ensure the image is divisible by LATENT_SCALE_FACTOR
|
||||||
# Ensure tile size is divisible by 8
|
|
||||||
if image_width % LATENT_SCALE_FACTOR != 0 or image_height % LATENT_SCALE_FACTOR != 0:
|
if image_width % LATENT_SCALE_FACTOR != 0 or image_height % LATENT_SCALE_FACTOR != 0:
|
||||||
raise ValueError(f"image size (({image_width}, {image_height})) must be divisible by {LATENT_SCALE_FACTOR}")
|
raise ValueError(f"image size (({image_width}, {image_height})) must be divisible by {LATENT_SCALE_FACTOR}")
|
||||||
|
|
||||||
# Calculate the overlap size based on the percentage and adjust it to be divisible by 8 (rounding up)
|
|
||||||
overlap_x = LATENT_SCALE_FACTOR * math.ceil(
|
|
||||||
int((image_width / num_tiles_x) * overlap_fraction) / LATENT_SCALE_FACTOR
|
|
||||||
)
|
|
||||||
overlap_y = LATENT_SCALE_FACTOR * math.ceil(
|
|
||||||
int((image_height / num_tiles_y) * overlap_fraction) / LATENT_SCALE_FACTOR
|
|
||||||
)
|
|
||||||
|
|
||||||
# Calculate the tile size based on the number of tiles and overlap, and ensure it's divisible by 8 (rounding down)
|
# Calculate the tile size based on the number of tiles and overlap, and ensure it's divisible by 8 (rounding down)
|
||||||
|
if num_tiles_x > 1:
|
||||||
|
# ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap
|
||||||
|
assert overlap <= image_width - (LATENT_SCALE_FACTOR * (num_tiles_x - 1))
|
||||||
tile_size_x = LATENT_SCALE_FACTOR * math.floor(
|
tile_size_x = LATENT_SCALE_FACTOR * math.floor(
|
||||||
((image_width + overlap_x * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR
|
((image_width + overlap * (num_tiles_x - 1)) // num_tiles_x) / LATENT_SCALE_FACTOR
|
||||||
)
|
)
|
||||||
|
assert overlap < tile_size_x
|
||||||
|
else:
|
||||||
|
tile_size_x = image_width
|
||||||
|
|
||||||
|
if num_tiles_y > 1:
|
||||||
|
# ensure the overlap is not more than the maximum overlap if we only have 1 tile then we dont care about overlap
|
||||||
|
assert overlap <= image_height - (LATENT_SCALE_FACTOR * (num_tiles_y - 1))
|
||||||
tile_size_y = LATENT_SCALE_FACTOR * math.floor(
|
tile_size_y = LATENT_SCALE_FACTOR * math.floor(
|
||||||
((image_height + overlap_y * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR
|
((image_height + overlap * (num_tiles_y - 1)) // num_tiles_y) / LATENT_SCALE_FACTOR
|
||||||
)
|
)
|
||||||
|
assert overlap < tile_size_y
|
||||||
|
else:
|
||||||
|
tile_size_y = image_height
|
||||||
|
|
||||||
# tiles[y * num_tiles_x + x] is the tile for the y'th row, x'th column.
|
# tiles[y * num_tiles_x + x] is the tile for the y'th row, x'th column.
|
||||||
tiles: list[Tile] = []
|
tiles: list[Tile] = []
|
||||||
@ -143,7 +147,7 @@ def calc_tiles_even_split(
|
|||||||
# Calculate tile coordinates. (Ignore overlap values for now.)
|
# Calculate tile coordinates. (Ignore overlap values for now.)
|
||||||
for tile_idx_y in range(num_tiles_y):
|
for tile_idx_y in range(num_tiles_y):
|
||||||
# Calculate the top and bottom of the row
|
# Calculate the top and bottom of the row
|
||||||
top = tile_idx_y * (tile_size_y - overlap_y)
|
top = tile_idx_y * (tile_size_y - overlap)
|
||||||
bottom = min(top + tile_size_y, image_height)
|
bottom = min(top + tile_size_y, image_height)
|
||||||
# For the last row adjust bottom to be the height of the image
|
# For the last row adjust bottom to be the height of the image
|
||||||
if tile_idx_y == num_tiles_y - 1:
|
if tile_idx_y == num_tiles_y - 1:
|
||||||
@ -151,7 +155,7 @@ def calc_tiles_even_split(
|
|||||||
|
|
||||||
for tile_idx_x in range(num_tiles_x):
|
for tile_idx_x in range(num_tiles_x):
|
||||||
# Calculate the left & right coordinate of each tile
|
# Calculate the left & right coordinate of each tile
|
||||||
left = tile_idx_x * (tile_size_x - overlap_x)
|
left = tile_idx_x * (tile_size_x - overlap)
|
||||||
right = min(left + tile_size_x, image_width)
|
right = min(left + tile_size_x, image_width)
|
||||||
# For the last tile in the row adjust right to be the width of the image
|
# For the last tile in the row adjust right to be the width of the image
|
||||||
if tile_idx_x == num_tiles_x - 1:
|
if tile_idx_x == num_tiles_x - 1:
|
||||||
|
@ -4,6 +4,7 @@ pip install <path_to_git_source>.
|
|||||||
"""
|
"""
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
from distutils.version import LooseVersion
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import psutil
|
import psutil
|
||||||
@ -31,10 +32,6 @@ else:
|
|||||||
console = Console(style=Style(color="grey74", bgcolor="grey19"))
|
console = Console(style=Style(color="grey74", bgcolor="grey19"))
|
||||||
|
|
||||||
|
|
||||||
def get_versions() -> dict:
|
|
||||||
return requests.get(url=INVOKE_AI_REL).json()
|
|
||||||
|
|
||||||
|
|
||||||
def invokeai_is_running() -> bool:
|
def invokeai_is_running() -> bool:
|
||||||
for p in psutil.process_iter():
|
for p in psutil.process_iter():
|
||||||
try:
|
try:
|
||||||
@ -50,6 +47,20 @@ def invokeai_is_running() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_pypi_versions():
|
||||||
|
url = "https://pypi.org/pypi/invokeai/json"
|
||||||
|
try:
|
||||||
|
data = requests.get(url).json()
|
||||||
|
except Exception:
|
||||||
|
raise Exception("Unable to fetch version information from PyPi")
|
||||||
|
|
||||||
|
versions = list(data["releases"].keys())
|
||||||
|
versions.sort(key=LooseVersion, reverse=True)
|
||||||
|
latest_version = [v for v in versions if "rc" not in v][0]
|
||||||
|
latest_release_candidate = [v for v in versions if "rc" in v][0]
|
||||||
|
return latest_version, latest_release_candidate, versions
|
||||||
|
|
||||||
|
|
||||||
def welcome(latest_release: str, latest_prerelease: str):
|
def welcome(latest_release: str, latest_prerelease: str):
|
||||||
@group()
|
@group()
|
||||||
def text():
|
def text():
|
||||||
@ -63,8 +74,7 @@ def welcome(latest_release: str, latest_prerelease: str):
|
|||||||
yield "[bold yellow]Options:"
|
yield "[bold yellow]Options:"
|
||||||
yield f"""[1] Update to the latest [bold]official release[/bold] ([italic]{latest_release}[/italic])
|
yield f"""[1] Update to the latest [bold]official release[/bold] ([italic]{latest_release}[/italic])
|
||||||
[2] Update to the latest [bold]pre-release[/bold] (may be buggy; caveat emptor!) ([italic]{latest_prerelease}[/italic])
|
[2] Update to the latest [bold]pre-release[/bold] (may be buggy; caveat emptor!) ([italic]{latest_prerelease}[/italic])
|
||||||
[3] Manually enter the [bold]tag name[/bold] for the version you wish to update to
|
[3] Manually enter the [bold]version[/bold] you wish to update to"""
|
||||||
[4] Manually enter the [bold]branch name[/bold] for the version you wish to update to"""
|
|
||||||
|
|
||||||
console.rule()
|
console.rule()
|
||||||
print(
|
print(
|
||||||
@ -92,44 +102,35 @@ def get_extras():
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
versions = get_versions()
|
|
||||||
released_versions = [x for x in versions if not (x["draft"] or x["prerelease"])]
|
|
||||||
prerelease_versions = [x for x in versions if not x["draft"] and x["prerelease"]]
|
|
||||||
latest_release = released_versions[0]["tag_name"] if len(released_versions) else None
|
|
||||||
latest_prerelease = prerelease_versions[0]["tag_name"] if len(prerelease_versions) else None
|
|
||||||
|
|
||||||
if invokeai_is_running():
|
if invokeai_is_running():
|
||||||
print(":exclamation: [bold red]Please terminate all running instances of InvokeAI before updating.[/red bold]")
|
print(":exclamation: [bold red]Please terminate all running instances of InvokeAI before updating.[/red bold]")
|
||||||
input("Press any key to continue...")
|
input("Press any key to continue...")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
latest_release, latest_prerelease, versions = get_pypi_versions()
|
||||||
|
|
||||||
welcome(latest_release, latest_prerelease)
|
welcome(latest_release, latest_prerelease)
|
||||||
|
|
||||||
tag = None
|
release = latest_release
|
||||||
branch = None
|
choice = Prompt.ask("Choice:", choices=["1", "2", "3"], default="1")
|
||||||
release = None
|
|
||||||
choice = Prompt.ask("Choice:", choices=["1", "2", "3", "4"], default="1")
|
|
||||||
|
|
||||||
if choice == "1":
|
if choice == "1":
|
||||||
release = latest_release
|
release = latest_release
|
||||||
elif choice == "2":
|
elif choice == "2":
|
||||||
release = latest_prerelease
|
release = latest_prerelease
|
||||||
elif choice == "3":
|
elif choice == "3":
|
||||||
while not tag:
|
while True:
|
||||||
tag = Prompt.ask("Enter an InvokeAI tag name")
|
release = Prompt.ask("Enter an InvokeAI version")
|
||||||
elif choice == "4":
|
release.strip()
|
||||||
while not branch:
|
if release in versions:
|
||||||
branch = Prompt.ask("Enter an InvokeAI branch name")
|
break
|
||||||
|
print(f":exclamation: [bold red]'{release}' is not a recognized InvokeAI release.[/red bold]")
|
||||||
|
|
||||||
extras = get_extras()
|
extras = get_extras()
|
||||||
|
|
||||||
print(f":crossed_fingers: Upgrading to [yellow]{tag or release or branch}[/yellow]")
|
print(f":crossed_fingers: Upgrading to [yellow]{release}[/yellow]")
|
||||||
if release:
|
cmd = f'pip install "invokeai{extras}=={release}" --use-pep517 --upgrade'
|
||||||
cmd = f'pip install "invokeai{extras} @ {INVOKE_AI_SRC}/{release}.zip" --use-pep517 --upgrade'
|
|
||||||
elif tag:
|
|
||||||
cmd = f'pip install "invokeai{extras} @ {INVOKE_AI_TAG}/{tag}.zip" --use-pep517 --upgrade'
|
|
||||||
else:
|
|
||||||
cmd = f'pip install "invokeai{extras} @ {INVOKE_AI_BRANCH}/{branch}.zip" --use-pep517 --upgrade'
|
|
||||||
print("")
|
print("")
|
||||||
print("")
|
print("")
|
||||||
if os.system(cmd) == 0:
|
if os.system(cmd) == 0:
|
||||||
|
@ -1109,7 +1109,10 @@
|
|||||||
"deletedInvalidEdge": "Eliminata connessione non valida {{source}} -> {{target}}",
|
"deletedInvalidEdge": "Eliminata connessione non valida {{source}} -> {{target}}",
|
||||||
"unknownInput": "Input sconosciuto: {{name}}",
|
"unknownInput": "Input sconosciuto: {{name}}",
|
||||||
"prototypeDesc": "Questa invocazione è un prototipo. Potrebbe subire modifiche sostanziali durante gli aggiornamenti dell'app e potrebbe essere rimossa in qualsiasi momento.",
|
"prototypeDesc": "Questa invocazione è un prototipo. Potrebbe subire modifiche sostanziali durante gli aggiornamenti dell'app e potrebbe essere rimossa in qualsiasi momento.",
|
||||||
"betaDesc": "Questa invocazione è in versione beta. Fino a quando non sarà stabile, potrebbe subire modifiche importanti durante gli aggiornamenti dell'app. Abbiamo intenzione di supportare questa invocazione a lungo termine."
|
"betaDesc": "Questa invocazione è in versione beta. Fino a quando non sarà stabile, potrebbe subire modifiche importanti durante gli aggiornamenti dell'app. Abbiamo intenzione di supportare questa invocazione a lungo termine.",
|
||||||
|
"newWorkflow": "Nuovo flusso di lavoro",
|
||||||
|
"newWorkflowDesc": "Creare un nuovo flusso di lavoro?",
|
||||||
|
"newWorkflowDesc2": "Il flusso di lavoro attuale presenta modifiche non salvate."
|
||||||
},
|
},
|
||||||
"boards": {
|
"boards": {
|
||||||
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
"autoAddBoard": "Aggiungi automaticamente bacheca",
|
||||||
@ -1629,7 +1632,10 @@
|
|||||||
"deleteWorkflow": "Elimina flusso di lavoro",
|
"deleteWorkflow": "Elimina flusso di lavoro",
|
||||||
"workflows": "Flussi di lavoro",
|
"workflows": "Flussi di lavoro",
|
||||||
"noDescription": "Nessuna descrizione",
|
"noDescription": "Nessuna descrizione",
|
||||||
"userWorkflows": "I miei flussi di lavoro"
|
"userWorkflows": "I miei flussi di lavoro",
|
||||||
|
"newWorkflowCreated": "Nuovo flusso di lavoro creato",
|
||||||
|
"downloadWorkflow": "Salva su file",
|
||||||
|
"uploadWorkflow": "Carica da file"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"storeNotInitialized": "Il negozio non è inizializzato"
|
"storeNotInitialized": "Il negozio non è inizializzato"
|
||||||
|
@ -138,7 +138,6 @@ dependencies = [
|
|||||||
"invokeai-node-web" = "invokeai.app.api_app:invoke_api"
|
"invokeai-node-web" = "invokeai.app.api_app:invoke_api"
|
||||||
"invokeai-import-images" = "invokeai.frontend.install.import_images:main"
|
"invokeai-import-images" = "invokeai.frontend.install.import_images:main"
|
||||||
"invokeai-db-maintenance" = "invokeai.backend.util.db_maintenance:main"
|
"invokeai-db-maintenance" = "invokeai.backend.util.db_maintenance:main"
|
||||||
"invokeai-migrate-models-to-db" = "invokeai.backend.model_manager.migrate_to_db:main"
|
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
"Homepage" = "https://invoke-ai.github.io/InvokeAI/"
|
"Homepage" = "https://invoke-ai.github.io/InvokeAI/"
|
||||||
|
@ -305,9 +305,7 @@ def test_calc_tiles_min_overlap_input_validation(
|
|||||||
|
|
||||||
def test_calc_tiles_even_split_single_tile():
|
def test_calc_tiles_even_split_single_tile():
|
||||||
"""Test calc_tiles_even_split() behavior when a single tile covers the image."""
|
"""Test calc_tiles_even_split() behavior when a single tile covers the image."""
|
||||||
tiles = calc_tiles_even_split(
|
tiles = calc_tiles_even_split(image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap=64)
|
||||||
image_height=512, image_width=1024, num_tiles_x=1, num_tiles_y=1, overlap_fraction=0.25
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_tiles = [
|
expected_tiles = [
|
||||||
Tile(
|
Tile(
|
||||||
@ -322,36 +320,34 @@ def test_calc_tiles_even_split_single_tile():
|
|||||||
def test_calc_tiles_even_split_evenly_divisible():
|
def test_calc_tiles_even_split_evenly_divisible():
|
||||||
"""Test calc_tiles_even_split() behavior when the image is evenly covered by multiple tiles."""
|
"""Test calc_tiles_even_split() behavior when the image is evenly covered by multiple tiles."""
|
||||||
# Parameters mimic roughly the same output as the original tile generations of the same test name
|
# Parameters mimic roughly the same output as the original tile generations of the same test name
|
||||||
tiles = calc_tiles_even_split(
|
tiles = calc_tiles_even_split(image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap=64)
|
||||||
image_height=576, image_width=1600, num_tiles_x=3, num_tiles_y=2, overlap_fraction=0.25
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_tiles = [
|
expected_tiles = [
|
||||||
# Row 0
|
# Row 0
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=320, left=0, right=624),
|
coords=TBLR(top=0, bottom=320, left=0, right=576),
|
||||||
overlap=TBLR(top=0, bottom=72, left=0, right=136),
|
overlap=TBLR(top=0, bottom=64, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=320, left=488, right=1112),
|
coords=TBLR(top=0, bottom=320, left=512, right=1088),
|
||||||
overlap=TBLR(top=0, bottom=72, left=136, right=136),
|
overlap=TBLR(top=0, bottom=64, left=64, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=320, left=976, right=1600),
|
coords=TBLR(top=0, bottom=320, left=1024, right=1600),
|
||||||
overlap=TBLR(top=0, bottom=72, left=136, right=0),
|
overlap=TBLR(top=0, bottom=64, left=64, right=0),
|
||||||
),
|
),
|
||||||
# Row 1
|
# Row 1
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=248, bottom=576, left=0, right=624),
|
coords=TBLR(top=256, bottom=576, left=0, right=576),
|
||||||
overlap=TBLR(top=72, bottom=0, left=0, right=136),
|
overlap=TBLR(top=64, bottom=0, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=248, bottom=576, left=488, right=1112),
|
coords=TBLR(top=256, bottom=576, left=512, right=1088),
|
||||||
overlap=TBLR(top=72, bottom=0, left=136, right=136),
|
overlap=TBLR(top=64, bottom=0, left=64, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=248, bottom=576, left=976, right=1600),
|
coords=TBLR(top=256, bottom=576, left=1024, right=1600),
|
||||||
overlap=TBLR(top=72, bottom=0, left=136, right=0),
|
overlap=TBLR(top=64, bottom=0, left=64, right=0),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
assert tiles == expected_tiles
|
assert tiles == expected_tiles
|
||||||
@ -360,36 +356,34 @@ def test_calc_tiles_even_split_evenly_divisible():
|
|||||||
def test_calc_tiles_even_split_not_evenly_divisible():
|
def test_calc_tiles_even_split_not_evenly_divisible():
|
||||||
"""Test calc_tiles_even_split() behavior when the image requires 'uneven' overlaps to achieve proper coverage."""
|
"""Test calc_tiles_even_split() behavior when the image requires 'uneven' overlaps to achieve proper coverage."""
|
||||||
# Parameters mimic roughly the same output as the original tile generations of the same test name
|
# Parameters mimic roughly the same output as the original tile generations of the same test name
|
||||||
tiles = calc_tiles_even_split(
|
tiles = calc_tiles_even_split(image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap=64)
|
||||||
image_height=400, image_width=1200, num_tiles_x=3, num_tiles_y=2, overlap_fraction=0.25
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_tiles = [
|
expected_tiles = [
|
||||||
# Row 0
|
# Row 0
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=224, left=0, right=464),
|
coords=TBLR(top=0, bottom=232, left=0, right=440),
|
||||||
overlap=TBLR(top=0, bottom=56, left=0, right=104),
|
overlap=TBLR(top=0, bottom=64, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=224, left=360, right=824),
|
coords=TBLR(top=0, bottom=232, left=376, right=816),
|
||||||
overlap=TBLR(top=0, bottom=56, left=104, right=104),
|
overlap=TBLR(top=0, bottom=64, left=64, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=224, left=720, right=1200),
|
coords=TBLR(top=0, bottom=232, left=752, right=1200),
|
||||||
overlap=TBLR(top=0, bottom=56, left=104, right=0),
|
overlap=TBLR(top=0, bottom=64, left=64, right=0),
|
||||||
),
|
),
|
||||||
# Row 1
|
# Row 1
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=168, bottom=400, left=0, right=464),
|
coords=TBLR(top=168, bottom=400, left=0, right=440),
|
||||||
overlap=TBLR(top=56, bottom=0, left=0, right=104),
|
overlap=TBLR(top=64, bottom=0, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=168, bottom=400, left=360, right=824),
|
coords=TBLR(top=168, bottom=400, left=376, right=816),
|
||||||
overlap=TBLR(top=56, bottom=0, left=104, right=104),
|
overlap=TBLR(top=64, bottom=0, left=64, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=168, bottom=400, left=720, right=1200),
|
coords=TBLR(top=168, bottom=400, left=752, right=1200),
|
||||||
overlap=TBLR(top=56, bottom=0, left=104, right=0),
|
overlap=TBLR(top=64, bottom=0, left=64, right=0),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -399,28 +393,26 @@ def test_calc_tiles_even_split_not_evenly_divisible():
|
|||||||
def test_calc_tiles_even_split_difficult_size():
|
def test_calc_tiles_even_split_difficult_size():
|
||||||
"""Test calc_tiles_even_split() behavior when the image is a difficult size to spilt evenly and keep div8."""
|
"""Test calc_tiles_even_split() behavior when the image is a difficult size to spilt evenly and keep div8."""
|
||||||
# Parameters are a difficult size for other tile gen routines to calculate
|
# Parameters are a difficult size for other tile gen routines to calculate
|
||||||
tiles = calc_tiles_even_split(
|
tiles = calc_tiles_even_split(image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap=64)
|
||||||
image_height=1000, image_width=1000, num_tiles_x=2, num_tiles_y=2, overlap_fraction=0.25
|
|
||||||
)
|
|
||||||
|
|
||||||
expected_tiles = [
|
expected_tiles = [
|
||||||
# Row 0
|
# Row 0
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=560, left=0, right=560),
|
coords=TBLR(top=0, bottom=528, left=0, right=528),
|
||||||
overlap=TBLR(top=0, bottom=128, left=0, right=128),
|
overlap=TBLR(top=0, bottom=64, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=0, bottom=560, left=432, right=1000),
|
coords=TBLR(top=0, bottom=528, left=464, right=1000),
|
||||||
overlap=TBLR(top=0, bottom=128, left=128, right=0),
|
overlap=TBLR(top=0, bottom=64, left=64, right=0),
|
||||||
),
|
),
|
||||||
# Row 1
|
# Row 1
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=432, bottom=1000, left=0, right=560),
|
coords=TBLR(top=464, bottom=1000, left=0, right=528),
|
||||||
overlap=TBLR(top=128, bottom=0, left=0, right=128),
|
overlap=TBLR(top=64, bottom=0, left=0, right=64),
|
||||||
),
|
),
|
||||||
Tile(
|
Tile(
|
||||||
coords=TBLR(top=432, bottom=1000, left=432, right=1000),
|
coords=TBLR(top=464, bottom=1000, left=464, right=1000),
|
||||||
overlap=TBLR(top=128, bottom=0, left=128, right=0),
|
overlap=TBLR(top=64, bottom=0, left=64, right=0),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -428,11 +420,13 @@ def test_calc_tiles_even_split_difficult_size():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap_fraction", "raises"],
|
["image_height", "image_width", "num_tiles_x", "num_tiles_y", "overlap", "raises"],
|
||||||
[
|
[
|
||||||
(128, 128, 1, 1, 0.25, False), # OK
|
(128, 128, 1, 1, 127, False), # OK
|
||||||
(128, 128, 1, 1, 0, False), # OK
|
(128, 128, 1, 1, 0, False), # OK
|
||||||
(128, 128, 2, 1, 0, False), # OK
|
(128, 128, 2, 2, 0, False), # OK
|
||||||
|
(128, 128, 2, 1, 120, True), # overlap equals tile_height.
|
||||||
|
(128, 128, 1, 2, 120, True), # overlap equals tile_width.
|
||||||
(127, 127, 1, 1, 0, True), # image size must be dividable by 8
|
(127, 127, 1, 1, 0, True), # image size must be dividable by 8
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -441,15 +435,15 @@ def test_calc_tiles_even_split_input_validation(
|
|||||||
image_width: int,
|
image_width: int,
|
||||||
num_tiles_x: int,
|
num_tiles_x: int,
|
||||||
num_tiles_y: int,
|
num_tiles_y: int,
|
||||||
overlap_fraction: float,
|
overlap: int,
|
||||||
raises: bool,
|
raises: bool,
|
||||||
):
|
):
|
||||||
"""Test that calc_tiles_even_split() raises an exception if the inputs are invalid."""
|
"""Test that calc_tiles_even_split() raises an exception if the inputs are invalid."""
|
||||||
if raises:
|
if raises:
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises((AssertionError, ValueError)):
|
||||||
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap_fraction)
|
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap)
|
||||||
else:
|
else:
|
||||||
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap_fraction)
|
calc_tiles_even_split(image_height, image_width, num_tiles_x, num_tiles_y, overlap)
|
||||||
|
|
||||||
|
|
||||||
#############################################
|
#############################################
|
||||||
|
Loading…
Reference in New Issue
Block a user