def process(img_mtx, dataset): img_mtx = apply_modality_lut(img_mtx, dataset) p1, p99 = np.percentile(img_mtx, (1, 99)) img_mtx[img_mtx < p1] = p1 img_mtx[img_mtx > p99] = p99 img_mtx = (img_mtx - p1) / (p99 - p1) return img_mtx
def get_image(self, group, i, slices, clear_cache=True): x = torch.zeros(slices, 512, 512, dtype=torch.uint8) # (C, H, W) rows = self.get_slices(group, i, slices) #sop_uids = np.empty([slices], dtype=np.dtype('U12')) sop_uids = [] for j, (idx, row) in enumerate(rows.iterrows()): if row.SOPInstanceUID not in self.cache: p = test_dir / 'test' / row.StudyInstanceUID / row.SeriesInstanceUID / f'{row.SOPInstanceUID}.dcm' dcm = pydicom.dcmread(str(p)) a = apply_modality_lut(dcm.pixel_array, dcm).astype( np.float32) # TODO: to float32 if memory runs out? t = torch.from_numpy(a) if t.shape != (384, 384): t = t.view(1, 1, *t.shape) # HW -> BCHW t = torch.nn.functional.interpolate( t, size=512, mode='nearest') # resize (FloatTensors only!) t = t.view(t.shape[-2:]) # BCHW -> HW ww, wc = h.window w0, w1 = wc - ww / 2, wc + ww / 2 t = torch.clamp(t, w0, w1) # window t = (t - w0) * 255. / ww # normalize self.cache[row.SOPInstanceUID] = t.to(torch.uint8) x[j] = self.cache[row.SOPInstanceUID] #sop_uids[j] = row.SOPInstanceUID sop_uids.append(row.SOPInstanceUID) if clear_cache: self.cache = {} return x, sop_uids
def read_dcm(path): dcm = pydicom.read_file(path) img = apply_voi_lut(apply_modality_lut(dcm.pixel_array, dcm), dcm) img = ((img - img.min()) / (img.max() - img.min()) * 255).astype(np.uint8) img = np.invert(img) if len(img.shape) < 3: img = np.repeat(img[..., None], 3, axis=-1) return img
def read_dicom_image(self, img_name, invert): dcm = pydicom.read_file(img_name) img = apply_voi_lut(apply_modality_lut(dcm.pixel_array, dcm), dcm) img = ((img - img.min()) / (img.max() - img.min()) * 255).astype( np.uint8) if invert != None: img = np.invert(img) img = np.expand_dims(img, axis=-1) return img
def dcm2png(path): dcm = pydicom.read_file(path) # extracting image (pixels data) img = apply_voi_lut(apply_modality_lut(dcm.pixel_array, dcm), dcm) if not (("PhotometricInterpretation" in dcm) and (dcm.PhotometricInterpretation == 'MONOCHROME2')): img = np.invert(img) img -= img.min() img = img / img.max() img = (img * 255) img = img.astype(np.uint8) path = str(path) + '.png' plt.imsave(path, img) return path
def __getitem__(self, index): path_to_folder = os.path.join(self.root_dir, self.case_ids[index]) files = [] for r, d, f in os.walk(path_to_folder): for file in f: if '.dcm' in file: files.append(os.path.join(r, file)) ct_scan = np.zeros(shape=(512, 512, len(files))) for file in files: dataset = pydicom.dcmread(file) pixel_array = dataset.pixel_array hu = apply_modality_lut(pixel_array, dataset) ct_scan[:, :, dataset.InstanceNumber - 1] = hu ct_scan = torch.from_numpy(ct_scan).float() # Resize to 224 by 224 and only take 1 out of 5 elements on the Z axis into account ct_scan = F.interpolate(ct_scan[None, None, ...], size=(224, 224, ct_scan.shape[-1] // 5), align_corners=True, mode='trilinear') ct_scan = ct_scan.squeeze(0).squeeze(0) torch.clamp(ct_scan, min=-500, max=1500, out=ct_scan) ct_scan = ct_scan[None, ...] if self.stage == 'train' and self.args.data_augment: # Default randomly mirroring the second and third axes ct_scan, _ = spatial_transforms.augment_mirroring(ct_scan, axes=(1, 2)) ct_scan = ct_scan[0] ###################################################### # Preprocessing for both train and validation data # ###################################################### # data should be a numpy array with shape [x, y, z] or [c, x, y, z] full_channel = np.stack([ct_scan, ct_scan, ct_scan]) full_channel = (full_channel - full_channel.min()) / ( full_channel.max() - full_channel.min()) if self.stage == 'train' and self.args.data_augment: full_channel = self.do_augmentation(full_channel) label = self.mapping[self.labels[index]] full_channel = np.transpose(full_channel, axes=(3, 0, 1, 2)) full_channel = torch.from_numpy(full_channel).float() return full_channel, label, self.case_ids[index]
def get_grayscale_from_FileDataset(dicomfile): """ Function converts pixel array of dicom file to grayscale and returns is. Before conversion Modality LUT operation is applied. :param dicomfile: object pydicom.dataset.FileDataset :return: numpy array in grayscale """ # Get pixel array image = dicomfile.pixel_array # Apply Modality LUT or Rescale Operation hu = apply_modality_lut(image, dicomfile) # Apply conversion to grayscale image_gray = convert_array_to_grayscale(hu) return image_gray
def __init__(self, folder_name): ''' Reads in the dcm files in a folder which corresponds to a patient. It follows carefully the physical slice locations and the frames in a hearth cycle. It does not matter if the location is getting higher or lower. ''' self.images_unordered = list( ) # contains the images and additional data_processing dcm_files = os.listdir(folder_name) for dcm_fl in dcm_files: # ignore if not a dcm file if not dcm_fl.endswith('.dcm'): continue # read the image path = os.path.join(folder_name, dcm_fl) try: dcm = dicom.dcmread(path, force=True) except InvalidDicomError: logger.error("Invalid dicom {}".format(path)) continue # if no valid dcm continue with the next file # access the required fields try: _, _, pos_z = self.__get_dcmdata(dcm, "ImagePositionPatient") timestamp = self.__get_dcmdata(dcm, "ContentTime") image_pixels = dcm.pixel_array # to get a numpy array except AttributeError as ex: logger.error( "Accessing elements in dcm file {} yielded: {}".format( path, ex)) continue # modality transformation on pixels (mainly for Philips) image = apply_modality_lut(image_pixels, dcm) # sequence is ordered according to ImagePatientPosition data = (pos_z, timestamp, image) self.images_unordered.append(data) # ordering the images _images_ = sorted(self.images_unordered, key=lambda data: data[0]) # apex to base _images_ = self.__group_frames(_images_) self.__order_frames(_images_) # create the image dictionary self.images = list() for group in _images_: img_in_slice = [] for data in group: img_in_slice.append(data[2]) self.images.append(img_in_slice)
def get_array_dicom_lut(file, folder): """ Function reads given dicom file and applies LUT modality operation in order to get pixel values in Hounsfield units. :param file: name of dicom file :param folder: source folder of dicom file :return: array of pixel data """ path = Path(folder) / file # Get dicom file dicomfile = pydicom.dcmread(path) # Get pixel array image = dicomfile.pixel_array # Apply Modality LUT or Rescale Operation hu = apply_modality_lut(image, dicomfile) return hu
def dicom2narray(path, voi_lut = False, fix_monochrome = True): """ Converts a DICOM into a NUMPY array and returns this array and its corresponding dataset. """ dicom = pydicom.read_file(path) # VOI LUT (if available by DICOM device) is used to transform raw DICOM data # to "human-friendly" view if voi_lut: # If the modality is CT (Scanner Image) we have to convert the values of # the image first with apply_modality # It uses the values of RescaleSlope and RescaleIntercept to convert the # values or the attribute LUT Sequence if dicom.Modality == "CT": data = apply_modality_lut(dicom.pixel_array, dicom) data = apply_voi_lut(data, dicom) else: data = apply_voi_lut(dicom.pixel_array, dicom) else: data = dicom.pixel_array # depending on this value, X-ray may look inverted - fix that: if fix_monochrome and dicom.PhotometricInterpretation == "MONOCHROME1": data = np.amax(data) - data #If the DICOM are not in one of these two formats, it can bring new problems. if dicom.PhotometricInterpretation != "MONOCHROME2" and \ dicom.PhotometricInterpretation != "MONOCHROME1": warnings.warn("PhotometricInterpretation " + dicom.PhotometricInterpretation + " can cause unexpected behaviors.\n" + "File concerned : " + path) data = data - np.min(data) data = data / np.max(data) data = (data * 255).astype(np.uint8) return (data, dicom)
def extract(image_name): ending = image_name.split('.')[-1] if (ending == 'dcm'): #dicom ct image dataset = pydicom.dcmread(image_name) hu = apply_modality_lut(dataset.pixel_array, dataset) density = (dataset.RescaleIntercept + dataset.RescaleSlope * hu) / 1000 + 1 (spacingX, spacingY) = dataset.PixelSpacing (sizeX, sizeY) = density.shape dimensions = (spacingX * sizeX, spacingY * sizeY) return density, dimensions else: # jpg or png img = Image.open(image_name).convert('L') #image data I = np.asarray(img) # image as greyscale I = I / 255 # rescale values to [0,1] J = deepcopy(np.flipud(I)) dimensions = (1, 1) # [cm] return J, dimensions
def check_dicom_lut_windowing(file, folder): """ Function applies LUT Modality and VOI Modality functions. :param file: source dicom filename :param folder: source folder :return: numpy array """ raise NotImplementedError("Needs to be checked") path = Path(folder) / file # Get dicom file dicomfile = pydicom.dcmread(path) # Get pixel array image = dicomfile.pixel_array # Apply Modality LUT or Rescale Operation hu = apply_modality_lut(image, dicomfile) width = dicomfile.WindowWidth center = dicomfile.WindowCenter lower_bound = int(center - width / 2) upper_bound = int(center + width / 2) array = hu array = np.where(array < lower_bound, lower_bound, array) array = np.where(array > upper_bound, upper_bound, array) return array
def process_image(image, dataset): preproc = apply_modality_lut(image, dataset) perc_cut = percentile_cut(preproc) return rescale(perc_cut)
# Specify the .dcm folder path folder_path = "stage_1_test_images" # Specify the output jpg/png/tiff folder path jpg_path = "JPG_test" png_path = "PNG_test" tiff_path = "TIFF_test" images_path = os.listdir(folder_path) for n, image in enumerate(images_path): ds = dicom.dcmread(os.path.join(folder_path, image)) arr = ds.pixel_array if choose == 1: image = image.replace('.dcm', '.jpg') # apply modality arr = util.apply_modality_lut(arr, ds) arr = util.apply_voi_lut(arr, ds, index=0) cv2.imwrite(os.path.join(jpg_path, image), arr) elif choose == 2: image = image.replace('.dcm', '.png') shape = ds.pixel_array.shape # Convert to float to avoid overflow or underflow losses. image_2d = ds.pixel_array.astype(float) # Rescaling grey scale between 0-255 image_2d_scaled = (np.maximum(image_2d,0) / image_2d.max()) * 255.0 # Convert to uint
def get_hounsfield(self): hu = apply_modality_lut(self.image_data.pixel_array, self.image_data) return hu
def get_manufacturer_independent_pixel_image2d_array(ds, has_TransferSyntax): # '1.2.840.10008.1.2.4.90' # # ds.file_meta.TransferSyntaxUID = '1.2.840.10008.1.2.4.99' # '1.2.840.10008.1.2.1.99' # has_TransferSyntax = True # print(f"syntax:{ds.file_meta.TransferSyntaxUID}") # RLE 1.2.840.10008.1.2.5: US-PAL-8-10x-echo.dcm is automatically handled as uncompressed case if (has_TransferSyntax and ds.file_meta.TransferSyntaxUID in [ "1.2.840.10008.1.2.4.50", "1.2.840.10008.1.2.4.51", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.4.81", "1.2.840.10008.1.2.4.90", "1.2.840.10008.1.2.4.91" ]): print("compressed case !!!!!!!!!") # return None, ds.PixelData # ref: https://github.com/pydicom/pydicom/blob/master/pydicom/pixel_data_handlers/pillow_handler.py print( "try to get compressed dicom's pixel data manually, can not handle by pydicom in pyodide, lack of some pyodide extension" ) try: print(f"pixeldata:{len(ds.PixelData)}") # TODO: only get 1st frame for multiple frame case and will improve later if getattr(ds, 'NumberOfFrames', 1) > 1: print("multi frame") j2k_precision, j2k_sign = None, None # multiple compressed frames # working case (50): # 1. 0002.dcm, some are [-5], [-4], [-6]. 512x512 # 2. color3d_jpeg_baseline , some frames needs [-1] but some do not need. size unknown? frame_count = 0 for frame in decode_data_sequence(ds.PixelData): frame_count += 1 # print(f"frame i:{frame_count}, len:{len(frame)}") # a = frame[0] # b = frame[1] # c = frame[len(frame)-2] # d = frame[len(frame)-1] # print(f"{a},{b},{c},{d}") if frame_count == 1: pixel_data = frame # im = _decompress_single_frame( # frame, # transfer_syntax, # ds.PhotometricInterpretation # ) # if 'YBR' in ds.PhotometricInterpretation: # im.draft('YCbCr', (ds.Rows, ds.Columns)) # pixel_bytes.extend(im.tobytes()) # if not j2k_precision: # params = get_j2k_parameters(frame) # j2k_precision = params.setdefault("precision", ds.BitsStored) # j2k_sign = params.setdefault("is_signed", None) # TODO: what is the rule of -5/-1? But even not using pixel_data[:-1], pixel_data[:-5], still work p2 = pixel_data else: print("single frame") # working case but browser can not render : # - JPGLosslessP14SV1_1s_1f_8b.dcm, DICOM made JPEG Lossless, 1.2.840.10008.1.2.4.70. 1024x768. local mac is able to view. # - JPEG57-MR-MONO2-12-shoulder.dcm from https://barre.dev/medical/samples/, JPEG Lossless, 1.2.840.10008.1.2.4.57. # https://products.groupdocs.app/viewer/jpg can be used to view. local mac seeme not able to view (all black) # - JPEG-lossy.dcm 1.2.840.10008.1.2.4.51 from https://github.com/pydicom/pydicom/blob/master/pydicom/data/test_files/JPEG-lossy.dcm, # https://products.groupdocs.app/viewer/jpg can be used to view, local mac seems not able to view (all black) pixel_data = defragment_data(ds.PixelData) p2 = pixel_data print(f"pixel_data:{len(pixel_data)}") # return None, pixel_data # try: # fio = BytesIO(ds.PixelData) # pixel_data) # image = Image.open(fio) # except Exception as e: # print(f"pillow error:{e}") # print('pillow done') # JPEG57-MR-MONO2-12-shoulder data:718940 -> data:718924 return None, p2 except Exception as e: print("failed to get compressed data") raise e print("incompressed") print( "start reading dicom pixel_array, uncompressed case uses apply_modality_lut" ) try: arr = ds.pixel_array except Exception as e: if has_TransferSyntax == True: raise e else: # http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_10.html print( "read data fail may due to no TransferSyntaxUID, set it as most often used and default ImplicitVRLittleEndian and try read dicom again" ) ds.file_meta.TransferSyntaxUID = pydicom.uid.ImplicitVRLittleEndian arr = ds.pixel_array print(f"read dicom pixel_array ok, shape:{arr.shape}") image2d = apply_modality_lut(arr, ds) return image2d, None
def __init__(self, folder_name): ''' Reads in the dcm files in a folder which corresponds to a patient. It follows carefully the physical slice locations and the frames in a hearth cycle. It does not matter if the location is getting higher or lower. ''' self.num_slices = 0 self.num_frames = 0 self.broken = False self.pixel_spacing = 0 # UPDATE: pixel spacing images = [] slice_locations = [] file_paths = [] dcm_files = sorted(os.listdir(folder_name)) dcm_files = [d for d in dcm_files if len(d.split('.')[-2]) < 4] if len(dcm_files ) == 0: # sometimes the order number is missing at the end dcm_files = sorted(os.listdir(folder_name)) for file in dcm_files: if file.find('.dcm') != -1: try: temp_ds = dicom.dcmread(os.path.join(folder_name, file)) images.append( apply_modality_lut( temp_ds.pixel_array, temp_ds)) # UPDATE: apply modality lut slice_locations.append(temp_ds.SliceLocation) file_paths.append(os.path.join(folder_name, file)) if self.pixel_spacing == 0: self.pixel_spacing = temp_ds.PixelSpacing # UPDATE: pixel spacing except: self.broken = True return current_sl = -1 frames = 0 increasing = False indices = [] for idx, slice_loc in enumerate(slice_locations): if abs(slice_loc - current_sl) > 0.01: # this means a new slice is started self.num_slices += 1 self.num_frames = max(self.num_frames, frames) frames = 0 indices.append(idx) if (slice_loc - current_sl) > 0.01: increasing = True else: increasing = False current_sl = slice_loc frames += 1 if self.num_slices != 0 and self.num_frames != 0: self.load_matrices(images, indices, increasing, slice_locations, file_paths) else: logger.warning( "There are no frames. This folder should be deleted. Path: {}". format(folder_name)) self.num_images = len(images)