File size: 9,826 Bytes
08f875f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
"""
Script doc lap de test model phan loai u nao da train (.keras).
Khong can toan bo pipeline training - chi can model + 1 file de test.

Ho tro 2 loai input:
  1. File .h5 (dung dinh dang giong data training, co dataset 'image')
  2. File anh thong thuong (.png, .jpg, .jpeg) - se duoc chuyen ve grayscale

Cach dung:
    python test_inference.py --model /path/to/model.keras --input /path/to/slice.h5
    python test_inference.py --model /path/to/model.keras --input /path/to/image.png
"""

import argparse
import numpy as np
import cv2
import h5py
import tensorflow as tf
from tensorflow.keras.layers import Layer, Dense, Dropout
from tensorflow.keras.applications.efficientnet_v2 import preprocess_input as efficientnetv2_preprocess

IMG_SIZE = (299, 299)
NUM_SLICES = 3
CLASS_NAMES = ["non-tumor", "tumor"]


# ============================================================
# CAC CUSTOM LAYER / LOSS - BAT BUOC PHAI CO DE load_model() HOAT DONG
# (copy y nguyen tu script training goc)
# ============================================================
class AttentionVisualizer(Layer):
    def __init__(self, **kwargs):
        super(AttentionVisualizer, self).__init__(**kwargs)

    def call(self, attention_weights):
        spatial_attention = tf.reduce_mean(attention_weights, axis=-1, keepdims=True)
        spatial_attention = (spatial_attention - tf.reduce_min(spatial_attention, axis=(1, 2), keepdims=True)) / \
                             (tf.reduce_max(spatial_attention, axis=(1, 2), keepdims=True) -
                              tf.reduce_min(spatial_attention, axis=(1, 2), keepdims=True) + 1e-8)
        return spatial_attention


class MCDropout(Layer):
    def __init__(self, rate, **kwargs):
        super(MCDropout, self).__init__(**kwargs)
        self.rate = rate
        self.dropout = Dropout(rate)

    def call(self, inputs, training=None):
        return self.dropout(inputs, training=True)


class EvidentialLoss(tf.keras.losses.Loss):
    def __init__(self, class_weights=None, name="evidential_loss",
                 reduction=tf.keras.losses.Reduction.SUM_OVER_BATCH_SIZE):
        super(EvidentialLoss, self).__init__(name=name, reduction=reduction)
        self.class_weights = class_weights if class_weights is not None else {}

    def call(self, y_true, y_pred):
        ce_loss = tf.keras.losses.categorical_crossentropy(y_true, y_pred)
        if self.class_weights:
            class_indices = tf.argmax(y_true, axis=1)
            weights = tf.gather(tf.constant(list(self.class_weights.values()), dtype=tf.float32), class_indices)
            ce_loss = ce_loss * weights
        reg_loss = tf.reduce_mean(tf.square(y_pred - y_true))
        return tf.reduce_mean(ce_loss + 0.01 * reg_loss)

    def get_config(self):
        config = super(EvidentialLoss, self).get_config()
        config.update({'class_weights': self.class_weights})
        return config


class EvidentialLayer(Layer):
    def __init__(self, num_classes, **kwargs):
        super(EvidentialLayer, self).__init__(**kwargs)
        self.num_classes = num_classes

    def build(self, input_shape):
        self.dense = Dense(self.num_classes, dtype=tf.float32)
        super(EvidentialLayer, self).build(input_shape)

    def call(self, inputs):
        evidence = tf.nn.softplus(self.dense(inputs))
        evidence = tf.clip_by_value(evidence, 0, 1e6)
        alpha = evidence + 1
        S = tf.reduce_sum(alpha, axis=1, keepdims=True) + 1e-10
        prob = alpha / S
        epistemic_uncertainty = self.num_classes / S
        aleatoric_uncertainty = tf.reduce_sum(prob * (1 - prob) / (S + 1), axis=1, keepdims=True)
        return prob, epistemic_uncertainty, aleatoric_uncertainty

    def get_config(self):
        config = super(EvidentialLayer, self).get_config()
        config.update({'num_classes': self.num_classes})
        return config


class ExplainableSelfAttention(Layer):
    def __init__(self, units, return_attention=False, **kwargs):
        super(ExplainableSelfAttention, self).__init__(**kwargs)
        self.units = units
        self.return_attention = return_attention

    def build(self, input_shape):
        self.W_q = Dense(self.units, use_bias=False)
        self.W_k = Dense(self.units, use_bias=False)
        self.W_v = Dense(self.units, use_bias=False)
        self.dense = Dense(input_shape[-1])
        self.attention_visualizer = AttentionVisualizer()
        super(ExplainableSelfAttention, self).build(input_shape)

    def call(self, inputs, return_attention=None):
        return_attention = return_attention if return_attention is not None else self.return_attention
        batch_size = tf.shape(inputs)[0]
        height, width, channels = inputs.shape[1:]
        x = tf.reshape(inputs, [batch_size, height * width, channels])
        q = self.W_q(x)
        k = self.W_k(x)
        v = self.W_v(x)
        scale = tf.cast(tf.sqrt(tf.cast(self.units, inputs.dtype)), inputs.dtype)
        scores = tf.matmul(q, k, transpose_b=True) / scale
        attention_weights = tf.nn.softmax(scores, axis=-1)
        attended = tf.matmul(attention_weights, v)
        output = self.dense(attended)
        output = tf.reshape(output, [batch_size, height, width, channels])
        output = inputs + output
        if return_attention:
            attention_spatial = tf.reshape(attention_weights, [batch_size, height, width, height * width])
            attention_map = self.attention_visualizer(attention_spatial)
            return output, attention_map
        return output

    def get_config(self):
        config = super(ExplainableSelfAttention, self).get_config()
        config.update({'units': self.units, 'return_attention': self.return_attention})
        return config


