| import numpy as np
|
| import pandas as pd
|
| import sklearn
|
| from sklearn.neighbors import KNeighborsClassifier
|
| from sklearn.model_selection import train_test_split, cross_val_score
|
| from sklearn.metrics import classification_report, confusion_matrix
|
| import matplotlib.pyplot as plt
|
| import seaborn as sns
|
| import os
|
| import re
|
| import joblib
|
| from sklearn.preprocessing import LabelEncoder
|
| import gc
|
| from sklearn.utils.class_weight import compute_class_weight
|
|
|
|
|
| print("Loading embeddings and image paths...")
|
| features = np.load('../extracted-features-test/features.npy')
|
| with open('../extracted-features-test/image_paths.txt', 'r') as f:
|
| image_paths = [line.strip() for line in f.readlines()]
|
|
|
| print(f"Loaded {len(features)} embedding vectors with shape {features.shape}")
|
|
|
|
|
| print("Extracting TCGA case IDs...")
|
| tcga_cases = []
|
| for path in image_paths:
|
|
|
| match = re.search(r'(TCGA-[A-Z0-9]{2}-[A-Z0-9]{4}-[0-9A-Z]{3}-[0-9A-Z]{2}-[A-Z0-9]{3})', path)
|
| if match:
|
| tcga_case = match.group(1)
|
| else:
|
|
|
| match = re.search(r'(TCGA-[A-Z0-9]{2}-[A-Z0-9]{4})', path)
|
| if match:
|
| tcga_case = match.group(1)
|
| else:
|
| tcga_case = os.path.basename(os.path.dirname(path))
|
|
|
| tcga_cases.append(tcga_case)
|
|
|
|
|
| csv_path = 'E:\\buse_thesis_prostate\\algorithms\\dinov1\\dino-code\\tcga\\prostate_tcga_wsi_paths_aws_DX_only.csv'
|
| df = pd.read_csv(csv_path)
|
| print(f"CSV loaded with {len(df)} rows")
|
|
|
|
|
| print("Creating case-to-grade mapping...")
|
| case_to_grade = {}
|
| for idx, row in df.iterrows():
|
| filename = row['filename']
|
| grade = row['gleason_grade']
|
|
|
|
|
| match = re.search(r'(TCGA-[A-Z0-9]{2}-[A-Z0-9]{4})', filename)
|
| if match:
|
| case_id = match.group(1)
|
| case_to_grade[case_id] = grade
|
|
|
| print(f"Created mapping for {len(case_to_grade)} unique cases")
|
|
|
|
|
| print("Matching embeddings with grades...")
|
| matched_features = []
|
| matched_labels = []
|
| matched_cases = []
|
|
|
|
|
| batch_size = 10000
|
| num_batches = len(tcga_cases) // batch_size + (1 if len(tcga_cases) % batch_size > 0 else 0)
|
|
|
| for batch_idx in range(num_batches):
|
| start_idx = batch_idx * batch_size
|
| end_idx = min((batch_idx + 1) * batch_size, len(tcga_cases))
|
|
|
| print(f"Processing batch {batch_idx+1}/{num_batches} (samples {start_idx}-{end_idx})...")
|
|
|
| batch_features = []
|
| batch_labels = []
|
| batch_cases = []
|
|
|
| for i in range(start_idx, end_idx):
|
| case_id = tcga_cases[i]
|
|
|
|
|
| if case_id in case_to_grade:
|
| batch_features.append(features[i])
|
| batch_labels.append(case_to_grade[case_id])
|
| batch_cases.append(case_id)
|
| else:
|
|
|
| short_match = re.search(r'(TCGA-[A-Z0-9]{2}-[A-Z0-9]{4})', case_id)
|
| if short_match:
|
| short_id = short_match.group(1)
|
| if short_id in case_to_grade:
|
| batch_features.append(features[i])
|
| batch_labels.append(case_to_grade[short_id])
|
| batch_cases.append(short_id)
|
|
|
|
|
| matched_features.extend(batch_features)
|
| matched_labels.extend(batch_labels)
|
| matched_cases.extend(batch_cases)
|
|
|
|
|
| del batch_features, batch_labels, batch_cases
|
| gc.collect()
|
|
|
|
|
| print(f"Total matched samples before subsampling: {len(matched_features)}")
|
|
|
|
|
| label_encoder = LabelEncoder()
|
| numeric_labels = label_encoder.fit_transform(matched_labels)
|
| unique_labels = label_encoder.classes_
|
|
|
|
|
| max_samples_per_class = 20000
|
|
|
|
|
| sampled_indices = []
|
| for label in np.unique(numeric_labels):
|
|
|
| class_indices = np.where(numeric_labels == label)[0]
|
|
|
|
|
| if len(class_indices) > max_samples_per_class:
|
| sampled_idx = np.random.choice(class_indices, max_samples_per_class, replace=False)
|
| else:
|
| sampled_idx = class_indices
|
|
|
| sampled_indices.extend(sampled_idx)
|
|
|
|
|
| X = np.array([matched_features[i] for i in sampled_indices])
|
| y = np.array([matched_labels[i] for i in sampled_indices])
|
| sampled_cases = [matched_cases[i] for i in sampled_indices]
|
|
|
| print(f"Subsampled to {len(X)} total examples")
|
| print(f"Unique Gleason grades: {np.unique(y)}")
|
| print(f"Class distribution after subsampling:")
|
| for grade in np.unique(y):
|
| print(f" Grade {grade}: {np.sum(y == grade)} samples")
|
|
|
|
|
| del matched_features, matched_labels, matched_cases
|
| gc.collect()
|
|
|
|
|
| X_train, X_test, y_train, y_test = train_test_split(
|
| X, y, test_size=0.2, random_state=42, stratify=y)
|
|
|
| mask = y_train != '2+4'
|
| X_train = X_train[mask]
|
| y_train = y_train[mask]
|
| mask_test = y_test != '2+4'
|
| X_test = X_test[mask_test]
|
| y_test = y_test[mask_test]
|
|
|
| print(f"Training set: {X_train.shape[0]} samples")
|
| print(f"Test set: {X_test.shape[0]} samples")
|
|
|
|
|
| class_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
|
| class_weight_dict = dict(zip(np.unique(y_train), class_weights))
|
|
|
| print("Class weights:", class_weight_dict)
|
|
|
|
|
| print("Training KNN model...")
|
|
|
| k_values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
|
| best_k = 5
|
| best_score = 0
|
|
|
| for k in k_values:
|
|
|
| knn = KNeighborsClassifier(n_neighbors=k, weights='distance', metric='cosine')
|
| scores = cross_val_score(knn, X_train, y_train, cv=3, scoring='accuracy')
|
| avg_score = np.mean(scores)
|
| print(f"k={k}, Cross-validation score: {avg_score:.4f}")
|
|
|
| if avg_score > best_score:
|
| best_score = avg_score
|
| best_k = k
|
|
|
| print(f"Best k value: {best_k} with score: {best_score:.4f}")
|
|
|
|
|
| final_model = KNeighborsClassifier(
|
| n_neighbors=best_k,
|
| weights='distance',
|
| metric='cosine'
|
| )
|
| final_model.fit(X_train, y_train)
|
|
|
|
|
| y_pred = final_model.predict(X_test)
|
| print("\nClassification Report:")
|
| print(classification_report(y_test, y_pred))
|
|
|
|
|
| cm = confusion_matrix(y_test, y_pred)
|
| plt.figure(figsize=(10, 8))
|
| sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
|
| xticklabels=np.unique(y_test),
|
| yticklabels=np.unique(y_test))
|
| plt.xlabel('Predicted')
|
| plt.ylabel('Actual')
|
| plt.title('Confusion Matrix')
|
| plt.savefig('confusion_matrix.png')
|
| print("Saved confusion matrix to confusion_matrix.png")
|
| print("ROC AUC", sklearn.metrics.roc_auc_score)
|
|
|
| joblib.dump(final_model, 'tcga_gleason_knn_model.joblib')
|
| print("Model saved to tcga_gleason_knn_model.joblib")
|
|
|
|
|
| result_df = pd.DataFrame({
|
| 'case_id': sampled_cases,
|
| 'gleason_grade': y
|
| })
|
| result_df.to_csv('matched_cases.csv', index=False)
|
| print("Saved case ID to class mapping in matched_cases.csv")
|
|
|
| np.save('X_train.npy', X_train)
|
| np.save('y_train.npy', y_train)
|
|
|
|
|
| joblib.dump(label_encoder, 'label_encoder.pkl')
|
|
|
|
|
| def predict_gleason_grade(embedding_vector, model_path='tcga_gleason_knn_model.joblib'):
|
| """Predict Gleason grade for a new DINO embedding vector"""
|
| model = joblib.load(model_path)
|
|
|
| embedding_vector = np.array(embedding_vector).reshape(1, -1)
|
| prediction = model.predict(embedding_vector)
|
| probabilities = model.predict_proba(embedding_vector)
|
|
|
| return {
|
| 'predicted_grade': prediction[0],
|
| 'probabilities': dict(zip(model.classes_, probabilities[0]))
|
| }
|
|
|
| print("\nDone! You can now use the trained model for predictions.")
|
|
|
|
|
| unique_classes, counts = np.unique(y, return_counts=True)
|
|
|
|
|
| class_counts = dict(zip(unique_classes, counts))
|
|
|
|
|
| print("Total number of samples per class:")
|
| for class_label, count in class_counts.items():
|
| print(f"Class {class_label}: {count} samples")
|
|
|
|
|
| total_samples = len(y)
|
| print(f"Total number of samples in the dataset: {total_samples}") |