diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 8232fc4b93..0000000000 --- a/.coveragerc +++ /dev/null @@ -1,6 +0,0 @@ -[run] -omit='.env/*' -source='.' - -[report] -show_missing = true diff --git a/.gitignore b/.gitignore index 031c102165..86f102dd7f 100644 --- a/.gitignore +++ b/.gitignore @@ -63,6 +63,7 @@ pip-delete-this-directory.txt htmlcov/ .tox/ .nox/ +.coveragerc .coverage .coverage.* .cache @@ -73,6 +74,7 @@ cov.xml *.py,cover .hypothesis/ .pytest_cache/ +.pytest.ini cover/ junit/ diff --git a/.pytest.ini b/.pytest.ini deleted file mode 100644 index 16ccfafe80..0000000000 --- a/.pytest.ini +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -DJANGO_SETTINGS_MODULE = webtas.settings -; python_files = tests.py test_*.py *_tests.py - -addopts = --cov=. --cov-config=.coveragerc --cov-report xml:cov.xml diff --git a/coverage/.gitignore b/coverage/.gitignore new file mode 100644 index 0000000000..86d0cb2726 --- /dev/null +++ b/coverage/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/docs/assets/contributing/html-detail.png b/docs/assets/contributing/html-detail.png new file mode 100644 index 0000000000..055218002f Binary files /dev/null and b/docs/assets/contributing/html-detail.png differ diff --git a/docs/assets/contributing/html-overview.png b/docs/assets/contributing/html-overview.png new file mode 100644 index 0000000000..1f288fde11 Binary files /dev/null and b/docs/assets/contributing/html-overview.png differ diff --git a/docs/contributing/LOCAL_DEVELOPMENT.md b/docs/contributing/LOCAL_DEVELOPMENT.md new file mode 100644 index 0000000000..956ef5ec03 --- /dev/null +++ b/docs/contributing/LOCAL_DEVELOPMENT.md @@ -0,0 +1,83 @@ +# Local Development + +If you are looking to contribute you will need to have a local development +environment. See the +[Developer Install](../installation/020_INSTALL_MANUAL.md#developer-install) for +full details. + +Broadly this involves cloning the repository, installing the pre-reqs, and +InvokeAI (in editable form). Assuming this is working, choose your area of +focus. + +## Documentation + +We use [mkdocs](https://www.mkdocs.org) for our documentation with the +[material theme](https://squidfunk.github.io/mkdocs-material/). Documentation is +written in markdown files under the `./docs` folder and then built into a static +website for hosting with GitHub Pages at +[invoke-ai.github.io/InvokeAI](https://invoke-ai.github.io/InvokeAI). + +To contribute to the documentation you'll need to install the dependencies. Note +the use of `"`. + +```zsh +pip install ".[docs]" +``` + +Now, to run the documentation locally with hot-reloading for changes made. + +```zsh +mkdocs serve +``` + +You'll then be prompted to connect to `http://127.0.0.1:8080` in order to +access. + +## Backend + +The backend is contained within the `./invokeai/backend` folder structure. To +get started however please install the development dependencies. + +From the root of the repository run the following command. Note the use of `"`. + +```zsh +pip install ".[test]" +``` + +This in an optional group of packages which is defined within the +`pyproject.toml` and will be required for testing the changes you make the the +code. + +### Running Tests + +We use [pytest](https://docs.pytest.org/en/7.2.x/) for our test suite. Tests can +be found under the `./tests` folder and can be run with a single `pytest` +command. Optionally, to review test coverage you can append `--cov`. + +```zsh +pytest --cov +``` + +Test outcomes and coverage will be reported in the terminal. In addition a more +detailed report is created in both XML and HTML format in the `./coverage` +folder. The HTML one in particular can help identify missing statements +requiring tests to ensure coverage. This can be run by opening +`./coverage/html/index.html`. + +For example. + +```zsh +pytest --cov; open ./coverage/html/index.html +``` + +??? info "HTML coverage report output" + + ![html-overview](../assets/contributing/html-overview.png) + + ![html-detail](../assets/contributing/html-detail.png) + +## Front End + + + +--8<-- "invokeai/frontend/web/README.md" diff --git a/invokeai/backend/generator/base.py b/invokeai/backend/generator/base.py index 125f208b0a..e30b77ec33 100644 --- a/invokeai/backend/generator/base.py +++ b/invokeai/backend/generator/base.py @@ -154,6 +154,7 @@ class InvokeAIGenerator(metaclass=ABCMeta): for i in iteration_count: results = generator.generate(prompt, conditioning=(uc, c, extra_conditioning_info), + step_callback=step_callback, sampler=scheduler, **generator_args, ) diff --git a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py index 793ba024cf..7c24eb8da8 100644 --- a/invokeai/backend/model_management/convert_ckpt_to_diffusers.py +++ b/invokeai/backend/model_management/convert_ckpt_to_diffusers.py @@ -378,16 +378,26 @@ def convert_ldm_unet_checkpoint(checkpoint, config, path=None, extract_ema=False for key in keys: if key.startswith("model.diffusion_model"): flat_ema_key = "model_ema." + "".join(key.split(".")[1:]) - unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop( - flat_ema_key - ) + flat_ema_key_alt = "model_ema." + "".join(key.split(".")[2:]) + if flat_ema_key in checkpoint: + unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop( + flat_ema_key + ) + elif flat_ema_key_alt in checkpoint: + unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop( + flat_ema_key_alt + ) + else: + unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop( + key + ) else: print( " | Extracting only the non-EMA weights (usually better for fine-tuning)" ) for key in keys: - if key.startswith(unet_key): + if key.startswith("model.diffusion_model") and key in checkpoint: unet_state_dict[key.replace(unet_key, "")] = checkpoint.pop(key) new_checkpoint = {} diff --git a/invokeai/backend/model_management/model_manager.py b/invokeai/backend/model_management/model_manager.py index 9464057f71..c64560baf8 100644 --- a/invokeai/backend/model_management/model_manager.py +++ b/invokeai/backend/model_management/model_manager.py @@ -362,6 +362,7 @@ class ModelManager(object): raise NotImplementedError( f"Unknown model format {model_name}: {model_format}" ) + self._add_embeddings_to_model(model) # usage statistics toc = time.time() @@ -436,7 +437,6 @@ class ModelManager(object): height = width print(f" | Default image dimensions = {width} x {height}") - self._add_embeddings_to_model(pipeline) return pipeline, width, height, model_hash diff --git a/invokeai/backend/stable_diffusion/concepts_lib.py b/invokeai/backend/stable_diffusion/concepts_lib.py index 50ff6c16c9..28167a0bd5 100644 --- a/invokeai/backend/stable_diffusion/concepts_lib.py +++ b/invokeai/backend/stable_diffusion/concepts_lib.py @@ -6,7 +6,6 @@ The interface is through the Concepts() object. """ import os import re -import traceback from typing import Callable from urllib import error as ul_error from urllib import request @@ -15,7 +14,6 @@ from huggingface_hub import ( HfApi, HfFolder, ModelFilter, - ModelSearchArguments, hf_hub_url, ) @@ -84,7 +82,7 @@ class HuggingFaceConceptsLibrary(object): """ if not concept_name in self.list_concepts(): print( - f"This concept is not a local embedding trigger, nor is it a HuggingFace concept. Generation will continue without the concept." + f"{concept_name} is not a local embedding trigger, nor is it a HuggingFace concept. Generation will continue without the concept." ) return None return self.get_concept_file(concept_name.lower(), "learned_embeds.bin") @@ -236,7 +234,7 @@ class HuggingFaceConceptsLibrary(object): except ul_error.HTTPError as e: if e.code == 404: print( - f"This concept is not known to the Hugging Face library. Generation will continue without the concept." + f"Concept {concept_name} is not known to the Hugging Face library. Generation will continue without the concept." ) else: print( @@ -246,7 +244,7 @@ class HuggingFaceConceptsLibrary(object): return False except ul_error.URLError as e: print( - f"ERROR: {str(e)}. This may reflect a network issue. Generation will continue without the concept." + f"ERROR while downloading {concept_name}: {str(e)}. This may reflect a network issue. Generation will continue without the concept." ) os.rmdir(dest) return False diff --git a/invokeai/frontend/web/public/locales/ar.json b/invokeai/frontend/web/public/locales/ar.json index 671341e9ab..e5168da4a8 100644 --- a/invokeai/frontend/web/public/locales/ar.json +++ b/invokeai/frontend/web/public/locales/ar.json @@ -8,7 +8,6 @@ "darkTheme": "داكن", "lightTheme": "فاتح", "greenTheme": "أخضر", - "text2img": "نص إلى صورة", "img2img": "صورة إلى صورة", "unifiedCanvas": "لوحة موحدة", "nodes": "عقد", diff --git a/invokeai/frontend/web/public/locales/de.json b/invokeai/frontend/web/public/locales/de.json index 29155a83c6..deeef34194 100644 --- a/invokeai/frontend/web/public/locales/de.json +++ b/invokeai/frontend/web/public/locales/de.json @@ -7,7 +7,6 @@ "darkTheme": "Dunkel", "lightTheme": "Hell", "greenTheme": "Grün", - "text2img": "Text zu Bild", "img2img": "Bild zu Bild", "nodes": "Knoten", "langGerman": "Deutsch", diff --git a/invokeai/frontend/web/public/locales/es.json b/invokeai/frontend/web/public/locales/es.json index ad3fdaf3ed..9c2b6bf983 100644 --- a/invokeai/frontend/web/public/locales/es.json +++ b/invokeai/frontend/web/public/locales/es.json @@ -8,7 +8,6 @@ "darkTheme": "Oscuro", "lightTheme": "Claro", "greenTheme": "Verde", - "text2img": "Texto a Imagen", "img2img": "Imagen a Imagen", "unifiedCanvas": "Lienzo Unificado", "nodes": "Nodos", @@ -70,7 +69,11 @@ "langHebrew": "Hebreo", "pinOptionsPanel": "Pin del panel de opciones", "loading": "Cargando", - "loadingInvokeAI": "Cargando invocar a la IA" + "loadingInvokeAI": "Cargando invocar a la IA", + "postprocessing": "Tratamiento posterior", + "txt2img": "De texto a imagen", + "accept": "Aceptar", + "cancel": "Cancelar" }, "gallery": { "generations": "Generaciones", @@ -404,7 +407,8 @@ "none": "ninguno", "pickModelType": "Elige el tipo de modelo", "v2_768": "v2 (768px)", - "addDifference": "Añadir una diferencia" + "addDifference": "Añadir una diferencia", + "scanForModels": "Buscar modelos" }, "parameters": { "images": "Imágenes", @@ -574,7 +578,7 @@ "autoSaveToGallery": "Guardar automáticamente en galería", "saveBoxRegionOnly": "Guardar solo región dentro de la caja", "limitStrokesToBox": "Limitar trazos a la caja", - "showCanvasDebugInfo": "Mostrar información de depuración de lienzo", + "showCanvasDebugInfo": "Mostrar la información adicional del lienzo", "clearCanvasHistory": "Limpiar historial de lienzo", "clearHistory": "Limpiar historial", "clearCanvasHistoryMessage": "Limpiar el historial de lienzo también restablece completamente el lienzo unificado. Esto incluye todo el historial de deshacer/rehacer, las imágenes en el área de preparación y la capa base del lienzo.", diff --git a/invokeai/frontend/web/public/locales/fr.json b/invokeai/frontend/web/public/locales/fr.json index 472c437702..cf215d7d06 100644 --- a/invokeai/frontend/web/public/locales/fr.json +++ b/invokeai/frontend/web/public/locales/fr.json @@ -8,7 +8,6 @@ "darkTheme": "Sombre", "lightTheme": "Clair", "greenTheme": "Vert", - "text2img": "Texte en image", "img2img": "Image en image", "unifiedCanvas": "Canvas unifié", "nodes": "Nœuds", @@ -47,7 +46,19 @@ "statusLoadingModel": "Chargement du modèle", "statusModelChanged": "Modèle changé", "discordLabel": "Discord", - "githubLabel": "Github" + "githubLabel": "Github", + "accept": "Accepter", + "statusMergingModels": "Mélange des modèles", + "loadingInvokeAI": "Chargement de Invoke AI", + "cancel": "Annuler", + "langEnglish": "Anglais", + "statusConvertingModel": "Conversion du modèle", + "statusModelConverted": "Modèle converti", + "loading": "Chargement", + "pinOptionsPanel": "Épingler la page d'options", + "statusMergedModels": "Modèles mélangés", + "txt2img": "Texte vers image", + "postprocessing": "Post-Traitement" }, "gallery": { "generations": "Générations", @@ -518,5 +529,15 @@ "betaDarkenOutside": "Assombrir à l'extérieur", "betaLimitToBox": "Limiter à la boîte", "betaPreserveMasked": "Conserver masqué" + }, + "accessibility": { + "uploadImage": "Charger une image", + "reset": "Réinitialiser", + "nextImage": "Image suivante", + "previousImage": "Image précédente", + "useThisParameter": "Utiliser ce paramètre", + "zoomIn": "Zoom avant", + "zoomOut": "Zoom arrière", + "showOptionsPanel": "Montrer la page d'options" } } diff --git a/invokeai/frontend/web/public/locales/he.json b/invokeai/frontend/web/public/locales/he.json index 1e760b8edb..c9b4ff3b17 100644 --- a/invokeai/frontend/web/public/locales/he.json +++ b/invokeai/frontend/web/public/locales/he.json @@ -125,7 +125,6 @@ "langSimplifiedChinese": "סינית", "langUkranian": "אוקראינית", "langSpanish": "ספרדית", - "text2img": "טקסט לתמונה", "img2img": "תמונה לתמונה", "unifiedCanvas": "קנבס מאוחד", "nodes": "צמתים", diff --git a/invokeai/frontend/web/public/locales/it.json b/invokeai/frontend/web/public/locales/it.json index 61aa5c6a08..7df34173df 100644 --- a/invokeai/frontend/web/public/locales/it.json +++ b/invokeai/frontend/web/public/locales/it.json @@ -8,7 +8,6 @@ "darkTheme": "Scuro", "lightTheme": "Chiaro", "greenTheme": "Verde", - "text2img": "Testo a Immagine", "img2img": "Immagine a Immagine", "unifiedCanvas": "Tela unificata", "nodes": "Nodi", @@ -70,7 +69,11 @@ "loading": "Caricamento in corso", "oceanTheme": "Oceano", "langHebrew": "Ebraico", - "loadingInvokeAI": "Caricamento Invoke AI" + "loadingInvokeAI": "Caricamento Invoke AI", + "postprocessing": "Post Elaborazione", + "txt2img": "Testo a Immagine", + "accept": "Accetta", + "cancel": "Annulla" }, "gallery": { "generations": "Generazioni", @@ -404,7 +407,8 @@ "v2_768": "v2 (768px)", "none": "niente", "addDifference": "Aggiungi differenza", - "pickModelType": "Scegli il tipo di modello" + "pickModelType": "Scegli il tipo di modello", + "scanForModels": "Cerca modelli" }, "parameters": { "images": "Immagini", @@ -574,7 +578,7 @@ "autoSaveToGallery": "Salvataggio automatico nella Galleria", "saveBoxRegionOnly": "Salva solo l'area di selezione", "limitStrokesToBox": "Limita i tratti all'area di selezione", - "showCanvasDebugInfo": "Mostra informazioni di debug della Tela", + "showCanvasDebugInfo": "Mostra ulteriori informazioni sulla Tela", "clearCanvasHistory": "Cancella cronologia Tela", "clearHistory": "Cancella la cronologia", "clearCanvasHistoryMessage": "La cancellazione della cronologia della tela lascia intatta la tela corrente, ma cancella in modo irreversibile la cronologia degli annullamenti e dei ripristini.", @@ -612,7 +616,7 @@ "copyMetadataJson": "Copia i metadati JSON", "exitViewer": "Esci dal visualizzatore", "zoomIn": "Zoom avanti", - "zoomOut": "Zoom Indietro", + "zoomOut": "Zoom indietro", "rotateCounterClockwise": "Ruotare in senso antiorario", "rotateClockwise": "Ruotare in senso orario", "flipHorizontally": "Capovolgi orizzontalmente", diff --git a/invokeai/frontend/web/public/locales/ko.json b/invokeai/frontend/web/public/locales/ko.json index 888cdc9925..47cde5fec3 100644 --- a/invokeai/frontend/web/public/locales/ko.json +++ b/invokeai/frontend/web/public/locales/ko.json @@ -11,7 +11,6 @@ "langArabic": "العربية", "langEnglish": "English", "langDutch": "Nederlands", - "text2img": "텍스트->이미지", "unifiedCanvas": "통합 캔버스", "langFrench": "Français", "langGerman": "Deutsch", diff --git a/invokeai/frontend/web/public/locales/nl.json b/invokeai/frontend/web/public/locales/nl.json index c06eae06a6..70b836dbc1 100644 --- a/invokeai/frontend/web/public/locales/nl.json +++ b/invokeai/frontend/web/public/locales/nl.json @@ -8,7 +8,6 @@ "darkTheme": "Donker", "lightTheme": "Licht", "greenTheme": "Groen", - "text2img": "Tekst naar afbeelding", "img2img": "Afbeelding naar afbeelding", "unifiedCanvas": "Centraal canvas", "nodes": "Knooppunten", diff --git a/invokeai/frontend/web/public/locales/pl.json b/invokeai/frontend/web/public/locales/pl.json index 7736b27943..246271658a 100644 --- a/invokeai/frontend/web/public/locales/pl.json +++ b/invokeai/frontend/web/public/locales/pl.json @@ -8,7 +8,6 @@ "darkTheme": "Ciemny", "lightTheme": "Jasny", "greenTheme": "Zielony", - "text2img": "Tekst na obraz", "img2img": "Obraz na obraz", "unifiedCanvas": "Tryb uniwersalny", "nodes": "Węzły", diff --git a/invokeai/frontend/web/public/locales/pt.json b/invokeai/frontend/web/public/locales/pt.json index 6e26b9ea56..6d19e3ad92 100644 --- a/invokeai/frontend/web/public/locales/pt.json +++ b/invokeai/frontend/web/public/locales/pt.json @@ -20,7 +20,6 @@ "langSpanish": "Espanhol", "langRussian": "Русский", "langUkranian": "Украї́нська", - "text2img": "Texto para Imagem", "img2img": "Imagem para Imagem", "unifiedCanvas": "Tela Unificada", "nodes": "Nós", diff --git a/invokeai/frontend/web/public/locales/pt_BR.json b/invokeai/frontend/web/public/locales/pt_BR.json index 18b7ab57e1..e77ef14719 100644 --- a/invokeai/frontend/web/public/locales/pt_BR.json +++ b/invokeai/frontend/web/public/locales/pt_BR.json @@ -8,7 +8,6 @@ "darkTheme": "Noite", "lightTheme": "Dia", "greenTheme": "Verde", - "text2img": "Texto Para Imagem", "img2img": "Imagem Para Imagem", "unifiedCanvas": "Tela Unificada", "nodes": "Nódulos", diff --git a/invokeai/frontend/web/public/locales/ru.json b/invokeai/frontend/web/public/locales/ru.json index d4178119e4..0280341dee 100644 --- a/invokeai/frontend/web/public/locales/ru.json +++ b/invokeai/frontend/web/public/locales/ru.json @@ -8,7 +8,6 @@ "darkTheme": "Темная", "lightTheme": "Светлая", "greenTheme": "Зеленая", - "text2img": "Изображение из текста (text2img)", "img2img": "Изображение в изображение (img2img)", "unifiedCanvas": "Универсальный холст", "nodes": "Ноды", diff --git a/invokeai/frontend/web/public/locales/uk.json b/invokeai/frontend/web/public/locales/uk.json index fbcc5014df..044cea64a4 100644 --- a/invokeai/frontend/web/public/locales/uk.json +++ b/invokeai/frontend/web/public/locales/uk.json @@ -8,7 +8,6 @@ "darkTheme": "Темна", "lightTheme": "Світла", "greenTheme": "Зелена", - "text2img": "Зображення із тексту (text2img)", "img2img": "Зображення із зображення (img2img)", "unifiedCanvas": "Універсальне полотно", "nodes": "Вузли", diff --git a/invokeai/frontend/web/public/locales/zh_CN.json b/invokeai/frontend/web/public/locales/zh_CN.json index 701933052e..b23ac8cc99 100644 --- a/invokeai/frontend/web/public/locales/zh_CN.json +++ b/invokeai/frontend/web/public/locales/zh_CN.json @@ -8,7 +8,6 @@ "darkTheme": "暗色", "lightTheme": "亮色", "greenTheme": "绿色", - "text2img": "文字到图像", "img2img": "图像到图像", "unifiedCanvas": "统一画布", "nodes": "节点", diff --git a/invokeai/frontend/web/public/locales/zh_Hant.json b/invokeai/frontend/web/public/locales/zh_Hant.json index af7b0cf328..98b4882018 100644 --- a/invokeai/frontend/web/public/locales/zh_Hant.json +++ b/invokeai/frontend/web/public/locales/zh_Hant.json @@ -33,7 +33,6 @@ "langBrPortuguese": "巴西葡萄牙語", "langRussian": "俄語", "langSpanish": "西班牙語", - "text2img": "文字到圖像", "unifiedCanvas": "統一畫布" } } diff --git a/pyproject.toml b/pyproject.toml index 4760035174..393db0cae4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ dependencies = [ "albumentations", "click", "clip_anytorch", # replacing "clip @ https://github.com/openai/CLIP/archive/eaa22acb90a5876642d0507623e859909230a52d.zip", - "compel==1.0.1", + "compel==1.0.4", "datasets", "diffusers[torch]~=0.14", "dnspython==2.2.1", @@ -139,8 +139,24 @@ version = { attr = "invokeai.version.__version__" } "invokeai.configs" = ["*.example", "**/*.yaml", "*.txt"] "invokeai.frontend.web.dist" = ["**"] +#=== Begin: PyTest and Coverage [tool.pytest.ini_options] -addopts = "-p pytest_cov --junitxml=junit/test-results.xml --cov-report=term:skip-covered --cov=ldm/invoke --cov=backend --cov-branch" +addopts = "--cov-report term --cov-report html --cov-report xml" +[tool.coverage.run] +branch = true +source = ["invokeai"] +omit = ["*tests*", "*migrations*", ".venv/*", "*.env"] +[tool.coverage.report] +show_missing = true +fail_under = 85 # let's set something sensible on Day 1 ... +[tool.coverage.json] +output = "coverage/coverage.json" +pretty_print = true +[tool.coverage.html] +directory = "coverage/html" +[tool.coverage.xml] +output = "coverage/index.xml" +#=== End: PyTest and Coverage [flake8] max-line-length = 120