Compare commits

...

10 Commits

Author SHA1 Message Date
livindra 8d00213055 fix 2026-06-15 05:14:13 +07:00
Prayoga K. I. a642eef7c4
Merge pull request #3 from livindwi58/bawah
up
2026-06-14 03:35:02 +00:00
Prayoga K. I. 0408077558
Merge branch 'main' into bawah 2026-06-14 03:33:55 +00:00
Prayoga K. I. 16282861de up 2026-06-14 10:26:15 +07:00
livindra 9f00edf4a1 update dataset 2026-06-13 14:14:43 +07:00
livindra 8559d07e25 update dataset 2026-06-13 11:08:56 +07:00
Prayoga K. I. d4b3a2d399 update 2026-06-10 14:28:04 +07:00
Prayoga K. I. c9936a90d5 muilti class knn 2026-06-10 13:38:53 +07:00
Prayoga K. I. e4ac6fb94b fix scikit-learn 2026-06-10 13:32:31 +07:00
Prayoga K. I. 846c0f0093 update fix healthty 2026-06-10 12:57:54 +07:00
1521 changed files with 3063 additions and 2075 deletions

8
.env
View File

@ -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

3
.gitignore vendored
View File

@ -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

View File

@ -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 -- FC01FC04
code VARCHAR(20) UNIQUE -- FC01FC05
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 (FC01FC04):**
**5 Aturan (FC01FC05):**
- 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.098.5% |
| **Confidence** | Custom weighted neighbor, clipped 50.089.5% |
| **Class detection** | Auto-detect all folders under `dataset/``healthy``sehat`, `pmk_*` → nama folder |
> **Jika menambah/mengubah fitur**, update:

35
app.py
View File

