Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
7e5180e
✨ Create perturbation_explainer.py
SEOYUNJE May 8, 2026
102ef57
✨ Create misc_explainer.py
SEOYUNJE May 8, 2026
065a228
📝 Add __repr__ for all explainer classes
SEOYUNJE May 8, 2026
857b308
✨ Add _resolve_block_idx
SEOYUNJE May 9, 2026
c1dd941
💚 Add PR CI workflow with ruff lint and pytest
SEOYUNJE May 9, 2026
c9f1041
💚 Add PR auto-labeler with path-based label rules
SEOYUNJE May 9, 2026
b84b267
✨ Add cam_mult_image.py for testing
SEOYUNJE May 9, 2026
cf97ec7
✨ Add perturbation_confidence.py for testing
SEOYUNJE May 9, 2026
92c3007
✨ Add road.py for testing
SEOYUNJE May 9, 2026
f060223
⚰️ Remove SegEigenCAM
SEOYUNJE May 11, 2026
36b5049
⚰️ Remove AblationCAM
SEOYUNJE May 11, 2026
d243356
✨ Add remove_padding_and_resize
SEOYUNJE May 11, 2026
294c715
♻️ Refactor display_heatmap with new arguements
SEOYUNJE May 11, 2026
fc436b8
✨ Add display_contour_on_image
SEOYUNJE May 11, 2026
3a6b9bb
✨ Add display_bbox_on_image
SEOYUNJE May 11, 2026
62561dd
✨ Add BinaryClassifierOutputSigmoidTarget
SEOYUNJE May 11, 2026
01cc4fe
✨ Create CAMEvaluator
SEOYUNJE May 12, 2026
d67a426
🎨 Add Reproductability for same result
SEOYUNJE May 13, 2026
9d0ce5b
🚚 Move base,cam,gradient,eigenvalue,perturbation,misc explainer.py to…
SEOYUNJE May 13, 2026
8c43e5e
✨ Create cam router for DeepFake XAI
SEOYUNJE May 13, 2026
ef7fb4c
🔥 Remove legacy file(setup.py)
SEOYUNJE May 13, 2026
91d4981
✨ Create pyproject.toml
SEOYUNJE May 13, 2026
27066ad
🚚 Rename Cam Router -> Explain Router
SEOYUNJE May 14, 2026
573ab4b
✨ Create explain_image
SEOYUNJE May 14, 2026
00a8416
🎨 Add response_class & response_model in inference routes
SEOYUNJE May 14, 2026
302aca4
📝 Add python-dotenv & cryptography
SEOYUNJE May 15, 2026
c8e5566
⚰️ Remove debugging print in upload_video, delete_video
SEOYUNJE May 15, 2026
b430278
✨ Add validation and description to image, video inference params
SEOYUNJE May 15, 2026
13bb4c6
⚰️ Remove Debugging print upload_image, delete_image
SEOYUNJE May 15, 2026
da1ab75
✨ Create ExplainRequest
SEOYUNJE May 15, 2026
086af00
♻️ Refactor BinaryClassifierOutputSigmoidTarget with removing category
SEOYUNJE May 15, 2026
b6bfd16
✨ Create PNGStreamingResponse(media_type="image/png")
SEOYUNJE May 15, 2026
dd81bba
✨ Create explain_svc.py
SEOYUNJE May 16, 2026
bc55e95
🎨 Add threshold argument in ExplainRequest
SEOYUNJE May 16, 2026
1b12a80
✨ Add DeepFake AI Explainability
SEOYUNJE May 16, 2026
4942477
🧹 Slim cache key to (model, dataset, type, branch)
SEOYUNJE May 16, 2026
8043fc6
✨ Add validate_explainer_for_eigen_smooth
SEOYUNJE May 19, 2026
cfee097
⚡️ Include explain_svc, image_svc, video_svc in celery_app
SEOYUNJE May 20, 2026
667e93c
✨ Add get_explain_image_result
SEOYUNJE May 20, 2026
7efe4e1
✨ Add cleanup_image_cam
SEOYUNJE May 20, 2026
8b89a07
✨ Add process_explain_image_task
SEOYUNJE May 20, 2026
1aa3e14
⚡️ Add Celery Task Status(STARTED, RETRY)
SEOYUNJE May 20, 2026
bf2a7b0
⚰️ Remove is_deleted flag in cleanup_image_cam
SEOYUNJE May 20, 2026
a64a483
🚚 Rename ExplainRequest to ExplainImageRequest
SEOYUNJE May 20, 2026
aabfc70
✨ Add _get_transform_from_array
SEOYUNJE May 21, 2026
3474cb8
🔥 Remove contour display type from image explainability
SEOYUNJE May 21, 2026
4b59f54
📝 Update __repr__ with removing unnecessary argument
SEOYUNJE May 21, 2026
d8fafd9
✨ Add display_heatmap_from_array, display_bbox_from_array
SEOYUNJE May 21, 2026
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
31 changes: 31 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
model:
- changed-files:
- any-glob-to-any-file: ["src/model/**", "models/**", "*.pt", "*.pth"]

