diff --git a/CTAI_flask/app.py b/CTAI_flask/app.py new file mode 100644 index 0000000..f491dad --- /dev/null +++ b/CTAI_flask/app.py @@ -0,0 +1,107 @@ +import datetime +import logging as rel_log +import os +import shutil +from datetime import timedelta + +import torch +from flask import * + +import core.main +import core.net.unet as net + +UPLOAD_FOLDER = r'./uploads' + +ALLOWED_EXTENSIONS = set(['dcm']) +app = Flask(__name__) +app.secret_key = 'secret!' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + +werkzeug_logger = rel_log.getLogger('werkzeug') +werkzeug_logger.setLevel(rel_log.ERROR) + +# 解决缓存刷新问题 +app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(seconds=1) + + +# 添加header解决跨域 +@app.after_request +def after_request(response): + response.headers['Access-Control-Allow-Origin'] = '*' + response.headers['Access-Control-Allow-Credentials'] = 'true' + response.headers['Access-Control-Allow-Methods'] = 'POST' + response.headers['Access-Control-Allow-Headers'] = 'Content-Type, X-Requested-With' + return response + + +def allowed_file(filename): + return '.' in filename and filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS + + +@app.route('/') +def hello_world(): + return redirect(url_for('static', filename='./index.html')) + + +@app.route('/upload', methods=['GET', 'POST']) +def upload_file(): + file = request.files['file'] + print(datetime.datetime.now(), file.filename) + if file and allowed_file(file.filename): + src_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename) + file.save(src_path) + shutil.copy(src_path, './tmp/ct') + image_path = os.path.join('./tmp/ct', file.filename) + # print(image_path) + pid, image_info = core.main.c_main(image_path, current_app.model) + # return jsonify({'status': 1, + # 'image_url': 'http://127.0.0.1:5003/tmp/image/' + pid, + # 'draw_url': 'http://127.0.0.1:5003/tmp/draw/' + pid, + # 'image_info': image_info + # }) + + return jsonify({'status': 1, + 'image_url': 'http://58.87.66.50:5003/tmp/image/' + pid, + 'draw_url': 'http://58.87.66.50:5003/tmp/draw/' + pid, + 'image_info': image_info + }) + return jsonify({'status': 0}) + + +@app.route("/download", methods=['GET']) +def download_file(): + # 需要知道2个参数, 第1个参数是本地目录的path, 第2个参数是文件名(带扩展名) + return send_from_directory('data', 'testfile.zip', as_attachment=True) + + +# show photo +@app.route('/tmp/', methods=['GET']) +def show_photo(file): + # print(file) + if request.method == 'GET': + if file is None: + pass + else: + image_data = open(f'tmp/{file}', "rb").read() + response = make_response(image_data) + response.headers['Content-Type'] = 'image/png' + return response + else: + pass + + +def init_model(): + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model = net.Unet(1, 1).to(device) + if torch.cuda.is_available(): + model.load_state_dict(torch.load("./core/net/model.pth")) + else: + model.load_state_dict(torch.load("./core/net/model.pth", map_location='cpu')) + model.eval() + return model + + +if __name__ == '__main__': + with app.app_context(): + current_app.model = init_model() + app.run(host='127.0.0.1', port=5003, debug=True) diff --git a/CTAI_flask/1 b/CTAI_flask/core/__init__.py similarity index 100% rename from CTAI_flask/1 rename to CTAI_flask/core/__init__.py diff --git a/CTAI_flask/core/get_feature.py b/CTAI_flask/core/get_feature.py new file mode 100644 index 0000000..95757aa --- /dev/null +++ b/CTAI_flask/core/get_feature.py @@ -0,0 +1,200 @@ +import inspect + +import SimpleITK as sitk +import cv2 +import numpy as np +import pandas as pd +from numba import jit + +np.set_printoptions(suppress=True) # 输出时禁止科学表示法,直接输出小数值 + +column_all_c = ['面积', '周长', '重心x', '重心y', '似圆度', '灰度均值', '灰度方差', '灰度偏度', + '灰度峰态', '小梯度优势', '大梯度优势', '灰度分布不均匀性', '梯度分布不均匀性', '能量', '灰度平均', '梯度平均', + '灰度均方差', '梯度均方差', '相关', '灰度熵', '梯度熵', '混合熵', '惯性', '逆差矩'] + +features_list = ['area', 'perimeter', 'focus_x', 'focus_y', 'ellipse', 'mean', 'std', 'piandu', 'fengdu', + 'small_grads_dominance', + 'big_grads_dominance', 'gray_asymmetry', 'grads_asymmetry', 'energy', 'gray_mean', 'grads_mean', + 'gray_variance', 'grads_variance', 'corelation', 'gray_entropy', 'grads_entropy', 'entropy', 'inertia', + 'differ_moment'] + + +# 最后俩偏度 峰度 + + +# 获取变量的名 +def get_variable_name(variable): + callers_local_vars = inspect.currentframe().f_back.f_locals.items() + return [var_name for var_name, var_val in callers_local_vars if var_val is variable] + + +def glcm(img_gray, ngrad=16, ngray=16): + '''Gray Level-Gradient Co-occurrence Matrix,取归一化后的灰度值、梯度值分别为16、16''' + # 利用sobel算子分别计算x-y方向上的梯度值 + gsx = cv2.Sobel(img_gray, cv2.CV_64F, 1, 0, ksize=3) + gsy = cv2.Sobel(img_gray, cv2.CV_64F, 0, 1, ksize=3) + height, width = img_gray.shape + grad = (gsx ** 2 + gsy ** 2) ** 0.5 # 计算梯度值 + grad = np.asarray(1.0 * grad * (ngrad - 1) / grad.max(), dtype=np.int16) + gray = np.asarray(1.0 * img_gray * (ngray - 1) / img_gray.max(), dtype=np.int16) # 0-255变换为0-15 + gray_grad = np.zeros([ngray, ngrad]) # 灰度梯度共生矩阵 + for i in range(height): + for j in range(width): + gray_value = gray[i][j] + grad_value = grad[i][j] + gray_grad[gray_value][grad_value] += 1 + gray_grad = 1.0 * gray_grad / (height * width) # 归一化灰度梯度矩阵,减少计算量 + get_glcm_features(gray_grad) + + +@jit +def get_gray_feature(): + # 灰度特征提取算法 + hist = cv2.calcHist([image_ROI_uint8[index]], [0], None, [256], [0, 256]) + # 假的 还没用灰度直方图 + + c_features['mean'].append(np.mean(image_ROI[index])) + c_features['std'].append(np.std(image_ROI[index])) + + s = pd.Series(image_ROI[index]) + c_features['piandu'].append(s.skew()) + c_features['fengdu'].append(s.kurt()) + + +def get_glcm_features(mat): + '''根据灰度梯度共生矩阵计算纹理特征量,包括小梯度优势,大梯度优势,灰度分布不均匀性,梯度分布不均匀性,能量,灰度平均,梯度平均, + 灰度方差,梯度方差,相关,灰度熵,梯度熵,混合熵,惯性,逆差矩''' + sum_mat = mat.sum() + small_grads_dominance = big_grads_dominance = gray_asymmetry = grads_asymmetry = energy = gray_mean = grads_mean = 0 + gray_variance = grads_variance = corelation = gray_entropy = grads_entropy = entropy = inertia = differ_moment = 0 + for i in range(mat.shape[0]): + gray_variance_temp = 0 + for j in range(mat.shape[1]): + small_grads_dominance += mat[i][j] / ((j + 1) ** 2) + big_grads_dominance += mat[i][j] * j ** 2 + energy += mat[i][j] ** 2 + if mat[i].sum() != 0: + gray_entropy -= mat[i][j] * np.log(mat[i].sum()) + if mat[:, j].sum() != 0: + grads_entropy -= mat[i][j] * np.log(mat[:, j].sum()) + if mat[i][j] != 0: + entropy -= mat[i][j] * np.log(mat[i][j]) + inertia += (i - j) ** 2 * np.log(mat[i][j]) + differ_moment += mat[i][j] / (1 + (i - j) ** 2) + gray_variance_temp += mat[i][j] ** 0.5 + + gray_asymmetry += mat[i].sum() ** 2 + gray_mean += i * mat[i].sum() ** 2 + gray_variance += (i - gray_mean) ** 2 * gray_variance_temp + for j in range(mat.shape[1]): + grads_variance_temp = 0 + for i in range(mat.shape[0]): + grads_variance_temp += mat[i][j] ** 0.5 + grads_asymmetry += mat[:, j].sum() ** 2 + grads_mean += j * mat[:, j].sum() ** 2 + grads_variance += (j - grads_mean) ** 2 * grads_variance_temp + small_grads_dominance /= sum_mat + big_grads_dominance /= sum_mat + gray_asymmetry /= sum_mat + grads_asymmetry /= sum_mat + gray_variance = gray_variance ** 0.5 + grads_variance = grads_variance ** 0.5 + for i in range(mat.shape[0]): + for j in range(mat.shape[1]): + corelation += (i - gray_mean) * (j - grads_mean) * mat[i][j] + glgcm_features = [small_grads_dominance, big_grads_dominance, gray_asymmetry, grads_asymmetry, energy, gray_mean, + grads_mean, + gray_variance, grads_variance, corelation, gray_entropy, grads_entropy, entropy, inertia, + differ_moment] + for i in range(len(glgcm_features)): + t = get_variable_name(glgcm_features[i])[0] + c_features[t].append(np.round(glgcm_features[i], 4)) + + +def get_geometry_feature(): + # 形态特征 分割mask获得一些特征 + im2, contours, x = cv2.findContours(mask_array.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) + tarea = [] + tperimeter = [] + for c in contours: + # 生成矩 + try: + M = cv2.moments(c) + cx = int(M['m10'] / M['m00']) + cy = int(M['m01'] / M['m00']) + c_features['focus_x'].append(cx) + c_features['focus_y'].append(cy) + except: + print('error') + + # 椭圆拟合 + try: + (x, y), (MA, ma), angle = cv2.fitEllipse(c) + c_features['ellipse'].append((ma - MA)) + except: + continue + # 面积周长 + tarea.append(cv2.contourArea(c)) + tperimeter.append(cv2.arcLength(c, True)) + + # 将mask里的最大值追加 有黑洞 + try: + c_features['area'].append(max(tarea)) + c_features['perimeter'].append(round(max(tperimeter), 4)) + except: + print('area error') + + +# 提取肿瘤特征 +def get_feature(image, mask): + global w + global image_ROI_uint8, index, image_ROI_mini, image_ROI, mask_array + + mask_array = cv2.imread(mask, 0) + image = sitk.ReadImage(image) + image_arrary = sitk.GetArrayFromImage(image)[0, :, :] + # 映射到CT获得特征 + image_ROI = np.zeros(shape=image_arrary.shape) + index = np.nonzero(mask_array) + if not index[0].any(): + # c_features['no'] = True + return None + image_ROI[index] = image_arrary[index] + image_ROI_uint8 = np.uint8(image_ROI) + # 获得只有肿瘤的图片 + x, y, w, h = cv2.boundingRect(mask_array) + image_ROI_mini = np.uint8(image_arrary[y:y + h, x:x + w]) + w = image_ROI_mini + + # 灰度梯度共生矩阵提取纹理特征 + + get_geometry_feature() + + get_gray_feature() + + glcm(image_ROI_mini, 15, 15) + + return c_features + + +def main(pid): + global w + + person_id = pid + global c_features + c_features = {} + for i in range(len(features_list)): + c_features[features_list[i]] = [column_all_c[i]] + + get_feature(f'tmp/ct/{pid}.dcm', f'tmp/mask/{pid}_mask.png') + + for j in c_features: + if j == 'id': + continue + c_features[j][1] = np.round(np.mean(c_features[j][1]), 4) + + return c_features + + +if __name__ == '__main__': + main() diff --git a/CTAI_flask/core/main.py b/CTAI_flask/core/main.py new file mode 100644 index 0000000..1ad4ed7 --- /dev/null +++ b/CTAI_flask/core/main.py @@ -0,0 +1,15 @@ +from core import process, predict, get_feature + + +def c_main(path,model): + image_data = process.pre_process(path) + # print(image_data) + predict.predict(image_data,model) + process.last_process(image_data[1]) + image_info = get_feature.main(image_data[1]) + + return image_data[1] + '.png', image_info + + +if __name__ == '__main__': + pass diff --git a/CTAI_model/1 b/CTAI_flask/core/net/__init__.py similarity index 100% rename from CTAI_model/1 rename to CTAI_flask/core/net/__init__.py diff --git a/CTAI_flask/core/net/unet.py b/CTAI_flask/core/net/unet.py new file mode 100644 index 0000000..841c904 --- /dev/null +++ b/CTAI_flask/core/net/unet.py @@ -0,0 +1,68 @@ +import torch.nn as nn +import torch +from torch import autograd + +class DoubleConv(nn.Module): + def __init__(self, in_ch, out_ch): + super(DoubleConv, self).__init__() + self.conv = nn.Sequential( + nn.Conv2d(in_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True), + nn.Conv2d(out_ch, out_ch, 3, padding=1), + nn.BatchNorm2d(out_ch), + nn.ReLU(inplace=True) + ) + + def forward(self, input): + return self.conv(input) + + +class Unet(nn.Module): + def __init__(self,in_ch,out_ch): + super(Unet, self).__init__() + + self.conv1 = DoubleConv(in_ch, 64) + self.pool1 = nn.MaxPool2d(2) + self.conv2 = DoubleConv(64, 128) + self.pool2 = nn.MaxPool2d(2) + self.conv3 = DoubleConv(128, 256) + self.pool3 = nn.MaxPool2d(2) + self.conv4 = DoubleConv(256, 512) + self.pool4 = nn.MaxPool2d(2) + self.conv5 = DoubleConv(512, 1024) + self.up6 = nn.ConvTranspose2d(1024, 512, 2, stride=2) + self.conv6 = DoubleConv(1024, 512) + self.up7 = nn.ConvTranspose2d(512, 256, 2, stride=2) + self.conv7 = DoubleConv(512, 256) + self.up8 = nn.ConvTranspose2d(256, 128, 2, stride=2) + self.conv8 = DoubleConv(256, 128) + self.up9 = nn.ConvTranspose2d(128, 64, 2, stride=2) + self.conv9 = DoubleConv(128, 64) + self.conv10 = nn.Conv2d(64,out_ch, 1) + + def forward(self,x): + c1=self.conv1(x) + p1=self.pool1(c1) + c2=self.conv2(p1) + p2=self.pool2(c2) + c3=self.conv3(p2) + p3=self.pool3(c3) + c4=self.conv4(p3) + p4=self.pool4(c4) + c5=self.conv5(p4) + up_6= self.up6(c5) + merge6 = torch.cat([up_6, c4], dim=1) + c6=self.conv6(merge6) + up_7=self.up7(c6) + merge7 = torch.cat([up_7, c3], dim=1) + c7=self.conv7(merge7) + up_8=self.up8(c7) + merge8 = torch.cat([up_8, c2], dim=1) + c8=self.conv8(merge8) + up_9=self.up9(c8) + merge9=torch.cat([up_9,c1],dim=1) + c9=self.conv9(merge9) + c10=self.conv10(c9) + out = nn.Sigmoid()(c10) + return out diff --git a/CTAI_flask/core/predict.py b/CTAI_flask/core/predict.py new file mode 100644 index 0000000..ac2e484 --- /dev/null +++ b/CTAI_flask/core/predict.py @@ -0,0 +1,39 @@ +import os +import sys +import cv2 +import torch +import core.net.unet as net +import numpy as np + +os.environ["CUDA_VISIBLE_DEVICES"] = "0" +torch.set_num_threads(4) +device = torch.device("cuda" if torch.cuda.is_available() else "cpu") +torch.cuda.empty_cache() + +import os + +rate = 0.5 + + +def predict(dataset,model): + + # unet = torch.load('./core/0.5unet.pkl').to(device) + # torch.save(unet.state_dict(), "model_new.pth") + + global res, img_y, mask_arrary + with torch.no_grad(): + x = dataset[0][0].to(device) + file_name = dataset[1] + y = model(x) + img_y = torch.squeeze(y).cpu().numpy() + img_y[img_y >= rate] = 1 + img_y[img_y < rate] = 0 + img_y = img_y * 255 + cv2.imwrite(f'./tmp/mask/{file_name}_mask.png', img_y, + (cv2.IMWRITE_PNG_COMPRESSION, 0)) + + +if __name__ == '__main__': + # 写保存模型 + # train() + predict() diff --git a/CTAI_flask/core/process.py b/CTAI_flask/core/process.py new file mode 100644 index 0000000..9c233db --- /dev/null +++ b/CTAI_flask/core/process.py @@ -0,0 +1,50 @@ +import os + +import SimpleITK as sitk +import cv2 +import numpy as np +import torch + + +def data_in_one(inputdata): + if not inputdata.any(): + return inputdata + inputdata = (inputdata - inputdata.min()) / (inputdata.max() - inputdata.min()) + return inputdata + + +def pre_process(data_path): + global test_image, test_mask + image_list, mask_list, image_data, mask_data = [], [], [], [] + + image = sitk.ReadImage(data_path) + image_array = sitk.GetArrayFromImage(image) + + ROI_mask = np.zeros(shape=image_array.shape) + ROI_mask_mini = np.zeros(shape=(1, 160, 100)) + ROI_mask_mini[0] = image_array[0][270:430, 200:300] + ROI_mask_mini = data_in_one(ROI_mask_mini) + ROI_mask[0][270:430, 200:300] = ROI_mask_mini[0] + test_image = ROI_mask + image_tensor = torch.from_numpy(ROI_mask).float().unsqueeze(1) + # print(image_tensor.shape) + image_data.append(image_tensor) + file_name = os.path.split(data_path)[1].replace('.dcm', '') + + # 转为图片写入image文件夹 + + image_array = image_array.swapaxes(0, 2) + image_array = np.rot90(image_array, -1) + image_array = np.fliplr(image_array).squeeze() + # ret, image_array = cv2.threshold(image_array, 150, 255, cv2.THRESH_BINARY) + cv2.imwrite(f'./tmp/image/{file_name}.png', image_array, (cv2.IMWRITE_PNG_COMPRESSION, 0)) + + return image_data, file_name + + +def last_process(file_name): + image = cv2.imread(f'./tmp/image/{file_name}.png') + mask = cv2.imread(f'./tmp/mask/{file_name}_mask.png', 0) + thresh, contours, hierarchy = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + draw = cv2.drawContours(image, contours, -1, (0, 255, 0), 2) + cv2.imwrite(f'./tmp/draw/{file_name}.png', draw) diff --git a/CTAI_flask/data/testfile.zip b/CTAI_flask/data/testfile.zip new file mode 100644 index 0000000..e7f1cb3 Binary files /dev/null and b/CTAI_flask/data/testfile.zip differ diff --git a/CTAI_flask/requirements.txt b/CTAI_flask/requirements.txt new file mode 100644 index 0000000..a0205a1 --- /dev/null +++ b/CTAI_flask/requirements.txt @@ -0,0 +1,6 @@ +pandas==0.23.4 +Flask==1.1.1 +numpy==1.15.0 +SimpleITK==1.2.2 +numba==0.45.1 +torch==1.2.0 diff --git a/CTAI_flask/static/index.html b/CTAI_flask/static/index.html new file mode 100644 index 0000000..bf878ed --- /dev/null +++ b/CTAI_flask/static/index.html @@ -0,0 +1,17 @@ + + + + + Title + + + + +
+

+ +

+ +下载 + + \ No newline at end of file