class MaxProbLayer(Layer):
    def __init__(self, **kwargs):
        super(MaxProbLayer, self).__init__(**kwargs)

    def call(self, inputs):
        return tf.reduce_max(inputs, axis=1, keepdims=True)


CUSTOM_OBJECTS = {
    'AttentionVisualizer': AttentionVisualizer,
    'MCDropout': MCDropout,
    'EvidentialLoss': EvidentialLoss,
    'EvidentialLayer': EvidentialLayer,
    'ExplainableSelfAttention': ExplainableSelfAttention,
    'MaxProbLayer': MaxProbLayer,
}


# ============================================================
# TIEN XU LY ANH - GIONG HET LOGIC TRONG GENERATOR TRAINING
# ============================================================
def load_slice_from_h5(path):
    """Doc 1 slice tu file .h5 (dataset ten 'image')."""
    with h5py.File(path, 'r') as f:
        if 'image' not in f:
            raise ValueError(f"File h5 khong co dataset 'image': {path}")
        img = f['image'][:].astype(np.float32)
        if img.ndim == 3:
            img = img[..., 0]
        elif img.ndim != 2:
            raise ValueError(f"Shape anh khong hop le: {img.shape}")
        return img


def load_slice_from_image(path):
    """Doc 1 slice tu file anh thuong (.png/.jpg), chuyen ve grayscale."""
    img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
    if img is None:
        raise ValueError(f"Khong doc duoc anh: {path}")
    return img.astype(np.float32)


def preprocess_slice(img_slice):
    """Resize, chuan hoa mean/std, clip, tao 3-channel stack, roi ap dung
    efficientnetv2_preprocess - dung y nhu trong _load_slice/_generate_batch
    cua ImprovedBrainTumorGenerator ban goc."""
    if img_slice.shape != IMG_SIZE:
        img_slice = cv2.resize(img_slice, IMG_SIZE, interpolation=cv2.INTER_LINEAR)

    mean = np.mean(img_slice)
    std = np.std(img_slice) + 1e-8
    img_slice = (img_slice - mean) / std
    img_slice = np.clip(img_slice, -3, 3)

    # gia lap 3 slice lan can bang cach lap lai slice trung tam
    stack = np.stack([img_slice, img_slice, img_slice], axis=-1)

    X = np.expand_dims(stack, axis=0).astype(np.float32)  # (1, 299, 299, 3)
    X = efficientnetv2_preprocess(X)
    return X


def load_and_preprocess(path):
    if path.lower().endswith(('.h5', '.hdf5')):
        img_slice = load_slice_from_h5(path)
    else:
        img_slice = load_slice_from_image(path)
    return preprocess_slice(img_slice)


# ============================================================
# CHAY INFERENCE
# ============================================================
def run_inference(model_path, input_path):
    print(f"Dang load model: {model_path}")
    model = tf.keras.models.load_model(model_path, custom_objects=CUSTOM_OBJECTS)

    print(f"Dang doc va tien xu ly file: {input_path}")
    X = load_and_preprocess(input_path)

    print("Dang du doan...")
    outputs = model.predict(X, verbose=0)

    # model tra ve dict cac output (classification, evidential_prob, confidence_score, ...)
    probs = outputs['classification'][0]
    pred_idx = int(np.argmax(probs))
    pred_label = CLASS_NAMES[pred_idx]

    print("\n" + "=" * 50)
    print("KET QUA DU DOAN")
    print("=" * 50)
    print(f"Nhan du doan: {pred_label}")
    for i, name in enumerate(CLASS_NAMES):
        print(f"  P({name}) = {probs[i]:.4f}")

    if 'confidence_score' in outputs:
        print(f"Confidence score: {float(outputs['confidence_score'][0][0]):.4f}")
    if 'epistemic_uncertainty' in outputs:
        print(f"Epistemic uncertainty: {float(outputs['epistemic_uncertainty'][0][0]):.4f}")
    if 'aleatoric_uncertainty' in outputs:
        print(f"Aleatoric uncertainty: {float(outputs['aleatoric_uncertainty'][0][0]):.4f}")
    print("=" * 50)

    return outputs


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Test model phan loai u nao voi 1 file don le")
    parser.add_argument("--model", required=True, help="Duong dan file model .keras")
    parser.add_argument("--input", required=True, help="Duong dan file .h5 hoac anh (.png/.jpg) can test")
    args = parser.parse_args()

    run_inference(args.model, args.input)