data:
- changed-files:
- any-glob-to-any-file: ["data/**", "datasets/**", "src/data/**"]

training:
- changed-files:
- any-glob-to-any-file: ["train*.py", "src/train/**", "configs/**"]

api:
- changed-files:
- any-glob-to-any-file: ["api/**", "routers/**", "app.py"]

test:
- changed-files:
- any-glob-to-any-file: ["tests/**", "test_*.py"]

docs:
- changed-files:
- any-glob-to-any-file: ["docs/**", "*.md", "*.rst"]

ci:
- changed-files:
- any-glob-to-any-file: [".github/**"]

dependencies:
- changed-files:
- any-glob-to-any-file: ["requirements*.txt", "pyproject.toml", "setup.py"]
14 changes: 0 additions & 14 deletions .github/workflows/issue-auto-label.yml

This file was deleted.

22 changes: 0 additions & 22 deletions .github/workflows/issue-stale-clean.yml

This file was deleted.

29 changes: 29 additions & 0 deletions .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: PR CI

on:
pull_request:
branches: [main, develop]

jobs:
lint-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"

- name: Install dependencies
run: pip install -r requirements.txt

- name: Lint (ruff)
run: ruff check . --output-format=github

- name: Format check (ruff)
run: ruff format --check .

- name: Test (pytest)
run: pytest --tb=short -q
18 changes: 18 additions & 0 deletions .github/workflows/pr-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: PR Auto Label

on:
pull_request:
types: [opened, synchronize, reopened]

jobs:
label:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
sync-labels: true # PR 파일 변경 시 라벨 재동기화
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
*.egg-info

# [FastAPI / Python - App 폴더 관련]
**/__pycache__/
Expand Down
16 changes: 6 additions & 10 deletions App/celery_app.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
import os
import sys
from pathlib import Path
from celery import Celery
from dotenv import load_dotenv

current_file = Path(__file__).resolve()
project_root = current_file.parent.parent

if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

# ------ .env 파일 가져오기 ------
load_dotenv()

# 기본값 설정
REDIS_HOST = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT = os.getenv("REDIS_PORT", 6379)

Expand All @@ -23,7 +15,11 @@
"deepguard",
broker=REDIS_URL, # 메세지를 전달할 브로커
backend=REDIS_URL, # 작업 결과를 저장할 백엔드
include=["services.inference_svc"] # 비동기 추론 작업 파일
# 비동기 추론 작업 파일
include=["services.inference_svc", # process_image_task, process_video_task
"services.explain_svc", # process_explain_image_task
"services.image_svc", # cleanup_anonymous_image, cleanup_image_cam
"services.video_svc"] # cleanup_anonymous_video
)

# Celery Instance 상세 설정
Expand Down
50 changes: 21 additions & 29 deletions App/main.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,35 @@
import os
import sys
from pathlib import Path
import warnings
import logging
from dotenv import load_dotenv

# ------ 상위 폴더 경로 설정 --------
current_file = Path(__file__).resolve()
project_root = current_file.parent.parent # App의 상위 폴더

if str(project_root) not in sys.path:
sys.path.insert(0, str(project_root))

# ------ .env 파일 가져오기 ------
load_dotenv()

# -------- Huggingface_Hub 인증 ----------
if os.getenv("HF_TOKEN"):
os.environ["HF_TOKEN"] = os.getenv("HF_TOKEN")
else:
print("[Warning] HF_TOKEN is not set")

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
from routes import auth, home, inference, image, video
from utils.common import lifespan
from utils import exc_handler, middleware
# from starlette.middleware.sessions import SessionMiddleware
from routes import auth, home, inference, image, video, explain
from utils import exc_handler, middleware, common


# 가상 인스턴스 생성
app = FastAPI(lifespan=lifespan)
# 가상 FastAPI 인스턴스 생성
app = FastAPI(lifespan=common.lifespan)

# StaticFile 등록하기
# StaticFile 등록 (이미지, 비디오 파일)
app.mount("/static", StaticFiles(directory="static"), name="static")

# Cross Origin Resource Sharing
# CORS Setup for cross-origin requests
# FrontEnd: React, BackEnd: FastAPI
origins_str = os.getenv("CORS_ALLOWED_ORIGINS")
allowed_origins = [origin.strip() for origin in origins_str.split(",")]

