Compare commits
10 Commits
d197569981
...
8d00213055
| Author | SHA1 | Date |
|---|---|---|
|
|
8d00213055 | |
|
|
a642eef7c4 | |
|
|
0408077558 | |
|
|
16282861de | |
|
|
9f00edf4a1 | |
|
|
8559d07e25 | |
|
|
d4b3a2d399 | |
|
|
c9936a90d5 | |
|
|
e4ac6fb94b | |
|
|
846c0f0093 |
8
.env
|
|
@ -1,9 +1,11 @@
|
|||
# MySQL Database Configuration
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_USER=kali
|
||||
DB_PASSWORD=asu
|
||||
DB_USER=root
|
||||
DB_PASSWORD=
|
||||
DB_NAME=deteksi_pmk
|
||||
|
||||
# Flask Configuration
|
||||
FLASK_SECRET=deteksi-pmk-secret-key-2026
|
||||
FLASK_SECRET=deteksi-pmk-secret-key-2026
|
||||
FLASK_DEBUG=1
|
||||
|
|
@ -5,6 +5,9 @@ models/*
|
|||
!models/scaler.pkl
|
||||
!models/label_encoder.pkl
|
||||
!models/pca.pkl
|
||||
!models/multiclass_knn_model.pkl
|
||||
!models/multiclass_scaler.pkl
|
||||
!models/multiclass_label_encoder.pkl
|
||||
results/*
|
||||
uploads/*
|
||||
.venv
|
||||
|
|
|
|||
16
AGENTS.md
|
|
@ -77,7 +77,7 @@ deteksi_PMK/
|
|||
│ ├── __init__.py
|
||||
│ ├── mysql_db.py # SQLAlchemy ORM — models, CRUD, seed data (916 baris)
|
||||
│ ├── preprocessing.py # Validasi sapi + preprocessing pipeline (340 baris)
|
||||
│ ├── feature_extraction.py # Ekstraksi fitur: RGB avg + HSV + GLCM + histogram + Hu (46 fitur)
|
||||
│ ├── feature_extraction.py # Ekstraksi fitur: RGB avg + HSV + GLCM + histogram + Hu (44 fitur)
|
||||
│ └── helpers.py # Load/save model, dataset prep, confidence estimation (237 baris)
|
||||
│
|
||||
├── templates/ # Jinja2 templates
|
||||
|
|
@ -121,7 +121,7 @@ User upload gambar
|
|||
│
|
||||
▼
|
||||
[feature_extraction.py] FeatureExtractor.extract_all_features()
|
||||
→ 46 fitur: [RGB avg (3), HSV mean+std (6), GLCM (6), histogram 24, Hu (7)]
|
||||
→ 44 fitur: [RGB avg (3), HSV mean+std (6), GLCM (4), histogram 24, Hu (7)]
|
||||
│
|
||||
▼
|
||||
[helpers.py] scaler.transform() → [BINARY MODEL] predict()
|
||||
|
|
@ -232,7 +232,7 @@ expert_diseases
|
|||
-- Aturan forward chaining (5 aturan)
|
||||
expert_rules
|
||||
id INT AUTO_INCREMENT PK
|
||||
code VARCHAR(20) UNIQUE -- FC01–FC04
|
||||
code VARCHAR(20) UNIQUE -- FC01–FC05
|
||||
symptom_codes TEXT -- JSON array of symptom codes
|
||||
result_disease_code VARCHAR(10) FK → expert_diseases.code
|
||||
description TEXT
|
||||
|
|
@ -262,12 +262,12 @@ expert_rules_expert_symptoms
|
|||
- P03 = PMK_LAKTASI (gejala ambing)
|
||||
- P05 = PMK_AKUT_GENERAL (gejala umum berat)
|
||||
|
||||
**4 Aturan (FC01–FC04):**
|
||||
**5 Aturan (FC01–FC05):**
|
||||
|
||||
- FC01 → P01 (oral): [G01, G02, G03, G04, G11, G18, G19, G20, G21]
|
||||
- FC02 → P02 (podal): [G01, G02, G05, G06, G15, G22, G23, G24, G25]
|
||||
- FC03 → P03 (laktasi): [G01, G02, G07, G08, G09, G26, G27]
|
||||
- FC04 → P05 (akut umum): [G01, G02, G03, G04, G05, G06, G07, G09, G11, G12, G14, G18, G20, G22, G23, G24, G26]
|
||||
- FC05 → P05 (akut umum): [G01, G02, G03, G04, G05, G06, G07, G09, G11, G12, G14, G18, G20, G22, G23, G24, G26]
|
||||
|
||||
> **Auto-check gejala dari hasil deteksi gambar** (di `app.py:pmk_to_symptoms`):
|
||||
> Mapping ini digunakan saat redirect ke expert system via mode=image. Hanya gejala spesifik body part yang diikutkan (tanpa gejala umum):
|
||||
|
|
@ -310,7 +310,7 @@ expert_rules_expert_symptoms
|
|||
**Arsitektur Hierarchical (2-level):**
|
||||
|
||||
```
|
||||
Input image → 46 fitur (RGB+HSV+GLCM+Histogram+Hu)
|
||||
Input image → 44 fitur (RGB+HSV+GLCM+Histogram+Hu)
|
||||
│
|
||||
┌──────┴──────┐
|
||||
▼ ▼
|
||||
|
|
@ -350,12 +350,12 @@ Input image → 46 fitur (RGB+HSV+GLCM+Histogram+Hu)
|
|||
|
||||
| Parameter | Value |
|
||||
| ----------------- | --------------------------------------------------------------------------- |
|
||||
| **Features** | 46: avg_red, avg_green, avg_blue, hsv_h_mean, hsv_h_std, hsv_s_mean, hsv_s_std, hsv_v_mean, hsv_v_std, contrast, homogeneity, correlation, energy, dissimilarity, asm, 8-bin histogram × 3 channels (24), hu_moment_0..6 (7) |
|
||||
| **Features** | 44: avg_red, avg_green, avg_blue, hsv_h_mean, hsv_h_std, hsv_s_mean, hsv_s_std, hsv_v_mean, hsv_v_std, contrast, homogeneity, correlation, energy, 8-bin histogram × 3 channels (24), hu_moment_0..6 (7) |
|
||||
| **Image size** | 256×256 |
|
||||
| **Preprocessing** | Otsu threshold, mask, resize |
|
||||
| **Scaling** | StandardScaler |
|
||||
| **Output** | Binary: `sehat` / `sakit`; Multi-class: `pmk_oral`, `pmk_podal`, `pmk_laktasi`, `pmk_akut_general`, ... |
|
||||
| **Confidence** | Custom weighted neighbor, clipped 50.0–98.5% |
|
||||
| **Confidence** | Custom weighted neighbor, clipped 50.0–89.5% |
|
||||
| **Class detection** | Auto-detect all folders under `dataset/` — `healthy` → `sehat`, `pmk_*` → nama folder |
|
||||
|
||||
> **Jika menambah/mengubah fitur**, update:
|
||||
|
|
|
|||
35
app.py
|
|
@ -13,7 +13,7 @@ from dotenv import load_dotenv
|
|||
load_dotenv()
|
||||
|
||||
from utils.helpers import load_model, estimate_prediction_confidence
|
||||
from utils.preprocessing import preprocess_image, preprocess_pipeline, validate_cattle_image
|
||||
from utils.preprocessing import preprocess_image, preprocess_pipeline, validate_cattle_image, detect_udder
|
||||
from utils.feature_extraction import FeatureExtractor
|
||||
from expert_system import ForwardChaining, KnowledgeBase # Import sistem pakar
|
||||
|
||||
|
|
@ -422,6 +422,20 @@ def _predict_single(filepath, original_filename):
|
|||
multi_enc = multiclass_model.predict(multi_scaled)[0]
|
||||
pmk_type = multiclass_label_encoder.inverse_transform([multi_enc])[0]
|
||||
|
||||
# Fallback heuristic: jika model menandai 'sehat' tapi gambar mengandung ciri ambing/puting,
|
||||
# anggap sebagai laktasi (pmk_laktasi) dan ubah prediksi agar sistem pakar terpicu.
|
||||
if prediction.lower() == 'sehat':
|
||||
try:
|
||||
udder_detected, udder_score = detect_udder(filepath)
|
||||
if udder_detected:
|
||||
prediction = 'sakit'
|
||||
pmk_type = 'pmk_laktasi'
|
||||
# update confidence minimal berdasarkan score heuristik
|
||||
confidence = max(confidence, min(udder_score * 100.0, 95.0))
|
||||
print(f"[HEURISTIC] Udder heuristic triggered for {original_filename} | score={udder_score:.2f}")
|
||||
except Exception as e:
|
||||
print(f"[HEURISTIC] Udder heuristic error: {e}")
|
||||
|
||||
features_dict = {name: float(features[i]) for i, name in enumerate(extractor.feature_names)}
|
||||
features_table = list(zip(extractor.feature_names, [round(float(x), 4) for x in features]))
|
||||
|
||||
|
|
@ -584,6 +598,10 @@ def predict():
|
|||
pmk_to_symptoms = {
|
||||
'pmk_oral': ['G02', 'G03', 'G04', 'G10', 'G14', 'G18', 'G19', 'G20', 'G21'],
|
||||
'pmk_podal': ['G05', 'G06', 'G15', 'G22', 'G23', 'G24'],
|
||||
# Udder (laktasi) — gunakan hanya gejala spesifik ambing/puting
|
||||
'pmk_laktasi': ['G07', 'G08', 'G09', 'G26', 'G27'],
|
||||
# General akut: kombinasi gejala yang menunjukkan PMK akut/menular luas
|
||||
'pmk_akut_general': ['G01', 'G02', 'G03', 'G04', 'G05', 'G06', 'G07', 'G09', 'G11', 'G12', 'G14', 'G18', 'G20', 'G22', 'G23', 'G24', 'G26']
|
||||
}
|
||||
|
||||
preselected = set()
|
||||
|
|
@ -592,13 +610,24 @@ def predict():
|
|||
if key in pmk_to_symptoms:
|
||||
preselected.update(pmk_to_symptoms[key])
|
||||
|
||||
# Jika lebih dari satu body-part berbeda terdeteksi sakit (misal: mulut+kaki, mulut+puting, kaki+puting),
|
||||
# anggap kemungkinan PMK akut/general dan preselect gejala umum akut juga.
|
||||
body_part_keys = {k for k in sick_pmk_types if k in {'pmk_oral', 'pmk_podal', 'pmk_laktasi'}}
|
||||
if len(body_part_keys) >= 2:
|
||||
# tambahkan gejala PMK akut general
|
||||
preselected.update(pmk_to_symptoms.get('pmk_akut_general', []))
|
||||
# juga tambahkan tipe pmk_akut_general ke hasil sehingga UI/riwayat menampilkan tipe ini
|
||||
sick_pmk_types.add('pmk_akut_general')
|
||||
|
||||
# Store all images in session for expert system display
|
||||
session['last_prediction'] = {
|
||||
'db_id': pred_id,
|
||||
'filename': upload_images[0]['filename'],
|
||||
'original_filename': upload_images[0]['original_filename'],
|
||||
'filepath': upload_images[0]['filepath'],
|
||||
'prediction': 'sakit' if sick_pmk_types else 'sehat',
|
||||
'confidence': upload_images[0]['confidence'],
|
||||
'features_table': upload_images[0]['features_table'],
|
||||
'source': 'image_processing',
|
||||
}
|
||||
session['upload_images_data'] = upload_images
|
||||
|
|
@ -809,7 +838,7 @@ def expert_system_page():
|
|||
|
||||
# Jika ada hasil prediksi berbasis image processing sebelumnya, tambahkan ke konteks
|
||||
last_prediction = session.get('last_prediction', {})
|
||||
upload_images = session.get('upload_images_data', [])
|
||||
upload_images = session.get('upload_images_data', []) if use_image_context else []
|
||||
image_info = None
|
||||
if use_image_context and last_prediction.get('source') == 'image_processing':
|
||||
image_info = {
|
||||
|
|
@ -837,7 +866,7 @@ def expert_system_page():
|
|||
|
||||
# Ambil informasi gambar dari session jika hasil sebelumnya berasal dari image processing
|
||||
last_prediction = session.get('last_prediction', {})
|
||||
upload_images = session.get('upload_images_data', [])
|
||||
upload_images = session.get('upload_images_data', []) if use_image_context else []
|
||||
image_info = None
|
||||
if use_image_context and last_prediction.get('source') == 'image_processing':
|
||||
image_info = {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 498 KiB After Width: | Height: | Size: 498 KiB |
|
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 282 KiB After Width: | Height: | Size: 282 KiB |
|
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 287 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 340 KiB After Width: | Height: | Size: 340 KiB |
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 321 KiB After Width: | Height: | Size: 321 KiB |
|
Before Width: | Height: | Size: 418 KiB After Width: | Height: | Size: 418 KiB |
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 362 KiB |
|
Before Width: | Height: | Size: 407 KiB After Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 269 KiB |
|
Before Width: | Height: | Size: 263 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 397 KiB After Width: | Height: | Size: 397 KiB |
|
Before Width: | Height: | Size: 467 KiB After Width: | Height: | Size: 467 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 642 KiB After Width: | Height: | Size: 642 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 325 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 345 KiB After Width: | Height: | Size: 345 KiB |
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 439 KiB After Width: | Height: | Size: 439 KiB |
|
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 343 KiB After Width: | Height: | Size: 343 KiB |
|
Before Width: | Height: | Size: 388 KiB After Width: | Height: | Size: 388 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 217 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 466 KiB After Width: | Height: | Size: 466 KiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
|
Before Width: | Height: | Size: 265 KiB After Width: | Height: | Size: 265 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 290 KiB After Width: | Height: | Size: 290 KiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 390 KiB After Width: | Height: | Size: 390 KiB |
|
Before Width: | Height: | Size: 732 KiB After Width: | Height: | Size: 732 KiB |
|
Before Width: | Height: | Size: 394 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 230 KiB |
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 250 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 440 KiB |
|
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 307 KiB After Width: | Height: | Size: 307 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 313 KiB |
|
Before Width: | Height: | Size: 312 KiB After Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 431 KiB |
|
Before Width: | Height: | Size: 253 KiB After Width: | Height: | Size: 253 KiB |
|
Before Width: | Height: | Size: 203 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 384 KiB |
|
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 231 KiB |
|
Before Width: | Height: | Size: 512 KiB After Width: | Height: | Size: 512 KiB |
|
Before Width: | Height: | Size: 291 KiB After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 428 KiB After Width: | Height: | Size: 428 KiB |
|
Before Width: | Height: | Size: 271 KiB After Width: | Height: | Size: 271 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 335 KiB After Width: | Height: | Size: 335 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 293 KiB |
|
Before Width: | Height: | Size: 330 KiB After Width: | Height: | Size: 330 KiB |
|
Before Width: | Height: | Size: 402 KiB After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 211 KiB |
|
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 399 KiB |
|
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 266 KiB After Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 272 KiB After Width: | Height: | Size: 272 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 392 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 385 KiB |
|
Before Width: | Height: | Size: 246 KiB After Width: | Height: | Size: 246 KiB |
|
Before Width: | Height: | Size: 444 KiB After Width: | Height: | Size: 444 KiB |
|
Before Width: | Height: | Size: 298 KiB After Width: | Height: | Size: 298 KiB |
|
Before Width: | Height: | Size: 404 KiB After Width: | Height: | Size: 404 KiB |
|
Before Width: | Height: | Size: 264 KiB After Width: | Height: | Size: 264 KiB |
|
Before Width: | Height: | Size: 196 KiB After Width: | Height: | Size: 196 KiB |