Pujan-Dev commited on
Commit
b8aa51a
·
1 Parent(s): b72f88f

push to testing branch

Browse files
.gitignore CHANGED
@@ -60,4 +60,6 @@ models/.gitattributes #<-- This line can stay if you only want to ignore that f
60
 
61
  todo.md
62
  np_text_model
63
- IMG_models
 
 
 
60
 
61
  todo.md
62
  np_text_model
63
+ IMG_Models
64
+ notebooks
65
+ static
README.md CHANGED
@@ -1,9 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Ai-Checker
3
- emoji: 🚀
4
- colorFrom: yellow
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  ---
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AI-Contain-Checker
2
+
3
+ A modular AI content detection system with support for **image classification**, **image edit detection**, **Nepali text classification**, and **general text classification**. Built for performance and extensibility, it is ideal for detecting AI-generated content in both visual and textual forms.
4
+
5
+
6
+ ## 🌟 Features
7
+
8
+ ### 🖼️ Image Classifier
9
+
10
+ * **Purpose**: Classifies whether an image is AI-generated or a real-life photo.
11
+ * **Model**: Fine-tuned **InceptionV3** CNN.
12
+ * **Dataset**: Custom curated dataset with **\~79,950 images** for binary classification.
13
+ * **Location**: [`features/image_classifier`](features/image_classifier)
14
+ * **Docs**: [`docs/features/image_classifier.md`](docs/features/image_classifier.md)
15
+
16
+ ### 🖌️ Image Edit Detector
17
+
18
+ * **Purpose**: Detects image tampering or post-processing.
19
+ * **Techniques Used**:
20
+
21
+ * **Error Level Analysis (ELA)**: Visualizes compression artifacts.
22
+ * **Fast Fourier Transform (FFT)**: Detects unnatural frequency patterns.
23
+ * **Location**: [`features/image_edit_detector`](features/image_edit_detector)
24
+ * **Docs**:
25
+
26
+ * [ELA](docs/detector/ELA.md)
27
+ * [FFT](docs/detector/fft.md )
28
+ * [Metadata Analysis](docs/detector/meta.md)
29
+ * [Backend Notes](docs/detector/note-for-backend.md)
30
+
31
+ ### 📝 Nepali Text Classifier
32
+
33
+ * **Purpose**: Determines if Nepali text content is AI-generated or written by a human.
34
+ * **Model**: Based on `XLMRClassifier` fine-tuned on Nepali language data.
35
+ * **Dataset**: Scraped dataset of **\~18,000** Nepali texts.
36
+ * **Location**: [`features/nepali_text_classifier`](features/nepali_text_classifier)
37
+ * **Docs**: [`docs/features/nepali_text_classifier.md`](docs/features/nepali_text_classifier.md)
38
+
39
+ ### 🌐 English Text Classifier
40
+
41
+ * **Purpose**: Detects if English text is AI-generated or human-written.
42
+ * **Pipeline**:
43
+
44
+ * Uses **GPT2 tokenizer** for input preprocessing.
45
+ * Custom binary classifier to differentiate between AI and human-written content.
46
+ * **Location**: [`features/text_classifier`](features/text_classifier)
47
+ * **Docs**: [`docs/features/text_classifier.md`](docs/features/text_classifier.md)
48
+
49
  ---
50
+
51
+ ## 🗂️ Project Structure
52
+
53
+ ```bash
54
+ AI-Checker/
55
+
56
+ ├── app.py # Main FastAPI entry point
57
+ ├── config.py # Configuration settings
58
+ ├── Dockerfile # Docker build script
59
+ ├── Procfile # Deployment file for Heroku or similar
60
+ ├── requirements.txt # Python dependencies
61
+ ├── README.md # You are here 📘
62
+
63
+ ├── features/ # Core detection modules
64
+ │ ├── image_classifier/
65
+ │ ├── image_edit_detector/
66
+ │ ├── nepali_text_classifier/
67
+ │ └── text_classifier/
68
+
69
+ ├── docs/ # Internal and API documentation
70
+ │ ├── api_endpoints.md
71
+ │ ├── deployment.md
72
+ │ ├── detector/
73
+ │ │ ├── ELA.md
74
+ │ │ ├── fft.md
75
+ │ │ ├── meta.md
76
+ │ │ └── note-for-backend.md
77
+ │ ├── functions.md
78
+ │ ├── nestjs_integration.md
79
+ │ ├── security.md
80
+ │ ├── setup.md
81
+ │ └── structure.md
82
+
83
+ ├── IMG_Models/ # Saved image classifier model(s)
84
+ │ └── latest-my_cnn_model.h5
85
+
86
+ ├── notebooks/ # Experimental and debug notebooks
87
+ ├── static/ # Static assets if needed
88
+ └── test.md # Test notes
89
+ ````
90
+
91
  ---
92
 
93
+ ## 📚 Documentation Links
94
+
95
+ * [API Endpoints](docs/api_endpoints.md)
96
+ * [Deployment Guide](docs/deployment.md)
97
+ * [Detector Documentation](docs/detector/)
98
+
99
+ * [Error Level Analysis (ELA)](docs/detector/ELA.md)
100
+ * [Fast Fourier Transform (FFT)](docs/detector/fft.md)
101
+ * [Metadata Analysis](docs/detector/meta.md)
102
+ * [Backend Notes](docs/detector/note-for-backend.md)
103
+ * [Functions Overview](docs/functions.md)
104
+ * [NestJS Integration Guide](docs/nestjs_integration.md)
105
+ * [Security Details](docs/security.md)
106
+ * [Setup Instructions](docs/setup.md)
107
+ * [Project Structure](docs/structure.md)
108
+
109
+ ---
110
+
111
+ ## 🚀 Usage
112
+
113
+ 1. **Install dependencies**
114
+
115
+ ```bash
116
+ pip install -r requirements.txt
117
+ ```
118
+
119
+ 2. **Run the API**
120
+
121
+ ```bash
122
+ uvicorn app:app --reload
123
+ ```
124
+
125
+ 3. **Build Docker (optional)**
126
+
127
+ ```bash
128
+ docker build -t ai-contain-checker .
129
+ docker run -p 8000:8000 ai-contain-checker
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 🔐 Security & Integration
135
+
136
+ * **Token Authentication** and **IP Whitelisting** supported.
137
+ * NestJS integration guide: [`docs/nestjs_integration.md`](docs/nestjs_integration.md)
138
+ * Rate limiting handled using `slowapi`.
139
+
140
+ ---
141
+
142
+ ## 🛡️ Future Plans
143
+
144
+ * Add **video classifier** module.
145
+ * Expand dataset for **multilingual** AI content detection.
146
+ * Add **fine-tuning UI** for models.
147
+
148
+ ---
149
+
150
+ ## 📄 License
151
+
152
+ See full license terms here: [`LICENSE.md`](license.md)
app.py CHANGED
@@ -1,44 +1,62 @@
1
  from fastapi import FastAPI, Request