app.add_middleware(CORSMiddleware,
allow_origins=allowed_origins,
allow_methods=["*"],
Expand All @@ -52,16 +43,17 @@
# app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY, max_age=3600)

# 세션 미들웨어 등록 - Redis 이용
app.add_middleware(middleware.RedisSessionMiddleware, max_age=7200)
app.add_middleware(middleware.RedisSessionMiddleware, max_age=3600)

# 라우터 등록
app.include_router(auth.router)
app.include_router(home.router)
app.include_router(inference.router)
app.include_router(image.router)
app.include_router(video.router)
# 라우터 등록 (View: Router, Controller: Service)
app.include_router(auth.router) # 로그인, 로그아웃, 회원가입
app.include_router(home.router) # 세션 유저 정보 가져오기
app.include_router(inference.router) # 이미지, 비디오 비동기 추론 접수, 추론 값 가져오기
app.include_router(image.router) # 이미지 삭제 및 가져오기
app.include_router(video.router) # 비디오 삭제 및 가져오기
app.include_router(explain.router) # 딥페이크 이미지, 비디오 위조 흔적 표시

# Custom HTTPException Handler
# 커스텀 예외 처리: HTTPException
app.add_exception_handler(StarletteHTTPException, exc_handler.custom_http_exception_handler)
# Custom RequestValidationError Handler
# 커스텀 예외 처리: RequestValidationError
app.add_exception_handler(RequestValidationError, exc_handler.validation_exception_handler)
94 changes: 94 additions & 0 deletions App/routes/explain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import os
import io
from pydantic import Field
from fastapi import APIRouter, status, Depends
from fastapi.exceptions import HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy import Connection
from db.database import context_get_conn
from schemas.explain_schema import ExplainImageRequest
from services import session_svc, explain_svc, image_svc
from celery_app import celery_app
from celery.result import AsyncResult

router = APIRouter(prefix="/explain", tags=["explain"])

@router.post("/image/{image_id}", status_code=status.HTTP_202_ACCEPTED,
response_class=JSONResponse, summary="딥페이크 이미지 위조 흔적 시각화 비동기 접수")
async def explain_image(
image_id: int,
explain_req: ExplainImageRequest,
conn: Connection = Depends(context_get_conn),
session_user = Depends(session_svc.get_session_user_prt), # 로그인 필수
):
# 딥페이크 이미지 추론 결과 가져오기
result = await image_svc.get_image_result(conn, image_id)

if result.status != "SUCCESS":
raise HTTPException(
status_code = status.HTTP_400_BAD_REQUEST,
detail = "이미지 위조 흔적 분석은 추론이 성공한 이미지만 가능합니다"
)

image_path = "." + result.image_loc

if not os.path.exists(image_path):
raise HTTPException(
status_code = status.HTTP_404_NOT_FOUND,
detail = f"요청하신 이미지 파일을 찾을 수 없습니다. 삭제하였는지 다시 확인해주세요."
)

if result.model_type == "pro" and explain_req.aug_smooth:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Pro 모델은 aug_smooth 기능을 지원하지 않습니다",
)

# Celery Task 호출(Redis Broker 활용)
task = explain_svc.process_explain_image_task.delay(
user_email = session_user["email"],
result_dict = result.model_dump(mode='json'),
explain_req_dict = explain_req.model_dump())
return {
"message": "딥페이크 이미지 위조 흔적 시각화 접수 완료. 시각화 분석 시작 ...",
"task_id": task.id,
}


@router.get("/image/result/{task_id}", status_code=status.HTTP_200_OK,
response_class=JSONResponse, summary="딥페이크 이미지 위조 흔적 시각화 결과 가져오기")
async def get_explain_image_result(
task_id: str,
session_user = Depends(session_svc.get_session_user_prt), # 로그인 필수
):

# Redis Broker에서 Task ID에 해당하는 비동기 작업 상태 가져오기
task = AsyncResult(task_id, app=celery_app)

if task.state in ("PENDING", "STARTED", "RETRY"):
return {
"status": "PENDING",
"message": "딥페이크 이미지 위조 흔적 시각화 분석 중 ..."
}

if task.state == "FAILURE":
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="딥페이크 이미지 위조 흔적 시각화 중 알 수 없는 오류가 발생하였습니다")

# Celery Task 결과 가져오기
result = task.result

if result["status"] == "FAILED":
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=result["message"])

return {
"status": result["status"],
"message": result["message"],
"cam_loc": result["cam_loc"],
}



Loading
Loading