Compare commits

...
15 Commits
Author SHA1 Message Date
pi-ux-ce a2ebb2be10 Update stash_tagger.md
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 13m5s
2026-01-24 16:42:46 +00:00
pi-ux-ce 2126d8e231 Add stash_tagger.md
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
2026-01-24 16:38:45 +00:00
Pierre Geeraert 3a96506bd3 removing extra steps
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 17m41s
2026-01-19 11:35:03 +00:00
Pierre Geeraert 0827430e81 adding workflow 2026-01-19 11:32:35 +00:00
pi-ux-ce c6978ab0b3 adding this to leave the pod on all the time 2026-01-19 11:28:35 +00:00
pierre geeraert 3df7523ecd adding files 2026-01-18 22:35:40 +00:00
corkborgandGitHub fcf50e428c Merge pull request #29 from corkborg/load-truncated-images
Allow images with broken headers to load.
2025-08-09 14:09:02 +09:00
corkborg 154c92937f Fix import. 2025-08-09 14:08:36 +09:00
corkborg 1689bbd2da Allow images with broken headers to load. 2025-07-26 23:22:46 +09:00
corkborgandGitHub aa0c06ab5c Merge pull request #25 from cmeka/rm-torch
Remove torch dependency
2025-04-29 20:11:39 +09:00
cmeka 5823f3a10e Remove torch dependency
Camie tagger doesn't need torch
This is useful to save disk space
2025-04-26 17:55:47 -04:00
corkborgandGitHub 8223ce153a Merge pull request #24 from allenday/feat-onnx-coreml
Improve ONNX provider selection with CoreML support for Apple Silicon
2025-04-19 21:27:06 +09:00
Allen DayandGitHub 707b2b7cf2 silence printf 2025-04-12 13:05:44 +08:00
Allen DayandGitHub 13d32c034d silence printf 2025-04-12 13:03:51 +08:00
Allen Day b530bb7571 Improve ONNX provider selection with CoreML support 2025-04-12 11:56:55 +07:00
9 changed files with 304 additions and 26 deletions
+80
View File
@@ -0,0 +1,80 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on:
push:
issue_comment:
types: [created]
jobs:
Explore-Gitea-Actions:
env:
RUNNER_TOOL_CACHE: /toolcache
container:
volumes:
- act-toolcache:/opt/hostedtoolcache # this is where Runner store their cache default
runs-on: ubuntu-latest
steps:
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- name: Compute next version
id: versioning
run: |
latest="${{ vars.VERSION }}"
next_version=$(echo "$latest" | awk -F. -v OFS=. '{$NF += 1 ; print}')
echo "next_version=$next_version" >> $GITHUB_ENV
echo "Variables used :"
echo "------------------------------------------------"
echo "Next version: $next_version"
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /opt/hostedtoolcache/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: inject cache into docker
uses: reproducible-containers/buildkit-cache-dance@v3.1.0
with:
cache-source: /opt/hostedtoolcache/pip
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to gitea
uses: docker/login-action@v3
with:
registry: gitea.tryton.geeraert.eu
username: ${{ secrets.USERNAME_GITEA }}
password: ${{ secrets.PASSWORD_GITEA }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
tags: |
gitea.tryton.geeraert.eu/pi-ux-ce/wd14-tagger:latest
gitea.tryton.geeraert.eu/pi-ux-ce/wd14-tagger:${{ env.next_version }}
cache-from: type=local,src=/opt/hostedtoolcache/.buildx-cache
cache-to: type=local,dest=/opt/hostedtoolcache/.buildx-cache-new,mode=max
- name: Move cache
run: |
rm -rf /opt/hostedtoolcache/.buildx-cache
mv /opt/hostedtoolcache/.buildx-cache-new /opt/hostedtoolcache/.buildx-cache
- name: update version in the variable
run: |
curl -X PUT -H "Content-Type: application/json" \
--user ${{ secrets.USERNAME_GITEA }}:${{ secrets.PASSWORD_GITEA }} \
https://gitea.tryton.geeraert.eu/api/v1/repos/pi-ux-ce/${{ github.event.repository.name }}/actions/variables/VERSION \
-d "{\"Value\":\"$next_version\"}"
+2 -1
View File
@@ -4,4 +4,5 @@ __pycache__/
venv/
.env
presets/
presets/
pictures/
+27
View File
@@ -0,0 +1,27 @@
FROM python:3.12-slim
WORKDIR /usr/src/app
COPY . .
#RUN cp .env.exp .env
#RUN sed -i 's|SERVER_HOST=127.0.0.1|SERVER_HOST=0.0.0.0|' .env
RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \
--mount=target=/var/cache/apt,type=cache,sharing=locked \
rm -f /etc/apt/apt.conf.d/docker-clean \
&& apt-get update \
&& apt-get install ffmpeg libsm6 libxext6 -y
#RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y
RUN pip install --cache-dir=/tmp/pipcache --upgrade pip
RUN pip list --cache-dir=/tmp/pipcache --outdated
RUN pip install --cache-dir=/tmp/pipcache -r requirements.txt
#RUN pip install pdm
#RUN pdm install
#CMD ["pdm", "run", "python3", "main.py"]
#CMD ["python3", "run.py"]
CMD ["sleep", "infinity"]
#CMD ["python3", "run.py" ,"--threshold 0.5" ,"--file thereneerose_0002.jpg" ,"--rawtag"]
#CMD ["python3", "run.py" ,"--threshold","0.5" ,"--file", "thereneerose_0002.jpg" ,"--rawtag"]
#CMD ["python3 run.py --threshold 0.5 --file thereneerose_0002.jpg --rawtag"]
+10
View File
@@ -0,0 +1,10 @@
---
version: "2"
services:
wd14-tagger-standalone:
build: .
container_name: wd14-tagger-standalone
volumes:
- ./pictures:/pictures
restart: unless-stopped
command: sleep 3600
+2 -3
View File
@@ -1,9 +1,8 @@
numpy==2.2.2
numpy
pandas==2.2.3
torchvision==0.21.0
Pillow==11.1.0
huggingface-hub==0.28.1
deepdanbooru==1.0.3
onnxruntime==1.20.1
onnxruntime
opencv-python==4.11.0.86
+4 -1
View File
@@ -2,12 +2,15 @@ import sys
from typing import Generator, Iterable
from tagger.interrogator.interrogator import AbsInterrogator
from PIL import Image
from PIL import Image, ImageFile
from pathlib import Path
import argparse
from tagger.interrogators import interrogators
# Allow images with broken headers to load
ImageFile.LOAD_TRUNCATED_IMAGES = True
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group(required=True)
+106
View File
@@ -0,0 +1,106 @@
Get the list of the performers
```
query {
findPerformers {
performers {
id
name
alias_list
}
}
}
```
Get the list of the performers , count them and don't add their alias
```
query {
findPerformers {
count
performers {
id
name
}
}
}
```
Find all the images split by 20 images
```
query FindImages {
findImages(
image_filter: {}
filter: { q: "", page: 1, per_page: 20, sort: "created_at", direction: DESC }
) {
count
images {
id
title
tags{
name
}
paths {
image
}
}
}
}
```
```
query GetImageFiles {
findImages(filter: { per_page: 20 }) {
images {
id
title
tags {
id
}
files {
path
basename
}
}
}
}
```
Get specific image ID by filename
```
query FindImageByPath {
findImages(
image_filter: { path: { value: "/data/1563-01.jpg", modifier: EQUALS } }
) {
images {
id
}
}
}
```
amateur-have-be-our-favourite-position-fm-MsM3gC.jpg
update specific image with specified tags
```
mutation UpdateImageTags($imageId: ID!, $newTagIds: [ID!]) {
imageUpdate(input: {
id: "58",
tag_ids: "5"
}) {
id
tags {
name
}
}
}
```
examples of query
```
curl -X POST -H "ApiKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJwaS11eC1jZSIsInN1YiI6IkFQSUtleSIsImlhdCI6MTc2OTI3MDg0NX0._E6j5vMFV087KrNSjAmF3tVun39-ZXEpTIva9cLRR4Y" -H "Content-Type: application/json" --data '{ "query": "query {findPerformers {performers{id name}}}"}' https://stash.tryton.geeraert.eu/graphql
curl -X POST -H "ApiKey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJwaS11eC1jZSIsInN1YiI6IkFQSUtleSIsImlhdCI6MTc2OTI3MDg0NX0._E6j5vMFV087KrNSjAmF3tVun39-ZXEpTIva9cLRR4Y" -H "Content-Type: application/json" --data '{ "query": "query {findPerformers {performers{id name}}}"}' https://stash.tryton.geeraert.eu/graphql |jq
```
+37 -19
View File
@@ -5,11 +5,8 @@ https://huggingface.co/Camais03/camie-tagger/blob/main/onnx_inference.py
"""
import sys
import json
from typing import cast
import numpy as np
import torch
from PIL import Image
from huggingface_hub import hf_hub_download
from onnxruntime import InferenceSession
@@ -71,11 +68,20 @@ class CamieTaggerInterrogator(AbsInterrogator):
if self.model is None:
raise Exception("Model not loading.")
img_tensor = preprocess_image(image)
img_numpy = img_tensor.unsqueeze(0).numpy()
# Use the NumPy preprocessing function
# Result is already a NumPy array with shape (C, H, W) and float32 type
img_array_chw = preprocess_image(image) # e.g., shape (3, 512, 512)
# ONNX models typically expect input in NCHW format (Batch, Channel, Height, Width)
# Add a batch dimension (N=1) at the beginning
img_numpy = np.expand_dims(img_array_chw, axis=0) # Now shape (1, 3, 512, 512)
input_ = self.model.get_inputs()[0]
# Ensure input data type matches model expectation (usually float32)
if input_.type == 'tensor(float)':
img_numpy = img_numpy.astype(np.float32)
# evaluate model
outputs = self.model.run(None, {input_.name: img_numpy})
@@ -100,12 +106,25 @@ class CamieTaggerInterrogator(AbsInterrogator):
tags_by_category[category].append((tag_name, prob))
# 'year', 'rating', 'general', 'character', 'copyright', 'artist', 'meta'
return dict(tags_by_category['rating']), dict(tags_by_category['general'])
rating_tags = dict(tags_by_category.get('rating', []))
general_tags = dict(tags_by_category.get('general', []))
def preprocess_image(img: Image.Image, image_size=512) -> torch.Tensor:
"""Process an image for inference"""
return rating_tags, general_tags
# Convert RGBA or Palette images to RGB
def preprocess_image(img: Image.Image, image_size: int = 512) -> np.ndarray:
"""
Process a PIL image for inference using NumPy.
Args:
img: The input PIL Image.
image_size: The target square size (width and height) for the output array.
Returns:
A NumPy array representing the processed image in CHW format,
with pixel values scaled to [0.0, 1.0] and dtype float32.
"""
# Convert to RGB
if img.mode in ('RGBA', 'P'):
img = img.convert('RGB')
@@ -130,14 +149,13 @@ def preprocess_image(img: Image.Image, image_size=512) -> torch.Tensor:
paste_y = (image_size - new_height) // 2
new_image.paste(img, (paste_x, paste_y))
import torchvision.transforms as transforms
# Convert PIL image to NumPy array (H, W, C) with values 0-255
img_array = np.array(new_image, dtype=np.float32)
# Initialize transform
transform = transforms.Compose([
transforms.ToTensor(),
])
# Scale pixel values from [0, 255] to [0.0, 1.0]
img_array /= 255.0
# Apply transforms
img_tensor= transform(new_image)
img_tensor = cast(torch.Tensor, img_tensor)
return img_tensor
# Change dimension order from HWC to CHW
img_array = img_array.transpose((2, 0, 1)) # C, H, W
return img_array
+36 -2
View File
@@ -6,7 +6,7 @@ import pandas as pd
from typing import Iterable, Tuple, List, Dict
from PIL import Image
from onnxruntime import InferenceSession
from onnxruntime import InferenceSession, get_available_providers
tag_escape_pattern = re.compile(r'([\\()])')
@@ -66,7 +66,8 @@ class AbsInterrogator:
def __init__(self, name: str) -> None:
self.name = name
self.providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
# Initialize with optimal provider selection
self.providers = self.get_optimal_provider()
def load(self):
raise NotImplementedError()
@@ -85,7 +86,40 @@ class AbsInterrogator:
return unloaded
def use_cpu(self) -> None:
"""Force CPU-only execution."""
self.providers = ['CPUExecutionProvider']
print(f'Forcing CPU execution for {self.name}', file=sys.stderr)
def get_available_providers(self) -> List[str]:
"""Get list of available execution providers."""
return get_available_providers()
def set_providers(self, providers: List[str]) -> None:
"""Set specific execution providers."""
self.providers = providers
#print(f'Set custom providers for {self.name}: {providers}', file=sys.stderr)
def get_optimal_provider(self) -> List[str]:
"""Get the optimal provider based on system capabilities.
Returns a list of providers in order of preference:
- CoreMLExecutionProvider (if on Apple Silicon)
- CUDAExecutionProvider (if NVIDIA GPU available)
- CPUExecutionProvider (always available as fallback)
"""
available = self.get_available_providers()
# Start with most optimal providers first
optimal_order = [
'CoreMLExecutionProvider', # Best for Apple Silicon
'CUDAExecutionProvider', # Best for NVIDIA GPUs
'CPUExecutionProvider' # Fallback
]
# Return list of available providers in optimal order
selected = [p for p in optimal_order if p in available]
#print(f'Selected optimal providers for {self.name}: {selected}', file=sys.stderr)
return selected
def interrogate(
self,