From d3bb4110d36ef6f40342d317d81daecf46428e95 Mon Sep 17 00:00:00 2001 From: EdgeNeko Date: Tue, 14 May 2024 17:30:45 +0800 Subject: [PATCH 1/3] Fix some lint error --- app/Models/api_models/admin_query_params.py | 12 ++++++--- app/Models/img_data.py | 4 +-- app/Services/ocr_services.py | 3 ++- app/Services/storage/__init__.py | 2 +- app/Services/storage/base.py | 2 -- app/Services/storage/s3_compatible_storage.py | 1 - app/Services/upload_service.py | 4 +-- tests/api/integrate_test.py | 26 ++++++++++--------- tests/unit/test_transformers_service.py | 2 +- 9 files changed, 29 insertions(+), 27 deletions(-) diff --git a/app/Models/api_models/admin_query_params.py b/app/Models/api_models/admin_query_params.py index 85c0073..1fbccdf 100644 --- a/app/Models/api_models/admin_query_params.py +++ b/app/Models/api_models/admin_query_params.py @@ -6,14 +6,18 @@ class UploadImageModel: def __init__(self, url: Optional[str] = Query(None, - description="The image's url. If the image is local, this field will be ignored. Otherwise it is required."), + description="The image's url. If the image is local, this field will be " + "ignored. Otherwise it is required."), thumbnail_url: Optional[str] = Query(None, - description="The image's thumbnail url. If the image is local, this field will be ignored."), + description="The image's thumbnail url. If the image is local, " + "this field will be ignored."), categories: Optional[str] = Query(None, - description="The categories of the image. The entries should be seperated by comma."), + description="The categories of the image. The entries should be " + "seperated by comma."), starred: bool = Query(False, description="If the image is starred."), local: bool = Query(False, - description="When set to true, the image will be uploaded to local storage. Otherwise, it will only be indexed in the database."), + description="When set to true, the image will be uploaded to local storage. " + "Otherwise, it will only be indexed in the database."), skip_ocr: bool = Query(False, description="Whether to skip the OCR process.")): self.url = url self.thumbnail_url = thumbnail_url diff --git a/app/Models/img_data.py b/app/Models/img_data.py index 738659c..deece6e 100644 --- a/app/Models/img_data.py +++ b/app/Models/img_data.py @@ -39,12 +39,12 @@ def payload(self): return result @classmethod - def from_payload(cls, id: str, payload: dict, + def from_payload(cls, img_id: str, payload: dict, image_vector: Optional[ndarray] = None, text_contain_vector: Optional[ndarray] = None): # Convert the datetime string back to datetime object index_date = datetime.fromisoformat(payload['index_date']) del payload['index_date'] - return cls(id=UUID(id), + return cls(id=UUID(img_id), index_date=index_date, **payload, image_vector=image_vector if image_vector is not None else None, diff --git a/app/Services/ocr_services.py b/app/Services/ocr_services.py index 63d4bff..3c0534a 100644 --- a/app/Services/ocr_services.py +++ b/app/Services/ocr_services.py @@ -36,7 +36,8 @@ def __init__(self): needWarmUp=True, devices=self._device, warmup_size=(960, 960), - model_local_dir=config.model.easypaddleocr if config.model.easypaddleocr else None) + model_local_dir=config.model.easypaddleocr if + config.model.easypaddleocr else None) logger.success("EasyPaddleOCR loaded successfully") @staticmethod diff --git a/app/Services/storage/__init__.py b/app/Services/storage/__init__.py index 89b16d1..5ba2034 100644 --- a/app/Services/storage/__init__.py +++ b/app/Services/storage/__init__.py @@ -15,6 +15,6 @@ def __init__(self): case StorageMode.DISABLED: return case _: - raise NotImplementedError(f"Storage method {config.storage.method} not implemented. " + raise NotImplementedError(f"Storage method {config.storage.method} not implemented. " f"Available methods: local, s3") self.active_storage.pre_check() diff --git a/app/Services/storage/base.py b/app/Services/storage/base.py index b320bd4..ec8f82f 100644 --- a/app/Services/storage/base.py +++ b/app/Services/storage/base.py @@ -2,8 +2,6 @@ import os from typing import TypeVar, Generic, TypeAlias, Optional, AsyncGenerator -from app.Models.img_data import ImageData - FileMetaDataT = TypeVar('FileMetaDataT') PathLikeType: TypeAlias = str | os.PathLike diff --git a/app/Services/storage/s3_compatible_storage.py b/app/Services/storage/s3_compatible_storage.py index cc7076a..a9f861e 100644 --- a/app/Services/storage/s3_compatible_storage.py +++ b/app/Services/storage/s3_compatible_storage.py @@ -13,7 +13,6 @@ from opendal.exceptions import NotFound, PermissionDenied, AlreadyExists from wcmatch import glob -from app.Models.img_data import ImageData from app.Services.storage.base import BaseStorage, FileMetaDataT, RemoteFilePathType, LocalFilePathType, \ LocalFileMetaDataType, RemoteFileMetaDataType from app.Services.storage.exception import LocalFileNotFoundError, RemoteFileNotFoundError, RemoteFilePermissionError, \ diff --git a/app/Services/upload_service.py b/app/Services/upload_service.py index bdf9af0..1b7cb6e 100644 --- a/app/Services/upload_service.py +++ b/app/Services/upload_service.py @@ -41,15 +41,13 @@ async def _upload_task(self, img: Image.Image, img_data: ImageData, img_bytes: b logger.info('Start indexing image {}. Local: {}. Size: {}', img_data.id, img_data.local, len(img_bytes)) file_name = f"{img_data.id}.{img_data.format}" thumb_path = f"thumbnails/{img_data.id}.webp" - img_thumb = None if img_data.local: img_data.url = await self._storage_service.active_storage.url(file_name) if len(img_bytes) > 1024 * 500: img_data.thumbnail_url = await self._storage_service.active_storage.url( f"thumbnails/{img_data.id}.webp") - await self._index_service.index_image(img, img_data, skip_ocr=skip_ocr, - background=True) # The img might be modified after calling this + await self._index_service.index_image(img, img_data, skip_ocr=skip_ocr, background=True) logger.success("Image {} indexed.", img_data.id) if img_data.local: diff --git a/tests/api/integrate_test.py b/tests/api/integrate_test.py index 18e0621..d21608f 100644 --- a/tests/api/integrate_test.py +++ b/tests/api/integrate_test.py @@ -17,15 +17,16 @@ async def test_integrate(test_client): credentials = {'x-admin-token': TEST_ADMIN_TOKEN, 'x-access-token': TEST_ACCESS_TOKEN} resp = test_client.get("/", headers=credentials) assert resp.status_code == 200 - img_ids = dict() - for img_cls in test_images: + img_ids = {} + for img_cls, item_images in test_images.items(): img_ids[img_cls] = [] - for image in test_images[img_cls]: + for image in item_images: print(f'upload image {image}...') - resp = test_client.post('/admin/upload', - files={'image_file': open(assets_path / 'test_images' / image, 'rb')}, - headers=credentials, - params={'local': True}) + with open(assets_path / 'test_images' / image, 'rb') as f: + resp = test_client.post('/admin/upload', + files={'image_file': f}, + headers=credentials, + params={'local': True}) assert resp.status_code == 200 img_ids[img_cls].append(resp.json()['image_id']) @@ -42,9 +43,10 @@ async def test_integrate(test_client): assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['cg'] - resp = test_client.post('/search/image', - files={'image': open(assets_path / 'test_images' / test_images['cat'][0], 'rb')}, - headers=credentials) + with open(assets_path / 'test_images' / test_images['cat'][0], 'rb') as f: + resp = test_client.post('/search/image', + files={'image': f}, + headers=credentials) assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['cat'] @@ -59,10 +61,10 @@ async def test_integrate(test_client): headers=credentials) assert resp.status_code == 200 - resp = test_client.get(f"/search/text/cat", params={'categories': 'bsn'}, headers=credentials) + resp = test_client.get("/search/text/cat", params={'categories': 'bsn'}, headers=credentials) assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['bsn'] - resp = test_client.get(f"/search/text/cat", params={'starred': True}, headers=credentials) + resp = test_client.get("/search/text/cat", params={'starred': True}, headers=credentials) assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['bsn'] diff --git a/tests/unit/test_transformers_service.py b/tests/unit/test_transformers_service.py index f1fbb8a..8fcbd3d 100644 --- a/tests/unit/test_transformers_service.py +++ b/tests/unit/test_transformers_service.py @@ -8,7 +8,7 @@ class TestTransformersService: - def setup_class(self): + def __init__(self): self.transformers_service = TransformersService() self.assets_root = Path(__file__).parent / '..' / 'assets' From 673138ef6e24e91de45a4d846269cac7571fbf5e Mon Sep 17 00:00:00 2001 From: EdgeNeko Date: Tue, 14 May 2024 19:59:08 +0800 Subject: [PATCH 2/3] Test delete and local get in integrate test --- tests/api/conftest.py | 25 ++++++++++++++----------- tests/api/integrate_test.py | 11 +++++++++++ tests/api/test_home.py | 13 ------------- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/api/conftest.py b/tests/api/conftest.py index 1a60c83..b643f57 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -1,3 +1,5 @@ +import importlib + import pytest from fastapi.testclient import TestClient @@ -6,21 +8,22 @@ TEST_ACCESS_TOKEN = 'test_token' TEST_ADMIN_TOKEN = 'test_admin_token' -config.config.qdrant.mode = "memory" -config.config.admin_api_enable = True -config.config.access_protected = True -config.config.access_token = TEST_ACCESS_TOKEN -config.config.admin_token = TEST_ADMIN_TOKEN -config.config.storage.method = config.StorageMode.LOCAL - - @pytest.fixture(scope="session") def test_client(tmp_path_factory) -> TestClient: # Modify the configuration for testing + config.config.qdrant.mode = "memory" + config.config.admin_api_enable = True + config.config.access_protected = True + config.config.access_token = TEST_ACCESS_TOKEN + config.config.admin_token = TEST_ADMIN_TOKEN + config.config.storage.method = config.StorageMode.LOCAL config.config.storage.local.path = tmp_path_factory.mktemp("static_files") - - from app.webapp import app # Start the application - with TestClient(app) as client: + with TestClient(importlib.import_module('app.webapp').app) as client: yield client + + +@pytest.fixture +def anyio_backend(): + return 'asyncio' diff --git a/tests/api/integrate_test.py b/tests/api/integrate_test.py index d21608f..562b20d 100644 --- a/tests/api/integrate_test.py +++ b/tests/api/integrate_test.py @@ -57,6 +57,10 @@ async def test_integrate(test_client): assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['bsn'] + image_request = test_client.get(resp.json()['result'][0]['img']['url']) + assert image_request.status_code == 200 + assert image_request.headers['Content-Type'] == 'image/jpeg' + resp = test_client.put(f"/admin/update_opt/{img_ids['bsn'][0]}", json={'categories': ['bsn'], 'starred': True}, headers=credentials) assert resp.status_code == 200 @@ -68,3 +72,10 @@ async def test_integrate(test_client): resp = test_client.get("/search/text/cat", params={'starred': True}, headers=credentials) assert resp.status_code == 200 assert resp.json()['result'][0]['img']['id'] in img_ids['bsn'] + + resp = test_client.delete(f"/admin/delete/{img_ids['bsn'][0]}", headers=credentials) + assert resp.status_code == 200 + + resp = test_client.get("/search/text/cat", params={'categories': 'bsn'}, headers=credentials) + assert resp.status_code == 200 + assert len(resp.json()['result']) == 0 diff --git a/tests/api/test_home.py b/tests/api/test_home.py index 1e86049..6f91a2e 100644 --- a/tests/api/test_home.py +++ b/tests/api/test_home.py @@ -1,16 +1,3 @@ -import pytest -from fastapi.testclient import TestClient - -from app.webapp import app - -client = TestClient(app) - - -@pytest.fixture -def anyio_backend(): - return 'asyncio' - - class TestHome: def test_get_home_no_tokens(self, test_client): From ef551cc51e2e03816b9a617313692495ed42463b Mon Sep 17 00:00:00 2001 From: EdgeNeko Date: Tue, 14 May 2024 20:13:37 +0800 Subject: [PATCH 3/3] Improve readme --- readme.md | 33 ++++++++++++++++++++++----------- readme_cn.md | 43 +++++++++++++++++++++++++++++-------------- tests/api/conftest.py | 1 + 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/readme.md b/readme.md index 61b0408..684b122 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,9 @@ [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/hv0905/NekoImageGallery/prod.yml?logo=github)](https://github.com/hv0905/NekoImageGallery/actions) [![codecov](https://codecov.io/gh/hv0905/NekoImageGallery/branch/master/graph/badge.svg?token=JK2KZBDIYP)](https://codecov.io/gh/hv0905/NekoImageGallery) +[![Maintainability](https://api.codeclimate.com/v1/badges/ac97a1146648996b68ea/maintainability)](https://codeclimate.com/github/hv0905/NekoImageGallery/maintainability) ![Man hours](https://img.shields.io/endpoint?url=https%3A%2F%2Fmanhours.aiursoft.cn%2Fr%2Fgithub.com%2Fhv0905%2FNekoImageGallery.json) +[![Docker Pulls](https://img.shields.io/docker/pulls/edgeneko/neko-image-gallery)](https://hub.docker.com/r/edgeneko/neko-image-gallery) An online AI image search engine based on the Clip model and Qdrant vector database. Supports keyword search and similar image search. @@ -117,19 +119,28 @@ Local file storage does not require an additional database deployment process, b NekoImageGallery's docker image are built and released on Docker Hub, including serval variants: -| Tags | Description | Latest Image Size | -|---------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `edgeneko/neko-image-gallery:`
`edgeneko/neko-image-gallery:-cuda`
`edgeneko/neko-image-gallery:-cuda12.1` | Supports GPU inferencing with CUDA12.1 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest?label=Docker%20Image%20(cuda))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | -| `edgeneko/neko-image-gallery:-cuda11.8` | Supports GPU inferencing with CUDA11.8 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cuda11.8?label=Docker%20Image%20(cuda11.8))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | -| `edgeneko/neko-image-gallery:-cpu` | Only supports CPU inferencing | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cpu?label=Docker%20Image%20(cpu))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| Tags | Description | Latest Image Size | +|---------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `edgeneko/neko-image-gallery:`
`edgeneko/neko-image-gallery:-cuda`
`edgeneko/neko-image-gallery:-cuda12.1` | Supports GPU inferencing with CUDA12.1 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest?label=Image%20(cuda))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| `edgeneko/neko-image-gallery:-cuda11.8` | Supports GPU inferencing with CUDA11.8 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cuda11.8?label=Image%20(cuda11.8))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| `edgeneko/neko-image-gallery:-cpu` | Only supports CPU inferencing | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cpu?label=Image%20(cpu))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | Where `` is the version number or version alias of NekoImageGallery, as follows: -| Version | Description | -|----------|--------------------------------------------------------------------------------------------------------| -| `latest` | The latest stable version of NekoImageGallery | -| `v0.1.0` | The specific version number (correspond to Git tags) | -| `edge` | The latest development version of NekoImageGallery, may contain unstable features and breaking changes | +| Version | Description | +|-------------------|--------------------------------------------------------------------------------------------------------| +| `latest` | The latest stable version of NekoImageGallery | +| `v*.*.*` / `v*.*` | The specific version number (correspond to Git tags) | +| `edge` | The latest development version of NekoImageGallery, may contain unstable features and breaking changes | + +In each image, we have bundled the necessary dependencies, `openai/clip-vit-large-patch14` model +weights, `bert-base-chinese` model weights and `easy-paddle-ocr` models to provide a complete and ready-to-use image. + +The images uses `/opt/NekoImageGallery/static` as volume to store image files, mount it to your own volume or directory +if local storage is required. + +For configuration, we suggest using environment variables to override the default configuration. Secrets (like API +tokens) can be provided by [docker secrets](https://docs.docker.com/engine/swarm/secrets/). #### Prepare `nvidia-container-runtime` (CUDA users only) @@ -192,4 +203,4 @@ define them more clearly. Copyright 2023 EdgeNeko -Licensed under GPLv3 license. +Licensed under AGPLv3 license. diff --git a/readme_cn.md b/readme_cn.md index e867d75..99380ec 100644 --- a/readme_cn.md +++ b/readme_cn.md @@ -2,7 +2,9 @@ [![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/hv0905/NekoImageGallery/prod.yml?logo=github)](https://github.com/hv0905/NekoImageGallery/actions) [![codecov](https://codecov.io/gh/hv0905/NekoImageGallery/branch/master/graph/badge.svg?token=JK2KZBDIYP)](https://codecov.io/gh/hv0905/NekoImageGallery) +[![Maintainability](https://api.codeclimate.com/v1/badges/ac97a1146648996b68ea/maintainability)](https://codeclimate.com/github/hv0905/NekoImageGallery/maintainability) ![Man hours](https://img.shields.io/endpoint?url=https%3A%2F%2Fmanhours.aiursoft.cn%2Fr%2Fgithub.com%2Fhv0905%2FNekoImageGallery.json) +[![Docker Pulls](https://img.shields.io/docker/pulls/edgeneko/neko-image-gallery)](https://hub.docker.com/r/edgeneko/neko-image-gallery) 基于Clip模型与Qdrant向量数据库的在线AI图片搜索引擎。支持关键字搜索以及相似图片搜索。 @@ -25,7 +27,6 @@ > 以上截图可能包含来自不同画师的版权图片,请不要将其用作其它用途。 - ## ✈️部署 ### 🖥️ 本地部署 @@ -53,6 +54,7 @@ NekoImageGallery支持两种元数据存储方式:Qdrant数据库存储与本 - 当你希望迁移到Qdrant数据库进行存储时,已索引的元数据可能难以直接迁移。 #### 部署NekoImageGallery + 1. 将项目目录clone到你自己的PC或服务器中。 2. 强烈建议在python venv虚拟环境中安装本项目所需依赖, 运行下面命令: ```shell @@ -60,12 +62,14 @@ NekoImageGallery支持两种元数据存储方式:Qdrant数据库存储与本 . .venv/bin/activate ``` 3. 安装PyTorch. 按照[PyTorch文档](https://pytorch.org/get-started/locally/)使用pip安装适合你的系统的torch版本 - > 如果您希望使用CUDA加速推理,务必在本步中安装支持Cuda的pytorch版本,安装完成后可以使用`torch.cuda.is_available()`确认CUDA是否可用。 + > 如果您希望使用CUDA加速推理,务必在本步中安装支持Cuda的pytorch版本,安装完成后可以使用`torch.cuda.is_available()` + 确认CUDA是否可用。 4. 安装其它本项目所需依赖: ```shell pip install -r requirements.txt ``` -5. 按需修改位于`config`目录下的配置文件,您可以直接修改`default.env`,但是建议创建一个名为`local.env`的文件,覆盖`default.env`中的配置。 +5. 按需修改位于`config`目录下的配置文件,您可以直接修改`default.env`,但是建议创建一个名为`local.env` + 的文件,覆盖`default.env`中的配置。 6. 初始化Qdrant数据库,运行下面命令: ```shell python main.py --init-database @@ -75,21 +79,23 @@ NekoImageGallery支持两种元数据存储方式:Qdrant数据库存储与本 ```shell python main.py --local-index ``` - 此操作会将位于``目录下的所有图片文件复制到`config.STATIC_FILE_PATH`目录下(默认为`./static`),并将图片信息写入Qdrant数据库。 - + 此操作会将位于``目录下的所有图片文件复制到`config.STATIC_FILE_PATH`目录下( + 默认为`./static`),并将图片信息写入Qdrant数据库。 + 然后运行下面的命令,为所有static目录下的图片生成缩略图: ```shell python main.py --local-create-thumbnail ``` - + 如果你希望大规模部署,可以使用类似`MinIO`的OSS存储服务,将图片文件存储在OSS中,然后将图片信息写入Qdrant数据库即可。 8. 运行本应用: ```shell python main.py ``` 你可以通过`--host`指定希望绑定到的ip地址(默认为0.0.0.0),通过`--port`指定希望绑定到的端口(默认为8000)。 -9. (可选)部署前端应用:[NekoImageGallery.App](https://github.com/hv0905/NekoImageGallery.App)是本项目的一个简易web前端应用,如需部署请参照它的[部署文档](https://github.com/hv0905/NekoImageGallery.App)。 +9. (可选)部署前端应用:[NekoImageGallery.App](https://github.com/hv0905/NekoImageGallery.App) + 是本项目的一个简易web前端应用,如需部署请参照它的[部署文档](https://github.com/hv0905/NekoImageGallery.App)。 ### 🐋 Docker 部署 @@ -97,11 +103,11 @@ NekoImageGallery支持两种元数据存储方式:Qdrant数据库存储与本 NekoImageGallery镜像发布在DockerHub上,并包含多个变种,设计于在不同的环境使用。 -| Tags | 介绍 | Latest 镜像尺寸 | -|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `edgeneko/neko-image-gallery:`
`edgeneko/neko-image-gallery:-cuda`
`edgeneko/neko-image-gallery:-cuda12.1` | 基于CUDA12.1, 支持GPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest?label=Docker%20Image%20(cuda))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | -| `edgeneko/neko-image-gallery:-cuda11.8` | 基于CUDA11.8, 支持GPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cuda11.8?label=Docker%20Image%20(cuda11.8))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | -| `edgeneko/neko-image-gallery:-cpu` | 仅支持CPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cpu?label=Docker%20Image%20(cpu))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| Tags | 介绍 | Latest 镜像尺寸 | +|---------------------------------------------------------------------------------------------------------------------------------------------|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `edgeneko/neko-image-gallery:`
`edgeneko/neko-image-gallery:-cuda`
`edgeneko/neko-image-gallery:-cuda12.1` | 基于CUDA12.1, 支持GPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest?label=Image%20(cuda))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| `edgeneko/neko-image-gallery:-cuda11.8` | 基于CUDA11.8, 支持GPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cuda11.8?label=Image%20(cuda11.8))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | +| `edgeneko/neko-image-gallery:-cpu` | 仅支持CPU推理的镜像 | [![Docker Image Size (tag)](https://img.shields.io/docker/image-size/edgeneko/neko-image-gallery/latest-cpu?label=Image%20(cpu))](https://hub.docker.com/r/edgeneko/neko-image-gallery) | 其中,``为NekoImageGallery的版本号或版本代称,具体如下: @@ -111,11 +117,20 @@ NekoImageGallery镜像发布在DockerHub上,并包含多个变种,设计于 | `v*.*.*` / `v*.*` | 特定版本号(与GitHub Tag对应) | | `edge` | 最新的开发版本,与master分支同步更新,可能包含未经完善测试的功能和breaking changes | +在每个镜像中,我们捆绑了必要的依赖项,包括 `openai/clip-vit-large-patch14` 模型权重、`bert-base-chinese` +模型权重和 `easy-paddle-ocr` 模型,以提供一个完整且可直接使用的镜像。 + +镜像使用 `/opt/NekoImageGallery/static` 作为存储图像文件的卷,如果需要本地存储,可以将其挂载到您自己的卷或目录。 + +对于配置,我们建议使用环境变量来覆盖默认配置。机密信息(如 API +令牌)可以通过 [docker secrets](https://docs.docker.com/engine/swarm/secrets/) 提供。 + #### 准备`nvidia-container-runtime` -如果你希望在推理时支持CUDA加速,请参考[Docker GPU相关文档](https://docs.docker.com/config/containers/resource_constraints/#gpu)准备支持GPU的容器运行时。 +如果你希望在推理时支持CUDA加速,请参考[Docker GPU相关文档](https://docs.docker.com/config/containers/resource_constraints/#gpu) +准备支持GPU的容器运行时。 -> 相关文档: +> 相关文档: > 1. https://docs.docker.com/config/containers/resource_constraints/#gpu > 2. https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html#docker > 3. https://nvidia.github.io/nvidia-container-runtime/ diff --git a/tests/api/conftest.py b/tests/api/conftest.py index b643f57..cbc9845 100644 --- a/tests/api/conftest.py +++ b/tests/api/conftest.py @@ -8,6 +8,7 @@ TEST_ACCESS_TOKEN = 'test_token' TEST_ADMIN_TOKEN = 'test_admin_token' + @pytest.fixture(scope="session") def test_client(tmp_path_factory) -> TestClient: # Modify the configuration for testing