2
  from slowapi import Limiter, _rate_limit_exceeded_handler
 
3
  from slowapi.middleware import SlowAPIMiddleware
4
  from slowapi.errors import RateLimitExceeded
5
  from slowapi.util import get_remote_address
6
  from fastapi.responses import JSONResponse
7
  from features.text_classifier.routes import router as text_classifier_router
8
- from features.nepali_text_classifier.routes import router as nepali_text_classifier_router
 
 
9
  from features.image_classifier.routes import router as image_classifier_router
10
  from features.image_edit_detector.routes import router as image_edit_detector_router
 
11
 
12
  from config import ACCESS_RATE
13
 
14
  import requests
 
15
  limiter = Limiter(key_func=get_remote_address, default_limits=[ACCESS_RATE])
16
 
17
  app = FastAPI()
18
-
19
  # Set up SlowAPI
20
  app.state.limiter = limiter
21
- app.add_exception_handler(RateLimitExceeded, lambda request, exc: JSONResponse(
22
- status_code=429,
23
- content={
24
- "status_code": 429,
25
- "error": "Rate limit exceeded",
26
- "message": "Too many requests. Chill for a bit and try again"
27
- }
28
- ))
 
 
 
29
  app.add_middleware(SlowAPIMiddleware)
30
 
31
  # Include your routes
32
  app.include_router(text_classifier_router, prefix="/text")
33
- app.include_router(nepali_text_classifier_router,prefix="/NP")
34
- app.include_router(image_classifier_router,prefix="/AI-image")
35
- app.include_router(image_edit_detector_router,prefix="/detect")
 
36
 
37
  @app.get("/")
38
  @limiter.limit(ACCESS_RATE)
39
  async def root(request: Request):
40
  return {
41
  "message": "API is working",
42
- "endpoints": ["/text/analyse", "/text/upload", "/text/analyse-sentences", "/text/analyse-sentance-file","/NP/analyse","/NP/upload","/NP/analyse-sentences","/NP/file-sentences-analyse","/AI-image/analyse"]
 
 
 
 
 
 
 
 
 
 
43
  }
44
-
 
1
  from fastapi import FastAPI, Request
2
  from slowapi import Limiter, _rate_limit_exceeded_handler
3
+ from fastapi.responses import FileResponse
4
  from slowapi.middleware import SlowAPIMiddleware
5
  from slowapi.errors import RateLimitExceeded
6
  from slowapi.util import get_remote_address
7
  from fastapi.responses import JSONResponse
8
  from features.text_classifier.routes import router as text_classifier_router
9
+ from features.nepali_text_classifier.routes import (
10
+ router as nepali_text_classifier_router,
11
+ )
12
  from features.image_classifier.routes import router as image_classifier_router
13
  from features.image_edit_detector.routes import router as image_edit_detector_router
14
+ from fastapi.staticfiles import StaticFiles
15
 
16
  from config import ACCESS_RATE
17
 
18
  import requests
19
+
20
  limiter = Limiter(key_func=get_remote_address, default_limits=[ACCESS_RATE])
21
 
22
  app = FastAPI()
23
+ # added the robots.txt
24
  # Set up SlowAPI
25
  app.state.limiter = limiter
26
+ app.add_exception_handler(
27
+ RateLimitExceeded,
28
+ lambda request, exc: JSONResponse(
29
+ status_code=429,
30
+ content={
31
+ "status_code": 429,
32
+ "error": "Rate limit exceeded",
33
+ "message": "Too many requests. Chill for a bit and try again",
34
+ },
35
+ ),
36
+ )
37
  app.add_middleware(SlowAPIMiddleware)
38
 
39
  # Include your routes
40
  app.include_router(text_classifier_router, prefix="/text")
41
+ app.include_router(nepali_text_classifier_router, prefix="/NP")
42
+ app.include_router(image_classifier_router, prefix="/AI-image")
43
+ app.include_router(image_edit_detector_router, prefix="/detect")
44
+
45
 
46
  @app.get("/")
47
  @limiter.limit(ACCESS_RATE)
48
  async def root(request: Request):
49
  return {
50
  "message": "API is working",
51
+ "endpoints": [
52
+ "/text/analyse",
53
+ "/text/upload",
54
+ "/text/analyse-sentences",
55
+ "/text/analyse-sentance-file",
56
+ "/NP/analyse",
57
+ "/NP/upload",
58
+ "/NP/analyse-sentences",
59
+ "/NP/file-sentences-analyse",
60
+ "/AI-image/analyse",
61
+ ],
62
  }
 
docs/api_endpoints.md CHANGED
@@ -2,13 +2,13 @@
2
 
3
  ### English (GPT-2) - `/text/`
4
 
5
- | Endpoint | Method | Description |
6
- | --------------------------------- | ------ | ----------------------------------------- |
7
- | `/text/analyse` | POST | Classify raw English text |
8
- | `/text/analyse-sentences` | POST | Sentence-by-sentence breakdown |
9
- | `/text/analyse-sentance-file` | POST | Upload file, per-sentence breakdown |
10
- | `/text/upload` | POST | Upload file for overall classification |
11
- | `/text/health` | GET | Health check |
12
 
13
  #### Example: Classify English text
14
 
@@ -20,6 +20,7 @@ curl -X POST http://localhost:8000/text/analyse \
20
  ```
21
 
22
  **Response:**
 
23
  ```json
24
  {
25
  "result": "AI-generated",
@@ -40,13 +41,13 @@ curl -X POST http://localhost:8000/text/upload \
40
 
41
  ### Nepali (SentencePiece) - `/NP/`
42
 
43
- | Endpoint | Method | Description |
44
- | --------------------------------- | ------ | ----------------------------------------- |
45
- | `/NP/analyse` | POST | Classify Nepali text |
46
- | `/NP/analyse-sentences` | POST | Sentence-by-sentence breakdown |
47
- | `/NP/upload` | POST | Upload Nepali PDF for classification |
48
- | `/NP/file-sentences-analyse` | POST | PDF upload, per-sentence breakdown |
49
- | `/NP/health` | GET | Health check |
50
 
51
  #### Example: Nepali text classification
52
 
@@ -58,6 +59,7 @@ curl -X POST http://localhost:8000/NP/analyse \
58
  ```
59
 
60
  **Response:**
 
61
  ```json
62
  {
63
  "label": "Human",
@@ -73,20 +75,18 @@ curl -X POST http://localhost:8000/NP/upload \
73
  -F 'file=@NepaliText.pdf;type=application/pdf'
74
  ```
75
 
76
-
77
  ### Image-Classification -`/verify-image/`
78
-
79
- | Endpoint | Method | Description |
80
- | --------------------------------- | ------ | ----------------------------------------- |
81
- | `/verify-image/analyse` | POST | Classify Image using ML |
82
 
 
 
 
 
 
83
 
84
- #### Example: Image-Classification
85
  ```bash
86
  curl -X POST http://localhost:8000/verify-image/analyse \
87
  -H "Authorization: Bearer <SECRET_TOKEN>" \
88
  -F 'file=@test1.png'
89
  ```
90
 
91
-
92
-
 
2
 
3
  ### English (GPT-2) - `/text/`
4
 
5
+ | Endpoint | Method | Description |
6
+ | ----------------------------- | ------ | -------------------------------------- |
7
+ | `/text/analyse` | POST | Classify raw English text |
8
+ | `/text/analyse-sentences` | POST | Sentence-by-sentence breakdown |
9
+ | `/text/analyse-sentance-file` | POST | Upload file, per-sentence breakdown |
10
+ | `/text/upload` | POST | Upload file for overall classification |
11
+ | `/text/health` | GET | Health check |
12
 
13
  #### Example: Classify English text
14
 
 
20
  ```
21
 
22
  **Response:**
23
+
24
  ```json
25
  {
26
  "result": "AI-generated",
 
41
 
42
  ### Nepali (SentencePiece) - `/NP/`
43
 
44
+ | Endpoint | Method | Description |
45
+ | ---------------------------- | ------ | ------------------------------------ |
46
+ | `/NP/analyse` | POST | Classify Nepali text |
47
+ | `/NP/analyse-sentences` | POST | Sentence-by-sentence breakdown |
48
+ | `/NP/upload` | POST | Upload Nepali PDF for classification |
49
+ | `/NP/file-sentences-analyse` | POST | PDF upload, per-sentence breakdown |
50
+ | `/NP/health` | GET | Health check |
51
 
52
  #### Example: Nepali text classification
53
 
 
59
  ```
60
 
61
  **Response:**
62
+
63
  ```json
64
  {
65
  "label": "Human",
 
75
  -F 'file=@NepaliText.pdf;type=application/pdf'
76
  ```
77
 
 
78
  ### Image-Classification -`/verify-image/`
 
 
 
 
79
 
80
+ | Endpoint | Method | Description |
81
+ | ----------------------- | ------ | ----------------------- |
82
+ | `/verify-image/analyse` | POST | Classify Image using ML |
83
+
84
+ #### Example: Image-Classification
85
 
 
86
  ```bash
87
  curl -X POST http://localhost:8000/verify-image/analyse \
88
  -H "Authorization: Bearer <SECRET_TOKEN>" \
89
  -F 'file=@test1.png'
90
  ```
91
 
92
+ [🔙 Back to Main README](../README.md)
 
docs/deployment.md CHANGED
@@ -105,3 +105,4 @@ Happy deploying!
105
  **P.S.** Try not to break stuff. 😅
106
 
107
 
 
 
105
  **P.S.** Try not to break stuff. 😅
106
 
107
 
108
+ [🔙 Back to Main README](../README.md)
docs/detector/ELA.md CHANGED
@@ -2,14 +2,12 @@
2
 
3
  This module provides a function to perform Error Level Analysis (ELA) on images to detect potential manipulations or edits.
4
 
5
-
6
  ## Function: `run_ela`
7
 
8
  ```python
9
  def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
10
  ```
11
 
12
-
13
  ### Description
14
 
15
  Error Level Analysis (ELA) works by recompressing an image at a specified JPEG quality level and comparing it to the original image. Differences between the two images reveal areas with inconsistent compression artifacts — often indicating image manipulation.
@@ -24,14 +22,12 @@ The function computes the maximum pixel difference across all color channels and
24
  | `quality` | `int` | 90 | JPEG compression quality used for recompression during analysis (lower = more compression). |
25
  | `threshold` | `int` | 15 | Pixel difference threshold to flag the image as edited. |
26
 
27
-
28
  ### Returns
29
 
30
  `bool`
31
 
32
- * `True` if the image is likely edited (max pixel difference > threshold).
33
- * `False` if the image appears unedited.
34
-
35
 
36
  ### Usage Example
37
 
@@ -48,13 +44,11 @@ is_edited = run_ela(img, quality=90, threshold=15)
48
  print("Image edited:", is_edited)
49
  ```
50
 
51
-
52
  ### Notes
53
 
54
- * The input image **must** be in RGB mode for accurate analysis.
55
- * ELA is a heuristic technique; combining it with other detection methods increases reliability.
56
- * Visualizing the enhanced difference image can help identify edited regions (not returned by this function but possible to add).
57
-
58
 
59
  ### Installation
60
 
@@ -64,13 +58,8 @@ Make sure you have Pillow installed:
64
  pip install pillow
65
  ```
66
 
67
-
68
  ### Running Locally
69
 
70
  Just put the function in a notebook or script file and run it with your image. It works well for basic images.
71
 
72
-
73
- ### Developer
74
-
75
- Pujan Neupane
76
-
 
2
 
3
  This module provides a function to perform Error Level Analysis (ELA) on images to detect potential manipulations or edits.
4
 
 
5
  ## Function: `run_ela`
6
 
7
  ```python
8
  def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
9
  ```
10
 
 
11
  ### Description
12
 
13
  Error Level Analysis (ELA) works by recompressing an image at a specified JPEG quality level and comparing it to the original image. Differences between the two images reveal areas with inconsistent compression artifacts — often indicating image manipulation.
 
22
  | `quality` | `int` | 90 | JPEG compression quality used for recompression during analysis (lower = more compression). |
23
  | `threshold` | `int` | 15 | Pixel difference threshold to flag the image as edited. |
24
 
 
25
  ### Returns
26
 
27
  `bool`
28
 
29
+ - `True` if the image is likely edited (max pixel difference > threshold).
30
+ - `False` if the image appears unedited.
 
31
 
32
  ### Usage Example
33
 
 
44
  print("Image edited:", is_edited)
45
  ```
46
 
 
47
  ### Notes
48
 
49
+ - The input image **must** be in RGB mode for accurate analysis.
50
+ - ELA is a heuristic technique; combining it with other detection methods increases reliability.
51
+ - Visualizing the enhanced difference image can help identify edited regions (not returned by this function but possible to add).
 
52
 
53
  ### Installation
54
 
 
58
  pip install pillow
59
  ```
60
 
 
61
  ### Running Locally
62
 
63
  Just put the function in a notebook or script file and run it with your image. It works well for basic images.
64
 
65
+ [🔙 Back to Main README](../README.md)
 
 
 
 
docs/detector/fft.md CHANGED
@@ -133,9 +133,4 @@ it is implemented in the api
133
  Just put the function in a notebook or script file and run it with your image. It works well for basic images.
134
 
135
 
136
- ### Worked By
137
-
138
- Pujan Neupane
139
-
140
-
141
-
 
133
  Just put the function in a notebook or script file and run it with your image. It works well for basic images.
134
 
135
 
136
+ [🔙 Back to Main README](../README.md)
 
 
 
 
 
docs/detector/meta.md CHANGED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Metadata Analysis for Image Edit Detection
2
+
3
+ This module inspects image metadata to detect possible signs of AI-generation or post-processing edits.
4
+
5
+ ## Overview
6
+
7
+ - Many AI-generated images and edited images leave identifiable traces in their metadata.
8
+ - This detector scans image EXIF metadata and raw bytes for known AI generation indicators and common photo editing software signatures.
9
+ - It classifies images as `"ai_generated"`, `"edited"`, or `"undetermined"` based on detected markers.
10
+ - Handles invalid image formats gracefully by reporting errors.
11
+
12
+ ## How It Works
13
+
14
+ - Opens the image from raw bytes using the Python Pillow library (`PIL`).
15
+ - Reads EXIF metadata and specifically looks for the "Software" tag that often contains the editing app name.
16
+ - Checks for common image editors such as Photoshop, GIMP, Snapseed, etc.
17
+ - Scans the entire raw byte content of the image for embedded AI generation identifiers like "midjourney", "stable-diffusion", "openai", etc.
18
+ - Returns a status string indicating the metadata classification.
19
+
20
+ [🔙 Back to Main README](../README.md)
docs/detector/note-for-backend.md CHANGED
@@ -90,3 +90,5 @@ POST /api/detect-image
90
  | Metadata | 🚀 Fast | ⚠️ Low confidence |
91
 
92
  > For high-throughput systems, consider running Metadata first and conditionally applying ELA/FFT if suspicious.
 
 
 
90
  | Metadata | 🚀 Fast | ⚠️ Low confidence |
91
 
92
  > For high-throughput systems, consider running Metadata first and conditionally applying ELA/FFT if suspicious.
93
+
94
+ [🔙 Back to Main README](../README.md)
docs/features/image_classifier.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Image Classifier
2
+
3
+ ## Overview
4
+
5
+ This module classifies whether an input image is AI-generated or a real-life photograph.
6
+
7
+ ## Model
8
+
9
+ - Architecture: InceptionV3
10
+ - Type: Binary Classifier (AI vs Real)
11
+ - Format: H5 model (`latest-my_cnn_model.h5`)
12
+
13
+ ## Dataset
14
+
15
+ - Total images: ~79,950
16
+ - Balanced between real and generated images
17
+ - Preprocessing: Resizing, normalization
18
+
19
+ ## Code Location
20
+
21
+ - Controller: `features/image_classifier/controller.py`
22
+ - Model Loader: `features/image_classifier/model_loader.py`
23
+ - Preprocessor: `features/image_classifier/preprocess.py`
24
+
25
+ ## API
26
+
27
+ - Endpoint: [ENDPOINTS](../api_endpoints.md)
28
+ - Input: Image file (PNG/JPG)
29
+ - Output: JSON response with classification result and confidence
30
+
31
+ [🔙 Back to Main README](../README.md)
docs/features/nepali_text_classifier.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Nepali Text Classifier
2
+
3
+ ## Overview
4
+
5
+ This classifier identifies whether Nepali-language text content is written by a human or AI.
6
+
7
+ ## Model
8
+
9
+ - Base Model: XLM-Roberta (XLMRClassifier)
10
+ - Language: Nepali (Multilingual model)
11
+ - Fine-tuned with scraped web content (~18,000 samples)
12
+
13
+ ## Dataset
14
+
15
+ - Custom scraped dataset with manual labeling
16
+ - Includes news, blogs, and synthetic content from various LLMs
17
+
18
+ ## Code Location
19
+
20
+ - Controller: `features/nepali_text_classifier/controller.py`
21
+ - Inference: `features/nepali_text_classifier/inferencer.py`
22
+ - Model Loader: `features/nepali_text_classifier/model_loader.py`
23
+
24
+ ## API
25
+
26
+ - Endpoint: [ENDPOINTS](../api_endpoints.md)
27
+ - Input: Raw text
28
+ - Output: JSON classification with label and confidence score
29
+
30
+ [🔙 Back to Main README](../README.md)
docs/features/text_classifier.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # English Text Classifier
2
+
3
+ ## Overview
4
+
5
+ Detects whether English-language text is AI-generated or human-written.
6
+
7
+ ## Model Pipeline
8
+
9
+ - Tokenizer: GPT-2 Tokenizer
10
+ - Model: Custom trained binary classifier
11
+
12
+ ## Dataset
13
+
14
+ - Balanced dataset: Human vs AI-generated (ChatGPT, Claude, etc.)
15
+ - Tokenized and fed into the model using PyTorch/TensorFlow
16
+
17
+ ## Code Location
18
+
19
+ - Controller: `features/text_classifier/controller.py`
20
+ - Inference: `features/text_classifier/inferencer.py`
21
+ - Model Loader: `features/text_classifier/model_loader.py`
22
+ - Preprocessor: `features/text_classifier/preprocess.py`
23
+
24
+ ## API
25
+
26
+ - Endpoint: [ENDPOINTS](../api_endpoints.md)
27
+ - Input: Raw English text
28
+ - Output: Prediction result with probability/confidence
29
+
30
+ [🔙 Back to Main README](../README.md)
docs/functions.md CHANGED
@@ -52,12 +52,11 @@
52
  ---
53
  ## for image_classifier
54
 
55
-
56
-
57
  - **`Classify_Image_router()`** – Handles image classification requests by routing and coordinating preprocessing and inference.
58
  - **`classify_image()`** – Performs AI vs human image classification using the loaded model.
59
  - **`load_model()`** – Loads the pretrained model from Hugging Face at server startup.
60
  - **`preprocess_image()`** – Applies all required preprocessing steps to the input image.
61
 
 
62
 
63
-
 
52
  ---
53
  ## for image_classifier
54
 
 
 
55
  - **`Classify_Image_router()`** – Handles image classification requests by routing and coordinating preprocessing and inference.
56
  - **`classify_image()`** – Performs AI vs human image classification using the loaded model.
57
  - **`load_model()`** – Loads the pretrained model from Hugging Face at server startup.
58
  - **`preprocess_image()`** – Applies all required preprocessing steps to the input image.
59
 
60
+ > Note: While many functions mirror those in the text classifier, the image classifier primarily uses TensorFlow rather than PyTorch.
61
 
62
+ [🔙 Back to Main README](../README.md)
docs/nestjs_integration.md CHANGED
@@ -80,3 +80,4 @@ export class AppController {
80
  }
81
  }
82
  ```
 
 
80
  }
81
  }
82
  ```
83
+ [🔙 Back to Main README](../README.md)
docs/security.md CHANGED
@@ -7,3 +7,4 @@ All endpoints require authentication via Bearer token:
7
 
8
  Unauthorized requests receive `403 Forbidden`.
9
 
 
 
7
 
8
  Unauthorized requests receive `403 Forbidden`.
9
 
10
+ [🔙 Back to Main README](../README.md)
docs/setup.md CHANGED
@@ -21,3 +21,4 @@ SECRET_TOKEN=your_secret_token_here
21
  ```bash
22
  uvicorn app:app --host 0.0.0.0 --port 8000
23
  ```
 
 
21
  ```bash
22
  uvicorn app:app --host 0.0.0.0 --port 8000
23
  ```
24
+ [🔙 Back to Main README](../README.md)
docs/status_code.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Error Codes Reference
2
+
3
+ ## 🔹 Summary Table
4
+
5
+ | Code | Message | Description |
6
+ | ---- | ----------------------------------------------------- | ------------------------------------------ |
7
+ | 400 | Text must contain at least two words | Input text too short |
8
+ | 400 | Text should be less than 10,000 characters | Input text too long |
9
+ | 404 | The file is empty or only contains whitespace | File has no usable content |
10
+ | 404 | Invalid file type. Only .docx, .pdf, and .txt allowed | Unsupported file format |
11
+ | 403 | Invalid or expired token | Authentication token is invalid or expired |
12
+ | 413 | Text must contain at least two words | Text too short (alternative condition) |
13
+ | 413 | Text must be less than 10,000 characters | Text too long (alternative condition) |
14
+ | 413 | The image error (preprocessing) | Image size/content issue |
15
+ | 500 | Error processing the file | Internal server error while processing |
16
+
17
+ ---
18
+
19
+ ## 🔍 Error Details
20
+
21
+ ### `400` - Bad Request
22
+
23
+ - **Text must contain at least two words**
24
+ The input text field is too short. Submit at least two words to proceed.
25
+
26
+ - **Text should be less than 10,000 characters**
27
+ Input text exceeds the maximum allowed character limit. Consider truncating or summarizing the content.
28
+
29
+ ---
30
+
31
+ ### `404` - Not Found
32
+
33
+ - **The file is empty or only contains whitespace**
34
+ The uploaded file is invalid due to lack of meaningful content. Ensure the file has readable, non-empty text.
35
+
36
+ - **Invalid file type. Only .docx, .pdf, and .txt are allowed**
37
+ The file format is not supported. Convert the file to one of the allowed formats before uploading.
38
+
39
+ ---
40
+
41
+ ### `403` - Forbidden
42
+
43
+ - **Invalid or expired token**
44
+ Your access token is either expired or incorrect. Try logging in again or refreshing the token.
45
+
46
+ ---
47
+
48
+ ### `413` - Payload Too Large
49
+
50
+ - **Text must contain at least two words**
51
+ The text payload is too small or malformed under a large upload context. Add more content.
52
+
53
+ - **Text must be less than 10,000 characters**
54
+ The payload exceeds the allowed character limit for a single request. Break it into smaller chunks if needed.
55
+
56
+ - **The image error**
57
+ The uploaded image is too large or corrupted. Try resizing or compressing it before retrying.
58
+
59
+ ---
60
+
61
+ ### `500` - Internal Server Error
62
+
63
+ - **Error processing the file**
64
+ An unexpected server-side failure occurred during file analysis. Retry later or contact support if persistent.
65
+
66
+ ---
67
+
68
+ > 📌 **Note:** Always validate inputs, check token status, and follow file guidelines before making requests.
docs/structure.md CHANGED
@@ -1,36 +1,58 @@
1
  ## 🏗️ Project Structure
2
 
3
- ```
4
- ├── app.py # Main FastAPI app entrypoint
5
- ├── config.py # Configuration loader (.env, settings)
6
- ├── features/
7
- ├── text_classifier/ # English (GPT-2) classifier
 
 
 
 
 
 
 
 
 
 
 
 
8
  │ │ ├── controller.py
9
  │ │ ├── inferencer.py
10
  │ │ ├── model_loader.py
11
- │ │ ├── preprocess.py
12
- └── routes.py
13
- │ └── nepali_text_classifier/ # Nepali (sentencepiece) classifier
14
  │ ├── controller.py
15
  │ ├── inferencer.py
16
  │ ├── model_loader.py
17
- ├── preprocess.py
18
- └── routes.py
19
- ├── np_text_model/ # Nepali model artifacts (auto-downloaded)
20
- │ ├── classifier/
21
- │ └── sentencepiece.bpe.model
22
- └── model_95_acc.pth
23
- ├── models/ # English GPT-2 model/tokenizer (auto-downloaded)
24
- │ ├── merges.txt
25
- │ ├── tokenizer.json
26
- │ └── model_weights.pth
27
- ├── Dockerfile # Container build config
28
- ├── Procfile # Deployment entrypoint (for PaaS)
29
- ├── requirements.txt # Python dependencies
30
- ├── README.md
31
- ├── Docs # documents
32
- └── .env # Secret token(s), environment config
 
 
 
 
 
 
 
 
 
 
33
  ```
 
34
  ### 🌟 Key Files and Their Roles
35
 
36
  - **`app.py`**: Entry point initializing FastAPI app and routes.
@@ -39,16 +61,14 @@
39
  - **`__init__.py`**: Package initializer for the root module and submodules.
40
  - **`features/text_classifier/`**
41
  - **`controller.py`**: Handles logic between routes and the model.
42
- - **`inferencer.py`**: Runs inference and returns predictions as well as file system
43
- utilities.
44
  - **`features/NP/`**
45
  - **`controller.py`**: Handles logic between routes and the model.
46
- - **`inferencer.py`**: Runs inference and returns predictions as well as file system
47
- utilities.
48
  - **`model_loader.py`**: Loads the ML model and tokenizer.
49
  - **`preprocess.py`**: Prepares input text for the model.
50
  - **`routes.py`**: Defines API routes for text classification.
51
 
52
-
53
-
54
- -[Main](../README.md)
 
1
  ## 🏗️ Project Structure
2
 
3
+ ```bash
4
+ AI-Checker/
5
+
6
+ ├── app.py # Main FastAPI entry point
7
+ ├── config.py # Configuration settings
8
+ ├── Dockerfile # Docker build script
9
+ ├── Procfile # Deployment entry for platforms like Heroku/Railway
10
+ ├── requirements.txt # Python dependency list
11
+ ├── README.md # Main project overview 📘
12
+
13
+ ├── features/ # Core AI content detection modules
14
+ │ ├── image_classifier/ # Classifies AI vs Real images
15
+ │ │ ├── controller.py
16
+ │ │ ├── model_loader.py
17
+ │ │ └── preprocess.py
18
+ │ ├── image_edit_detector/ # Detects tampered or edited images
19
+ │ ├── nepali_text_classifier/ # Classifies Nepali text as AI or Human
20
  │ │ ├── controller.py
21
  │ │ ├── inferencer.py
22
  │ │ ├── model_loader.py
23
+ │ │ └── preprocess.py
24
+ │ └── text_classifier/ # Classifies English text as AI or Human
 
25
  │ ├── controller.py
26
  │ ├── inferencer.py
27
  │ ├── model_loader.py
28
+ └── preprocess.py
29
+
30
+ ├── docs/ # Internal documentation and API references
31
+ │ ├── api_endpoints.md
32
+ ├── deployment.md
33
+ ├── detector/
34
+ │ │ ├── ELA.md
35
+ ├── fft.md
36
+ ├── meta.md
37
+ └── note-for-backend.md
38
+ ├── features/
39
+ │ │ ├── image_classifier.md
40
+ │ │ ├── nepali_text_classifier.md
41
+ │ │ └── text_classifier.md
42
+ ├── functions.md
43
+ │ ├── nestjs_integration.md
44
+ │ ├── security.md
45
+ │ ├── setup.md
46
+ │ └── structure.md
47
+
48
+ ├── IMG_Models/ # Stored model weights
49
+ │ └── latest-my_cnn_model.h5
50
+
51
+ ├── notebooks/ # Experimental/debug Jupyter notebooks
52
+ ├── static/ # Static files (e.g., UI assets, test inputs)
53
+ └── test.md # Test usage notes
54
  ```
55
+
56
  ### 🌟 Key Files and Their Roles
57
 
58
  - **`app.py`**: Entry point initializing FastAPI app and routes.
 
61
  - **`__init__.py`**: Package initializer for the root module and submodules.
62
  - **`features/text_classifier/`**
63
  - **`controller.py`**: Handles logic between routes and the model.
64
+ - **`inferencer.py`**: Runs inference and returns predictions as well as file system
65
+ utilities.
66
  - **`features/NP/`**
67
  - **`controller.py`**: Handles logic between routes and the model.
68
+ - **`inferencer.py`**: Runs inference and returns predictions as well as file system
69
+ utilities.
70
  - **`model_loader.py`**: Loads the ML model and tokenizer.
71
  - **`preprocess.py`**: Prepares input text for the model.
72
  - **`routes.py`**: Defines API routes for text classification.
73
 
74
+ [🔙 Back to Main README](../README.md)
 
 
features/image_classifier/controller.py CHANGED
@@ -1,11 +1,16 @@
1
- from fastapi import HTTPException,File,UploadFile
2
  from .preprocess import preprocess_image
3
  from .inferencer import classify_image
 
 
4
  async def Classify_Image_router(file: UploadFile = File(...)):
5
  try:
6
  image_array = preprocess_image(file)
7
- result = classify_image(image_array)
8
- return result
9
- except Exception as e:
10
- raise HTTPException(status_code=400, detail=str(e))
 
11
 
 
 
 
1
+ from fastapi import HTTPException, File, UploadFile
2
  from .preprocess import preprocess_image
3
  from .inferencer import classify_image
4
+
5
+
6
  async def Classify_Image_router(file: UploadFile = File(...)):
7
  try:
8
  image_array = preprocess_image(file)
9
+ try:
10
+ result = classify_image(image_array)
11
+ return result
12
+ except:
13
+ raise HTTPException(status_code=423, detail="something went wrong")
14
 
15
+ except Exception as e:
16
+ raise HTTPException(status_code=413, detail=str(e))
features/image_classifier/inferencer.py CHANGED
@@ -1,22 +1,42 @@
1
  import numpy as np
2
- from .model_loader import load_model
3
- model = load_model()
4
-
5
- def classify_image(image: np.ndarray):
6
- predictions = model.predict(image)[0]
7
- human_conf = float(predictions[0])
8
- ai_conf = float(predictions[1])
9
-
10
- if ai_conf > 0.55:
11
- label = "AI Generated"
12
- elif ai_conf < 0.45:
13
- label = "Human Generated"
14
- else:
15
- label = "Maybe AI"
16
-
17
- return {
18
- "label": label,
19
- "ai_confidence": round(ai_conf * 100, 2),
20
- "human_confidence": round(human_conf * 100, 2)
21
- }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy as np
2
+ from .model_loader import get_model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ # Thresholds
5
+ AI_THRESHOLD = 0.55
6
+ HUMAN_THRESHOLD = 0.45
7
+
8
+
9
+ def classify_image(image_array: np.ndarray) -> dict:
10
+ try:
11
+ model = get_model()
12
+ predictions = model.predict(image_array)
13
+
14
+ if predictions.ndim != 2 or predictions.shape[1] != 1:
15
+ raise ValueError(
16
+ "Model output shape is invalid. Expected shape: (batch, 1)"
17
+ )
18
+
19
+ ai_conf = float(np.clip(predictions[0][0], 0.0, 1.0))
20
+ human_conf = 1.0 - ai_conf
21
+
22
+ # Classification logic
23
+ if ai_conf > AI_THRESHOLD:
24
+ label = "AI Generated"
25
+ elif ai_conf < HUMAN_THRESHOLD:
26
+ label = "Human Generated"
27
+ else:
28
+ label = "Uncertain (Maybe AI)"
29
+
30
+ return {
31
+ "label": label,
32
+ "ai_confidence": round(ai_conf * 100, 2),
33
+ "human_confidence": round(human_conf * 100, 2),
34
+ }
35
+
36
+ except Exception as e:
37
+ return {
38
+ "error": str(e),
39
+ "label": "Classification Failed",
40
+ "ai_confidence": None,
41
+ "human_confidence": None,
42
+ }
features/image_classifier/model_loader.py CHANGED
@@ -1,43 +1,58 @@
1
- import tensorflow as tf
2
- from tensorflow.keras.models import load_model as keras_load_model
3
  import os
4
- from huggingface_hub import snapshot_download
5
  import shutil
 
 
 
 
6
 
7
- # Constants
8
  REPO_ID = "can-org/AI-VS-HUMAN-IMAGE-classifier"
9
- MODEL_DIR = "./IMG_models"
10
- MODEL_PATH = os.path.join(MODEL_DIR, 'latest-my_cnn_model.h5') # adjust path as needed
 
 
 
 
 
 
 
11
 
12
- _model_img = None # global model variable
 
 
 
13
 
14
  def warmup():
15
  global _model_img
16
- if not os.path.exists(MODEL_DIR):
17
- download_model_Repo()
18
  _model_img = load_model()
 
19
 
20
- def download_model_Repo():
21
- if os.path.exists(MODEL_DIR):
 
22
  return
23
  snapshot_path = snapshot_download(repo_id=REPO_ID)
24
  os.makedirs(MODEL_DIR, exist_ok=True)
25
  shutil.copytree(snapshot_path, MODEL_DIR, dirs_exist_ok=True)
26
 
27
  def load_model():
28
- if not os.path.exists(MODEL_DIR):
29
- download_model_Repo()
30
-
31
- # Check for GPU availability
32
- gpus = tf.config.list_physical_devices('GPU')
33
- if gpus:
34
- # GPU is available, load model normally
35
- print("GPU detected, loading model on GPU.")
36
- model = keras_load_model(MODEL_PATH)
37
- else:
38
- # No GPU, force CPU usage
39
- print("No GPU detected, forcing model loading on CPU.")
40
- with tf.device('/CPU:0'):
41
- model = keras_load_model(MODEL_PATH)
42
- return model
 
 
 
43
 
 
 
 
1
  import os
 
2
  import shutil
3
+ import logging
4
+ import tensorflow as tf
5
+ from tensorflow.keras.layers import Layer
6
+ from huggingface_hub import snapshot_download
7
 
8
+ # Model config
9
  REPO_ID = "can-org/AI-VS-HUMAN-IMAGE-classifier"
10
+ MODEL_DIR = "./IMG_Models"
11
+ WEIGHTS_PATH = os.path.join(MODEL_DIR, "latest-my_cnn_model.h5")
12
+
13
+ # Device info (for logging)
14
+ gpus = tf.config.list_physical_devices("GPU")
15
+ device = "cuda" if gpus else "cpu"
16
+
17
+ # Global model reference
18
+ _model_img = None
19
 
20
+ # Custom layer used in the model
21
+ class Cast(Layer):
22
+ def call(self, inputs):
23
+ return tf.cast(inputs, tf.float32)
24
 
25
  def warmup():
26
  global _model_img
27
+ download_model_repo()
 
28
  _model_img = load_model()
29
+ logging.info("Image model is ready.")
30
 
31
+ def download_model_repo():
32
+ if os.path.exists(MODEL_DIR) and os.path.isdir(MODEL_DIR):
33
+ logging.info("Image model already exists, skipping download.")
34
  return
35
  snapshot_path = snapshot_download(repo_id=REPO_ID)
36
  os.makedirs(MODEL_DIR, exist_ok=True)
37
  shutil.copytree(snapshot_path, MODEL_DIR, dirs_exist_ok=True)
38
 
39
  def load_model():
40
+ global _model_img
41
+ if _model_img is not None:
42
+ return _model_img
43
+
44
+ print(f"{'GPU detected' if device == 'cuda' else 'No GPU detected'}, loading model on {device.upper()}.")
45
+
46
+ _model_img = tf.keras.models.load_model(
47
+ WEIGHTS_PATH, custom_objects={"Cast": Cast}
48
+ )
49
+ print("Model input shape:", _model_img.input_shape)
50
+ return _model_img
51
+
52
+ def get_model():
53
+ global _model_img
54
+ if _model_img is None:
55
+ download_model_repo()
56
+ _model_img = load_model()
57
+ return _model_img
58
 
features/image_classifier/preprocess.py CHANGED
@@ -1,18 +1,26 @@
1
  import numpy as np
2
  import cv2
 
 
3
 
4
  def preprocess_image(file):
5
- # Read bytes from UploadFile
6
- image_bytes = file.file.read()
7
- # Convert bytes to NumPy array
8
- nparr = np.frombuffer(image_bytes, np.uint8)
9
- img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
10
- if img is None:
11
- raise ValueError("Could not decode image.")
12
 
13
- img = cv2.resize(img, (256, 256)) # Changed size to 256x256
14
- img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
15
- img = img / 255.0
16
- img = np.expand_dims(img, axis=0)
17
- return img
18
 
 
 
 
 
 
 
 
1
  import numpy as np
2
  import cv2
3
+ from fastapi import HTTPException
4
+
5
 
6
  def preprocess_image(file):
7
+ try:
8
+ file.file.seek(0)
9
+ image_bytes = file.file.read()
10
+ nparr = np.frombuffer(image_bytes, np.uint8)
11
+ img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
12
+ if img is None:
13
+ raise HTTPException(status_code=500, detail="Could not decode image.")
14
 
15
+ img = cv2.resize(img, (299, 299))
16
+ img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
17
+ img = img / 255.0
18
+ img = np.expand_dims(img, axis=0).astype(np.float32)
19
+ return img
20
 
21
+ except HTTPException:
22
+ raise # Re-raise already defined HTTP errors
23
+ except Exception as e:
24
+ raise HTTPException(
25
+ status_code=500, detail=f"Image preprocessing failed: {str(e)}"
26
+ )
features/image_edit_detector/detectors/ela.py CHANGED
@@ -1,6 +1,7 @@
1
  from PIL import Image, ImageChops, ImageEnhance
2
  import io
3
 
 
4
  def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
5
  """
6
  Perform Error Level Analysis to detect image manipulation.
@@ -16,7 +17,7 @@ def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
16
 
17
  # Recompress the image into JPEG format in memory
18
  buffer = io.BytesIO()
19
- image.save(buffer, format='JPEG', quality=quality)
20
  buffer.seek(0)
21
  recompressed = Image.open(buffer)
22
 
@@ -29,4 +30,3 @@ def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
29
  _ = ImageEnhance.Brightness(diff).enhance(10)
30
 
31
  return max_diff > threshold
32
-
 
1
  from PIL import Image, ImageChops, ImageEnhance
2
  import io
3
 
4
+
5
  def run_ela(image: Image.Image, quality: int = 90, threshold: int = 15) -> bool:
6
  """
7
  Perform Error Level Analysis to detect image manipulation.
 
17
 
18
  # Recompress the image into JPEG format in memory
19
  buffer = io.BytesIO()
20
+ image.save(buffer, format="JPEG", quality=quality)
21
  buffer.seek(0)
22
  recompressed = Image.open(buffer)
23
 
 
30
  _ = ImageEnhance.Brightness(diff).enhance(10)
31
 
32
  return max_diff > threshold
 
license.md ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # License - All Rights Reserved
2
+
3
+ Copyright (c) 2025 CyberAlertNepal
4
+
5
+ This software and all associated materials are **not open source** and are protected under a custom license.
6
+
7
+ ## Strict Usage Terms
8
+
9
+ Unless explicit written permission is granted by **CyberAlertNepal**, **no individual or entity** is allowed to:
10
+
11
+ - Use this codebase or its models in any capacity — personal, educational, or commercial.
12
+ - Modify, copy, distribute, or sublicense any part of this project.
13
+ - Deploy, mirror, or host this project, either publicly or privately.
14
+ - Incorporate any component of this project into derivative works or other applications.
15
+
16
+ This project is intended for **private, internal use by the author(s) only**.
17
+
18
+ Any unauthorized usage, reproduction, or distribution is strictly prohibited and may result in legal action.
19
+
20
+ **All rights reserved.**
requirements.txt CHANGED
@@ -15,3 +15,4 @@ tensorflow
15
  opencv-python
16
  pillow
17
  scipy
 
 
15
  opencv-python
16
  pillow
17
  scipy
18
+ fitz