diff --git a/Assets/3dmodel.raw.meta b/Assets/3dmodel.raw.meta new file mode 100644 index 0000000..3cb8b05 --- /dev/null +++ b/Assets/3dmodel.raw.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: fffe7f5410ec88c45acb9d6a0d8e4214 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 4076eb95b581041b5bb78255c2b05ea4, type: 3} + width: 512 + height: 512 + depth: 121 + bit: 2 + smooth: 2 diff --git a/Assets/5. Transfer Function/Materials/VolumeRendering3.mat b/Assets/5. Transfer Function/Materials/VolumeRendering3.mat index 35c50f9..d406288 100644 --- a/Assets/5. Transfer Function/Materials/VolumeRendering3.mat +++ b/Assets/5. Transfer Function/Materials/VolumeRendering3.mat @@ -24,7 +24,7 @@ m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _Volume: - m_Texture: {fileID: 4849145913767034428, guid: fde1733cb0568314489ecd9a39dde6af, + m_Texture: {fileID: 4849145913767034428, guid: fffe7f5410ec88c45acb9d6a0d8e4214, type: 3} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} @@ -32,9 +32,9 @@ - _BlendDst: 10 - _BlendSrc: 5 - _Intensity: 1 - - _Iteration: 1000 + - _Iteration: 2000 - _MaxX: 1 - - _MaxY: 0.801 + - _MaxY: 1 - _MaxZ: 1 - _MinX: 0 - _MinY: 0 diff --git a/Assets/5. Transfer Function/Scenes/Transfer Function.unity b/Assets/5. Transfer Function/Scenes/Transfer Function.unity index c7d684b..9805137 100644 --- a/Assets/5. Transfer Function/Scenes/Transfer Function.unity +++ b/Assets/5. Transfer Function/Scenes/Transfer Function.unity @@ -222,6 +222,7 @@ serializedVersion: 6 m_Component: - component: {fileID: 1000675181} + - component: {fileID: 1000675182} m_Layer: 0 m_Name: SystemManager m_TagString: Untagged @@ -243,6 +244,111 @@ m_Father: {fileID: 0} m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1000675182 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1000675180} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ade87100a2e14fa4e818d9571b0aff1b, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &1507480465 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1507480469} + - component: {fileID: 1507480468} + - component: {fileID: 1507480467} + - component: {fileID: 1507480466} + m_Layer: 0 + m_Name: Sphere + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!135 &1507480466 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1507480465} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1507480467 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1507480465} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 +--- !u!33 &1507480468 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1507480465} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1507480469 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1507480465} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -7.12667, y: 47.705956, z: -297.5} + m_LocalScale: {x: 10, y: 10, z: 10} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1873212973 GameObject: m_ObjectHideFlags: 0 @@ -331,8 +437,8 @@ m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1873212973} m_LocalRotation: {x: 0, y: 0, z: 1, w: 0} - m_LocalPosition: {x: -0.51, y: 0, z: 0.53} - m_LocalScale: {x: 3.4, y: 3.4, z: 6.05} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 340.48, y: 340.48, z: 605} m_Children: [] m_Father: {fileID: 0} m_RootOrder: 2 diff --git a/Assets/Scripts/load_setting.cs b/Assets/Scripts/load_setting.cs index df3ebe6..6b84543 100644 --- a/Assets/Scripts/load_setting.cs +++ b/Assets/Scripts/load_setting.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.IO; using System.Collections.Generic; using UnityEngine; @@ -9,6 +10,8 @@ public float x_scale; public float y_scale; public float z_scale; + + public string[] points_str; } public class load_setting : MonoBehaviour @@ -16,7 +19,13 @@ // Start is called before the first frame update void Start() { - + string data_str = ""; + StreamReader reader; + Debug.Log(Application.dataPath + "/setting.json"); + reader = new StreamReader(Application.dataPath + "/setting.json"); + data_str = reader.ReadToEnd(); + Setting inputJson = JsonUtility.FromJson(data_str); + Debug.Log(inputJson.x_scale); } // Update is called once per frame diff --git a/InitUnitySettings/DicomProcessor.py b/InitUnitySettings/DicomProcessor.py new file mode 100644 index 0000000..82990d9 --- /dev/null +++ b/InitUnitySettings/DicomProcessor.py @@ -0,0 +1,150 @@ +import pydicom +import glob +import sys, os +import numpy as np +import cv2 +import matplotlib.pyplot as plt +from tqdm import tqdm + + +class DicomProcessor: + + def __init__(self, dicom_dir, args): + self.args = args + self.dicom_dir = dicom_dir + self.dicom_file_list = glob.glob(os.path.join(dicom_dir, '*')) + series_set = set([pydicom.read_file(x).SeriesDescription for x in self.dicom_file_list]) + show_dict = {(i + 1): series_name for i, series_name in enumerate(series_set)} + print("対象シリーズ名を選択してください") + for key, item in show_dict.items(): + print("{}: {}".format(key, item)) + target = show_dict[int(input())] + self.dicom_file_list = [x for x in self.dicom_file_list if pydicom.read_file(x).SeriesDescription == target] + print("対象シリーズを抽出しました.({})".format(target)) + self.dicom_file_list = sorted(self.dicom_file_list, key=lambda x: float(pydicom.read_file(x).SliceLocation), reverse=False) + self.dicom_size = len(self.dicom_file_list) + + if self.dicom_size == 0: + print("dicom file has no files") + raise + + sample_dfile = pydicom.read_file(self.dicom_file_list[0]) + sample_dfile2 = pydicom.read_file(self.dicom_file_list[1]) + self.dcm_wc = sample_dfile.WindowCenter + self.dcm_ww = sample_dfile.WindowWidth + + # 一時的に変更 + try: + self.dcm_wc = self.dcm_wc[0] + self.dcm_ww = self.dcm_ww[0] + except: + pass + + print("original wc: {}".format(self.dcm_wc)) + print("original ww: {}".format(self.dcm_ww)) + self.dicom_row_num = sample_dfile.Rows + self.dicom_col_num = sample_dfile.Columns + + self.pixel_spacing_dx, self.pixel_spacing_dy = sample_dfile.PixelSpacing + self.slice_spacing = abs(sample_dfile.ImagePositionPatient[2] - sample_dfile2.ImagePositionPatient[2]) + self.image_position_patient = sample_dfile.ImagePositionPatient + #なぜか3DSlicer上ではSが-0.25されている + # self.image_position_patient[2] -= 2.5 + self.CT_for_imshow = self.load_initial_CT() + + def calc_ijk2LPS_mat(self, dst=None): + ijk_samples = self.calc_ijk_samples() + LPS_samples = self.calc_LPS_samples() + + Ls = LPS_samples[:, 0] + Ps = LPS_samples[:, 1] + Ss = LPS_samples[:, 2] + + row1 = np.linalg.solve(ijk_samples, Ls) + row2 = np.linalg.solve(ijk_samples, Ps) + row3 = np.linalg.solve(ijk_samples, Ss) + row4 = np.array([0, 0, 0, 1]) + + IJKtoLPS_mat = np.array([row1, row2, row3, row4]) + + if dst != None: + dst = IJKtoLPS_mat + + return IJKtoLPS_mat + + def calc_ijk_samples(self): + origin_k = len(self.dicom_file_list) - 1 + ijk = np.array([[0, 0, origin_k, 1], + [1, 0, origin_k, 1], + [0, 1, origin_k, 1], + [0, 0, origin_k - 1, 1]]) + + return ijk + + def calc_LPS_samples(self): + origin_L, origin_P, origin_S = self.image_position_patient + + LPS = np.array([[origin_L, origin_P, origin_S], + [origin_L + self.pixel_spacing_dx, origin_P, origin_S], + [origin_L, origin_P + self.pixel_spacing_dy, origin_S], + [origin_L, origin_P, origin_S - self.slice_spacing]]) + + return LPS + + def load_initial_CT(self): + CT_data = pydicom.read_file(self.dicom_file_list[0]) + CT_row = CT_data.pixel_array + CT_data.RescaleIntercept + + return self.row2uint8(CT_row) + + def row2uint8(self, CT_row, delete_0s=False): + if delete_0s: + CT_row = np.where(CT_row == 0, 0, CT_row - np.min(CT_row[CT_row != 0])) + + window_max = self.dcm_wc + self.dcm_ww / 2 + window_min = self.dcm_wc - self.dcm_ww / 2 + + CT_img = CT_row.astype(np.float) + CT_img[CT_img < window_min] = window_min + CT_img[window_max < CT_img] = window_max + + CT_img -= np.mean(CT_img) + CT_img = CT_img / (np.max(np.abs(CT_img)) + 1e-5) * 256.0 + CT_img -= np.mean(CT_img) + CT_img = np.clip(CT_img, 0, 255).astype(np.uint8) + CT_img = cv2.cvtColor(CT_img, cv2.COLOR_GRAY2BGR) + + return CT_img + + def adapt_window(self, array, window_low, window_high): + return np.clip(array, window_low, window_high) + + def get_CT_by_index(self, dicom_index): + # print(self.dicom_file_list[dicom_index]) + CT_data = pydicom.read_file(self.dicom_file_list[dicom_index]) + CT_row = CT_data.pixel_array + CT_data.RescaleIntercept + + return self.row2uint8(CT_row) + + def calc_k_on_ijk_coordinates(self, dicom_index): + return self.dicom_size - dicom_index - 1 + + def linear_pad_list(self, ijk_eso_centers): + pad_center_list = [] + Is_first = True + prev_index = 0 + for index, eso_center in enumerate(ijk_eso_centers): + if (eso_center is not None) and Is_first: + prev_index = index + Is_first = False + elif (eso_center is not None) and (Is_first is not True): + item_distance = index - prev_index + if item_distance != 1: + average_weight = 1 / item_distance + for new_index in range((prev_index + 1), index, 1): + pad_center = (ijk_eso_centers[prev_index] * (average_weight * (index - new_index)) + + ijk_eso_centers[index] * (average_weight * (new_index - prev_index))) + pad_center_list[new_index] = pad_center + prev_index = index + pad_center_list.append(eso_center) + return pad_center_list diff --git a/InitUnitySettings/GUI.py b/InitUnitySettings/GUI.py new file mode 100644 index 0000000..a160b46 --- /dev/null +++ b/InitUnitySettings/GUI.py @@ -0,0 +1,271 @@ +import numpy as np +import cv2 +from DicomProcessor import DicomProcessor + +GUI_LOAD_NEXT = 1 +GUI_LOAD_PREV = 0 + + +class GUIController: + + def __init__(self, args, setting_json): + self.args = args + self.setting_json = setting_json + self.Is_continue = True + self.Is_zoomed = False + self.dicom_processor = DicomProcessor(args.dicom_dir, args) + self.dicom_size = len(self.dicom_processor.dicom_file_list) + self.CT_img_for_show = self.dicom_processor.CT_for_imshow + self.cur_src_CT_img = self.CT_img_for_show + self.src_CT_size = self.cur_src_CT_img.shape + self.zoomed_CT_size = self.preprocess_zoomed_CT_size() + self.zoom_point = [0, 0] + + # menuボタン系 + self.target_color = (24, 185, 237) + self.non_target_color = (220, 220, 220) + + self.window_button_state = { + "pushed_wc": True, + "pushed_ww": False + } + self.wc_width = None + self.ww_width = None + self.state_board = None + self.button_height, self.button_width = 0, 0 + self.show_wc_str = "WC: {}".format(round(self.dicom_processor.dcm_wc)) + self.show_ww_str = "WW: {}".format(round(self.dicom_processor.dcm_ww)) + self.wc_str_color = self.target_color + self.ww_str_color = self.non_target_color + self.button_img = self.create_button_img() + + # まだ画像のピクセル幅がx, yで同じときのみに対応している. + self.set_init_window() + + #出力用変数 + self.dicom_index = 0 + self.ijk_eso_centers = [None] * self.dicom_size + self.start_point = None + + self.GUI_imshow() + + def set_init_window(self): + cv2.namedWindow('menu') + cv2.namedWindow('CT image') + + def preprocess_zoomed_CT_size(self): + zoomed_CT_size = [int(x / self.args.magnification_ratio) for x in self.CT_img_for_show.shape][:2] + if zoomed_CT_size[0] % 2 == 0: + zoomed_CT_size[0] -= 1 + if zoomed_CT_size[1] % 2 == 0: + zoomed_CT_size[1] -= 1 + return zoomed_CT_size + + def create_button_img(self): + buttons = ['back', 'next', 'export'] + button_size = (self.args.resized_button_width, self.args.resized_button_height) + for i, button_name in enumerate(buttons): + if i == 0: + button_img = cv2.imread('./button_img/' + button_name + '.png') + button_img = cv2.resize(button_img, button_size) + self.button_height, self.button_width, _ = button_img.shape + else: + tmp = cv2.imread('./button_img/' + button_name + '.png') + tmp = cv2.resize(tmp, button_size) + button_img = cv2.hconcat([button_img, tmp]) + + # 2021/1/12追加のウィンドウ処理処理用のボタン + wc_pushed = cv2.imread("./button_img/wc_pushed.png") + aspect_rate = self.args.resized_button_height / wc_pushed.shape[0] + wc_pushed = cv2.resize(wc_pushed, (round(aspect_rate * wc_pushed.shape[1]), round(aspect_rate * wc_pushed.shape[0]))) + + ww = cv2.imread("./button_img/ww.png") + aspect_rate = self.args.resized_button_height / ww.shape[0] + ww = cv2.resize(ww, (round(aspect_rate * ww.shape[1]), round(aspect_rate * ww.shape[0]))) + + width = button_img.shape[1] - wc_pushed.shape[1] - ww.shape[1] + self.state_board = np.zeros((ww.shape[0], width, 3)).astype(np.uint8) + cv2.putText(self.state_board, self.show_wc_str, (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, self.wc_str_color, + 1, cv2.LINE_AA) + cv2.putText(self.state_board, self.show_ww_str, (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, self.ww_str_color, + 1, cv2.LINE_AA) + + window_buttons = np.hstack([wc_pushed, ww, self.state_board]) + + button_img = np.vstack([button_img, window_buttons]) + self.wc_width = wc_pushed.shape[1] + self.ww_width = ww.shape[1] + + return button_img + + def change_cur_CT(self, mode): + if mode is GUI_LOAD_NEXT: + self.dicom_index += 1 + if mode is GUI_LOAD_PREV: + self.dicom_index -= 1 + print('index : {}'.format(self.dicom_index)) + self.CT_img_for_show = self.dicom_processor.get_CT_by_index(self.dicom_index) + self.cur_src_CT_img = self.CT_img_for_show + self.Is_zoomed = False + self.GUI_imshow() + + def GUI_imshow(self): + cv2.imshow("menu", self.button_img) + cv2.imshow("CT image", self.CT_img_for_show) + cv2.waitKey(1) + + def encode_array2str(self, array): + encoded = "" + for item in array: + encoded += str(item) + "," + encoded = encoded[:-1] + return encoded + + def menu_callbacks(self, event, x, y, flags, param): + # click back + if event == cv2.EVENT_LBUTTONUP and (0 <= x and x <= self.button_width) and y <= self.button_height and self.dicom_index != 0: + self.change_cur_CT(GUI_LOAD_PREV) + + # click next + elif event == cv2.EVENT_LBUTTONUP and (self.button_width < x and x < (2 * self.button_width)) and y <= self.button_height and self.dicom_index != (self.dicom_size - 1): + self.change_cur_CT(GUI_LOAD_NEXT) + + # click export + # TODO startpointがNoneの時に警告を出す機能を入れる + elif event == cv2.EVENT_LBUTTONUP and ((2 * self.button_width) < x and x < (3 * self.button_width)) and y <= self.button_height: + print("click export") + # 1.中心からの距離からを計算 + self.Is_continue = False + self.setting_json["start_point"] = self.encode_array2str(self.start_point) + print('exit') + + # click wc + elif event == cv2.EVENT_LBUTTONUP and x < self.wc_width and self.button_height < y and not self.window_button_state["pushed_wc"]: + # この辺は関数か出来る. + replaced_button = cv2.resize(cv2.imread("./button_img/wc_pushed.png"), (self.wc_width, self.button_height)) + self.button_img[self.button_height:, :self.wc_width, :] = replaced_button + replaced_button = cv2.resize(cv2.imread("./button_img/ww.png"), (self.ww_width, self.button_height)) + self.button_img[self.button_height:, self.wc_width:(self.wc_width + self.ww_width), :] = replaced_button + self.window_button_state["pushed_wc"], self.wc_str_color = True, self.target_color + self.window_button_state["pushed_ww"], self.ww_str_color = False, self.non_target_color + self.update_state_board() + + # click ww + elif event == cv2.EVENT_LBUTTONUP and self.wc_width < self.wc_width + self.ww_width and self.button_height < y and not self.window_button_state["pushed_ww"]: + # この辺は関数か出来る. + replaced_button = cv2.resize(cv2.imread("./button_img/wc.png"), (self.wc_width, self.button_height)) + self.button_img[self.button_height:, :self.wc_width, :] = replaced_button + replaced_button = cv2.resize(cv2.imread("./button_img/ww_pushed.png"), (self.ww_width, self.button_height)) + self.button_img[self.button_height:, self.wc_width:(self.wc_width + self.ww_width), :] = replaced_button + self.window_button_state["pushed_wc"], self.wc_str_color = False, self.non_target_color + self.window_button_state["pushed_ww"], self.ww_str_color = True, self.target_color + self.update_state_board() + + def CT_image_callbacks(self, event, x, y, flags, param): + if event is cv2.EVENT_LBUTTONDBLCLK and self.Is_zoomed is True: + zoom_i_ratio, zoom_j_ratio = ((self.src_CT_size[0] / self.zoomed_CT_size[0]), + (self.src_CT_size[1] / self.zoomed_CT_size[1])) + cv2.circle(self.CT_img_for_show, (x, y), 5, (0, 255, 0), -1, cv2.LINE_AA) + non_zoomed_i, non_zoomed_j = self.calc_non_zoomed_ij(x, y, zoom_i_ratio, zoom_j_ratio) + print('start point i:{} j:{} k:{}'.format(non_zoomed_i, non_zoomed_j, + self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index))) + self.start_point = np.array([non_zoomed_i, non_zoomed_j, + self.dicom_processor.calc_k_on_ijk_coordinates( + self.dicom_index)]) + + elif event is cv2.EVENT_LBUTTONDBLCLK and self.Is_zoomed is False: + cv2.circle(self.CT_img_for_show, (x, y), 5, (0, 255, 0), -1, lineType=cv2.LINE_AA) + print('start point i:{} j:{} k:{}'.format(x, y, self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index))) + self.start_point = np.array([x, y, self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index)]) + + elif event is cv2.EVENT_LBUTTONDOWN and self.Is_zoomed is True: + zoom_i_ratio, zoom_j_ratio = ((self.src_CT_size[0] / self.zoomed_CT_size[0]), + (self.src_CT_size[1] / self.zoomed_CT_size[1])) + cv2.circle(self.CT_img_for_show, (x, y), 5, (0, 0, 255), -1, cv2.LINE_AA) + non_zoomed_i, non_zoomed_j = self.calc_non_zoomed_ij(x, y, zoom_i_ratio, zoom_j_ratio) + print('clicked i:{} j:{} k:{}'.format(non_zoomed_i, non_zoomed_j, + self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index))) + self.ijk_eso_centers[self.dicom_index] = np.array([non_zoomed_i, non_zoomed_j, + self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index)]) + elif event is cv2.EVENT_LBUTTONDOWN and self.Is_zoomed is False: + cv2.circle(self.CT_img_for_show, (x, y), 5, (0, 0, 255), -1, lineType=cv2.LINE_AA) + print('clicked i:{} j:{} k:{}'.format(x, y, self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index))) + self.ijk_eso_centers[self.dicom_index] = np.array([x, y, self.dicom_processor.calc_k_on_ijk_coordinates(self.dicom_index)]) + elif event is cv2.EVENT_RBUTTONDOWN and self.Is_zoomed is False: + self.zoom_function(x, y) + elif event is cv2.EVENT_RBUTTONDOWN and self.Is_zoomed is True: + self.CT_img_for_show = self.cur_src_CT_img + self.Is_zoomed = False + + + def calc_non_zoomed_ij(self, i, j, zoom_i_ratio, zoom_j_ratio): + non_zoomed_i, non_zoomed_j = (self.zoom_point[0] + (i / zoom_i_ratio), + self.zoom_point[1] + (j / zoom_j_ratio)) + return non_zoomed_i, non_zoomed_j + + def zoom_function(self, x, y): + self.zoom_point = [int(x - ((self.zoomed_CT_size[0] - 1) / 2)), int(y - ((self.zoomed_CT_size[1] - 1) / 2))] + if self.zoom_point[0] < 0: + self.zoom_point[0] = 0 + if self.src_CT_size[1] <= 0: + self.zoom_point[1] = 0 + tmp = self.cur_src_CT_img[self.zoom_point[1]:(self.zoom_point[1] + self.zoomed_CT_size[1]), + self.zoom_point[0]:(self.zoom_point[0] + self.zoomed_CT_size[0]), :] + self.CT_img_for_show = cv2.resize(tmp, self.src_CT_size[:2], interpolation=cv2.INTER_LANCZOS4) + self.Is_zoomed = True + + def key_function(self): + key = cv2.waitKey(50) + if key is ord('d') and self.dicom_index != (self.dicom_size - 1): + self.change_cur_CT(GUI_LOAD_NEXT) + elif key is ord('a') and self.dicom_index != 0: + self.change_cur_CT(GUI_LOAD_PREV) + + # 2021/1/12追加の + elif key is ord('w'): + if self.window_button_state["pushed_wc"]: + self.dicom_processor.dcm_wc += 1 + print("changed => wc: ", self.dicom_processor.dcm_wc) + self.CT_img_for_show = self.dicom_processor.get_CT_by_index(self.dicom_index) + self.update_state_board() + + elif self.window_button_state["pushed_ww"]: + self.dicom_processor.dcm_ww += 1 + print("changed => ww: ", self.dicom_processor.dcm_ww) + self.CT_img_for_show = self.dicom_processor.get_CT_by_index(self.dicom_index) + self.update_state_board() + + elif key is ord('x'): + if self.window_button_state["pushed_wc"]: + self.dicom_processor.dcm_wc -= 1 + print("changed => wc: ", self.dicom_processor.dcm_wc) + self.CT_img_for_show = self.dicom_processor.get_CT_by_index(self.dicom_index) + self.update_state_board() + + elif self.window_button_state["pushed_ww"]: + self.dicom_processor.dcm_ww -= 1 + print("changed => ww: ", self.dicom_processor.dcm_ww) + self.CT_img_for_show = self.dicom_processor.get_CT_by_index(self.dicom_index) + self.update_state_board() + + def update_state_board(self): + self.state_board = np.zeros_like(self.state_board).astype(np.uint8) + self.show_wc_str = "WC: {}".format(round(self.dicom_processor.dcm_wc)) + self.show_ww_str = "WW: {}".format(round(self.dicom_processor.dcm_ww)) + cv2.putText(self.state_board, self.show_wc_str, (20, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, + self.wc_str_color, + 1, cv2.LINE_AA) + cv2.putText(self.state_board, self.show_ww_str, (20, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, + self.ww_str_color, + 1, cv2.LINE_AA) + self.button_img[self.button_height:, (self.wc_width + self.ww_width):, :] = self.state_board + + def run(self): + cv2.setMouseCallback('menu', self.menu_callbacks) + cv2.setMouseCallback('CT image', self.CT_image_callbacks) + + while True: + self.key_function() + self.GUI_imshow() + if not self.Is_continue: + break diff --git a/InitUnitySettings/button_img/back.png b/InitUnitySettings/button_img/back.png new file mode 100644 index 0000000..432cf61 --- /dev/null +++ b/InitUnitySettings/button_img/back.png Binary files differ diff --git a/InitUnitySettings/button_img/export.png b/InitUnitySettings/button_img/export.png new file mode 100644 index 0000000..75716e7 --- /dev/null +++ b/InitUnitySettings/button_img/export.png Binary files differ diff --git a/InitUnitySettings/button_img/next.png b/InitUnitySettings/button_img/next.png new file mode 100644 index 0000000..604e426 --- /dev/null +++ b/InitUnitySettings/button_img/next.png Binary files differ diff --git a/InitUnitySettings/button_img/wc.png b/InitUnitySettings/button_img/wc.png new file mode 100644 index 0000000..708a4a0 --- /dev/null +++ b/InitUnitySettings/button_img/wc.png Binary files differ diff --git a/InitUnitySettings/button_img/wc_pushed.png b/InitUnitySettings/button_img/wc_pushed.png new file mode 100644 index 0000000..bc0f720 --- /dev/null +++ b/InitUnitySettings/button_img/wc_pushed.png Binary files differ diff --git a/InitUnitySettings/button_img/ww.png b/InitUnitySettings/button_img/ww.png new file mode 100644 index 0000000..29987a5 --- /dev/null +++ b/InitUnitySettings/button_img/ww.png Binary files differ diff --git a/InitUnitySettings/button_img/ww_pushed.png b/InitUnitySettings/button_img/ww_pushed.png new file mode 100644 index 0000000..cede782 --- /dev/null +++ b/InitUnitySettings/button_img/ww_pushed.png Binary files differ diff --git a/InitUnitySettings/init_Unity_settings.py b/InitUnitySettings/init_Unity_settings.py new file mode 100644 index 0000000..50e015a --- /dev/null +++ b/InitUnitySettings/init_Unity_settings.py @@ -0,0 +1,45 @@ +from glob import glob +import pydicom +import json +import os.path as osp +import numpy as np + +dicom_dir = "D:/3Dsyokudo" + +out_name = "3D.raw" +out_base_path = "../Assets/" +dicom_file_list = glob(osp.join(dicom_dir, "*")) +dicom_file_list = sorted(dicom_file_list, key=lambda x: float(pydicom.read_file(x).SliceLocation), reverse=False) +sample_dcm1, sample_dcm2 = pydicom.read_file(dicom_file_list[0]), pydicom.read_file(dicom_file_list[1]) + +height, width = pydicom.read_file(dicom_file_list[0]).pixel_array.shape +depth = len(dicom_file_list) + +x_scale_f, y_scale_f = sample_dcm1.PixelSpacing +z_scale_f = abs(sample_dcm1.ImagePositionPatient[2] - sample_dcm2.ImagePositionPatient[2]) + +stack_img = np.stack([pydicom.read_file(x).pixel_array + pydicom.read_file(x).RescaleIntercept for x in dicom_file_list], axis=2) +row_format = stack_img.transpose((1, 0, 2)).reshape(-1, order="F") + +# ウィンドウ使うバージョン +window_max = 330 +window_min = -270 + +scaled_row = row_format.copy().astype(np.float) +scaled_row[scaled_row < window_min] = window_min +scaled_row[window_max < scaled_row] = window_max + +scaled_row -= np.mean(scaled_row) +scaled_row = scaled_row / (np.max(np.abs(scaled_row)) + 1e-5) * (2 ** 16) +scaled_row -= np.mean(scaled_row) +scaled_row = np.clip(scaled_row, 0, 2 ** 16).astype(np.uint16) +scaled_row.tofile(osp.join(out_base_path, out_name)) + +to_json = { + "x_scale": width * x_scale_f, + "y_scale": height * y_scale_f, + "z_scale": depth * z_scale_f +} + +with open(osp.join(out_base_path, "setting.json"), "w") as f: + json.dump(to_json, f, indent=4) diff --git a/InitUnitySettings/main.py b/InitUnitySettings/main.py new file mode 100644 index 0000000..c890960 --- /dev/null +++ b/InitUnitySettings/main.py @@ -0,0 +1,61 @@ +from glob import glob +import pydicom +import json +import os.path as osp +import numpy as np +import argparse +from GUI import GUIController + +parser = argparse.ArgumentParser() +parser.add_argument("--dicom_dir", type=str, required=True, help="DICOMファイルへのパス") +parser.add_argument("--output_dir", type=str, default="../Assets", help='モデルファイルの出力先を指定') +parser.add_argument("--resized_button_height", type=int, default=72, help="メニューボタンの高さ") +parser.add_argument("--resized_button_width", type=int, default=157, help="メニューボタンの幅") +parser.add_argument("--magnification_ratio", type=int, default=4, help='ダブルクリック時の拡大倍率(偶数のほうがいいかも)') +parser.add_argument("--window_low", type=float, default=-500.0, help="ウィンドウサイズの下限") +parser.add_argument("--window_high", type=float, default=500.0, help="ウィンドウサイズの上限") +args = parser.parse_args() + + +def makePatientRowFile(args): + dicom_file_list = glob(osp.join(args.dicom_dir, "*")) + dicom_file_list = sorted(dicom_file_list, key=lambda x: float(pydicom.read_file(x).SliceLocation), reverse=False) + sample_dcm1, sample_dcm2 = pydicom.read_file(dicom_file_list[0]), pydicom.read_file(dicom_file_list[1]) + + height, width = pydicom.read_file(dicom_file_list[0]).pixel_array.shape + depth = len(dicom_file_list) + + x_scale_f, y_scale_f = sample_dcm1.PixelSpacing + z_scale_f = abs(sample_dcm1.ImagePositionPatient[2] - sample_dcm2.ImagePositionPatient[2]) + + stack_img = np.stack( + [np.fliplr(pydicom.read_file(x).pixel_array) + pydicom.read_file(x).RescaleIntercept for x in dicom_file_list], axis=2) + row_format = stack_img.transpose((1, 0, 2)).reshape(-1, order="F") + + # ウィンドウ使うバージョン + window_max = sample_dcm1.WindowCenter + sample_dcm1.WindowWidth + window_min = sample_dcm1.WindowCenter - sample_dcm1.WindowWidth + + scaled_row = row_format.copy().astype(np.float) + scaled_row[scaled_row < window_min] = window_min + scaled_row[window_max < scaled_row] = window_max + + scaled_row -= np.mean(scaled_row) + scaled_row = scaled_row / (np.max(np.abs(scaled_row)) + 1e-5) * (2 ** 16) + scaled_row -= np.mean(scaled_row) + scaled_row = np.clip(scaled_row, 0, 2 ** 16).astype(np.uint16) + scaled_row.tofile(osp.join(args.output_dir, "3dmodel.raw")) + + to_json = { + "x_scale": width * x_scale_f, + "y_scale": height * y_scale_f, + "z_scale": depth * z_scale_f + } + + return to_json + + +if __name__ == "__main__": + setting_json = makePatientRowFile(args) + gui = GUIController(args, setting_json) + gui.run() diff --git a/init_Unity_settings.py b/init_Unity_settings.py deleted file mode 100644 index 475d1b0..0000000 --- a/init_Unity_settings.py +++ /dev/null @@ -1,45 +0,0 @@ -from glob import glob -import pydicom -import json -import os.path as osp -import numpy as np - -dicom_dir = "D:3Dsyokudo/" - -out_name = "3D.raw" -out_base_path = "./Assets/" -dicom_file_list = glob(osp.join(dicom_dir, "*")) -dicom_file_list = sorted(dicom_file_list, key=lambda x: float(pydicom.read_file(x).SliceLocation), reverse=False) -sample_dcm1, sample_dcm2 = pydicom.read_file(dicom_file_list[0]), pydicom.read_file(dicom_file_list[1]) - -height, width = pydicom.read_file(dicom_file_list[0]).pixel_array.shape -depth = len(dicom_file_list) - -x_scale_f, y_scale_f = sample_dcm1.PixelSpacing -z_scale_f = abs(sample_dcm1.ImagePositionPatient[2] - sample_dcm2.ImagePositionPatient[2]) - -stack_img = np.stack([pydicom.read_file(x).pixel_array + pydicom.read_file(x).RescaleIntercept for x in dicom_file_list], axis=2) -row_format = stack_img.transpose((1, 0, 2)).reshape(-1, order="F") - -# ウィンドウ使うバージョン -window_max = 330 -window_min = -270 - -scaled_row = row_format.copy().astype(np.float) -scaled_row[scaled_row < window_min] = window_min -scaled_row[window_max < scaled_row] = window_max - -scaled_row -= np.mean(scaled_row) -scaled_row = scaled_row / (np.max(np.abs(scaled_row)) + 1e-5) * (2 ** 16) -scaled_row -= np.mean(scaled_row) -scaled_row = np.clip(scaled_row, 0, 2 ** 16).astype(np.uint16) -scaled_row.tofile(osp.join(out_base_path, out_name)) - -to_json = { - "x_scale": width * x_scale_f, - "y_scale": height * y_scale_f, - "z_scale": depth * z_scale_f -} - -with open(osp.join(out_base_path, "setting.json"), "w") as f: - json.dump(to_json, f, indent=4)