From 4ddb8ec4d20c2b7aedf1b3c5a5366532693e5271 Mon Sep 17 00:00:00 2001 From: AshkanPakzad Date: Tue, 25 May 2021 19:00:14 +0100 Subject: [PATCH 1/5] Added option to save as compressed nifti image. Added nifti orientation support for exporting, nifti images are now saved in the original orientation to the source image. --- External/mim/Gui/Controllers/MimSaveAs.m | 5 +- External/mim/Library/File/MimSaveAsNifti.m | 42 +++++++++-- .../mim/Library/File/WriteNiftiOrientation.m | 71 +++++++++++++++++++ 3 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 External/mim/Library/File/WriteNiftiOrientation.m diff --git a/External/mim/Gui/Controllers/MimSaveAs.m b/External/mim/Gui/Controllers/MimSaveAs.m index a4c90f03b..2ef7d525e 100644 --- a/External/mim/Gui/Controllers/MimSaveAs.m +++ b/External/mim/Gui/Controllers/MimSaveAs.m @@ -63,6 +63,7 @@ '*.nii', 'NIFTI (*.nii)'; '*.mhd', '8-bit metaheader and raw data (*.mhd)'; '*.mhd', '16-bit metaheader and raw data (*.mhd)'; + '*.nii.gz', 'Compressed NIFTI [beta] (*.nii.gz)'; }; if path_name == 0 @@ -83,7 +84,7 @@ function SaveImage(image_data, filename, pathname, filter_index, patient_name, i MimSaveImageAsDicom(image_data, pathname, filename, patient_name, is_secondary_capture, dicom_metadata, reporting) case 2 MimSaveAsMatlab(image_data, pathname, filename, reporting); - case 3 + case {3,6} MimSaveAsNifti(image_data, pathname, filename, reporting); case 4 MimSaveAsMetaheaderAndRaw(image_data, pathname, filename, 'char', reporting) @@ -92,7 +93,7 @@ function SaveImage(image_data, filename, pathname, filter_index, patient_name, i MimSaveAsMetaheaderAndRaw(image_data, pathname, filename, 'short', reporting) else MimSaveAsMetaheaderAndRaw(image_data, pathname, filename, 'ushort', reporting) - end + end end end end diff --git a/External/mim/Library/File/MimSaveAsNifti.m b/External/mim/Library/File/MimSaveAsNifti.m index d10fd13fe..3da6d722b 100644 --- a/External/mim/Library/File/MimSaveAsNifti.m +++ b/External/mim/Library/File/MimSaveAsNifti.m @@ -28,6 +28,15 @@ function MimSaveAsNifti(image_to_save, path, filename, reporting) reporting.Error('MimSaveAsNifti:InputMustBePTKImage', 'Requires a PTKImage as input'); end + % check if .nii.gz given + [~,~,ext] = fileparts(filename); + if strcmp(ext,'.gz') + compressed = 1; + filename = filename(1:end-3); + else + compressed = 0; + end + image_data = image_to_save.RawImage; full_filename = fullfile(path, filename); @@ -35,17 +44,40 @@ function MimSaveAsNifti(image_to_save, path, filename, reporting) resolution = image_to_save.VoxelSize([2, 1, 3]); offset = [0 0 0]; - if isa(image_data, 'PTKDicomImage') - metadata = image_data. MetaHeader; - if isfield(metadata, 'Offset') - offset = metadata.Offset; + if isa(image_to_save, 'PTKDicomImage') + metadata = image_to_save. MetaHeader; + if isfield(metadata, 'ImagePositionPatient') + offset = metadata.ImagePositionPatient; end end image_data = permute(image_data, [2, 1, 3]); - image_data = flip(flip(flip(image_data, 3), 2), 1); + image_data = flip(image_data, 3); % only flip last dimension + nii_data = make_nii(image_data, resolution, offset, [], image_to_save.Title); save_nii(nii_data, full_filename); + + % set orientation + if isa(image_to_save, 'PTKDicomImage') + % get LPS form from dicom + if isfield(metadata, 'ImagePositionPatient') + dicomorientation = metadata.ImageOrientationPatient; + d1 = dicomorientation(1:3); + d2 = dicomorientation(4:6); + d3 = cross(d1,d2); + LPS_affine4 = [d1, d2, d3, offset]; + LPS_affine4 = [LPS_affine4; [0,0,0,1]]; + % convert to RAS for nifti + RAS_affine4 = diag([-1,-1,1,1])*LPS_affine4; + WriteNiftiOrientation(full_filename, RAS_affine4) + end + end + + % if .nii.gz save as .nii.gz + if compressed == 1 + gzip(full_filename) + end + end diff --git a/External/mim/Library/File/WriteNiftiOrientation.m b/External/mim/Library/File/WriteNiftiOrientation.m new file mode 100644 index 000000000..805f10043 --- /dev/null +++ b/External/mim/Library/File/WriteNiftiOrientation.m @@ -0,0 +1,71 @@ +function WriteNiftiOrientation(inputfile, aff) +% By Ashkan Pakzad May 2021 (ashkanpakzad.github.io) +% Copyright Ashkan Pakzad 2021. Distributed under MIT licence. + +% Give name of nifti file ending either .nii or .nii.gz to have orientation +% written from given affine + +% aff is a 4x4 matrix that represents the orientation of the given image in +% inputfile. As a nifti affine it maps the data coordinate directions ijk +% to image coordinates in RAS. The last row of aff must be [0 0 0 1]. +% The input aff matrix should not consider image spacing the top left 3x3 +% matrix should only be of [-1,0,1]. The image spacing saved in the +% original image is written. + +% For more information regarding medical image orientation consider +% https://medium.com/@ashkanpakzad/understanding-3d-medical-image-orientation-for-programmers-fcf79c7beed0 + +% decompress and load +[~,~,ext] = fileparts(inputfile); +if strcmp(ext,'.gz') + filename = string(gunzip(inputfile)); +else + filename = inputfile; +end + +% Find Voxel dimensions; format = numdims,x,y,z,t +vox_dims = fieldread(76, 8, 'float'); +vox_dims = vox_dims(2:4); + +% write given affine into file +% only use sform, set qform to 0 and sform to 1 +fieldwrite(0, 252, 'short'); % q +fieldwrite(1, 254, 'short'); % s + +% add spacing info to input affine +aff(:,1) = aff(:,1)*vox_dims(1); +aff(:,2) = aff(:,2)*vox_dims(2); +aff(:,3) = aff(:,3)*vox_dims(3); + +Sx = aff(1,:); +Sy = aff(2,:); +Sz = aff(3,:); + +fieldwrite(Sx, 280, 'float') +fieldwrite(Sy, 296, 'float') +fieldwrite(Sz, 312, 'float') + +% re-gzip if originally gzipped +if strcmp(ext,'.gz') + gzip(filename); + delete(filename) +end + +% read and write functions + +function field = fieldread(offset, size, precision) + % open file and read binary field from given offset into size and type. + fid=fopen(filename); + fseek(fid,offset,'bof'); + field = fread(fid,size,precision); + fclose(fid); +end + +function fieldwrite(field, offset, precision) + % open file and read binary field from given offset into size and type. + fid=fopen(filename, 'r+'); + fseek(fid,offset,'bof'); + fwrite(fid,field,precision); + fclose(fid); +end +end From ba64e4b77b046bc40ea4394b7cccede28042a180 Mon Sep 17 00:00:00 2001 From: AshkanPakzad Date: Tue, 25 May 2021 19:02:56 +0100 Subject: [PATCH 2/5] Added support for loading compressed nifti images --- .../Gui/Controllers/MimChooseImagingFiles.m | 5 ++-- External/mim/Library/File/MimGuessFileType.m | 5 ++++ .../mim/Library/File/MimLoadOtherFormat.m | 26 +++++++++++++++++-- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/External/mim/Gui/Controllers/MimChooseImagingFiles.m b/External/mim/Gui/Controllers/MimChooseImagingFiles.m index 3eaaef54e..d9da52e2f 100644 --- a/External/mim/Gui/Controllers/MimChooseImagingFiles.m +++ b/External/mim/Gui/Controllers/MimChooseImagingFiles.m @@ -27,7 +27,8 @@ '*.xif', 'HDllab/ATL Ultrasound'; '*.vtk', 'Visualization Toolkit (VTK)'; '*.vff', 'MicroCT'; - '*.par;*.rec', 'Philips PAR/REC' + '*.par;*.rec', 'Philips PAR/REC'; + '*.nii.gz', 'Compressed NIFTI [beta]' }; [image_path, filenames, filter_index] = CoreDiskUtilities.ChooseFiles('Select the file to import', image_path, true, filespec); @@ -72,7 +73,7 @@ image_info = PTKImageInfo(image_path, {filenames{1}}, image_type, [], [], []); return; - elseif (filter_index == 7) + elseif (filter_index == 7) || (filter_index == 14) image_type = MimImageFileFormat.Nifti; image_info = PTKImageInfo(image_path, {filenames{1}}, image_type, [], [], []); return; diff --git a/External/mim/Library/File/MimGuessFileType.m b/External/mim/Library/File/MimGuessFileType.m index 9c8cd0ff0..ad07225e8 100644 --- a/External/mim/Library/File/MimGuessFileType.m +++ b/External/mim/Library/File/MimGuessFileType.m @@ -37,6 +37,11 @@ principal_filename = {image_filename}; secondary_filenames = {}; return; + elseif strcmp(ext, '.gz') + image_type = MimImageFileFormat.Nifti; + principal_filename = {image_filename}; + secondary_filenames = {}; + return; elseif strcmp(ext, '.img') hdr_filename = [name '.hdr']; diff --git a/External/mim/Library/File/MimLoadOtherFormat.m b/External/mim/Library/File/MimLoadOtherFormat.m index c2af55567..abb0915d4 100644 --- a/External/mim/Library/File/MimLoadOtherFormat.m +++ b/External/mim/Library/File/MimLoadOtherFormat.m @@ -61,10 +61,32 @@ switch image_file_format case MimImageFileFormat.Nifti - header_data = nii_read_header(header_filename); + [~,~,ext] = fileparts(header_filename); + % if compressed (*.nii.gz) extract to read + if strcmp(ext,'.gz') + compressed = 1; + if isfile(header_filename(1:end-3)) + reporting.Error('MimLoadNiiGz:extractedniiexists', ... + ['Non-compressed *.nii of ', header_filename,... + ' already exists at path. Move/delete and try again.']); + end + gunzip(header_filename) + readfile = header_filename(1:end-3); + else + readfile = header_filename(1:end-3); + compressed = 0; + end + + header_data = nii_read_header(readfile); data = nii_read_volume(header_data); + if compressed == 1 + % remove temp decompressed file + delete(readfile); + % change header data to point back to original file + header_data.Filename = header_data.Filename; + end [new_dimension_order, flip_orientation] = MimImageCoordinateUtilities.GetDimensionPermutationVectorFromNiiOrientation(header_data, reporting); - + case MimImageFileFormat.Analyze % Experimental: assumes fixed orientation header_data = hdr_read_header(header_filename); data = hdr_read_volume(header_data); From 5a4dc0cc46cac3a785b1062871f386e974b34ef3 Mon Sep 17 00:00:00 2001 From: AshkanPakzad Date: Tue, 25 May 2021 19:23:44 +0100 Subject: [PATCH 3/5] added error failsafe to prevent deleting noncompressed nifti version if it already exists. --- External/mim/Library/File/MimSaveAsNifti.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/External/mim/Library/File/MimSaveAsNifti.m b/External/mim/Library/File/MimSaveAsNifti.m index 3da6d722b..e463d8671 100644 --- a/External/mim/Library/File/MimSaveAsNifti.m +++ b/External/mim/Library/File/MimSaveAsNifti.m @@ -27,20 +27,24 @@ function MimSaveAsNifti(image_to_save, path, filename, reporting) if ~isa(image_to_save, 'PTKImage') reporting.Error('MimSaveAsNifti:InputMustBePTKImage', 'Requires a PTKImage as input'); end + full_filename = fullfile(path, filename); % check if .nii.gz given [~,~,ext] = fileparts(filename); if strcmp(ext,'.gz') compressed = 1; filename = filename(1:end-3); + if isfile(fullfile(path, filename)) + reporting.Error('MimSaveAsNifti:rawniiexists', ... + ['Non-compressed *.nii of ', full_filename,... + ' already exists at path. Move/delete and try again.']); + end else compressed = 0; end image_data = image_to_save.RawImage; - full_filename = fullfile(path, filename); - resolution = image_to_save.VoxelSize([2, 1, 3]); offset = [0 0 0]; From 4f245c94909bf50ecc7cc2c0d7dc2d6bd90fd066 Mon Sep 17 00:00:00 2001 From: AshkanPakzad Date: Fri, 28 May 2021 09:48:17 +0100 Subject: [PATCH 4/5] fixed error where compressed nifti files ended up being saved as .nii.gz.gz --- External/mim/Library/File/MimSaveAsNifti.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/External/mim/Library/File/MimSaveAsNifti.m b/External/mim/Library/File/MimSaveAsNifti.m index e463d8671..0b8e72127 100644 --- a/External/mim/Library/File/MimSaveAsNifti.m +++ b/External/mim/Library/File/MimSaveAsNifti.m @@ -33,12 +33,12 @@ function MimSaveAsNifti(image_to_save, path, filename, reporting) [~,~,ext] = fileparts(filename); if strcmp(ext,'.gz') compressed = 1; - filename = filename(1:end-3); - if isfile(fullfile(path, filename)) + if isfile(fullfile(path, filename(1:end-3))) reporting.Error('MimSaveAsNifti:rawniiexists', ... ['Non-compressed *.nii of ', full_filename,... ' already exists at path. Move/delete and try again.']); end + full_filename = fullfile(path, filename(1:end-3)); else compressed = 0; end From 5d9d518eb04381eb557bea02e4586035726eb94a Mon Sep 17 00:00:00 2001 From: AshkanPakzad Date: Fri, 28 May 2021 09:54:54 +0100 Subject: [PATCH 5/5] fixed error where saving as compressed nifti resulted in both the extracted and compressed to be kept --- External/mim/Library/File/MimSaveAsNifti.m | 1 + 1 file changed, 1 insertion(+) diff --git a/External/mim/Library/File/MimSaveAsNifti.m b/External/mim/Library/File/MimSaveAsNifti.m index 0b8e72127..08649a8c5 100644 --- a/External/mim/Library/File/MimSaveAsNifti.m +++ b/External/mim/Library/File/MimSaveAsNifti.m @@ -81,6 +81,7 @@ function MimSaveAsNifti(image_to_save, path, filename, reporting) % if .nii.gz save as .nii.gz if compressed == 1 gzip(full_filename) + delete(full_filename) end end