Commit a5ac5797 authored by exp's avatar exp

同步--随访、配准、质控、检测、分类、分割

parents
Pipeline #491 canceled with stages
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import os
import sys
import glob
import logging
import numpy as np
import pandas as pd
from tqdm import tqdm
from collections import *
import SimpleITK as sitk
from form_process import *
logger = logging.getLogger()
fh = logging.FileHandler('./log/ClsFormProcessor.log',encoding='utf-8')
sh = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
fh.setFormatter(formatter)
sh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(sh)
logger.setLevel(10)
class ClsFormProcessor(object):
def __init__(self,**kwargs):
self.paths = kwargs.get('paths')
######################## 应该保证输入的多个表格格式一致
self.df = LoadDFs(self.paths)
self.type_kws = kwargs.get('type_kws') ########### 用于指定 指定类别的列名
self.type_cor_kws = kwargs.get('type_cor_kws')
self.form_save_path = kwargs.get('form_save_path')
self.uid_save_path = kwargs.get('uid_save_path')
self.data_base_path = kwargs.get('data_base_path') ########### 原图(dicom/nii.gz数据的存储路径)
self.folder_name = kwargs.get('folder_name')
self.bbox_kws = ['x1','x2','y1','y2','z1','z2']
self.iou_th = kwargs.get('iou_th',1.0)
self.data_kw = kwargs.get('data_kw',u'影像结果')
self.uid_kw = kwargs.get('uid_kw',u'序列编号')
self.task_id_kw = kwargs.get('task_id_kw',u'任务ID')
self.data_kw_convert = 'position'
def _getInfoMapDict(self):
'''
需要子类重写
'''
type_map_dict = {}
for kw_before,kw_after in zip(self.type_kws,self.type_cor_kws):
type_map_dict[kw_before] = kw_after
self.rename_dict = {
self.task_id_kw:'task_id',
self.uid_kw:'uid',
self.data_kw:'data',
}
self.rename_dict.update(type_map_dict)
################# update kw related info
def __removeChars(self,x):
x = str(x).replace('[','').replace(']','')
if x[-1]==',':
x = x[:-1]
return x
def __splitComma(self,x,idx):
return float(str(x).split(',')[idx])
def __GetSaveName(self):
self.task_ids = self.df[self.task_id_kw].drop_duplicates().values
self.task_ids.sort()
self.task_ids = 'ALGRESULT_'+'_'.join(list([str(val) for val in self.task_ids]))
def __call__(self):
self._getInfoMapDict()
self._FilterForm()
self._SplitForm()
self._CombineInfo()
self._RemoveOverlap()
self._ConverToPixelCoord()
self._saveResult()
def _FilterForm(self):
'''
需要子类重写
'''
return
def _SplitForm(self):
### 1. Extract useful info
'''
keep off bad tata
'''
self.__GetSaveName()
target_columns = self.rename_dict.keys()
target_columns = list(target_columns)
self.df = self.df[target_columns]
self.df = self.df.rename(index=str,columns=self.rename_dict)
self.df = self.df.assign(**self.df['data'].astype(str).apply(eval).apply(pd.Series))
target_columns_v2 = [self.rename_dict[key] for key in self.rename_dict.keys()] + ['position']
target_columns.append(self.data_kw_convert)
df1 = self.df[self.df[self.data_kw_convert].isnull()]
df2 = self.df[~self.df[self.data_kw_convert].isnull()]
if len(df1)>0:
df1[self.data_kw_convert] = df1['bounds']
self.df = df2
self.df = pd.concat([df1,df2],ignore_index=True)
self.df = self.df[target_columns_v2]
self.df[self.data_kw_convert] = self.df[self.data_kw_convert].apply(lambda x:self.__removeChars(x))
############# Split to bbox
for idx in range(len(self.bbox_kws)):
self.df[self.bbox_kws[idx]] = self.df[self.data_kw_convert].apply(lambda x:self.__splitComma(x,idx))
def _CombineInfo(self):
'''
necessary info
1. center_x,center_y,center_z
2. diameter_x,diameter_y,diameter_z
'''
self.df['center_x'] = (self.df['x1']+self.df['x2'])/2.0
self.df['center_y'] = (self.df['y1']+self.df['y2'])/2.0
self.df['center_z'] = (self.df['z1']+self.df['z2'])/2.0
self.df['diameter_x_mm'] = abs(self.df['x1']-self.df['x2'])
self.df['diameter_y_mm'] = abs(self.df['y1']-self.df['y2'])
self.df['diameter_z_mm'] = abs(self.df['z1']-self.df['z2'])
self.df['diameter_mm'] = self.df[['diameter_x_mm','diameter_y_mm','diameter_z_mm']].max(axis=1)
def __RemoveOverlapSingleCase(self,uid):
'''
目前的逻辑是只要当前病灶与其他病灶iou大于阈值,就舍弃这个数据
'''
map_dict = defaultdict(set)
df = self.df
indices = df[df['uid']==uid].index.tolist()
for idx_1 in indices:
bbox_1 = [float(val) for val in df.position[idx_1].split(',')]
for idx_2 in indices:
bbox_2 = [float(val) for val in df.position[idx_2].split(',')]
if idx_1 == idx_2:
map_dict[idx_1].add(idx_1)
continue
iou = bbox_iou(bbox_1,bbox_2)
if iou>self.iou_th:
map_dict[idx_1].add(idx_2)
valid_keys = [key for key in map_dict if len(map_dict[key])==1]
return valid_keys
def _RemoveOverlap(self):
uids = self.df['uid'].drop_duplicates().values
self.uids = uids
indices = []
pbar = tqdm(uids)
for uid in pbar:
case_indices = self.__RemoveOverlapSingleCase(uid)
indices += case_indices
self.df = self.df.take(indices)
self.df.reset_index(inplace=True)
logger.info('Number of records after remove overlap %d '%(len(self.df)))
def __converToPixelCoordSingleCase(self,uid):
image_path = '%s/%s.nii.gz'%(self.data_base_path,uid)
image_path = str(image_path)
records = []
if os.path.exists(image_path):
image = sitk.ReadImage(image_path)
indices = self.df[self.df['uid']==uid].index.tolist()
spacing = image.GetSpacing()
for idx in indices:
center_x,center_y,center_z = self.df.center_x[idx],self.df.center_y[idx],self.df.center_z[idx]
px_x,px_y,px_z = image.TransformPhysicalPointToContinuousIndex([float(val) for val in [center_x,center_y,center_z]])
current_record = [image_path] +list((spacing))+ [px_x,px_y,px_z] + [center_x,center_y,center_z]
records.append(current_record)
return records
def _ConverToPixelCoord(self):
all_records = []
pbar = tqdm(self.df['uid'].drop_duplicates().values)
for uid in pbar:
case_records = self.__converToPixelCoordSingleCase(uid)
if len(case_records)==0:
continue
all_records += case_records
df0 = pd.DataFrame(all_records,columns=['image_path','spac_x','spac_y','spac_z','coordX','coordY','coordZ',
'center_x','center_y','center_z'])
self.df[['center_x','center_y','center_z']] = self.df[['center_x','center_y','center_z']].astype('float').round(2)
df0[['center_x','center_y','center_z']] = df0[['center_x','center_y','center_z']].astype('float').round(2)
self.df = self.df.merge(df0,on=['center_x','center_y','center_z'])
def _saveResult(self):
form_save_base_path = '%s/%s/'%(self.form_save_path,self.folder_name)
uid_save_base_path = '%s/%s/'%(self.uid_save_path,self.folder_name)
form_save_path = '%s/%s.csv'%(form_save_base_path,self.task_ids)
uid_save_path = '%s/%s.txt'%(uid_save_base_path,self.task_ids)
if not os.path.exists(form_save_base_path):
os.makedirs(form_save_base_path)
if not os.path.exists(uid_save_base_path):
os.makedirs(uid_save_base_path)
self.df.to_csv(form_save_path,index=False)
uid = self.df['uid'].drop_duplicates().values
WriteTxt(self.uids,uid_save_path)
if __name__ == "__main__":
paths = ['/fileser/xupl/cls_data_preprocess/NoduleSur/20200710/CZ_LobuSameResult.csv']
type_kws = [u'结节表征1',u'结节表征2',u'结节表征3-是否分叶',u'结节表征4-胸膜是否凹陷',u'结节表征5-卫星灶',
u'结节表征6-空泡',u'结节表征7-囊状多选']
type_cor_kws = ['regular','smooth','lobulated','p_sunken','satelliteFocal','Vacuolus','sacciform']
form_save_path = '/fileser/xupl/cls_data_preprocess/NoduleSur/preprocess/label'
uid_save_path = '/fileser/xupl/cls_data_preprocess/NoduleSur/preprocess/uid'
data_base_path = '/fileser/DATA/IMAGE/SOURCE/PROXIMA/RAW_NII'
folder_name = '20210107_test'
iou_th = 1.0
data_kw = u'影像结果'
uid_kw = u'序列号'
param_list = {
'paths':paths,
'type_kws':type_kws,
'type_cor_kws':type_cor_kws,
'form_save_path':form_save_path,
'uid_save_path':uid_save_path,
'data_base_path':data_base_path,
'folder_name':folder_name,
'iou_th':iou_th,
'data_kw':data_kw,
'uid_kw':uid_kw
}
processor = ClsFormProcessor(**param_list)
processor()
\ No newline at end of file
import os
import sys
import glob
import numpy as np
import pandas as pd
import SimpleITK as sitk
from tqdm import tqdm
from form_process import *
from image_process import *
class ClsPatchCrop(object):
def __init__(self,**kwargs):
self.df = LoadDFs(kwargs.get('paths'))
self.spacing = kwargs.get('spacing',None)
self.patch_size = kwargs.get('patch_size',96)
self.uid_save_paths = kwargs.get('uid_save_paths')
self.base_path = kwargs.get('base_path')
self.folder_name = kwargs.get('folder_name')
self.modes = kwargs.get('kws')
self.split_ratios = kwargs.get('split_ratios')
self.image_base_path = kwargs.get('image_base_path','/fileser/CT_RIB/data/image/res0/')
self.coord_kws = kwargs.get('coord_kws',['image_path','coordZ','coordY','coordX','diameter_z','diameter_y','diameter_x','diameter'])
self.type_kws = kwargs.get('type_kws',['frac_type','poroma'])
self.spacing_kws = kwargs.get('spacing_kws')
print (' self.uid_save_paths ', self.uid_save_paths ,self.folder_name)
def __call__(self):
self._GeneratePath()
self._SplitDataset()
self._GenerateData()
def _GeneratePath(self):
'''
用于生成 uid存储路径和图像存储路径
'''
self.uid_path = os.path.join(self.uid_save_paths,self.folder_name)
if not os.path.exists(self.uid_path):
os.makedirs(self.uid_path)
self.data_save_base_path = os.path.join(self.base_path,self.folder_name)
if not os.path.exists(self.data_save_base_path):
os.makedirs(self.data_save_base_path)
self.uid_save_paths = [os.path.join(self.uid_path,'%s_uid.txt'%val) for val in self.modes]
def _imagePathReplace(self,df):
if 'image_path' in df.columns:
image_paths = df['image_path'].drop_duplicates().values
map_dict = {}
for path in image_paths:
filename = path.split('/')[-1]
new_path = '%s/%s'%(self.image_base_path,filename)
map_dict[path] = new_path
df['image_path'] = df['image_path'].replace(map_dict)
else:
map_dict = {}
uids = df['uid'].drop_duplicates().values
for uid in uids:
new_path = '%s/%s.nii.gz'%(self.image_base_path,uid)
map_dict[uid] = new_path
df['image_path'] = df['uid'].replace(map_dict)
return df
def _GenerateSingleRecord(self,df,uid, id=None):
if id is not None:
print(id)
self._imagePathReplace(df)
pad_val = -1024.0
output_dim = [self.patch_size for _ in range(3)]
spacing=[self.spacing for _ in range(3)]
result = GeneratePatchFromDataFrame(uid,df,output_dim,self.coord_kws,self.type_kws,pad_val,spacing=spacing,kw1=self.spacing_kws[0],kw2=self.spacing_kws[1])
data_list,info_list = result[:2]
return data_list,info_list
def _GenerateDataNoMulti(self,df):
uids = df['uid'].drop_duplicates().values
pbar = tqdm(uids)
final_data_list,final_info_list = 0,0
for uid in pbar:
current_df = df[df['uid']==uid]
current_df.reset_index(inplace=True)
data_list,info_list = self._GenerateSingleRecord(current_df,uid)
if data_list is None:
continue
try:
if(type(final_info_list)==int):
final_data_list = np.array(data_list)
final_info_list = np.array(info_list)
else:
final_data_list = np.concatenate([final_data_list,data_list])
final_info_list = np.concatenate([final_info_list,info_list])
except:
continue
return final_data_list,final_info_list
def _GenerateDataWithMulti(self, df):
from multiprocessing import Pool
pool = Pool(30)
uids = df['uid'].drop_duplicates().values
final_data_list, final_info_list = 0, 0
pool_returns = []
for id, uid in enumerate(uids):
current_df = df[df['uid'] == uid]
current_df.reset_index(inplace=True)
# data_list, info_list = self._GenerateSingleRecord(current_df, uid)
pool_returns.append(pool.apply_async(self._GenerateSingleRecord, (current_df, uid, id)))
pool.close()
pool.join()
pbar = tqdm(pool_returns)
for pool_return in pbar:
data_list, info_list = pool_return.get()
if data_list is None:
continue
try:
if (type(final_info_list) == int):
final_data_list = np.array(data_list)
final_info_list = np.array(info_list)
else:
final_data_list = np.concatenate([final_data_list, data_list])
final_info_list = np.concatenate([final_info_list, info_list])
except:
continue
return final_data_list, final_info_list
def _GenerateData(self):
for idx in range(3):
df = self.dfs[idx]
if df is None:
continue
spacing = 'raw' if self.spacing is None else self.spacing
img_path = '%s/%s_spacing_data_%s.npy'%(self.data_save_base_path,str(spacing),self.modes[idx])
info_path = '%s/%s_spacing_info_%s.npy'%(self.data_save_base_path,str(spacing),self.modes[idx])
data_list, info_list = self._GenerateDataWithMulti(df)
np.save(img_path,data_list)
np.save(info_path,info_list)
###################### np.save()
def _SplitDataset(self):
uids = self.df['uid'].drop_duplicates().values
uid_list = split_dataset(uids,self.split_ratios)
for current_uids in uid_list:
if current_uids is not None:
print ('='*60)
print ('length of current_uids %d'%(len(current_uids)))
print ('='*60)
for idx in range(len(uid_list)):
if uid_list[idx] is not None:
WriteTxt(uid_list[idx],self.uid_save_paths[idx])
self.train_uids,self.val_uids,self.test_uids = uid_list
self.train_df = self.df[self.df['uid'].isin(self.train_uids)]
if self.val_uids is not None:
self.val_df = self.df[self.df['uid'].isin(self.val_uids)]
if self.test_uids is not None:
self.test_df = self.df[self.df['uid'].isin(self.test_uids)]
else:
self.test_df = None
else:
self.val_df,self.test_df = None,None
self.dfs = [self.train_df,self.val_df,self.test_df]
if __name__=="__main__":
json_path = "./files/NoduleCls/ForDataPrep/prep/20210107Test/para.json"
params = ReadJson(json_path)
for str_kw in ['kws','coord_kws','type_kws','spacing_kws']:
params[str_kw] = eval(params[str_kw])
data_path = params['csv_path']
data_json = ReadJson(data_path)
paths = [record['df_path'] for record in data_json]
params['paths'] = paths
print ('params is ',params)
patch_cropper = ClsPatchCrop(**params)
patch_cropper()
\ No newline at end of file
import os
import sys
import glob
import numpy as np
import pandas as pd
from tqdm import tqdm
import logging
from ClsFormProcessor import ClsFormProcessor
logger = logging.getLogger()
fh = logging.FileHandler('./log/RibFormProcessor.log',encoding='utf-8')
sh = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
fh.setFormatter(formatter)
sh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(sh)
logger.setLevel(10)
class RibFormProcessor(ClsFormProcessor):
def __init__(self,**kwargs):
ClsFormProcessor.__init__(self,**kwargs)
self.position_type_kw = u'骨折部位' ##### target val is 3
self.frac_flag_kw = u'是否骨折' ##### target val is 1
self.keep_flag_kw = u'是否保留' ####### target val is 0
self.target_organs = [3]
def _getInfoMapDict(self):
'''
需要子类重写
'''
type_map_dict = {}
for kw_before,kw_after in zip(self.type_kws,self.type_cor_kws):
type_map_dict[kw_before] = kw_after
self.rename_dict = {
self.task_id_kw:'task_id',
self.uid_kw:'uid',
self.data_kw:'data',
self.position_type_kw:'organ',
self.frac_flag_kw:'frac_flag',
self.keep_flag_kw:'keep_flag'
}
self.rename_dict.update(type_map_dict)
################# update kw related info
def _FilterForm(self):
print ('='*60)
print (self.df.columns)
self.df = self.df[(self.df[self.frac_flag_kw]==1)&(self.df[self.keep_flag_kw]==0)]
self.df.reset_index(drop=True,inplace=True)
logger.info('Number of records before filter %d '%(len(self.df)))
self.df = self.df[self.df[self.position_type_kw].isin(self.target_organs)]
self.df.reset_index(drop=True,inplace=True)
logger.info('Number of records after filter %d '%(len(self.df)))
if __name__ == "__main__":
paths = ['/fileser/zrx/Rib/infos/20201026/Rib_result_TASK_3813_3814_3815.csv']
type_kws = [u'骨痂(v2)',u'骨折类型']
type_cor_kws = ['poroma','frac_type']
form_save_path = '/fileser/xupl/cls_data_preprocess/RibCls/label'
uid_save_path = '/fileser/xupl/cls_data_preprocess/RibCls/info/uid'
data_base_path = '/fileser/CT_RIB/data/image/res0/'
folder_name = '20210107_test'
iou_th = 0.0
param_list = {
'paths':paths,
'type_kws':type_kws,
'type_cor_kws':type_cor_kws,
'form_save_path':form_save_path,
'uid_save_path':uid_save_path,
'data_base_path':data_base_path,
'folder_name':folder_name,
'iou_th':iou_th
}
processor = RibFormProcessor(**param_list)
processor()
import os
import sys
import json
import glob
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
def bbox_iou(point1,point2):
z,y,x,dz,dy,dx = point1
z1,y1,x1,dz1,dy1,dx1 = point2
z_min,z_max = z-dz/2.0,z+dz/2.0
y_min,y_max = y-dy/2.0,y+dy/2.0
x_min,x_max = x-dx/2.0,x+dx/2.0
z_min_1,z_max_1 = z1-dz1/2.0,z1+dz1/2.0
y_min_1,y_max_1 = y1-dy1/2.0,y1+dy1/2.0
x_min_1,x_max_1 = x1-dx1/2.0,x1+dx1/2.0
####### calculate intersection region
z_min_max,z_max_min = max(z_min,z_min_1),min(z_max,z_max_1)
y_min_max,y_max_min = max(y_min,y_min_1),min(y_max,y_max_1)
x_min_max,x_max_min = max(x_min,x_min_1),min(x_max,x_max_1)
depth_inter = z_max_min - z_min_max
height_inter = y_max_min - y_min_max
width_inter = x_max_min - x_min_max
iou_val = 0
if ((depth_inter<0) or(height_inter<0) or (width_inter<0)):
return iou_val
intersection = (depth_inter*height_inter*width_inter)
cubaA = dz*dy*dx
cubaB = dz1*dy1*dx1
union = cubaA+cubaB-intersection
return intersection/float(union)
def split_dataset(uids,split_ratios):
'''
根据比例将uid进行分组,返回分组后的uid信息
'''
if len(split_ratios)==0 or split_ratios[0]==1:
return [uids,None,None]
else:
train_uids,val_uids = train_test_split(uids, test_size=split_ratios[0], random_state=42)
if len(split_ratios)==1 or split_ratios[1]==1:
return [train_uids,val_uids,None]
else:
val_uids,test_uids = train_test_split(val_uids, test_size=split_ratios[1], random_state=42)
return [train_uids,val_uids,test_uids]
def WriteTxt(info,path):
with open(path,'w') as f:
for record in info:
f.write(record+'\n')
f.close()
def ReadTxt(path):
with open(path,'r') as f:
result = f.readlines()
f.close()
return [x.strip() for x in result]
def ReadCSV(path):
if 'csv' in path:
df = pd.read_csv(path,encoding='utf_8_sig')
return df
elif 'xlsx' in path:
df = pd.read_excel(path,encoding='utf_8_sig')
return df
def LoadDFs(paths):
result = []
for path in paths:
if ('csv' not in path) and ('xlsx' not in path):
continue
df = ReadCSV(path)
result.append(df)
if len(result)>0:
return pd.concat(result,ignore_index=True)
def ReadJson(path):
f = open(path)
content = f.read()
result = json.loads(content)
return result
import os
import sys
import glob
import SimpleITK as sitk
def ReadImageITK(path):
series_IDs = sitk.ImageSeriesReader.GetGDCMSeriesIDs(path)
nb_series = len(series_IDs)
print(nb_series)
series_file_names = sitk.ImageSeriesReader.GetGDCMSeriesFileNames(path, series_IDs[0])
series_reader = sitk.ImageSeriesReader()
series_reader.SetFileNames(series_file_names)
image3D = series_reader.Execute()
return image3D,sitk.GetArrayFromImage(image3D)
def loadImage(path):
if os.path.isdir(path):
img = ReadImageITK(path)
else:
img_itk = sitk.ReadImage(path)
return img_itk,sitk.GetArrayFromImage(img_itk)
def WriteMask(mask,origin=None,spacing=None,path=''):
if origin is None and spacing is None:
itk_mask = mask
else:
itk_mask = sitk.GetImageFromArray(mask)
itk_mask = sitk.Cast(itk_mask,sitk.sitkUInt8)
itk_mask.SetSpacing(spacing)
itk_mask.SetOrigin(origin)
print ('Saving img to %s '%path)
itkWriter = sitk.ImageFileWriter()
itkWriter.SetUseCompression(True)
itkWriter.SetFileName(path)
itkWriter.Execute(itk_mask)
\ No newline at end of file
import os
import sys
import glob
import numpy as np
import pandas as pd
import SimpleITK as sitk
from imageIO import loadImage
from respacing_func import *
from skimage.measure import *
def get_bbox(image,val=None):
if val is None:
points = np.where(image>0)
else:
points = np.where(image==val)
bbox = []
for idx in range(len(points)):
if len(points[idx])>0:
min_val,max_val = np.amin(points[idx]),np.amax(points[idx])
bbox += [min_val,max_val]
return bbox
def PadImage(image,offset,pad_val=0):
'''
在image两侧都pad(offset)大小
'''
image = np.squeeze(image)
image_shape = image.shape
new_image_shape = [image_shape[idx] + offset[idx] * 2 for idx in range(3)]
temp_image = np.ones(new_image_shape)*pad_val
margin_0,margin_1,margin_2 = offset
depth,height,width = image_shape
temp_image[margin_0:margin_0+depth,margin_1:margin_1+height,margin_2:margin_2+width] = image
return temp_image
def CropPatch(image,center,offset):
'''
以center为中心,在image裁取2倍offset大小的的patch
'''
center_z,center_y,center_x = [int(round(float(val))) for val in center]
offset_z,offset_y,offset_x = [int(round(float(val))) for val in offset]
current_patch = image[center_z-offset_z:center_z+offset_z,
center_y-offset_y:center_y+offset_y,
center_x-offset_x:center_x+offset_x]
return current_patch
def GeneratePatchFromDataFrame(uid,df,output_dim,coord_kws,type_kws,pad_val=0.0,mode='image',crop_mode='mask',spacing=None,kw1=None,kw2=None):
'''
input:
uid
df: dataframe
output_dim:patch的大小
info_kws:按照顺序包含[图像路径,中心点(3),长径(4),征象]信息
'''
data_list,info_list,mask_list = [],[],[]
target_df = df[df['uid']==uid]
target_df.reset_index(drop=True,inplace=True)
info_kws = list(coord_kws)+list(type_kws)
records = target_df[info_kws].values
if len(records)==0:
return None,None
image_path = records[0][0]
if not os.path.exists(image_path):
return None,None
# sub_str = str(spacing[0])
image_itk_ori,image_ori = loadImage(image_path)
if spacing is not None:
# target_image_path = image_path.replace('RAW_NII','SPACING_%.1f_NII'%spacing[0])
target_image_path = image_path.replace(kw1,kw2)
# print ('='*60)
print ('target_image_path is ',target_image_path)
if os.path.exists(target_image_path):
image_itk,image = loadImage(target_image_path)
else:
respacing_result = LinearResample(image_itk,spacing)
image_itk = respacing_result[0]
image = sitk.GetArrayFromImage(image_itk)
else:
image_itk,image = image_itk_ori,image_ori
image_spacing = image_itk.GetSpacing()[::-1]
offset = [int(x/2) for x in output_dim]
pad_img = PadImage(image,offset,pad_val)
min_val,max_val,mean_val,std_val = np.amin(image),np.amax(image),np.mean(image),np.std(image)
for record in target_df[info_kws].values:
image_path,z,y,x = record[:4]
point_px = [int(round(float(val))) for val in [z,y,x]]
type_infos = list(record[len(coord_kws):])
pad_mask = None
if mode=='seg':
mask_path = type_infos[0]
print ('mask_path is',mask_path)
mask_itk,mask = loadImage(mask_path)
mask = np.array(mask>0).astype(np.uint8)
if crop_mode=='mask':
mask_centroid = regionprops(label(mask))[0].centroid
mask_centroid = [int(round(float(val))) for val in mask_centroid]
point_px = mask_centroid
if spacing is not None:
mask_itk = NearestResample(mask_itk,spacing)
mask = sitk.GetArrayFromImage(mask_itk)
pad_mask = PadImage(mask,offset,0)
if spacing is not None:
point_phy = image_itk_ori.TransformContinuousIndexToPhysicalPoint(point_px[::-1])
point_px = image_itk.TransformPhysicalPointToContinuousIndex(point_phy)[::-1]
new_center = [point_px[idx]+offset[idx] for idx in range(3)]
current_patch = CropPatch(pad_img,new_center,offset)
mask_patch = None
if mode=='seg':
mask_patch = CropPatch(pad_mask,new_center,offset)
data_list.append(current_patch)
current_info = list(record[:8])+list(image_spacing)+[min_val,max_val,mean_val,std_val]+type_infos
info_list.append(current_info)
if mask_patch is not None:
mask_list.append(mask_patch)
if len(mask_list)==0:
mask_list = None
return np.array(data_list),np.array(info_list),mask_list
import os
import sys
import glob
import math
import numpy as np
import pandas as pd
import SimpleITK as sitk
def npyBsplineResample(npyImage, inSpacing, outSpacing, interpolator='linear', order=3):
itkImage = sitk.GetImageFromArray(npyImage)
itkImage.SetSpacing(inSpacing)
resampler = sitk.ResampleImageFilter()
resampler.SetOutputSpacing(outSpacing)
resampler.SetDefaultPixelValue(0)
resampler.SetInterpolator(sitk.sitkBSpline)
identity = sitk.Transform(itkImage.GetDimension(), sitk.sitkIdentity)
resampler.SetTransform(identity)
inSize = np.array(itkImage.GetSize(), dtype=np.int)
inSpacing = itkImage.GetSpacing()
outSize = inSize * (np.array(inSpacing) / np.array(outSpacing))
outSize = outSize.astype(np.int) # Image dimensions are in integers
outSize = [int(s) for s in outSize]
resampler.SetSize(outSize)
outImage = resampler.Execute(itkImage)
outImage = sitk.GetArrayFromImage(outImage)
return outImage
def NearestResample(itk_image, outputspacing,outsize=None):
outputspacing = [float(val) for val in outputspacing]
image = itk_image
#image = image.astype(dtype=float)
size = image.GetSize()
#print(size)
spacing = image.GetSpacing()
transform = sitk.Transform()
transform.SetIdentity()
resampler = sitk.ResampleImageFilter()
resampler.SetInterpolator(sitk.sitkNearestNeighbor)
resampler.SetDefaultPixelValue(0)
resampler.SetTransform(transform)
resampler.SetOutputSpacing(outputspacing)
resampler.SetOutputDirection(image.GetDirection())
resampler.SetOutputOrigin(image.GetOrigin())
if outsize is None:
outsize = (int(math.ceil(size[0]*spacing[0]/outputspacing[0])),\
int(math.ceil(size[1]*spacing[1]/outputspacing[1])),\
int(math.ceil(size[2]*spacing[2]/outputspacing[2])))
#print(outsize)
resampler.SetSize(outsize)
outimgsitk = resampler.Execute(image)
return outimgsitk#,outsize,ori,dirction
def LinearResample(image, outputspacing):
#image = image.astype(dtype=float)
# print ('enter',type(image))
OriginalSize = image.GetSize()
# print(OriginalSize)
OriginalSpacing = image.GetSpacing()
# print(OriginalSpacing)
#ori = image.GetOrigin()
#dirction = image.GetDirection()
transform = sitk.Transform()
transform.SetIdentity()
resampler = sitk.ResampleImageFilter()
resampler.SetInterpolator(sitk.sitkLinear)
resampler.SetDefaultPixelValue(0)
resampler.SetTransform(transform)
resampler.SetOutputSpacing(outputspacing)
resampler.SetOutputDirection(image.GetDirection())
resampler.SetOutputOrigin(image.GetOrigin())
outsize = (int(math.ceil(OriginalSize[0]*OriginalSpacing[0]/outputspacing[0])),\
int(math.ceil(OriginalSize[1]*OriginalSpacing[1]/outputspacing[1])),\
int(math.ceil(OriginalSize[2]*OriginalSpacing[2]/outputspacing[2])))
resampler.SetSize(outsize)
outimgsitk = resampler.Execute(image)
#writer = sitk.ImageFileWriter()
#writer.SetFileName("D:/Data/LUNG/fissure/chenbotian/Resample.nii")
#writer.Execute(outimgsitk)
return outimgsitk,OriginalSpacing, OriginalSize#, ori, dirction
\ No newline at end of file
import torch
import torch.nn as nn
import torch.nn.functional as F
from collections import OrderedDict
def weight_init(m):
if isinstance(m, nn.Linear):
nn.init.xavier_normal_(m.weight)
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Conv3d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm3d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
class ConvStage(nn.Module):
def __init__(self, **kwargs):
super().__init__()
self.negative_slop = kwargs.get('negative_slop', 0)
self.norm_choice = kwargs.get('norm_choice', 'BN')
self.num_in_features = kwargs.get('num_in_features')
self.num_out_features = kwargs.get('num_out_features')
self.kernel_size = kwargs.get('kernel_size', 3)
self.repeat_times = kwargs.get('repeat_times', 3)
self.func_list = {}
for idx in range(self.repeat_times):
if idx == 0:
current_func = ConvBlock(negative_slop=self.negative_slop, norm_choice=self.norm_choice,
num_in_features=self.num_in_features,
num_out_features=self.num_out_features, kernel_size=self.kernel_size)
else:
current_func = ConvBlock(negative_slop=self.negative_slop, norm_choice=self.norm_choice,
num_in_features=self.num_out_features,
num_out_features=self.num_out_features, kernel_size=self.kernel_size)
self.func_list['convBlock_%d' % idx] = current_func
self.convStage_func = nn.Sequential(OrderedDict([(key, self.func_list[key]) for key in self.func_list.keys()]))
def forward(self, x):
output = x
return self.convStage_func(x)
class ConvBlock(nn.Module):
def __init__(self, **kwargs):
super().__init__()
self.negative_slop = kwargs.get('negative_slop', 0)
self.norm_choice = kwargs.get('norm_choice', 'BN')
self.num_in_features = kwargs.get('num_in_features')
self.num_out_features = kwargs.get('num_out_features')
self.kernel_size = kwargs.get('kernel_size', 3)
self.conv = nn.Conv3d(in_channels=self.num_in_features, out_channels=self.num_out_features,
kernel_size=self.kernel_size, padding=[int(self.kernel_size / 2) for _ in range(3)])
self.post_func = BNReLU(negative_slop=self.negative_slop, norm_choice=self.norm_choice,
num_features=self.num_out_features)
def forward(self, x):
output = x
output = self.conv(output)
output = self.post_func(output)
return output
class BNReLU(nn.Module):
def __init__(self, **kwargs):
super().__init__()
self.negative_slop = kwargs.get('negative_slop', 0)
self.norm_choice = kwargs.get('norm_choice', 'BN')
self.num_features = kwargs.get('num_features')
if self.negative_slop <= 0:
self.relu = nn.ReLU()
else:
self.relu = nn.LeakyReLU(negative_slope=self.negative_slop)
if self.norm_choice == 'IN':
self.norm = nn.InstanceNorm3d(num_features=self.num_features)
else:
self.norm = nn.BatchNorm3d(num_features=self.num_features)
def forward(self, x):
x = self.norm(x)
x = self.relu(x)
return x
class ConvFunc3D(nn.Module):
def __init__(self, **kwargs):
super().__init__()
self.num_in_features = kwargs.get('num_in_features')
self.num_out_features = kwargs.get('num_out_features')
self.stride = kwargs.get('stride', 1)
self.conv_choice = kwargs.get('conv_choice', 'basic')
self.kernel_size = kwargs.get('kernel_size', 1)
if self.conv_choice == 'basic' or self.kernel_size == 1:
if self.kernel_size == 1:
self.conv = nn.Conv3d(in_channels=self.num_in_features, out_channels=self.num_out_features,
kernel_size=self.kernel_size,
stride=self.stride)
else:
self.conv = nn.Conv3d(in_channels=self.num_in_features, out_channels=self.num_out_features,
kernel_size=self.kernel_size,
stride=self.stride, padding=[int(self.kernel_size / 2) for _ in range(3)])
else:
self.conv = nn.Sequential(
nn.Conv3d(in_channels=self.num_in_features, out_channels=self.num_in_features,
kernel_size=self.kernel_size,
stride=self.stride, padding=[int(self.kernel_size / 2) for _ in range(3)],
groups=self.num_in_features),
nn.Conv3d(in_channels=self.num_in_features, out_channels=self.num_out_features, kernel_size=1)
)
def forward(self, x):
return self.conv(x)
class BasicBlock(nn.Module):
expansion = 1
def __init__(self, **kwargs):
super().__init__()
# self.expansion = 4
self.negative_slope = kwargs.get('negative_slop', 0)
self.norm_choice = kwargs.get('norm_choice', 'BN')
self.num_in_features = kwargs.get('num_in_features')
self.num_out_features = kwargs.get('num_out_features')
self.kernel_size = kwargs.get('kernel_size', 3)
self.stride = kwargs.get('stride', 1)
self.downsample = kwargs.get('downsample', None)
self.conv_choice = kwargs.get('conv_choice', 'basic')
self.conv1 = ConvFunc3D(num_in_features=self.num_in_features, num_out_features=self.num_out_features,
conv_choice=self.conv_choice)
self.bn1 = BNReLU(negative_slop=self.negative_slope, norm_choice=self.norm_choice,
num_features=self.num_out_features)
self.conv2 = ConvFunc3D(num_in_features=self.num_out_features, num_out_features=self.num_out_features,
stride=self.stride, kernel_size=self.kernel_size, conv_choice=self.conv_choice)
self.bn2 = BNReLU(negative_slop=self.negative_slope, norm_choice=self.norm_choice,
num_features=self.num_out_features)
self.conv3 = ConvFunc3D(num_in_features=self.num_out_features,
num_out_features=self.num_out_features * self.expansion, conv_choice=self.conv_choice)
self.bn3 = BNReLU(negative_slop=self.negative_slope, norm_choice=self.norm_choice,
num_features=self.num_out_features * self.expansion)
self.relu = nn.LeakyReLU(negative_slope=self.negative_slope)
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
out = self.relu(out)
out = self.conv3(out)
out = self.bn3(out)
if self.downsample is not None:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class DarkNetShortCut(nn.Module):
def __init__(self, **kwargs):
super().__init__()
def forward(self, x, y):
if x.shape[1] == y.shape[1]:
return x + y
elif x.shape[1] < y.shape[1]:
y[:x.shape[1]] += x.shape[1]
return y
else:
x[:y.shape[1]] += y.shape[1]
return x
class SCIModule(nn.Module):
def __init__(self, **kwargs):
super().__init__()
n_input_channel = kwargs.get('n_input_channel')
n_output_channel = kwargs.get('n_output_channel')
self.conv_choice = kwargs.get('conv_choice')
self.kernel_size = kwargs.get('kernel_size', 3)
self.softmax = nn.Softmax(dim=2)
self.conv_func = ConvBlock(num_in_features=n_input_channel, num_out_features=n_output_channel,
negative_slop=0.05,
kernel_size=self.kernel_size)
self.conv_func1x1 = ConvBlock(num_in_features=n_input_channel, num_out_features=n_output_channel,
negative_slop=0.05,
kernel_size=1)
def forward(self, x):
'''
1. reshape
2.
'''
batch_size, channel, depth, height, width = x.shape
x0 = x.view((batch_size, channel, -1))
x_t = torch.transpose(x0, 1, 2)
weights = torch.zeros((batch_size, channel, channel)).cuda()
for slice_idx in range(batch_size):
weights[slice_idx] = -torch.matmul(x0[slice_idx], x_t[slice_idx])
norm_weights = self.softmax(weights)
weighted_feature = torch.zeros_like(x0)
for slice_idx in range(batch_size):
weighted_feature[slice_idx] = torch.matmul(norm_weights[slice_idx], x0[slice_idx])
weighted_feature = weighted_feature.view((batch_size, channel, depth, height, width))
output = self.conv_func(weighted_feature)
final_output = self.conv_func1x1(output + x)
return final_output
from collections import OrderedDict
import torch.nn.functional as F
from .BasicModules import *
class DarknetLayer(nn.Module):
def __init__(self,**kwargs):
super().__init__()
self.negative_slope = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.kernel_size = kwargs.get('kernel_size',3)
self.conv_choice = kwargs.get('conv_choice','basic')
num_in_features = kwargs.get('num_in_features',1)
self.num_in_features = num_in_features
self.conv0 = ConvBlock(negative_slope=self.negative_slope,norm_choice=self.norm_choice,num_in_features=num_in_features,
num_out_features=int(num_in_features/2),kernel_size=1)
self.conv1 = ConvBlock(negative_slope=self.negative_slope,norm_choice=self.norm_choice,num_in_features=int(num_in_features/2),
num_out_features=num_in_features,kernel_size=self.kernel_size)
self.shortcut = DarkNetShortCut()
def forward(self,x):
x0 = self.conv0(x)
x0 = self.conv1(x0)
x = self.shortcut(x0,x)
return x
class Darknet(nn.Module):
def __init__(self,**kwargs):
super().__init__()
self.block_inplanes = kwargs.get('block_inplanes')
self.negative_slope = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.kernel_size = kwargs.get('kernel_size',3)
self.num_stages = kwargs.get('num_stages',4)
self.pooling_ratios = kwargs.get('pooling_ratios')
self.repeat_time_list = kwargs.get('repeat_time_list')
self.dropout_rate = kwargs.get('dropout_rate',0)
self.num_class = kwargs.get('num_class',1)
self.conv_choice = kwargs.get('conv_choice','basic')
n_input_channel = kwargs.get('n_input_channel',1)
n_in_features = int(self.block_inplanes[0]/2)
layer_map_dict = {}
layer_map_dict['conv_0'] = ConvBlock(negative_slope=self.negative_slope,norm_choice=self.norm_choice,num_in_features=n_input_channel,
num_out_features=n_in_features,kernel_size=3)
num_in_features = n_in_features
final_feature_num = num_in_features
for stage_idx in range(self.num_stages):
output_features = self.block_inplanes[stage_idx]
layer_map_dict['Stage%02d_PreConv'%(stage_idx+1)] = ConvBlock(negative_slope=self.negative_slope,norm_choice=self.norm_choice,num_in_features=num_in_features,
num_out_features=output_features,kernel_size=3)
for repeat_idx in range(self.repeat_time_list[stage_idx]):
layer_map_dict['Stage%02d_InnerConvBlock%02d'%(stage_idx+1,repeat_idx+1)] = self._make_layer(output_features)
if self.dropout_rate>0:
layer_map_dict['Stage%02d_Dropout'%(stage_idx+1)] = nn.Dropout3d(p=self.dropout_rate)
if self.pooling_ratios[stage_idx]>1:
layer_map_dict['Stage%02d_MP'%(stage_idx+1)] = nn.MaxPool3d(kernel_size=self.pooling_ratios[stage_idx])
final_feature_num = output_features
num_in_features = output_features
self.dark_layer_func = nn.Sequential(OrderedDict([(key,layer_map_dict[key]) for key in layer_map_dict.keys()]))
self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1))
self.fc0 = nn.Linear(final_feature_num,int(final_feature_num/4))
self.dp0 = nn.Dropout3d(p=0.5)
self.fc = nn.Linear(int(final_feature_num/4),self.num_class)
def _make_layer(self,num_in_features):
'''
Generate structures like
input-conv1*1-conv3*3-shortcut
'''
func = DarknetLayer(negative_slope = self.negative_slope,norm_choice = self.norm_choice,kernel_size=self.kernel_size,
conv_choice = self.conv_choice,num_in_features = num_in_features)
return func
def forward(self,x):
x = self.dark_layer_func(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc0(x)
x = self.dp0(x)
x = self.fc(x)
return x
\ No newline at end of file
from collections import OrderedDict
import torch.nn.functional as F
from .BasicModules import *
class FSe_ResNet(nn.Module):
def __init__(self,**kwargs):
super().__init__()
self.block = kwargs.get('block')
self.block_inplanes = kwargs.get('block_inplanes')
self.negative_slop = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.kernel_size = kwargs.get('kernel_size',3)
self.num_stages = kwargs.get('num_stages',4)
self.pooling_ratios = kwargs.get('pooling_ratios')
self.repeat_time_list = kwargs.get('repeat_time_list')
self.dropout_rate = kwargs.get('dropout_rate',0)
self.num_class = kwargs.get('num_class',1)
self.shortcut_type = kwargs.get('shortcut_type','B')
self.widen_factor = kwargs.get('widen_factor',1.0)
self.block_inplanes = [int(val*self.widen_factor) for val in self.block_inplanes]
self.block_in_channels = self.block_inplanes[0]
n_input_channel = 1
self.conv1 = nn.Conv3d(n_input_channel,self.block_in_channels,kernel_size=self.kernel_size,padding=[int(self.kernel_size/2) for _ in range(3)])
self.bn1 = BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=self.block_in_channels)
################# is this part necessary?
#self.maxpool = nn.MaxPool3d(kernel_size = self.kernel_size,stride=self.pooling_ratios[0],padding=1)
layer_map_dict = {}
final_feature_num = 0
for stage_idx in range(self.num_stages):
final_feature_num = self.block_inplanes[stage_idx]
current_layer = self._make_layer(self.block,self.block_inplanes[stage_idx],self.repeat_time_list[stage_idx],
self.shortcut_type,self.pooling_ratios[stage_idx])
layer_map_dict['ResNet_%02d'%(stage_idx+1)] = current_layer
if self.dropout_rate>0:
current_dropout_func = nn.Dropout3d(p=self.dropout_rate)
else:
current_dropout_func = None
layer_map_dict['ResNet_%02d_dropout'%(stage_idx+1)] = current_dropout_func
self.res_layer_func = nn.Sequential(OrderedDict([(key,layer_map_dict[key]) for key in layer_map_dict.keys()]))
self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1))
############ todo change this part
self.fc0 = nn.Linear(final_feature_num,int(final_feature_num/4))
self.dp0 = nn.Dropout3d(p=0.5)
self.fc = nn.Linear(int(final_feature_num/4),self.num_class)
self.fc2 = nn.Linear(int(final_feature_num/2),1)
def _downsample_basic_block(self,x,out_num_channel,stride):
print ('stride is ',stride)
out = F.avg_pool3d(x,kernel_size=1,stride=stride)
zero_pads = torch.zeros(out.size(0),out_num_channel-out.size(1),out.size(2),out.size(3),out.size(4))
if isinstance(out.data,torch.cuda.FloatTensor):
zero_pads = zero_pads.cuda()
out = torch.cat([out.data,zero_pads],dim=1)
return out
def _make_layer(self,block_func,planes,repeat_time,shortcut_type,stride=1):
downsample = None
if stride!=1 or self.block_in_channels!=planes*self.block.expansion:
if shortcut_type=='A':
######### downsample only
downsample = partial(self._downsample_basic_block,planes*block_func*expansion,stride)
else:
downsample = nn.Sequential(nn.Conv3d(in_channels=self.block_in_channels,out_channels=planes*block_func.expansion,kernel_size=1,stride=stride),
BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=planes*block_func.expansion))
layers = []
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,downsample=downsample,stride=stride))
self.block_in_channels = planes*block_func.expansion
for layer_idx in range(1,repeat_time):
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,stride=1))
return nn.Sequential(*layers)
def forward(self,x_list):
x,x2 = x_list
x = self.conv1(x)
x = self.bn1(x)
# print ('shape of x is ',x.shape)
x = self.res_layer_func(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc0(x)
feature0 = x
x = self.dp0(x)
x = self.fc(x)
x2 = self.conv1(x2)
x2 = self.bn1(x2)
x2 = self.res_layer_func(x2)
x2 = self.avgpool(x2)
x2 = x2.view(x2.size(0), -1)
x2 = self.fc0(x2)
feature1 = x2
x2 = self.dp0(x2)
x2 = self.fc(x2)
conc_output = self.fc2(torch.cat([feature0,feature1],dim=1))
return x,x2,feature0,feature1,conc_output
\ No newline at end of file
import math
import torch
from torch import nn
from scipy.special import binom
class LSoftmaxLinear(nn.Module):
def __init__(self, input_features, output_features, margin, device=None):
super().__init__()
self.input_dim = input_features # number of input feature i.e. output of the last fc layer
self.output_dim = output_features # number of output = class numbers
self.margin = margin # m
self.beta = 100
self.beta_min = 0
self.scale = 0.99
self.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
device = self.device
# Initialize L-Softmax parameters
self.weight = nn.Parameter(torch.FloatTensor(input_features, output_features))
self.divisor = math.pi / self.margin # pi/m
self.C_m_2n = torch.Tensor(binom(margin, range(0, margin + 1, 2))).to(device) # C_m{2n}
self.cos_powers = torch.Tensor(range(self.margin, -1, -2)).to(device) # m - 2n
self.sin2_powers = torch.Tensor(range(len(self.cos_powers))).to(device) # n
self.signs = torch.ones(margin // 2 + 1).to(device) # 1, -1, 1, -1, ...
self.signs[1::2] = -1
def calculate_cos_m_theta(self, cos_theta):
sin2_theta = 1 - cos_theta**2
cos_terms = cos_theta.unsqueeze(1) ** self.cos_powers.unsqueeze(0) # cos^{m - 2n}
sin2_terms = (sin2_theta.unsqueeze(1) # sin2^{n}
** self.sin2_powers.unsqueeze(0))
cos_m_theta = (self.signs.unsqueeze(0) * # -1^{n} * C_m{2n} * cos^{m - 2n} * sin2^{n}
self.C_m_2n.unsqueeze(0) *
cos_terms *
sin2_terms).sum(1).to(self.device) # summation of all terms
return cos_m_theta
def reset_parameters(self):
nn.init.kaiming_normal_(self.weight.data.t())
def find_k(self, cos):
# to account for acos numerical errors
eps = 1e-7
cos = torch.clamp(cos, -1 + eps, 1 - eps)
acos = cos.acos()
k = (acos / self.divisor).floor().detach()
return k
def forward(self, input, target=None):
if self.training:
if target is None:
target = 1
assert target is not None
x, w = input, self.weight
beta = max(self.beta, self.beta_min)
logit = x.mm(w)
indexes = range(logit.size(0))
logit_target = logit[indexes, target]
# cos(theta) = w * x / ||w||*||x||
w_target_norm = w[:, target].norm(p=2, dim=0)
x_norm = x.norm(p=2, dim=1)
cos_theta_target = logit_target / (w_target_norm * x_norm + 1e-10)
# equation 7
cos_m_theta_target = self.calculate_cos_m_theta(cos_theta_target)
# find k in equation 6
k = self.find_k(cos_theta_target)
# f_y_i
logit_target_updated = (w_target_norm *
x_norm *
(((-1) ** k * cos_m_theta_target) - 2 * k))
logit_target_updated_beta = (logit_target_updated + beta * logit[indexes, target]) / (1 + beta)
logit[indexes, target] = logit_target_updated_beta
self.beta *= self.scale
return logit
else:
assert target is None
return input.mm(self.weight)
from collections import OrderedDict
import torch.nn.functional as F
from .BasicModules import *
class ResNet(nn.Module):
def __init__(self,**kwargs):
super().__init__()
self.block = kwargs.get('block')
self.block_inplanes = kwargs.get('block_inplanes')
self.negative_slop = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.kernel_size = kwargs.get('kernel_size',3)
self.num_stages = kwargs.get('num_stages',4)
self.pooling_ratios = kwargs.get('pooling_ratios')
self.repeat_time_list = kwargs.get('repeat_time_list')
self.dropout_rate = kwargs.get('dropout_rate',0)
self.num_class = kwargs.get('num_class',1)
self.shortcut_type = kwargs.get('shortcut_type','B')
self.widen_factor = kwargs.get('widen_factor',1.0)
self.conv_choice = kwargs.get('conv_choice','basic')
self.SCI_choice = kwargs.get('SCI_choice',False)
self.block_inplanes = [int(val*self.widen_factor) for val in self.block_inplanes]
self.block_in_channels = self.block_inplanes[0]
n_input_channel = 1
self.conv1 = ConvFunc3D(num_in_features=n_input_channel,num_out_features=self.block_in_channels,conv_choice=self.conv_choice,
kernel_size=self.kernel_size)
self.bn1 = BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=self.block_in_channels)
################# is this part necessary?
#self.maxpool = nn.MaxPool3d(kernel_size = self.kernel_size,stride=self.pooling_ratios[0],padding=1)
layer_map_dict = {}
final_feature_num = 0
for stage_idx in range(self.num_stages):
final_feature_num = self.block_inplanes[stage_idx]
current_layer = self._make_layer(self.block,self.block_inplanes[stage_idx],self.repeat_time_list[stage_idx],
self.shortcut_type,self.pooling_ratios[stage_idx])
layer_map_dict['ResNet_%02d'%(stage_idx+1)] = current_layer
if self.dropout_rate>0:
current_dropout_func = nn.Dropout3d(p=self.dropout_rate)
else:
current_dropout_func = None
layer_map_dict['ResNet_%02d_dropout'%(stage_idx+1)] = current_dropout_func
self.res_layer_func = nn.Sequential(OrderedDict([(key,layer_map_dict[key]) for key in layer_map_dict.keys()]))
self.SCI = SCIModule(n_input_channel=final_feature_num,n_output_channel=final_feature_num,conv_choice = self.conv_choice)
self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1))
############ todo change this part
self.fc0 = nn.Linear(final_feature_num,int(final_feature_num/4))
self.dp0 = nn.Dropout3d(p=0.5)
self.fc = nn.Linear(int(final_feature_num/4),self.num_class)
def _downsample_basic_block(self,x,out_num_channel,stride):
print ('stride is ',stride)
out = F.avg_pool3d(x,kernel_size=1,stride=stride)
zero_pads = torch.zeros(out.size(0),out_num_channel-out.size(1),out.size(2),out.size(3),out.size(4))
if isinstance(out.data,torch.cuda.FloatTensor):
zero_pads = zero_pads.cuda()
out = torch.cat([out.data,zero_pads],dim=1)
return out
def _make_layer(self,block_func,planes,repeat_time,shortcut_type,stride=1):
downsample = None
if stride!=1 or self.block_in_channels!=planes*self.block.expansion:
if shortcut_type=='A':
######### downsample only
downsample = partial(self._downsample_basic_block,planes*block_func*expansion,stride)
else:
downsample = nn.Sequential(nn.Conv3d(in_channels=self.block_in_channels,out_channels=planes*block_func.expansion,kernel_size=1,stride=stride),
BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=planes*block_func.expansion))
layers = []
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,downsample=downsample,stride=stride,conv_choice=self.conv_choice))
self.block_in_channels = planes*block_func.expansion
for layer_idx in range(1,repeat_time):
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,stride=1,conv_choice=self.conv_choice))
return nn.Sequential(*layers)
def forward(self,x,**kwargs):
labels = kwargs.get('labels',None)
x = self.conv1(x)
x = self.bn1(x)
# print ('shape of x is ',x.shape)
x = self.res_layer_func(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc0(x)
x = self.dp0(x)
x = self.fc(x)
return x
\ No newline at end of file
from collections import OrderedDict
import torch.nn.functional as F
from .BasicModules import *
from .LSoftmax import *
import sys
import os
from ..utils.metrics import ArcMarginProduct
class Se_ResNet(nn.Module):
def __init__(self,**kwargs):
super().__init__()
self.block = kwargs.get('block')
self.block_inplanes = kwargs.get('block_inplanes')
self.negative_slop = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.kernel_size = kwargs.get('kernel_size',3)
self.num_stages = kwargs.get('num_stages',4)
self.pooling_ratios = kwargs.get('pooling_ratios')
self.repeat_time_list = kwargs.get('repeat_time_list')
self.dropout_rate = kwargs.get('dropout_rate',0)
self.num_class = kwargs.get('num_class',1)
self.shortcut_type = kwargs.get('shortcut_type','B')
self.widen_factor = kwargs.get('widen_factor',1.0)
n_input_channel = kwargs.get('n_input_channel',1)
self.largeMargin = kwargs.get("largeMargin",-1)
self.SCI_choice = kwargs.get('SCI_choice',False)
self.conv_choice = kwargs.get('conv_choice','basic')
self.regression_choice = kwargs.get('regression_choice',False)
self.last_metric = kwargs.get('last_metric',None)
self.arcMarginS_val = kwargs.get('arcMarginS_val',1)
self.arcMarginM_val = kwargs.get('arcMarginM_val',0)
self.freeze_blocks = kwargs.get('freeze_blocks',[])
self.block_inplanes = [int(val*self.widen_factor) for val in self.block_inplanes]
self.block_in_channels = self.block_inplanes[0]
self.conv1 = nn.Conv3d(n_input_channel,self.block_in_channels,kernel_size=self.kernel_size,padding=[int(self.kernel_size/2) for _ in range(3)])
self.bn1 = BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=self.block_in_channels)
################# is this part necessary?
#self.maxpool = nn.MaxPool3d(kernel_size = self.kernel_size,stride=self.pooling_ratios[0],padding=1)
layer_map_dict = {}
final_feature_num = 0
for stage_idx in range(self.num_stages):
final_feature_num = self.block_inplanes[stage_idx]
current_layer = self._make_layer(self.block,self.block_inplanes[stage_idx],self.repeat_time_list[stage_idx],
self.shortcut_type,self.pooling_ratios[stage_idx])
if stage_idx+1 in self.freeze_blocks:
for p in current_layer.parameters():
p.requires_grad=False
layer_map_dict['ResNet_%02d'%(stage_idx+1)] = current_layer
if self.dropout_rate>0:
current_dropout_func = nn.Dropout3d(p=self.dropout_rate)
else:
current_dropout_func = None
layer_map_dict['ResNet_%02d_dropout'%(stage_idx+1)] = current_dropout_func
if self.SCI_choice:
layer_map_dict['ResNet_%02d_SCI'%(stage_idx+1)]=SCIModule(n_input_channel=final_feature_num,n_output_channel=final_feature_num,conv_choice = self.conv_choice)
self.res_layer_func = nn.Sequential(OrderedDict([(key,layer_map_dict[key]) for key in layer_map_dict.keys()]))
self.avgpool = nn.AdaptiveAvgPool3d((1, 1, 1))
self.fc0 = nn.Linear(final_feature_num,int(final_feature_num/4))
if 'last' in self.freeze_blocks:
for p in self.avgpool.parameters():
p.requires_grad=False
for p in self.fc0.parameters():
p.requires_grad=False
self.dp0 = nn.Dropout3d(p=0.5)
if self.largeMargin>0:
self.fc = LSoftmaxLinear(int(final_feature_num/4),self.num_class,self.largeMargin)
else:
if self.last_metric == 'arc_margin':
self.fc = ArcMarginProduct(int(final_feature_num/4), self.num_class, s=self.arcMarginS_val, m=self.arcMarginM_val, easy_margin=kwargs.get('easy_margin',True))
else:
self.fc = nn.Linear(int(final_feature_num/4),self.num_class,bias=False)
if self.regression_choice:
self.fc2 = nn.Linear(int(final_feature_num/4),1)
else:
self.fc2 = 0
def _downsample_basic_block(self,x,out_num_channel,stride):
out = F.avg_pool3d(x,kernel_size=1,stride=stride)
zero_pads = torch.zeros(out.size(0),out_num_channel-out.size(1),out.size(2),out.size(3),out.size(4))
if isinstance(out.data,torch.cuda.FloatTensor):
zero_pads = zero_pads.cuda()
out = torch.cat([out.data,zero_pads],dim=1)
return out
def _make_layer(self,block_func,planes,repeat_time,shortcut_type,stride=1):
downsample = None
if stride!=1 or self.block_in_channels!=planes*self.block.expansion:
if shortcut_type=='A':
######### downsample only
downsample = partial(self._downsample_basic_block,planes*block_func*expansion,stride)
else:
downsample = nn.Sequential(nn.Conv3d(in_channels=self.block_in_channels,out_channels=planes*block_func.expansion,kernel_size=1,stride=stride),
BNReLU(negative_slop=self.negative_slop,norm_choice=self.norm_choice,num_features=planes*block_func.expansion))
layers = []
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,downsample=downsample,stride=stride))
self.block_in_channels = planes*block_func.expansion
for layer_idx in range(1,repeat_time):
layers.append(block_func(negative_slop = self.negative_slop,norm_choice=self.norm_choice,num_in_features=self.block_in_channels,
num_out_features=planes,kernel_size = self.kernel_size,stride=1))
return nn.Sequential(*layers)
def forward(self,x_list, **kwargs):
labels = kwargs.get('labels',None)
outputs = []
if type(x_list)!=list:
x = x_list
x = self.conv1(x)
x = self.bn1(x)
x = self.res_layer_func(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc0(x)
x = self.dp0(x)
x1 = self.fc(x)
if self.regression_choice:
x2 = self.fc2(x)
return [[x1,x2]]
else:
return [x1]
else:
for x_idx,x_0 in enumerate(x_list):
x = self.conv1(x_0)
x = self.bn1(x)
x = self.res_layer_func(x)
x = self.avgpool(x)
x = x.view(x.size(0), -1)
x = self.fc0(x)
x = self.dp0(x)
if self.last_metric == 'arc_margin':
if labels is not None:
x1 = self.fc(x,labels[x_idx])
else:
x1 = self.fc(x,mode='test')
else:
x1 = self.fc(x)
if self.regression_choice:
x2 = self.fc2(x)
outputs.append([x1,x2])
else:
outputs.append(x1)
return outputs
\ No newline at end of file
from collections import OrderedDict
import torch.nn.functional as F
from .BasicModules import *
class VGG3D(nn.Module):
def __init__(self,**kwargs):
super(VGG3D,self).__init__()
self.negative_slop = kwargs.get('negative_slop',0)
self.norm_choice = kwargs.get('norm_choice','BN')
self.base_filters = kwargs.get('base_filters',16)
self.kernel_size = kwargs.get('kernel_size',3)
self.num_stages = kwargs.get('num_stages',4)
self.pooling_ratios = kwargs.get('pooling_ratios')
self.repeat_time_list = kwargs.get('repeat_time_list')
self.dropout_rate = kwargs.get('dropout_rate',0)
self.num_class = kwargs.get('num_class',1)
self.num_dense = kwargs.get('num_dense',256)
self.num_task = kwargs.get('num_task',1)
assert len(self.pooling_ratios)>self.num_stages
assert len(self.repeat_time_list)>self.num_stages
self.func_dict = {}
final_features = 0
size_after_gap = 1
for idx in range(self.num_stages):
in_filters = self.base_filters*(2**(idx-1)) if idx>0 else 1
out_filters = self.base_filters*(2**idx)
final_features = out_filters
current_func = ConvStage(negative_slope=self.negative_slop,norm_choice=self.norm_choice,num_in_features=in_filters,
num_out_features=out_filters,kernel_size=self.kernel_size,repeat_times = self.repeat_time_list[idx])
if self.dropout_rate>0:
current_dropout_func = nn.Dropout3d(p=self.dropout_rate)
else:
current_dropout_func = None
self.func_dict['convStage_%02d_BlockFunc'%(idx+1)] = current_func
if current_dropout_func is not None:
self.func_dict['convStage_%02d_Dropout'%(idx+1)] = current_dropout_func
self.func_dict['convStage_%02d_MP'%(idx+1)] = nn.MaxPool3d(kernel_size=self.pooling_ratios[idx])
self.vgg_basic_func = nn.Sequential(OrderedDict([(key,self.func_dict[key]) for key in self.func_dict.keys()]))
self.fc1 = nn.AdaptiveAvgPool3d(output_size=size_after_gap)
self.fc2 = torch.nn.Linear(in_features=final_features,out_features=self.num_class)
self.fc3 = torch.nn.Linear(in_features=final_features,out_features=self.num_class)
self.fc4 = torch.nn.Linear(in_features=final_features,out_features=self.num_class)
# self.ac_final = torch.nn.Sigmoid()
def forward(self,x):
output = x
output = self.vgg_basic_func(output)
output = self.fc1(output)
output = output.view(output.size(0),-1)
outputs = []
if self.num_task>1:
for idx in range(self.num_task):
if idx==0:
current_output = self.fc2(output)
elif idx==1:
current_output = self.fc3(output)
else:
current_output = self.fc4(output)
outputs.append(current_output)
return outputs
else:
output = self.fc2(output)
return output
class TempModel(nn.Module):
def __init__(self,**kwargs):
super(TempModel,self).__init__()
self.conv = nn.Conv3d(in_channels=1,out_channels=16,
kernel_size=3)
def forward(self,x):
print ('self.conv',self.conv)
return self.conv(x)
\ No newline at end of file
import sys
import os
from .model_utils import get_block
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from ..utils.RWconfig import LoadJson
# os.environ["CUDA_VISIBLE_DEVICES"]="2"
import torch
# from torchsummary import summary
def build_model(model_name,cfg):
func = get_block(model_name)
if "ResNet" in model_name :
cfg['block'] = get_block(cfg['block'])
return func(**cfg)
if __name__ == "__main__":
cfg_path = '../files/modelParams/model_parameters.json'
model_name = "FSe_ResNet"
cfg = LoadJson(cfg_path)[model_name]
print ('cfg is ',cfg)
model = build_model(model_name,cfg)
# summary(model.cuda(),(1,48,48,48))
total_params = sum(p.numel() for p in model.parameters())
print(f'{total_params:,} total parameters.')
# summary(model,(1,24,24,24),device='cpu')
import os
import sys
import six
import glob
import numpy as np
from .VGG_model import VGG3D,TempModel
from .ResNet import *
from .Se_ResNet import *
from .DarkNet import *
from .FSe_ResNet import *
def get_block(identifier):
if isinstance(identifier,six.string_types):
res = globals().get(identifier)
if not res:
raise ValueError('Invalid {}'.format(identifier))
return res
return identifier
\ No newline at end of file
import os
import glob
import torch
import timeit
import numpy as np
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".."))
__package__ = "BaseClassifier"
from .utils.RWconfig import LoadJson
from .model.modelBuild import build_model
import SimpleITK as sitk
from torchsummary import summary
from torch.autograd import Variable
import os
# os.environ["CUDA_VISIBLE_DEVICES"] = "6"
def set_requires_grad(nets, requires_grad=False):
"""Set requies_grad=Fasle for all the networks to avoid unnecessary computations
Parameters:
nets (network list) -- a list of networks
requires_grad (bool) -- whether the networks require gradients or not
"""
if not isinstance(nets, list):
nets = [nets]
for net in nets:
if net is not None:
for param in net.parameters():
param.requires_grad = requires_grad
if __name__ == "__main__":
path = '/fileser/xupl/lungclassifier3D'
dir_names = [dir_name for dir_name in os.listdir(path) if dir_name.startswith('Nodule') or dir_name.startswith('Rib')]
# dir_names = ['NoduleDensityClassifier']
for dir_name in dir_names:
infer_chocie = False
convert_choice = True
model = [file for file in os.listdir(os.path.join(path, dir_name, 'best')) if file.startswith('CP')][0]
checkpoint = os.path.join(path, dir_name, 'best', model)
config_path = os.path.join('..', dir_name,'model_parameters.json')
train_config_path = os.path.join('..', dir_name, 'config.json')
model_name = LoadJson(train_config_path)['training']['model_name']
model_param = LoadJson(config_path)[model_name]
model = build_model(model_name, model_param)
model.eval()
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
model.to(device)
model.load_state_dict(torch.load(checkpoint, map_location=device))
set_requires_grad(model)
print("model successfully loaded")
if infer_chocie:
paths = sorted(glob.glob('/fileser/zrx/Rib/alpha/493/temp/*nii.gz'))
for path in paths:
print('image_path is', path)
img = sitk.GetArrayFromImage(sitk.ReadImage(path))
img = img[np.newaxis, np.newaxis, ...]
print('image range is', np.amin(img), np.amax(img))
data = torch.from_numpy(img).to(device=device, dtype=torch.float32)
output = model(data, data)
print('output is', output[0][0].item())
probs = torch.sigmoid(output[0])
print('result is', probs[0].item())
# # load data
# pth to torchscript
if convert_choice:
patch_size = 48
input_shape = [1, 1] + [patch_size for _ in range(3)]
input_data = np.zeros(input_shape)
input_data = torch.from_numpy(input_data).to(device=device, dtype=torch.float32)
print('type of input_data is', type(input_data))
model_base_path = os.path.dirname(checkpoint)
model_path = '%s/model.pt' % model_base_path
traced_model = torch.jit.trace(model, [input_data])
traced_model.save(model_path)
print('Successful save model to %s' % model_path)
summary(model.cuda(), (1, 48, 48, 48))
import os
import sys
import glob
import math
import random
import numpy as np
import pandas as pd
import scipy.ndimage as nd
from scipy.ndimage import zoom
# from keras.utils import to_categorical
from matplotlib import pyplot as plt
def GenerateRatios(info,idx):
values = []
for val in info:
if ',' in val[idx]:
continue
else:
if float(val[idx]) not in cls_map_dict.keys():
continue
values.append(cls_map_dict[float(val[idx])])
counts = Counter(values)
counts = [counts[key]/float(len(info)) for key in sorted(counts.keys())]
return counts
This diff is collapsed.
# -*- coding:utf-8 -*-
import math
import torch
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
class GradientBoostFocal(nn.Module):
r"""
This criterion is a implemenation of Focal Loss, which is proposed in
Focal Loss for Dense Object Detection.
Loss(x, class) = - \alpha (1-softmax(x)[class])^gamma \log(softmax(x)[class])
The losses are averaged across observations for each minibatch.
Args:
alpha(1D Tensor, Variable) : the scalar factor for this criterion
gamma(float, double) : gamma > 0; reduces the relative loss for well-classified examples (p > .5),
putting more focus on hard, misclassified examples
size_average(bool): By default, the losses are averaged over observations for each minibatch.
However, if the field size_average is set to False, the losses are
instead summed for each minibatch.
"""
def __init__(self, class_num, alpha=None, gamma=2, size_average=False,show_loss=False,sigmoid_choice=False,k=1):
super(GradientBoostFocal, self).__init__()
if alpha is None:
self.alpha = Variable(torch.ones(class_num, 1))
else:
if isinstance(alpha, Variable):
self.alpha = alpha
else:
new_alpha = [1.0/val for val in alpha]
sum_val = sum(new_alpha)
new_alpha = [val/float(sum_val) for val in new_alpha]
self.alpha = torch.tensor(new_alpha)
self.gamma = gamma
self.class_num = class_num
self.size_average = size_average
self.show_loss = show_loss
self.sigmoid_choice = False
self.k = k
def _maskOutEasyClasses(self,probs,label,e_val=1e-6):
zero_vals = torch.ones_like(probs) *e_val
label_indices_full = torch.zeros_like(probs)
ids = label.view(-1, 1)
label_indices_full.scatter_(1, ids.data, 1.)
zeroOut_probs = torch.where(label_indices_full==0,probs,zero_vals)
values,indices = zeroOut_probs.topk(k=self.k,dim=1,largest =True)
indices = indices.view(-1,1)
label_indices_full.scatter_(1,indices.data,1.)
probs = torch.where(label_indices_full>0,probs,zero_vals)
# print ('probs is',probs)
return probs
def _exp(self,probs):
values,indices = probs.topk(k=self.k,dim=1,largest =True)
new_probs = probs - values
exp_probs = torch.exp(new_probs)
return exp_probs
def forward(self, loss_input):
inputs, targets,_ = loss_input
inputs = inputs[0]
targets = targets[0]
N = inputs.size(0)
C = inputs.size(1)
# P = torch.softmax(inputs,dim=1)
P = self._exp(inputs)
zeroOutProbs = self._maskOutEasyClasses(P,targets)
total_loss = torch.sum(zeroOutProbs,dim=1)
total_loss = total_loss.repeat((C,1)).transpose(0,1)
P = zeroOutProbs/total_loss
class_mask = inputs.data.new(N, C).fill_(0)
class_mask = Variable(class_mask)
ids = targets.view(-1, 1)
class_mask.scatter_(1, ids.data, 1.)
if inputs.is_cuda and not self.alpha.is_cuda:
self.alpha = self.alpha.cuda()
alpha = self.alpha[ids.data.view(-1)]
probs = (P*class_mask).sum(1).view(-1,1).squeeze(-1)
log_p = probs.log()
temp_loss =-(torch.pow((1-probs), self.gamma))*log_p
batch_loss = -alpha*(torch.pow((1-probs), self.gamma))*log_p
if self.size_average:
loss = batch_loss.mean()
else:
loss = batch_loss.sum()
return loss
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment