Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
.git/
uploads/
results/
annotated/
tests/
*.md
.github/
79 changes: 42 additions & 37 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,79 @@ name: Build Docker Images

on:
push:
branches: [ main ]
branches: [ main, docker_beauty ] # TODO: remove docker_beauty before merging
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
IMAGE: breedinginsight/nemaquant # Docker Hub org/image

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
strategy:
fail-fast: false
matrix:
include:
- variant: cpu
dockerfile: Dockerfile
tag_suffix: "" # cpu is the default, no suffix on latest/version tags
- variant: gpu
dockerfile: Dockerfile.gpu
tag_suffix: "-gpu"

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Extract version from CHANGELOG.md
id: version
run: |
VERSION=$(grep -m1 '^## \[' CHANGELOG.md | sed 's/.*\[\(.*\)\].*/\1/')
if [ -z "$VERSION" ]; then
echo "ERROR: Could not extract version from CHANGELOG.md" >&2
exit 1
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Detected version: $VERSION"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# - name: Log in to Docker Hub
# if: ${{ secrets.DOCKERHUB_USERNAME }}
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# ${{ secrets.DOCKERHUB_USERNAME }}/nemaquant
images: ${{ env.IMAGE }}
tags: |
type=raw,value=latest
type=sha,prefix=main-
# version from CHANGELOG.md: produce 1.2.3 / 1.2.3-gpu
type=raw,value=${{ steps.version.outputs.version }}${{ matrix.tag_suffix }}
# on main branch: produce latest / latest-gpu
type=raw,value=latest,suffix=${{ matrix.tag_suffix }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
platforms: linux/amd64
provenance: false
sbom: false
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: type=gha,scope=${{ matrix.variant }}
cache-to: type=gha,mode=max,scope=${{ matrix.variant }}

- name: Summary
run: |
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
echo "- **GitHub Container Registry**: \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest\`" >> $GITHUB_STEP_SUMMARY
echo "- **Platform**: linux/amd64 (optimized for Docker Desktop)" >> $GITHUB_STEP_SUMMARY
# if [ -n "${{ secrets.DOCKERHUB_USERNAME }}" ]; then
# echo "- **Docker Hub**: \`${{ secrets.DOCKERHUB_USERNAME }}/nemaquant:latest\`" >> $GITHUB_STEP_SUMMARY
# fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Pull the image:" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`bash" >> $GITHUB_STEP_SUMMARY
echo "docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest" >> $GITHUB_STEP_SUMMARY
echo "## ${{ matrix.variant }} image" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY

126 changes: 64 additions & 62 deletions .github/workflows/deploy-to-hf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Deploy to Hugging Face Spaces

on:
push:
branches: [ main ]
branches: [ main, docker_beauty ] # TODO: remove docker_beauty before merging
workflow_dispatch:

jobs:
Expand All @@ -11,94 +11,96 @@ jobs:
permissions:
contents: read

env:
HF_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
HF_SPACE_REPO: ${{ secrets.HUGGINGFACE_SPACE_REPO }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v5
with:
lfs: true

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Validate secrets
run: |
if [ -z "$HF_TOKEN" ]; then
echo "ERROR: HUGGINGFACE_TOKEN secret is not set." >&2
exit 1
fi
if [ -z "$HF_SPACE_REPO" ]; then
echo "ERROR: HUGGINGFACE_SPACE_REPO secret is not set." >&2
exit 1
fi

- name: Install dependencies
- name: Configure git
run: |
pip install huggingface_hub
# Install Git LFS
sudo apt-get update
sudo apt-get install -y git-lfs
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git config --global credential.helper store
# HF accepts any username when authenticating via token
printf 'https://user:%s@huggingface.co\n' "$HF_TOKEN" > ~/.git-credentials
git lfs install

- name: Push to Hugging Face Space
env:
HF_TOKEN: ${{ secrets.HUGGINGFACE_TOKEN }}
- name: Clone existing HF Space
run: |
# Check if HUGGINGFACE_SPACE_REPO is set
if [ -z "${{ secrets.HUGGINGFACE_SPACE_REPO }}" ]; then
echo "HUGGINGFACE_SPACE_REPO secret not set. Please set it to your space repository name (e.g., 'username/nemaquant')"
exit 1
fi

# Configure git credentials for Hugging Face
git config --global credential.helper store
echo "https://user:${HF_TOKEN}@huggingface.co" > ~/.git-credentials

# Create a temporary directory for the space
mkdir -p space_repo
git clone "https://huggingface.co/spaces/$HF_SPACE_REPO" space_repo
cd space_repo

# Initialize git repository with LFS
git init -b main
git lfs install
git remote add origin https://huggingface.co/spaces/${{ secrets.HUGGINGFACE_SPACE_REPO }}

# Create .gitattributes for LFS BEFORE copying files
echo "*.pt filter=lfs diff=lfs merge=lfs -text" > .gitattributes
echo "*.pth filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.bin filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.h5 filter=lfs diff=lfs merge=lfs -text" >> .gitattributes
echo "*.onnx filter=lfs diff=lfs merge=lfs -text" >> .gitattributes

# Copy necessary files
cp ../README.md .

- name: Sync files into Space
run: |
cd space_repo

# Ensure LFS tracks model file types
cat > .gitattributes <<'EOF'
*.pt filter=lfs diff=lfs merge=lfs -text
*.pth filter=lfs diff=lfs merge=lfs -text
*.bin filter=lfs diff=lfs merge=lfs -text
*.h5 filter=lfs diff=lfs merge=lfs -text
*.onnx filter=lfs diff=lfs merge=lfs -text
EOF

# Sync application files
cp ../app.py .
cp ../requirements.txt .
cp ../Dockerfile .
cp ../yolo_utils.py .
cp ../README.md .
cp -r ../templates .
cp -r ../static .

# Copy all .pt and .onnx files from the root directory
echo "Checking for model files..."
find .. -maxdepth 1 -type f -name "*.pt" -exec cp {} . \;
find .. -maxdepth 1 -type f -name "*.onnx" -exec cp {} . \;

# List what we're about to commit

# Sync model weight files
find .. -maxdepth 1 -type f \( -name "*.pt" -o -name "*.onnx" \) -exec cp {} . \;

echo "Files to be committed:"
ls -lh

# Add and commit
git add .gitattributes

- name: Commit and push
run: |
cd space_repo
echo "--- Remote URL ---"
git remote -v
echo "--- Current HEAD ---"
git log --oneline -3
git add .
git config user.name "GitHub Actions"
git config user.email "actions@github.com"
git commit -m "Update NemaQuant app from GitHub Actions - ${{ github.sha }}"

# Push to Hugging Face Space (force push to main branch)
git push --force origin main
if git diff --cached --quiet; then
echo "No changes to deploy."
else
git commit -m "deploy: sync from GitHub ${{ github.sha }}"
git push -v origin main
fi

- name: Summary
run: |
echo "## Deployment Summary" >> $GITHUB_STEP_SUMMARY
echo "- **Hugging Face Space**: \`https://huggingface.co/spaces/${{ secrets.HUGGINGFACE_SPACE_REPO }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Space**: [spaces/$HF_SPACE_REPO](https://huggingface.co/spaces/$HF_SPACE_REPO)" >> $GITHUB_STEP_SUMMARY
echo "- **Commit**: \`${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **Status**: Successfully deployed to HF Spaces" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Model Files:" >> $GITHUB_STEP_SUMMARY
echo "### Model files deployed:" >> $GITHUB_STEP_SUMMARY
cd space_repo
for file in *.pt *.onnx; do
if [ -f "$file" ]; then
echo "- ✅ $file ($(ls -lh "$file" | awk '{print $5}'))" >> $GITHUB_STEP_SUMMARY
fi
[ -f "$file" ] && echo "- \`$file\` ($(ls -lh "$file" | awk '{print $5}'))" >> $GITHUB_STEP_SUMMARY
done
for file in *.pt *.onnx; do
[ -f "$file" ] && echo "- \`$file\` ($(ls -lh "$file" | awk '{print $5}'))" >> $GITHUB_STEP_SUMMARY
done
58 changes: 58 additions & 0 deletions .github/workflows/update-dockerhub-meta.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Update Docker Hub Metadata

on:
push:
branches: [ main ]
paths:
- README.md # re-run when README changes
- .github/workflows/update-dockerhub-meta.yml
workflow_dispatch:

env:
DOCKERHUB_REPO: breedinginsight/nemaquant

jobs:
update-meta:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Update Docker Hub description
uses: peter-evans/dockerhub-description@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
repository: ${{ env.DOCKERHUB_REPO }}
short-description: "YOLO-based nematode egg detection with real-time processing"
readme-filepath: ./README.md # used as the full (long) description on Docker Hub

- name: Set Docker Hub category via API
run: |
# Authenticate and get JWT token
TOKEN=$(curl -s -X POST "https://hub.docker.com/v2/users/login" \
-H "Content-Type: application/json" \
-d "{\"username\": \"${{ secrets.DOCKERHUB_USERNAME }}\", \"password\": \"${{ secrets.DOCKERHUB_PASSWORD }}\"}" \
| jq -r '.token')

if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "ERROR: Failed to authenticate with Docker Hub" >&2
exit 1
fi

# Set repository category (Machine Learning)
# Full list: https://hub.docker.com/search?categories=
curl -s -X PATCH "https://hub.docker.com/v2/repositories/${{ env.DOCKERHUB_REPO }}/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"categories": [{"name": "Machine Learning"}]}' \
| jq .

- name: Summary
run: |
echo "## Docker Hub Metadata Updated" >> $GITHUB_STEP_SUMMARY
echo "- **Repository**: [hub.docker.com/r/${{ env.DOCKERHUB_REPO }}](https://hub.docker.com/r/${{ env.DOCKERHUB_REPO }})" >> $GITHUB_STEP_SUMMARY
echo "- **Short description**: YOLO-based nematode egg detection with real-time processing" >> $GITHUB_STEP_SUMMARY
echo "- **Full description**: synced from \`README.md\`" >> $GITHUB_STEP_SUMMARY
echo "- **Category**: Machine Learning" >> $GITHUB_STEP_SUMMARY
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ __pycache__/
uploads/
results/
annotated/
tests/
Loading
Loading