| @@ -1,201 +0,0 @@ | |||
| Apache License | |||
| Version 2.0, January 2004 | |||
| http://www.apache.org/licenses/ | |||
| TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
| 1. Definitions. | |||
| "License" shall mean the terms and conditions for use, reproduction, | |||
| and distribution as defined by Sections 1 through 9 of this document. | |||
| "Licensor" shall mean the copyright owner or entity authorized by | |||
| the copyright owner that is granting the License. | |||
| "Legal Entity" shall mean the union of the acting entity and all | |||
| other entities that control, are controlled by, or are under common | |||
| control with that entity. For the purposes of this definition, | |||
| "control" means (i) the power, direct or indirect, to cause the | |||
| direction or management of such entity, whether by contract or | |||
| otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
| outstanding shares, or (iii) beneficial ownership of such entity. | |||
| "You" (or "Your") shall mean an individual or Legal Entity | |||
| exercising permissions granted by this License. | |||
| "Source" form shall mean the preferred form for making modifications, | |||
| including but not limited to software source code, documentation | |||
| source, and configuration files. | |||
| "Object" form shall mean any form resulting from mechanical | |||
| transformation or translation of a Source form, including but | |||
| not limited to compiled object code, generated documentation, | |||
| and conversions to other media types. | |||
| "Work" shall mean the work of authorship, whether in Source or | |||
| Object form, made available under the License, as indicated by a | |||
| copyright notice that is included in or attached to the work | |||
| (an example is provided in the Appendix below). | |||
| "Derivative Works" shall mean any work, whether in Source or Object | |||
| form, that is based on (or derived from) the Work and for which the | |||
| editorial revisions, annotations, elaborations, or other modifications | |||
| represent, as a whole, an original work of authorship. For the purposes | |||
| of this License, Derivative Works shall not include works that remain | |||
| separable from, or merely link (or bind by name) to the interfaces of, | |||
| the Work and Derivative Works thereof. | |||
| "Contribution" shall mean any work of authorship, including | |||
| the original version of the Work and any modifications or additions | |||
| to that Work or Derivative Works thereof, that is intentionally | |||
| submitted to Licensor for inclusion in the Work by the copyright owner | |||
| or by an individual or Legal Entity authorized to submit on behalf of | |||
| the copyright owner. For the purposes of this definition, "submitted" | |||
| means any form of electronic, verbal, or written communication sent | |||
| to the Licensor or its representatives, including but not limited to | |||
| communication on electronic mailing lists, source code control systems, | |||
| and issue tracking systems that are managed by, or on behalf of, the | |||
| Licensor for the purpose of discussing and improving the Work, but | |||
| excluding communication that is conspicuously marked or otherwise | |||
| designated in writing by the copyright owner as "Not a Contribution." | |||
| "Contributor" shall mean Licensor and any individual or Legal Entity | |||
| on behalf of whom a Contribution has been received by Licensor and | |||
| subsequently incorporated within the Work. | |||
| 2. Grant of Copyright License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| copyright license to reproduce, prepare Derivative Works of, | |||
| publicly display, publicly perform, sublicense, and distribute the | |||
| Work and such Derivative Works in Source or Object form. | |||
| 3. Grant of Patent License. Subject to the terms and conditions of | |||
| this License, each Contributor hereby grants to You a perpetual, | |||
| worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
| (except as stated in this section) patent license to make, have made, | |||
| use, offer to sell, sell, import, and otherwise transfer the Work, | |||
| where such license applies only to those patent claims licensable | |||
| by such Contributor that are necessarily infringed by their | |||
| Contribution(s) alone or by combination of their Contribution(s) | |||
| with the Work to which such Contribution(s) was submitted. If You | |||
| institute patent litigation against any entity (including a | |||
| cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
| or a Contribution incorporated within the Work constitutes direct | |||
| or contributory patent infringement, then any patent licenses | |||
| granted to You under this License for that Work shall terminate | |||
| as of the date such litigation is filed. | |||
| 4. Redistribution. You may reproduce and distribute copies of the | |||
| Work or Derivative Works thereof in any medium, with or without | |||
| modifications, and in Source or Object form, provided that You | |||
| meet the following conditions: | |||
| (a) You must give any other recipients of the Work or | |||
| Derivative Works a copy of this License; and | |||
| (b) You must cause any modified files to carry prominent notices | |||
| stating that You changed the files; and | |||
| (c) You must retain, in the Source form of any Derivative Works | |||
| that You distribute, all copyright, patent, trademark, and | |||
| attribution notices from the Source form of the Work, | |||
| excluding those notices that do not pertain to any part of | |||
| the Derivative Works; and | |||
| (d) If the Work includes a "NOTICE" text file as part of its | |||
| distribution, then any Derivative Works that You distribute must | |||
| include a readable copy of the attribution notices contained | |||
| within such NOTICE file, excluding those notices that do not | |||
| pertain to any part of the Derivative Works, in at least one | |||
| of the following places: within a NOTICE text file distributed | |||
| as part of the Derivative Works; within the Source form or | |||
| documentation, if provided along with the Derivative Works; or, | |||
| within a display generated by the Derivative Works, if and | |||
| wherever such third-party notices normally appear. The contents | |||
| of the NOTICE file are for informational purposes only and | |||
| do not modify the License. You may add Your own attribution | |||
| notices within Derivative Works that You distribute, alongside | |||
| or as an addendum to the NOTICE text from the Work, provided | |||
| that such additional attribution notices cannot be construed | |||
| as modifying the License. | |||
| You may add Your own copyright statement to Your modifications and | |||
| may provide additional or different license terms and conditions | |||
| for use, reproduction, or distribution of Your modifications, or | |||
| for any such Derivative Works as a whole, provided Your use, | |||
| reproduction, and distribution of the Work otherwise complies with | |||
| the conditions stated in this License. | |||
| 5. Submission of Contributions. Unless You explicitly state otherwise, | |||
| any Contribution intentionally submitted for inclusion in the Work | |||
| by You to the Licensor shall be under the terms and conditions of | |||
| this License, without any additional terms or conditions. | |||
| Notwithstanding the above, nothing herein shall supersede or modify | |||
| the terms of any separate license agreement you may have executed | |||
| with Licensor regarding such Contributions. | |||
| 6. Trademarks. This License does not grant permission to use the trade | |||
| names, trademarks, service marks, or product names of the Licensor, | |||
| except as required for reasonable and customary use in describing the | |||
| origin of the Work and reproducing the content of the NOTICE file. | |||
| 7. Disclaimer of Warranty. Unless required by applicable law or | |||
| agreed to in writing, Licensor provides the Work (and each | |||
| Contributor provides its Contributions) on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
| implied, including, without limitation, any warranties or conditions | |||
| of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
| PARTICULAR PURPOSE. You are solely responsible for determining the | |||
| appropriateness of using or redistributing the Work and assume any | |||
| risks associated with Your exercise of permissions under this License. | |||
| 8. Limitation of Liability. In no event and under no legal theory, | |||
| whether in tort (including negligence), contract, or otherwise, | |||
| unless required by applicable law (such as deliberate and grossly | |||
| negligent acts) or agreed to in writing, shall any Contributor be | |||
| liable to You for damages, including any direct, indirect, special, | |||
| incidental, or consequential damages of any character arising as a | |||
| result of this License or out of the use or inability to use the | |||
| Work (including but not limited to damages for loss of goodwill, | |||
| work stoppage, computer failure or malfunction, or any and all | |||
| other commercial damages or losses), even if such Contributor | |||
| has been advised of the possibility of such damages. | |||
| 9. Accepting Warranty or Additional Liability. While redistributing | |||
| the Work or Derivative Works thereof, You may choose to offer, | |||
| and charge a fee for, acceptance of support, warranty, indemnity, | |||
| or other liability obligations and/or rights consistent with this | |||
| License. However, in accepting such obligations, You may act only | |||
| on Your own behalf and on Your sole responsibility, not on behalf | |||
| of any other Contributor, and only if You agree to indemnify, | |||
| defend, and hold each Contributor harmless for any liability | |||
| incurred by, or claims asserted against, such Contributor by reason | |||
| of your accepting any such warranty or additional liability. | |||
| END OF TERMS AND CONDITIONS | |||
| APPENDIX: How to apply the Apache License to your work. | |||
| To apply the Apache License to your work, attach the following | |||
| boilerplate notice, with the fields enclosed by brackets "[]" | |||
| replaced with your own identifying information. (Don't include | |||
| the brackets!) The text should be enclosed in the appropriate | |||
| comment syntax for the file format. We also recommend that a | |||
| file or class name and description of purpose be included on the | |||
| same "printed page" as the copyright notice for easier | |||
| identification within third-party archives. | |||
| Copyright [yyyy] [name of copyright owner] | |||
| Licensed under the Apache License, Version 2.0 (the "License"); | |||
| you may not use this file except in compliance with the License. | |||
| You may obtain a copy of the License at | |||
| http://www.apache.org/licenses/LICENSE-2.0 | |||
| Unless required by applicable law or agreed to in writing, software | |||
| distributed under the License is distributed on an "AS IS" BASIS, | |||
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
| See the License for the specific language governing permissions and | |||
| limitations under the License. | |||
| @@ -0,0 +1,12 @@ | |||
| import pyplastimatch as pypla | |||
| from pyplastimatch.utils.install import install_precompiled_binaries | |||
| install_precompiled_binaries("") | |||
| # convert one of the NIFTI images to DICOM: name: <patient1>, output folder: <dicom_output> | |||
| convert_args_ct = { | |||
| "input": "datasets/004428_01_02_182-242.nii.gz", | |||
| "patient-id": "patient1", | |||
| "output-dicom": "dicom_output", | |||
| } | |||
| pypla.convert(verbose=True, **convert_args_ct) | |||
| @@ -0,0 +1,225 @@ | |||
| import numpy as np | |||
| import nibabel as nib | |||
| import os | |||
| import cv2 | |||
| import csv | |||
| import nibabel as nib | |||
| import pydicom | |||
| from pydicom.dataset import FileDataset | |||
| import numpy as np | |||
| import os | |||
| import datetime | |||
| import zipfile | |||
| def slices2nifti(ims, fn_out, spacing): | |||
| """Save 2D slices to 3D NIfTI file considering the spacing.""" | |||
| if len(ims) < 300: # cv2.merge does not support too many channels | |||
| V = cv2.merge(ims) | |||
| else: | |||
| V = np.empty((ims[0].shape[0], ims[0].shape[1], len(ims))) | |||
| for i in range(len(ims)): | |||
| V[:, :, i] = ims[i] | |||
| # The transformation matrix suitable for 3D slicer and ITK-SNAP | |||
| T = np.array([[0, -spacing[1], 0, 0], | |||
| [-spacing[0], 0, 0, 0], | |||
| [0, 0, -spacing[2], 0], | |||
| [0, 0, 0, 1]]) | |||
| img = nib.Nifti1Image(V, T) | |||
| path_out = os.path.join(dir_out, fn_out) | |||
| nib.save(img, path_out) | |||
| return path_out | |||
| def load_slices(dir, slice_idxs): | |||
| """Load slices from 16-bit PNG files and return images with their filenames.""" | |||
| slice_idxs = np.array(slice_idxs) | |||
| if not np.all(slice_idxs[1:] - slice_idxs[:-1] == 1): | |||
| print(f"⚠️ Slice indices are not consecutive") | |||
| ims = [] | |||
| filenames = [] | |||
| for slice_idx in slice_idxs: | |||
| fn = f'{slice_idx:03d}.png' | |||
| path = os.path.join(dir_in, dir, fn) | |||
| im = cv2.imread(path, -1) # -1 to preserve 16-bit depth | |||
| assert im is not None, f'Error reading {path}' | |||
| im_corrected = (im.astype(np.int32) - 32768).astype(np.int16) | |||
| ims.append(im_corrected) | |||
| filenames.append(fn.split('.')[0]) | |||
| return ims, filenames | |||
| def read_DL_info(): | |||
| """Read spacings and image indices in DeepLesion.""" | |||
| spacings = [] | |||
| idxs = [] | |||
| with open(info_fn, 'r') as csvfile: # Use 'r' mode for reading text files | |||
| reader = csv.reader(csvfile) | |||
| rownum = 0 | |||
| for row in reader: | |||
| if rownum == 0: | |||
| header = row | |||
| rownum += 1 | |||
| else: | |||
| idxs.append([int(d) for d in row[1:4]]) | |||
| spacings.append([float(d) for d in row[12].split(',')]) | |||
| idxs = np.array(idxs) | |||
| spacings = np.array(spacings) | |||
| return idxs, spacings | |||
| def nii_to_dicom(nii_path, output_folder, filenames): | |||
| nii_img = nib.load(nii_path) | |||
| data = nii_img.get_fdata() | |||
| affine = nii_img.affine | |||
| num_slices = data.shape[2] | |||
| assert len(filenames) == num_slices, "Length of filename list must match number of slices." | |||
| nii_base = os.path.splitext(os.path.basename(nii_path))[0] | |||
| nii_base = nii_base.split('.')[0] | |||
| dicom_subfolder = os.path.join(output_folder, nii_base) | |||
| os.makedirs(dicom_subfolder, exist_ok=True) | |||
| for i in range(num_slices): | |||
| filename = os.path.join(dicom_subfolder, filenames[i])+'.dcm' # Use provided filename | |||
| file_meta = pydicom.Dataset() | |||
| file_meta.MediaStorageSOPClassUID = pydicom.uid.SecondaryCaptureImageStorage | |||
| file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() | |||
| file_meta.ImplementationClassUID = "1.2.3.4.5.6.7.8.9.0" | |||
| file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian | |||
| ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) | |||
| dt = datetime.datetime.now() | |||
| ds.PatientName = "Test^Patient" | |||
| ds.PatientID = "123456" | |||
| ds.Modality = "MR" | |||
| ds.StudyInstanceUID = pydicom.uid.generate_uid() | |||
| ds.SeriesInstanceUID = pydicom.uid.generate_uid() | |||
| ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID | |||
| ds.SOPClassUID = file_meta.MediaStorageSOPClassUID | |||
| ds.StudyDate = dt.strftime('%Y%m%d') | |||
| ds.StudyTime = dt.strftime('%H%M%S') | |||
| ds.Rows, ds.Columns = data.shape[:2] | |||
| ds.InstanceNumber = i + 1 | |||
| ds.ImagePositionPatient = [float(affine[0,3]), float(affine[1,3]), float(affine[2,3] + i)] | |||
| ds.ImageOrientationPatient = [1,0,0,0,1,0] | |||
| ds.PixelSpacing = [1.0, 1.0] | |||
| ds.SliceThickness = 1.0 | |||
| ds.SamplesPerPixel = 1 | |||
| ds.PhotometricInterpretation = "MONOCHROME2" | |||
| ds.BitsAllocated = 16 | |||
| ds.BitsStored = 16 | |||
| ds.HighBit = 15 | |||
| ds.PixelRepresentation = 1 | |||
| pixel_array = data[:, :, i].astype(np.uint16) | |||
| ds.PixelData = pixel_array.tobytes() | |||
| ds.is_little_endian = True | |||
| ds.is_implicit_VR = False | |||
| ds.save_as(filename) | |||
| def extract_and_collect_main_folders(zip_root_dir): | |||
| extracted_paths = [] | |||
| for zip_name in sorted(os.listdir(zip_root_dir)): | |||
| zip_path = os.path.join(zip_root_dir, zip_name) | |||
| # فقط فایلهای .zip واقعی | |||
| if zip_name.lower().endswith('.zip') and os.path.isfile(zip_path): | |||
| try: | |||
| extract_folder = os.path.join(zip_root_dir, zip_name.replace('.zip', '')) | |||
| os.makedirs(extract_folder, exist_ok=True) | |||
| with zipfile.ZipFile(zip_path, 'r') as zip_ref: | |||
| zip_ref.extractall(extract_folder) | |||
| os.remove(zip_path) | |||
| # حذف پوشههای مخفی | |||
| extracted_subfolders = [ | |||
| os.path.join(extract_folder, name) | |||
| for name in os.listdir(extract_folder) | |||
| if os.path.isdir(os.path.join(extract_folder, name)) and not name.startswith('.') | |||
| ] | |||
| if not extracted_subfolders: | |||
| extracted_paths.append(extract_folder) | |||
| else: | |||
| extracted_paths.extend(extracted_subfolders) | |||
| except zipfile.BadZipFile: | |||
| print(f"⚠️ Skipping bad zip file: {zip_name}") | |||
| continue | |||
| return extracted_paths | |||
| # Main | |||
| zip_root_dir ='' | |||
| folders = extract_and_collect_main_folders(zip_root_dir) | |||
| def find_image_folders(directory): | |||
| image_folders = [] | |||
| # | |||
| for root, dirs, files in os.walk(directory): | |||
| for dir_name in dirs: | |||
| if dir_name.startswith("Images_png_"): | |||
| image_folders.append(os.path.join(root, dir_name)) | |||
| return image_folders | |||
| folder_path = '' | |||
| result = find_image_folders(folder_path) | |||
| print(result) | |||
| dir_out = '' | |||
| out_fmt = '%s.nii.gz' # format of the nifti file name to output | |||
| info_fn ='' # file name of the information file | |||
| idxs, spacings = read_DL_info() | |||
| for folder in result : | |||
| dir_in = folder + '/Images_png' | |||
| if not os.path.exists(dir_out): | |||
| os.mkdir(dir_out) | |||
| img_dirs = os.listdir(dir_in) | |||
| img_dirs.sort() | |||
| for dir1 in img_dirs: | |||
| #Find the image info according to the folder's name | |||
| idxs1 = np.array([int(d) for d in dir1.split('_')]) | |||
| i1 = np.where(np.all(idxs == idxs1, axis=1))[0] | |||
| spacings1 = spacings[i1[0]] | |||
| fns = os.listdir(os.path.join(dir_in, dir1)) | |||
| slices = [int(d[:-4]) for d in fns if d.endswith('.png')] | |||
| slices.sort() | |||
| groups = [slices] | |||
| for group in groups: | |||
| # Group contains slices indices of a sub-volume | |||
| ims,names = load_slices(dir1, group) | |||
| fn_out = out_fmt % (dir1) | |||
| path_out = slices2nifti(ims, fn_out, spacings1) | |||
| nii_to_dicom(path_out, "",names) | |||
| @@ -0,0 +1,77 @@ | |||
| import os | |||
| import nibabel as nib | |||
| import numpy as np | |||
| import cv2 | |||
| import glob | |||
| from natsort import natsorted | |||
| from organList import * # لیست اندامها مانند Organ = ['liver', 'spleen', ...] | |||
| # اندامهای مورد نظر برای استخراج ماسک | |||
| target_organs = ["liver", "spleen", "kidney_right", "kidney_left", "gallbladder", "stomach", "pancreas"] | |||
| # مسیرها | |||
| NIFTI_data_dir = '/media/external_10T/mehran_advand/segment/myenv/monai_wholeBody_ct_segmentation/Segmentation_Output' | |||
| DCM_data_dir = '/media/external_10T/mehran_advand/DeepLesion/Images_dicom_test' | |||
| output_dir = 'MONAI/' | |||
| # دریافت لیست بیماران (فولدرهایی مثل 000001_01_01) | |||
| patient_folders = natsorted(os.listdir(NIFTI_data_dir)) | |||
| for patient_id in patient_folders: | |||
| # if patient_id != '000053_06_01': | |||
| # continue | |||
| print(f"\n🧾 Processing patient: {patient_id}") | |||
| # ساخت مسیر فایل NIfTI و فولدر DICOM | |||
| nii_path = os.path.join(NIFTI_data_dir, patient_id, f"{patient_id}_trans.nii.gz") | |||
| dcm_folder = os.path.join(DCM_data_dir, patient_id) | |||
| if not os.path.isfile(nii_path): | |||
| print(f"❌ NIfTI file not found for {patient_id}") | |||
| continue | |||
| dcm_files = glob.glob(os.path.join(dcm_folder, "*.dcm")) | |||
| if not dcm_files: | |||
| print(f"❌ No DICOM files found for {patient_id}") | |||
| continue | |||
| dcm_files = natsorted(dcm_files) | |||
| # بارگذاری دادهی NIfTI | |||
| print(f"📥 Reading NIfTI: {nii_path}") | |||
| nii = nib.load(nii_path) | |||
| label_data = nii.get_fdata() | |||
| # بررسی و تصحیح محورها بر اساس affine | |||
| if nii.affine[0, 0] > 0: | |||
| label_data = np.flip(label_data, axis=0) | |||
| if nii.affine[1, 1] > 0: | |||
| label_data = np.flip(label_data, axis=1) | |||
| if nii.affine[2, 2] > 0: | |||
| label_data = np.flip(label_data, axis=2) | |||
| label_data = np.transpose(label_data, (2, 1, 0)) # Z, Y, X | |||
| num_slices = min(len(label_data), len(dcm_files)) | |||
| # پردازش هر اندام | |||
| for target_organ in target_organs: | |||
| if target_organ not in Organ: | |||
| print(f"⚠️ Organ '{target_organ}' not in Organ list. Skipping...") | |||
| continue | |||
| organ_index = Organ.index(target_organ) | |||
| print(f"🧠 Processing organ: {target_organ} (index {organ_index})") | |||
| for idx in range(num_slices): | |||
| binary_mask = (label_data[idx] == organ_index).astype(np.uint8) * 255 | |||
| #if np.any(binary_mask): | |||
| dcm_path = dcm_files[idx] | |||
| out_path = dcm_path.replace(DCM_data_dir, output_dir) | |||
| out_path = out_path.replace(patient_id, f"{patient_id}/MONAI_{target_organ}") | |||
| out_path = out_path.replace('.dcm', '_OUT.png') | |||
| out_path = out_path.replace('\\', '/') | |||
| os.makedirs(os.path.dirname(out_path), exist_ok=True) | |||
| cv2.imwrite(out_path, binary_mask) | |||
| @@ -0,0 +1,127 @@ | |||
| import os | |||
| import pandas as pd | |||
| import cv2 | |||
| import numpy as np | |||
| from tqdm import tqdm | |||
| SEG_ROOT = "MONAI" | |||
| DL_INFO_PATH = "" | |||
| OUTPUT_PATH = "" | |||
| DEBUG_DIR = "" | |||
| os.makedirs(DEBUG_DIR, exist_ok=True) | |||
| TARGET_ORGANS = ["liver", "spleen", "kidney_right", "kidney_left", "gallbladder", "stomach", "pancreas"] | |||
| OVERLAP_THRESHOLD = 0.1 | |||
| df = pd.read_csv(DL_INFO_PATH) | |||
| results = [] | |||
| for idx, row in tqdm(df.iterrows(), total=len(df)): | |||
| try: | |||
| filename = row["File_name"] | |||
| patient_id, study_id, series_id = filename.split("_")[:3] | |||
| series_name = f"{patient_id}_{study_id}_{series_id}" | |||
| series_path = os.path.join(SEG_ROOT, series_name) | |||
| if not os.path.exists(series_path): | |||
| continue | |||
| bbox = np.array(row["Bounding_boxes"].strip("[]").split(","), dtype=float).astype(int).tolist() | |||
| bbox_x1, bbox_y1, bbox_x2, bbox_y2 = bbox | |||
| lesion_area = (bbox_x2 - bbox_x1) * (bbox_y2 - bbox_y1) | |||
| if lesion_area == 0: | |||
| continue | |||
| slice_start, slice_end = map(int, str(row["Slice_range"]).strip().split(",")) | |||
| key_slice = int(row["Key_slice_index"]) | |||
| matched_organs = set() | |||
| found_overlap = False | |||
| overlap_slices = range(max(key_slice - 2, slice_start), min(key_slice + 2, slice_end) + 1) | |||
| for organ in TARGET_ORGANS: | |||
| monai_organ = f"MONAI_{organ}" | |||
| for slice_idx in overlap_slices: | |||
| slice_filename = f"{slice_idx:03}_OUT.png" | |||
| mask_path = os.path.join(series_path, monai_organ, slice_filename) | |||
| if not os.path.exists(mask_path): | |||
| continue | |||
| mask_gray = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) | |||
| if mask_gray is None or mask_gray.shape[0] == 0: | |||
| continue | |||
| mask_gray = cv2.rotate(mask_gray, cv2.ROTATE_90_COUNTERCLOCKWISE) | |||
| mask_gray = cv2.flip(mask_gray, 0) | |||
| mask_crop = mask_gray[bbox_y1:bbox_y2, bbox_x1:bbox_x2] | |||
| overlap_area = np.sum(mask_crop > 0) | |||
| overlap_ratio = overlap_area / lesion_area | |||
| if overlap_ratio > OVERLAP_THRESHOLD: | |||
| matched_organs.add(organ) | |||
| found_overlap = True | |||
| break | |||
| if not found_overlap: | |||
| proximity_distances = [] | |||
| all_distances = [] | |||
| for organ in TARGET_ORGANS: | |||
| monai_organ = f"MONAI_{organ}" | |||
| slice_filename = f"{key_slice:03}_OUT.png" | |||
| mask_path = os.path.join(series_path, monai_organ, slice_filename) | |||
| if not os.path.exists(mask_path): | |||
| continue | |||
| mask_gray = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) | |||
| if mask_gray is None or np.sum(mask_gray > 0) == 0: | |||
| continue | |||
| mask_gray = cv2.rotate(mask_gray, cv2.ROTATE_90_COUNTERCLOCKWISE) | |||
| mask_gray = cv2.flip(mask_gray, 0) | |||
| mask_bin = (mask_gray > 0).astype(np.uint8) | |||
| dist_transform = cv2.distanceTransform(255 - mask_bin * 255, cv2.DIST_L2, 5) | |||
| lesion_mask = np.zeros_like(mask_bin) | |||
| lesion_mask[bbox_y1:bbox_y2, bbox_x1:bbox_x2] = 1 | |||
| lesion_dist = dist_transform * lesion_mask | |||
| if np.any(lesion_dist > 0): | |||
| min_dist = lesion_dist[lesion_dist > 0].min() | |||
| all_distances.append((organ, min_dist)) | |||
| if min_dist <= 10: | |||
| matched_organs.add(organ) | |||
| found_overlap = True | |||
| proximity_distances.clear() | |||
| if min_dist >10 and min_dist <= 20 and not found_overlap : | |||
| proximity_distances.append((organ, min_dist)) | |||
| for organ, _ in proximity_distances: | |||
| matched_organs.add(organ + "_prox") | |||
| results.append({ | |||
| "series": series_name, | |||
| "slice_range": f"{slice_start}~{slice_end}", | |||
| "key_slice": key_slice, | |||
| "lesion_id": idx, | |||
| "matched_organs": ";".join(sorted(matched_organs)) if matched_organs else "none" | |||
| }) | |||
| except Exception as e: | |||
| print(f"⚠️ error {idx}: {e}") | |||
| continue | |||
| out_df = pd.DataFrame(results) | |||
| out_df.to_csv(OUTPUT_PATH, index=False) | |||
| @@ -0,0 +1,13 @@ | |||
| from monai.bundle import ConfigParser | |||
| config_path = "monai_wholeBody_ct_segmentation/configs/inference.json" | |||
| parser = ConfigParser() | |||
| parser.read_config(config_path) | |||
| evaluator = parser.get_parsed_content("evaluator") | |||
| try: | |||
| evaluator.run() | |||
| except Exception as e: | |||
| print(f"⚠️ خطا در اجرای evaluator: {e}") | |||
| @@ -0,0 +1,8 @@ | |||
| # Default ignored files | |||
| /shelf/ | |||
| /workspace.xml | |||
| # Editor-based HTTP Client requests | |||
| /httpRequests/ | |||
| # Datasource local storage ignored files | |||
| /dataSources/ | |||
| /dataSources.local.xml | |||
| @@ -0,0 +1,33 @@ | |||
| <component name="InspectionProjectProfileManager"> | |||
| <profile version="1.0"> | |||
| <option name="myName" value="Project Default" /> | |||
| <inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" /> | |||
| <inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true"> | |||
| <option name="ignoredPackages"> | |||
| <value> | |||
| <list size="18"> | |||
| <item index="0" class="java.lang.String" itemvalue="scipy" /> | |||
| <item index="1" class="java.lang.String" itemvalue="protobuf" /> | |||
| <item index="2" class="java.lang.String" itemvalue="thop" /> | |||
| <item index="3" class="java.lang.String" itemvalue="opencv-python" /> | |||
| <item index="4" class="java.lang.String" itemvalue="PyYAML" /> | |||
| <item index="5" class="java.lang.String" itemvalue="cython" /> | |||
| <item index="6" class="java.lang.String" itemvalue="ipython" /> | |||
| <item index="7" class="java.lang.String" itemvalue="numpy" /> | |||
| <item index="8" class="java.lang.String" itemvalue="requests" /> | |||
| <item index="9" class="java.lang.String" itemvalue="psutil" /> | |||
| <item index="10" class="java.lang.String" itemvalue="tqdm" /> | |||
| <item index="11" class="java.lang.String" itemvalue="pandas" /> | |||
| <item index="12" class="java.lang.String" itemvalue="tensorboard" /> | |||
| <item index="13" class="java.lang.String" itemvalue="seaborn" /> | |||
| <item index="14" class="java.lang.String" itemvalue="matplotlib" /> | |||
| <item index="15" class="java.lang.String" itemvalue="Pillow" /> | |||
| <item index="16" class="java.lang.String" itemvalue="line-bot-sdk" /> | |||
| <item index="17" class="java.lang.String" itemvalue="monai" /> | |||
| </list> | |||
| </value> | |||
| </option> | |||
| </inspection_tool> | |||
| <inspection_tool class="VulnerableLibrariesLocal" enabled="false" level="WARNING" enabled_by_default="false" /> | |||
| </profile> | |||
| </component> | |||
| @@ -0,0 +1,6 @@ | |||
| <component name="InspectionProjectProfileManager"> | |||
| <settings> | |||
| <option name="USE_PROJECT_PROFILE" value="false" /> | |||
| <version value="1.0" /> | |||
| </settings> | |||
| </component> | |||
| @@ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="ProjectModuleManager"> | |||
| <modules> | |||
| <module fileurl="file://$PROJECT_DIR$/.idea/monai_wholeBody_ct_segmentation.iml" filepath="$PROJECT_DIR$/.idea/monai_wholeBody_ct_segmentation.iml" /> | |||
| </modules> | |||
| </component> | |||
| </project> | |||
| @@ -0,0 +1,8 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <module type="PYTHON_MODULE" version="4"> | |||
| <component name="NewModuleRootManager"> | |||
| <content url="file://$MODULE_DIR$" /> | |||
| <orderEntry type="inheritedJdk" /> | |||
| <orderEntry type="sourceFolder" forTests="false" /> | |||
| </component> | |||
| </module> | |||
| @@ -0,0 +1,6 @@ | |||
| <?xml version="1.0" encoding="UTF-8"?> | |||
| <project version="4"> | |||
| <component name="VcsDirectoryMappings"> | |||
| <mapping directory="$PROJECT_DIR$" vcs="Git" /> | |||
| </component> | |||
| </project> | |||