Compare commits

...

1 Commits

Author SHA1 Message Date
psychedelicious
c72480fd1b fix: opencv dependency conflict (#8095)
* build: prevent `opencv-python` from being installed

Fixes this error: `AttributeError: module 'cv2.ximgproc' has no attribute 'thinning'`

`opencv-contrib-python` supersedes `opencv-python`, providing the same API + additional features. The two packages should not be installed at the same time to avoid conflicts and/or errors.

The `invisible-watermark` package requires `opencv-python`, but we require the contrib variant.

This change updates `pyproject.toml` to prevent `opencv-python` from ever being installed using a `uv` features called dependency overrides.

* feat(ui): data viewer supports disabling wrap

* feat(api): list _all_ pkgs in app deps endpoint

* chore(ui): typegen

* feat(ui): update about modal to display new full deps list

* chore: uv lock
2025-06-10 08:34:00 -04:00
8 changed files with 46 additions and 129 deletions

View File

@@ -1,8 +1,7 @@
import typing
from enum import Enum
from importlib.metadata import PackageNotFoundError, version
from importlib.metadata import distributions
from pathlib import Path
from platform import python_version
from typing import Optional
import torch
@@ -44,24 +43,6 @@ class AppVersion(BaseModel):
highlights: Optional[list[str]] = Field(default=None, description="Highlights of release")
class AppDependencyVersions(BaseModel):
"""App depencency Versions Response"""
accelerate: str = Field(description="accelerate version")
compel: str = Field(description="compel version")
cuda: Optional[str] = Field(description="CUDA version")
diffusers: str = Field(description="diffusers version")
numpy: str = Field(description="Numpy version")
opencv: str = Field(description="OpenCV version")
onnx: str = Field(description="ONNX version")
pillow: str = Field(description="Pillow (PIL) version")
python: str = Field(description="Python version")
torch: str = Field(description="PyTorch version")
torchvision: str = Field(description="PyTorch Vision version")
transformers: str = Field(description="transformers version")
xformers: Optional[str] = Field(description="xformers version")
class AppConfig(BaseModel):
"""App Config Response"""
@@ -76,27 +57,19 @@ async def get_version() -> AppVersion:
return AppVersion(version=__version__)
@app_router.get("/app_deps", operation_id="get_app_deps", status_code=200, response_model=AppDependencyVersions)
async def get_app_deps() -> AppDependencyVersions:
@app_router.get("/app_deps", operation_id="get_app_deps", status_code=200, response_model=dict[str, str])
async def get_app_deps() -> dict[str, str]:
deps: dict[str, str] = {dist.metadata["Name"]: dist.version for dist in distributions()}
try:
xformers = version("xformers")
except PackageNotFoundError:
xformers = None
return AppDependencyVersions(
accelerate=version("accelerate"),
compel=version("compel"),
cuda=torch.version.cuda,
diffusers=version("diffusers"),
numpy=version("numpy"),
opencv=version("opencv-python"),
onnx=version("onnx"),
pillow=version("pillow"),
python=python_version(),
torch=torch.version.__version__,
torchvision=version("torchvision"),
transformers=version("transformers"),
xformers=xformers,
)
cuda = torch.version.cuda or "N/A"
except Exception:
cuda = "N/A"
deps["CUDA"] = cuda
sorted_deps = dict(sorted(deps.items(), key=lambda item: item[0].lower()))
return sorted_deps
@app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig)

View File

@@ -19,6 +19,7 @@ type Props = {
withDownload?: boolean;
withCopy?: boolean;
extraCopyActions?: { label: string; getData: (data: unknown) => unknown }[];
wrapData?: boolean;
} & FlexProps;
const overlayscrollbarsOptions = getOverlayScrollbarsParams({
@@ -29,7 +30,16 @@ const overlayscrollbarsOptions = getOverlayScrollbarsParams({
const ChakraPre = chakra('pre');
const DataViewer = (props: Props) => {
const { label, data, fileName, withDownload = true, withCopy = true, extraCopyActions, ...rest } = props;
const {
label,
data,
fileName,
withDownload = true,
withCopy = true,
extraCopyActions,
wrapData = true,
...rest
} = props;
const dataString = useMemo(() => (isString(data) ? data : formatter.Serialize(data)) ?? '', [data]);
const shift = useShiftModifier();
const clipboard = useClipboard();
@@ -53,7 +63,7 @@ const DataViewer = (props: Props) => {
<Flex bg="base.800" borderRadius="base" flexGrow={1} w="full" h="full" position="relative" {...rest}>
<Box position="absolute" top={0} left={0} right={0} bottom={0} overflow="auto" p={2} fontSize="sm">
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayscrollbarsOptions}>
<ChakraPre whiteSpace="pre-wrap">{dataString}</ChakraPre>
<ChakraPre whiteSpace={wrapData ? 'pre-wrap' : undefined}>{dataString}</ChakraPre>
</OverlayScrollbarsComponent>
</Box>
<Flex position="absolute" top={0} insetInlineEnd={0} p={2}>

View File

@@ -58,7 +58,7 @@ const AboutModal = ({ children }: AboutModalProps) => {
{cloneElement(children, {
onClick: onOpen,
})}
<Modal isOpen={isOpen} onClose={onClose} isCentered size="2xl" useInert={false}>
<Modal isOpen={isOpen} onClose={onClose} isCentered size="5xl" useInert={false}>
<ModalOverlay />
<ModalContent maxH="80vh" h="34rem">
<ModalHeader>{t('accessibility.about')}</ModalHeader>
@@ -66,7 +66,7 @@ const AboutModal = ({ children }: AboutModalProps) => {
<ModalBody display="flex" flexDir="column" gap={4}>
<Grid templateColumns="repeat(2, 1fr)" h="full">
<GridItem backgroundColor="base.750" borderRadius="base" p="4" h="full">
<DataViewer label={t('common.systemInformation')} data={localData} />
<DataViewer label={t('common.systemInformation')} data={localData} wrapData={false} />
</GridItem>
<GridItem>
<Flex flexDir="column" gap={3} justifyContent="center" alignItems="center" h="full">

View File

@@ -1,7 +1,7 @@
import { $openAPISchemaUrl } from 'app/store/nanostores/openAPISchemaUrl';
import type { OpenAPIV3_1 } from 'openapi-types';
import type { paths } from 'services/api/schema';
import type { AppConfig, AppDependencyVersions, AppVersion } from 'services/api/types';
import type { AppConfig, AppVersion } from 'services/api/types';
import { api, buildV1Url } from '..';
@@ -22,7 +22,10 @@ export const appInfoApi = api.injectEndpoints({
}),
providesTags: ['FetchOnReconnect'],
}),
getAppDeps: build.query<AppDependencyVersions, void>({
getAppDeps: build.query<
paths['/api/v1/app/app_deps']['get']['responses']['200']['content']['application/json'],
void
>({
query: () => ({
url: buildAppInfoUrl('app_deps'),
method: 'GET',

View File

@@ -1925,77 +1925,6 @@ export type components = {
*/
watermarking_methods: string[];
};
/**
* AppDependencyVersions
* @description App depencency Versions Response
*/
AppDependencyVersions: {
/**
* Accelerate
* @description accelerate version
*/
accelerate: string;
/**
* Compel
* @description compel version
*/
compel: string;
/**
* Cuda
* @description CUDA version
*/
cuda: string | null;
/**
* Diffusers
* @description diffusers version
*/
diffusers: string;
/**
* Numpy
* @description Numpy version
*/
numpy: string;
/**
* Opencv
* @description OpenCV version
*/
opencv: string;
/**
* Onnx
* @description ONNX version
*/
onnx: string;
/**
* Pillow
* @description Pillow (PIL) version
*/
pillow: string;
/**
* Python
* @description Python version
*/
python: string;
/**
* Torch
* @description PyTorch version
*/
torch: string;
/**
* Torchvision
* @description PyTorch Vision version
*/
torchvision: string;
/**
* Transformers
* @description transformers version
*/
transformers: string;
/**
* Xformers
* @description xformers version
*/
xformers: string | null;
};
/**
* AppVersion
* @description App Version Response
@@ -24226,7 +24155,9 @@ export interface operations {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AppDependencyVersions"];
"application/json": {
[key: string]: string;
};
};
};
};

View File

@@ -31,7 +31,6 @@ export type InvocationJSONSchemaExtra = S['UIConfigBase'];
// App Info
export type AppVersion = S['AppVersion'];
export type AppConfig = S['AppConfig'];
export type AppDependencyVersions = S['AppDependencyVersions'];
// Images
export type ImageDTO = S['ImageDTO'];

View File

@@ -109,6 +109,12 @@ dependencies = [
"humanize==4.12.1",
]
[tool.uv]
# Prevent opencv-python from ever being chosen during dependency resolution.
# This prevents conflicts with opencv-contrib-python, which Invoke requires.
override-dependencies = ["opencv-python; sys_platform=='never'"]
[project.scripts]
"invokeai-web" = "invokeai.app.run_app:run_app"

15
uv.lock generated
View File

@@ -13,6 +13,9 @@ resolution-markers = [
"(python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.11' and sys_platform != 'darwin' and sys_platform != 'linux')",
]
[manifest]
overrides = [{ name = "opencv-python", marker = "sys_platform == 'never'" }]
[[package]]
name = "absl-py"
version = "2.2.1"
@@ -948,7 +951,7 @@ version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "opencv-python" },
{ name = "opencv-python", marker = "sys_platform == 'never'" },
{ name = "pillow" },
{ name = "pywavelets" },
{ name = "torch" },
@@ -2043,17 +2046,9 @@ name = "opencv-python"
version = "4.9.0.80"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
]
sdist = { url = "https://files.pythonhosted.org/packages/25/72/da7c69a3542071bf1e8f65336721b8b2659194425438d988f79bc14ed9cc/opencv-python-4.9.0.80.tar.gz", hash = "sha256:1a9f0e6267de3a1a1db0c54213d022c7c8b5b9ca4b580e80bdc58516c922c9e1", size = 92896686 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/35/69/b657974ddcbba54d59d7d62b01e60a8b815e35f415b996e4d355be0ac7b4/opencv_python-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:7e5f7aa4486651a6ebfa8ed4b594b65bd2d2f41beeb4241a3e4b1b85acbbbadb", size = 55689340 },
{ url = "https://files.pythonhosted.org/packages/77/df/b56175c3fb5bc058774bdcf35f5a71cf9c3c5b909f98a1c688eb71cd3b1f/opencv_python-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71dfb9555ccccdd77305fc3dcca5897fbf0cf28b297c51ee55e079c065d812a3", size = 35354525 },
{ url = "https://files.pythonhosted.org/packages/52/00/2adf376707c7965bb4569f28f73fafe303c404d01047b10e3b52761be086/opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b34a52e9da36dda8c151c6394aed602e4b17fa041df0b9f5b93ae10b0fcca2a", size = 41289855 },
{ url = "https://files.pythonhosted.org/packages/d9/64/7fdfb9386511cd6805451e012c537073a79a958a58795c4e602e538c388c/opencv_python-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4088cab82b66a3b37ffc452976b14a3c599269c247895ae9ceb4066d8188a57", size = 62208946 },
{ url = "https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:dcf000c36dd1651118a2462257e3a9e76db789a78432e1f303c7bac54f63ef6c", size = 28546907 },
{ url = "https://files.pythonhosted.org/packages/c7/ec/9dabb6a9abfdebb3c45b0cc52dec901caafef2b2c7e7d6a839ed86d81e91/opencv_python-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:3f16f08e02b2a2da44259c7cc712e779eff1dd8b55fdb0323e8cab09548086c0", size = 38624911 },
]
[[package]]
name = "opt-einsum"