@ -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 = {

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 382 KiB

After

Width:  |  Height:  |  Size: 382 KiB

View File

Before

Width:  |  Height:  |  Size: 498 KiB

After

Width:  |  Height:  |  Size: 498 KiB

View File

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 390 KiB

View File

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

Before

Width:  |  Height:  |  Size: 287 KiB

After

Width:  |  Height:  |  Size: 287 KiB

View File

Before

Width:  |  Height:  |  Size: 242 KiB

After

Width:  |  Height:  |  Size: 242 KiB

View File

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 340 KiB

View File

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 174 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

Before

Width:  |  Height:  |  Size: 321 KiB

After

Width:  |  Height:  |  Size: 321 KiB

View File

Before

Width:  |  Height:  |  Size: 418 KiB

After

Width:  |  Height:  |  Size: 418 KiB

View File

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 362 KiB

View File

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 407 KiB

View File

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 297 KiB

View File

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 290 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 288 KiB

After

Width:  |  Height:  |  Size: 288 KiB

View File

Before

Width:  |  Height:  |  Size: 269 KiB

After

Width:  |  Height:  |  Size: 269 KiB

View File

Before

Width:  |  Height:  |  Size: 263 KiB

After

Width:  |  Height:  |  Size: 263 KiB

View File

Before

Width:  |  Height:  |  Size: 397 KiB

After

Width:  |  Height:  |  Size: 397 KiB

View File

Before

Width:  |  Height:  |  Size: 467 KiB

After

Width:  |  Height:  |  Size: 467 KiB

View File

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 642 KiB

After

Width:  |  Height:  |  Size: 642 KiB

View File

Before

Width:  |  Height:  |  Size: 325 KiB

After

Width:  |  Height:  |  Size: 325 KiB

View File

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

View File

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 345 KiB

View File

Before

Width:  |  Height:  |  Size: 258 KiB

After

Width:  |  Height:  |  Size: 258 KiB

View File

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 439 KiB

View File

Before

Width:  |  Height:  |  Size: 293 KiB

After

Width:  |  Height:  |  Size: 293 KiB

View File

Before

Width:  |  Height:  |  Size: 343 KiB

After

Width:  |  Height:  |  Size: 343 KiB

View File

Before

Width:  |  Height:  |  Size: 388 KiB

After

Width:  |  Height:  |  Size: 388 KiB

View File

Before

Width:  |  Height:  |  Size: 314 KiB

After

Width:  |  Height:  |  Size: 314 KiB

View File

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 265 KiB

View File

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 217 KiB

View File

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 371 KiB

View File

Before

Width:  |  Height:  |  Size: 466 KiB

After

Width:  |  Height:  |  Size: 466 KiB

View File

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 273 KiB

View File

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 265 KiB

After

Width:  |  Height:  |  Size: 265 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 270 KiB

After

Width:  |  Height:  |  Size: 270 KiB

View File

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 284 KiB

View File

Before

Width:  |  Height:  |  Size: 290 KiB

After

Width:  |  Height:  |  Size: 290 KiB

View File

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

Before

Width:  |  Height:  |  Size: 390 KiB

After

Width:  |  Height:  |  Size: 390 KiB

View File

Before

Width:  |  Height:  |  Size: 732 KiB

After

Width:  |  Height:  |  Size: 732 KiB

View File

Before

Width:  |  Height:  |  Size: 394 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 230 KiB

View File

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 77 KiB

View File

Before

Width:  |  Height:  |  Size: 276 KiB

After

Width:  |  Height:  |  Size: 276 KiB

View File

Before

Width:  |  Height:  |  Size: 250 KiB

After

Width:  |  Height:  |  Size: 250 KiB

View File

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 440 KiB

View File

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 338 KiB

View File

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 307 KiB

View File

Before

Width:  |  Height:  |  Size: 313 KiB

After

Width:  |  Height:  |  Size: 313 KiB

View File

Before

Width:  |  Height:  |  Size: 312 KiB

After

Width:  |  Height:  |  Size: 312 KiB

View File

Before

Width:  |  Height:  |  Size: 431 KiB

After

Width:  |  Height:  |  Size: 431 KiB

View File

Before

Width:  |  Height:  |  Size: 253 KiB

After

Width:  |  Height:  |  Size: 253 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View File

Before

Width:  |  Height:  |  Size: 384 KiB

After

Width:  |  Height:  |  Size: 384 KiB

View File

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 231 KiB

View File

Before

Width:  |  Height:  |  Size: 512 KiB

After

Width:  |  Height:  |  Size: 512 KiB

View File

Before

Width:  |  Height:  |  Size: 291 KiB

After

Width:  |  Height:  |  Size: 291 KiB

View File

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 428 KiB

View File

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

View File

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 240 KiB

View File

Before

Width:  |  Height:  |  Size: 335 KiB

After

Width:  |  Height:  |  Size: 335 KiB

View File

Before

Width:  |  Height:  |  Size: 161 KiB

After

Width:  |  Height:  |  Size: 161 KiB

View File

Before

Width:  |  Height:  |  Size: 293 KiB

After

Width:  |  Height:  |  Size: 293 KiB

View File

Before

Width:  |  Height:  |  Size: 330 KiB

After

Width:  |  Height:  |  Size: 330 KiB

View File

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 402 KiB

View File

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 371 KiB

View File

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

Before

Width:  |  Height:  |  Size: 399 KiB

After

Width:  |  Height:  |  Size: 399 KiB

View File

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 256 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

Before

Width:  |  Height:  |  Size: 266 KiB

After

Width:  |  Height:  |  Size: 266 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 272 KiB

View File

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 280 KiB

View File

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 274 KiB

View File

Before

Width:  |  Height:  |  Size: 392 KiB

After

Width:  |  Height:  |  Size: 392 KiB

View File

Before

Width:  |  Height:  |  Size: 704 KiB

After

Width:  |  Height:  |  Size: 704 KiB

View File

Before

Width:  |  Height:  |  Size: 219 KiB

After

Width:  |  Height:  |  Size: 219 KiB

View File

Before

Width:  |  Height:  |  Size: 226 KiB

After

Width:  |  Height:  |  Size: 226 KiB

View File

Before

Width:  |  Height:  |  Size: 209 KiB

After

Width:  |  Height:  |  Size: 209 KiB

View File

Before

Width:  |  Height:  |  Size: 385 KiB

After

Width:  |  Height:  |  Size: 385 KiB

View File

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 246 KiB

View File

Before

Width:  |  Height:  |  Size: 444 KiB

After

Width:  |  Height:  |  Size: 444 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 404 KiB

View File

Before

Width:  |  Height:  |  Size: 264 KiB

After

Width:  |  Height:  |  Size: 264 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

Some files were not shown because too many files have changed in this diff Show More