diff --git a/CT/dicomCopier_gui.m b/CT/dicomCopier_gui.m index 3384098..52b4519 100644 --- a/CT/dicomCopier_gui.m +++ b/CT/dicomCopier_gui.m @@ -1,448 +1,448 @@ -function varargout = dicomCopier_gui(varargin) -% DICOMCOPIER_GUI MATLAB code for dicomCopier_gui.fig -% DICOMCOPIER_GUI, by itself, creates a new DICOMCOPIER_GUI or raises the existing -% singleton*. -% -% H = DICOMCOPIER_GUI returns the handle to a new DICOMCOPIER_GUI or the handle to -% the existing singleton*. -% -% DICOMCOPIER_GUI('CALLBACK',hObject,eventData,handles,...) calls the local -% function named CALLBACK in DICOMCOPIER_GUI.M with the given input arguments. -% -% DICOMCOPIER_GUI('Property','Value',...) creates a new DICOMCOPIER_GUI or raises the -% existing singleton*. Starting from the left, property value pairs are -% applied to the GUI before dicomCopier_gui_OpeningFcn gets called. An -% unrecognized property name or invalid value makes property application -% stop. All inputs are passed to dicomCopier_gui_OpeningFcn via varargin. -% -% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one -% instance to run (singleton)". -% -% See also: GUIDE, GUIDATA, GUIHANDLES - -% Edit the above text to modify the response to help dicomCopier_gui - -% Last Modified by GUIDE v2.5 31-Oct-2013 09:01:18 - -% Begin initialization code - DO NOT EDIT -gui_Singleton = 1; -gui_State = struct('gui_Name', mfilename, ... - 'gui_Singleton', gui_Singleton, ... - 'gui_OpeningFcn', @dicomCopier_gui_OpeningFcn, ... - 'gui_OutputFcn', @dicomCopier_gui_OutputFcn, ... - 'gui_LayoutFcn', [] , ... - 'gui_Callback', []); -if nargin && ischar(varargin{1}) - gui_State.gui_Callback = str2func(varargin{1}); -end - -if nargout - [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); -else - gui_mainfcn(gui_State, varargin{:}); -end -% End initialization code - DO NOT EDIT - - -% --- Executes just before dicomCopier_gui is made visible. -function dicomCopier_gui_OpeningFcn(hObject, eventdata, handles, varargin) -% This function has no output args, see OutputFcn. -% hObject handle to figure -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) -% varargin command line arguments to dicomCopier_gui (see VARARGIN) - -% Choose default command line output for dicomCopier_gui -handles.output = hObject; - -% Update handles structure -guidata(hObject, handles); - -% UIWAIT makes dicomCopier_gui wait for user response (see UIRESUME) -% uiwait(handles.figure1); -movegui('center') - - -% --- Outputs from this function are returned to the command line. -function varargout = dicomCopier_gui_OutputFcn(hObject, eventdata, handles) -% varargout cell array for returning output args (see VARARGOUT); -% hObject handle to figure -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Get default command line output from handles structure -varargout{1} = handles.output; - - -% --- Executes on button press in loadbutton. -function loadbutton_Callback(hObject, eventdata, handles) -% hObject handle to loadbutton (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% make clean -if isfield(handles, 'myinfo'); - handles = rmfield(handles, 'myinfo'); -end - -if isfield(handles, 'mydicom'); - handles = rmfield(handles, 'mydicom'); -end - -%[handles.dicomlist, handles.dicomdir] = uigetfile('D:\DICOM\*.*','Select the images to load','MultiSelect', 'on'); -[handles.dicomlist, handles.dicomdir] = uigetfile('C:\Users\dewarrat\Documents\current projects\Scapula segmentation\raw\Cases 12.2014 n2\*.*','Select the images to load','MultiSelect', 'on'); -if ~handles.dicomdir - return -end -handles.N = length(handles.dicomlist); - -set(handles.slider_slice, 'Max', handles.N) -set(handles.slider_slice, 'Value', int32(handles.N/2)) -set(handles.slider_slice, 'SliderStep',[1/handles.N 10/handles.N]) -set(handles.slider_slice, 'Min', 1) - -set(handles.info_text, 'ForegroundColor', 'black', 'FontWeight', 'normal') - -% Read dicom data -handles.myinfo(1) = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(1)))); - -set(handles.IPP_text, 'String', sprintf('%s (%s%s)', handles.myinfo(1).PatientID, handles.myinfo(1).PatientName.FamilyName(1), handles.myinfo(1).PatientName.GivenName(1))) -set(handles.resolution_text, 'String', sprintf('%f mm', handles.myinfo(1).PixelSpacing(1))) -set(handles.CTdate_text, 'String', sprintf('%s', handles.myinfo(1).AcquisitionDate)) - - -if isfield(handles.myinfo(1), 'ConvolutionKernel') - if strfind(handles.myinfo(1).ConvolutionKernel, 'BONE') - set(handles.radiobutton0, 'Value', 0) - set(handles.radiobutton1, 'Value', 1) - set(handles.radiobutton2, 'Value', 0) - elseif strfind(handles.myinfo(1).ConvolutionKernel, 'STANDARD') - set(handles.radiobutton0, 'Value', 0) - set(handles.radiobutton1, 'Value', 0) - set(handles.radiobutton2, 'Value', 1) - else - set(handles.radiobutton0, 'Value', 1) - set(handles.radiobutton1, 'Value', 0) - set(handles.radiobutton2, 'Value', 0) - end -else - set(handles.radiobutton0, 'Value', 1) - set(handles.radiobutton1, 'Value', 0) - set(handles.radiobutton2, 'Value', 0) -end - -% pre-allocate ? -% handles.mydicom = zeros(handles.myinfo(1).Height, handles.myinfo(1).Width, handles.N,'int16'); -handles.mydicom(:,:,int32(handles.N/2)) = dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(int32(handles.N/2))))); - -handles.slice = int32(handles.N / 2); -handles.CTmin = -200; -handles.CTmax = 2000; - -handles.bigmax = 3000; -handles.bigmin = -2000; - -set(handles.slider_contrast, 'Max', handles.bigmax - handles.bigmin - 1) -set(handles.slider_contrast, 'Min', 0) -set(handles.slider_contrast, 'Value', 900) - -set(handles.slider_brightness, 'Max', handles.bigmax) -set(handles.slider_brightness, 'Min', handles.bigmin) -set(handles.slider_brightness, 'Value', 1100) - -set(handles.slice_num, 'String', handles.slice) -imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) -guidata(hObject,handles) - - -% --- Executes on slider movement. -function slider_slice_Callback(hObject, eventdata, handles) -% hObject handle to slider_slice (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hints: get(hObject,'Value') returns position of slider -% get(hObject,'Min') and get(hObject,'Max') to determine range of slider - -handles.slice = int32(get(hObject,'Value')); -set(handles.slice_num, 'String', handles.slice) - -handles.mydicom(:,:,handles.slice) = dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(handles.slice)))); -imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) - -guidata(hObject,handles) - - -% --- Executes during object creation, after setting all properties. -function slider_slice_CreateFcn(hObject, eventdata, handles) -% hObject handle to slider_slice (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles empty - handles not created until after all CreateFcns called - -% Hint: slider controls usually have a light gray background. -if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) - set(hObject,'BackgroundColor',[.9 .9 .9]); -end - - - -function patient_num_Callback(hObject, eventdata, handles) -% hObject handle to patient_num (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hints: get(hObject,'String') returns contents of patient_num as text -% str2double(get(hObject,'String')) returns contents of patient_num as a double - - -% --- Executes during object creation, after setting all properties. -function patient_num_CreateFcn(hObject, eventdata, handles) -% hObject handle to patient_num (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles empty - handles not created until after all CreateFcns called - -% Hint: edit controls usually have a white background on Windows. -% See ISPC and COMPUTER. -if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) - set(hObject,'BackgroundColor','white'); -end - - - - -% --- Executes on slider movement. -function slider_contrast_Callback(hObject, eventdata, handles) -% hObject handle to slider_contrast (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hints: get(hObject,'Value') returns position of slider -% get(hObject,'Min') and get(hObject,'Max') to determine range of slider - -if ~isfield(handles, 'mydicom'); - return -end - -handles.CTmax = (get(handles.slider_brightness,'Max') + get(handles.slider_brightness,'Min') - get(handles.slider_brightness,'Value')) + (get(hObject,'Max') - get(hObject,'Value'))/2; -handles.CTmin = (get(handles.slider_brightness,'Max') + get(handles.slider_brightness,'Min') - get(handles.slider_brightness,'Value')) - (get(hObject,'Max') - get(hObject,'Value'))/2; - -if handles.CTmax > handles.bigmax - handles.CTmax = handles.bigmax; -end - -if handles.CTmin < handles.bigmin - handles.CTmin = handles.bigmin; -end - -imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) - -guidata(hObject,handles) - -% --- Executes during object creation, after setting all properties. -function slider_contrast_CreateFcn(hObject, eventdata, handles) -% hObject handle to slider_contrast (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles empty - handles not created until after all CreateFcns called - -% Hint: slider controls usually have a light gray background. -if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) - set(hObject,'BackgroundColor',[.9 .9 .9]); -end - - - -% --- Executes on slider movement. -function slider_brightness_Callback(hObject, eventdata, handles) -% hObject handle to slider_brightness (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hints: get(hObject,'Value') returns position of slider -% get(hObject,'Min') and get(hObject,'Max') to determine range of slider - -if ~isfield(handles, 'mydicom'); - return -end - -handles.CTmax = (get(hObject,'Max') + get(hObject,'Min') - get(hObject,'Value')) + (get(handles.slider_contrast,'Max') - get(handles.slider_contrast,'Value'))/2; -handles.CTmin = (get(hObject,'Max') + get(hObject,'Min') - get(hObject,'Value')) - (get(handles.slider_contrast,'Max') - get(handles.slider_contrast,'Value'))/2; - -if handles.CTmax > handles.bigmax - handles.CTmax = handles.bigmax; -end - -if handles.CTmin < handles.bigmin - handles.CTmin = handles.bigmin; -end - -imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) - -guidata(hObject,handles) - - -% --- Executes during object creation, after setting all properties. -function slider_brightness_CreateFcn(hObject, eventdata, handles) -% hObject handle to slider_brightness (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles empty - handles not created until after all CreateFcns called - -% Hint: slider controls usually have a light gray background. -if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) - set(hObject,'BackgroundColor',[.9 .9 .9]); -end - - -% --- Executes on button press in copy_button. -function copy_button_Callback(hObject, eventdata, handles) -% hObject handle to copy_button (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -sortPictures = 1; - -if isempty(get(handles.patient_num,'String')) - set(handles.info_text, 'ForegroundColor', 'red', 'FontWeight', 'bold', 'String', 'Please give a patient number') - return -else - patient = get(handles.patient_num,'String'); -end - -if isempty(get(handles.output_dir,'String')) - outputdir = uigetdir; - if outputdir - set(handles.output_dir,'String', outputdir) - end -else - outputdir = get(handles.output_dir,'String'); -end - -if ~outputdir - set(handles.info_text, 'ForegroundColor', 'red', 'FontWeight', 'bold', 'String', 'Please select an output directoy') - return -end - -set(handles.info_text, 'ForegroundColor', 'black', 'FontWeight', 'normal') - -% Create name string -newname = [patient, '-', handles.myinfo(1).PatientID, '_', handles.myinfo(1).PatientName.FamilyName(1), handles.myinfo(1).PatientName.GivenName(1)]; -if get(handles.radiobutton1,'Value') - newname = [newname, '-bone.']; -elseif get(handles.radiobutton2,'Value') - newname = [newname, '-soft.']; -else - newname = [newname, '.']; -end - -% Create output directory -outputdir = [outputdir, '\', patient, '-', handles.myinfo(1).PatientID, '\', 'CT-', patient, '-', handles.myinfo(1).PatientID, '-xxx\dicom\']; -mkdir(outputdir) - -% Sort pictures in correct order -if sortPictures == 1 - for h = 1:handles.N - infoTemp = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(h)))); - SliceLoc(h,1) = h; - SliceLoc(h,2) = infoTemp.SliceLocation; - end - SliceSorted = sortrows(SliceLoc,2); -end - -% Read all data -for i = 1:handles.N - str = sprintf('Copying dicom...\n%d / %d\n', i, handles.N); - set(handles.info_text, 'String', str) - drawnow - % Anonymous - if sortPictures == 1 - caseID = SliceSorted(i,1); - else - caseID = i; - end - - handles.myinfo(i) = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(caseID)))); - - if get(handles.radiobutton1,'Value') - handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1), '_bone']; - elseif get(handles.radiobutton2,'Value') - handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1), '_soft']; - else - handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1)]; - end - - handles.myinfo(i).PatientName.FamilyName = [patient, '_', handles.myinfo(1).PatientID]; - - % Read and copy - dicomwrite(dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(caseID)))),[outputdir, newname, sprintf('%04d',i), '.dcm'], handles.myinfo(i)); -end - -set(handles.info_text, 'String', 'Copied') - - -function output_dir_Callback(hObject, eventdata, handles) -% hObject handle to output_dir (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hints: get(hObject,'String') returns contents of output_dir as text -% str2double(get(hObject,'String')) returns contents of output_dir as a double - - -% --- Executes during object creation, after setting all properties. -function output_dir_CreateFcn(hObject, eventdata, handles) -% hObject handle to output_dir (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles empty - handles not created until after all CreateFcns called - -% Hint: edit controls usually have a white background on Windows. -% See ISPC and COMPUTER. -if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) - set(hObject,'BackgroundColor','white'); -end - - -% --- Executes on button press in radiobutton1. -function radiobutton1_Callback(hObject, eventdata, handles) -% hObject handle to radiobutton1 (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hint: get(hObject,'Value') returns toggle state of radiobutton1 - -if get(hObject,'Value') - set(handles.radiobutton0,'Value',0) - set(handles.radiobutton2,'Value',0) -else - set(hObject,'Value',1) -end - - -% --- Executes on button press in radiobutton2. -function radiobutton2_Callback(hObject, eventdata, handles) -% hObject handle to radiobutton2 (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hint: get(hObject,'Value') returns toggle state of radiobutton2 - -if get(hObject,'Value') - set(handles.radiobutton0,'Value',0) - set(handles.radiobutton1,'Value',0) -else - set(hObject,'Value',1) -end - - -% --- Executes on button press in radiobutton0. -function radiobutton0_Callback(hObject, eventdata, handles) -% hObject handle to radiobutton0 (see GCBO) -% eventdata reserved - to be defined in a future version of MATLAB -% handles structure with handles and user data (see GUIDATA) - -% Hint: get(hObject,'Value') returns toggle state of radiobutton0 - -if get(hObject,'Value') - set(handles.radiobutton1,'Value',0) - set(handles.radiobutton2,'Value',0) -else - set(hObject,'Value',1) -end +function varargout = dicomCopier_gui(varargin) +% DICOMCOPIER_GUI MATLAB code for dicomCopier_gui.fig +% DICOMCOPIER_GUI, by itself, creates a new DICOMCOPIER_GUI or raises the existing +% singleton*. +% +% H = DICOMCOPIER_GUI returns the handle to a new DICOMCOPIER_GUI or the handle to +% the existing singleton*. +% +% DICOMCOPIER_GUI('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in DICOMCOPIER_GUI.M with the given input arguments. +% +% DICOMCOPIER_GUI('Property','Value',...) creates a new DICOMCOPIER_GUI or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before dicomCopier_gui_OpeningFcn gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to dicomCopier_gui_OpeningFcn via varargin. +% +% *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one +% instance to run (singleton)". +% +% See also: GUIDE, GUIDATA, GUIHANDLES + +% Edit the above text to modify the response to help dicomCopier_gui + +% Last Modified by GUIDE v2.5 31-Oct-2013 09:01:18 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @dicomCopier_gui_OpeningFcn, ... + 'gui_OutputFcn', @dicomCopier_gui_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before dicomCopier_gui is made visible. +function dicomCopier_gui_OpeningFcn(hObject, eventdata, handles, varargin) +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to dicomCopier_gui (see VARARGIN) + +% Choose default command line output for dicomCopier_gui +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes dicomCopier_gui wait for user response (see UIRESUME) +% uiwait(handles.figure1); +movegui('center') + + +% --- Outputs from this function are returned to the command line. +function varargout = dicomCopier_gui_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in loadbutton. +function loadbutton_Callback(hObject, eventdata, handles) +% hObject handle to loadbutton (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% make clean +if isfield(handles, 'myinfo'); + handles = rmfield(handles, 'myinfo'); +end + +if isfield(handles, 'mydicom'); + handles = rmfield(handles, 'mydicom'); +end + +%[handles.dicomlist, handles.dicomdir] = uigetfile('D:\DICOM\*.*','Select the images to load','MultiSelect', 'on'); +[handles.dicomlist, handles.dicomdir] = uigetfile('C:\Users\dewarrat\Documents\current projects\Scapula segmentation\raw\Cases 12.2014 n2\*.*','Select the images to load','MultiSelect', 'on'); +if ~handles.dicomdir + return +end +handles.N = length(handles.dicomlist); + +set(handles.slider_slice, 'Max', handles.N) +set(handles.slider_slice, 'Value', int32(handles.N/2)) +set(handles.slider_slice, 'SliderStep',[1/handles.N 10/handles.N]) +set(handles.slider_slice, 'Min', 1) + +set(handles.info_text, 'ForegroundColor', 'black', 'FontWeight', 'normal') + +% Read dicom data +handles.myinfo(1) = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(1)))); + +set(handles.IPP_text, 'String', sprintf('%s (%s%s)', handles.myinfo(1).PatientID, handles.myinfo(1).PatientName.FamilyName(1), handles.myinfo(1).PatientName.GivenName(1))) +set(handles.resolution_text, 'String', sprintf('%f mm', handles.myinfo(1).PixelSpacing(1))) +set(handles.CTdate_text, 'String', sprintf('%s', handles.myinfo(1).AcquisitionDate)) + + +if isfield(handles.myinfo(1), 'ConvolutionKernel') + if strfind(handles.myinfo(1).ConvolutionKernel, 'BONE') + set(handles.radiobutton0, 'Value', 0) + set(handles.radiobutton1, 'Value', 1) + set(handles.radiobutton2, 'Value', 0) + elseif strfind(handles.myinfo(1).ConvolutionKernel, 'STANDARD') + set(handles.radiobutton0, 'Value', 0) + set(handles.radiobutton1, 'Value', 0) + set(handles.radiobutton2, 'Value', 1) + else + set(handles.radiobutton0, 'Value', 1) + set(handles.radiobutton1, 'Value', 0) + set(handles.radiobutton2, 'Value', 0) + end +else + set(handles.radiobutton0, 'Value', 1) + set(handles.radiobutton1, 'Value', 0) + set(handles.radiobutton2, 'Value', 0) +end + +% pre-allocate ? +% handles.mydicom = zeros(handles.myinfo(1).Height, handles.myinfo(1).Width, handles.N,'int16'); +handles.mydicom(:,:,int32(handles.N/2)) = dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(int32(handles.N/2))))); + +handles.slice = int32(handles.N / 2); +handles.CTmin = -200; +handles.CTmax = 2000; + +handles.bigmax = 3000; +handles.bigmin = -2000; + +set(handles.slider_contrast, 'Max', handles.bigmax - handles.bigmin - 1) +set(handles.slider_contrast, 'Min', 0) +set(handles.slider_contrast, 'Value', 900) + +set(handles.slider_brightness, 'Max', handles.bigmax) +set(handles.slider_brightness, 'Min', handles.bigmin) +set(handles.slider_brightness, 'Value', 1100) + +set(handles.slice_num, 'String', handles.slice) +imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) +guidata(hObject,handles) + + +% --- Executes on slider movement. +function slider_slice_Callback(hObject, eventdata, handles) +% hObject handle to slider_slice (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider + +handles.slice = int32(get(hObject,'Value')); +set(handles.slice_num, 'String', handles.slice) + +handles.mydicom(:,:,handles.slice) = dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(handles.slice)))); +imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) + +guidata(hObject,handles) + + +% --- Executes during object creation, after setting all properties. +function slider_slice_CreateFcn(hObject, eventdata, handles) +% hObject handle to slider_slice (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + +function patient_num_Callback(hObject, eventdata, handles) +% hObject handle to patient_num (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of patient_num as text +% str2double(get(hObject,'String')) returns contents of patient_num as a double + + +% --- Executes during object creation, after setting all properties. +function patient_num_CreateFcn(hObject, eventdata, handles) +% hObject handle to patient_num (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + + + +% --- Executes on slider movement. +function slider_contrast_Callback(hObject, eventdata, handles) +% hObject handle to slider_contrast (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider + +if ~isfield(handles, 'mydicom'); + return +end + +handles.CTmax = (get(handles.slider_brightness,'Max') + get(handles.slider_brightness,'Min') - get(handles.slider_brightness,'Value')) + (get(hObject,'Max') - get(hObject,'Value'))/2; +handles.CTmin = (get(handles.slider_brightness,'Max') + get(handles.slider_brightness,'Min') - get(handles.slider_brightness,'Value')) - (get(hObject,'Max') - get(hObject,'Value'))/2; + +if handles.CTmax > handles.bigmax + handles.CTmax = handles.bigmax; +end + +if handles.CTmin < handles.bigmin + handles.CTmin = handles.bigmin; +end + +imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) + +guidata(hObject,handles) + +% --- Executes during object creation, after setting all properties. +function slider_contrast_CreateFcn(hObject, eventdata, handles) +% hObject handle to slider_contrast (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + + +% --- Executes on slider movement. +function slider_brightness_Callback(hObject, eventdata, handles) +% hObject handle to slider_brightness (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'Value') returns position of slider +% get(hObject,'Min') and get(hObject,'Max') to determine range of slider + +if ~isfield(handles, 'mydicom'); + return +end + +handles.CTmax = (get(hObject,'Max') + get(hObject,'Min') - get(hObject,'Value')) + (get(handles.slider_contrast,'Max') - get(handles.slider_contrast,'Value'))/2; +handles.CTmin = (get(hObject,'Max') + get(hObject,'Min') - get(hObject,'Value')) - (get(handles.slider_contrast,'Max') - get(handles.slider_contrast,'Value'))/2; + +if handles.CTmax > handles.bigmax + handles.CTmax = handles.bigmax; +end + +if handles.CTmin < handles.bigmin + handles.CTmin = handles.bigmin; +end + +imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) + +guidata(hObject,handles) + + +% --- Executes during object creation, after setting all properties. +function slider_brightness_CreateFcn(hObject, eventdata, handles) +% hObject handle to slider_brightness (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: slider controls usually have a light gray background. +if isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor',[.9 .9 .9]); +end + + +% --- Executes on button press in copy_button. +function copy_button_Callback(hObject, eventdata, handles) +% hObject handle to copy_button (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +sortPictures = 1; + +if isempty(get(handles.patient_num,'String')) + set(handles.info_text, 'ForegroundColor', 'red', 'FontWeight', 'bold', 'String', 'Please give a patient number') + return +else + patient = get(handles.patient_num,'String'); +end + +if isempty(get(handles.output_dir,'String')) + outputdir = uigetdir; + if outputdir + set(handles.output_dir,'String', outputdir) + end +else + outputdir = get(handles.output_dir,'String'); +end + +if ~outputdir + set(handles.info_text, 'ForegroundColor', 'red', 'FontWeight', 'bold', 'String', 'Please select an output directoy') + return +end + +set(handles.info_text, 'ForegroundColor', 'black', 'FontWeight', 'normal') + +% Create name string +newname = [patient, '-', handles.myinfo(1).PatientID, '_', handles.myinfo(1).PatientName.FamilyName(1), handles.myinfo(1).PatientName.GivenName(1)]; +if get(handles.radiobutton1,'Value') + newname = [newname, '-bone.']; +elseif get(handles.radiobutton2,'Value') + newname = [newname, '-soft.']; +else + newname = [newname, '.']; +end + +% Create output directory +outputdir = [outputdir, '\', patient, '-', handles.myinfo(1).PatientID, '\', 'CT-', patient, '-', handles.myinfo(1).PatientID, '-xxx\dicom\']; +mkdir(outputdir) + +% Sort pictures in correct order +if sortPictures == 1 + for h = 1:handles.N + infoTemp = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(h)))); + SliceLoc(h,1) = h; + SliceLoc(h,2) = infoTemp.SliceLocation; + end + SliceSorted = sortrows(SliceLoc,2); +end + +% Read all data +for i = 1:handles.N + str = sprintf('Copying dicom...\n%d / %d\n', i, handles.N); + set(handles.info_text, 'String', str) + drawnow + % Anonymous + if sortPictures == 1 + caseID = SliceSorted(i,1); + else + caseID = i; + end + + handles.myinfo(i) = dicominfo(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(caseID)))); + + if get(handles.radiobutton1,'Value') + handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1), '_bone']; + elseif get(handles.radiobutton2,'Value') + handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1), '_soft']; + else + handles.myinfo(i).PatientName.GivenName = [handles.myinfo(i).PatientName.FamilyName(1), handles.myinfo(i).PatientName.GivenName(1)]; + end + + handles.myinfo(i).PatientName.FamilyName = [patient, '_', handles.myinfo(1).PatientID]; + + % Read and copy + dicomwrite(dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(caseID)))),[outputdir, newname, sprintf('%04d',i), '.dcm'], handles.myinfo(i)); +end + +set(handles.info_text, 'String', 'Copied') + + +function output_dir_Callback(hObject, eventdata, handles) +% hObject handle to output_dir (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hints: get(hObject,'String') returns contents of output_dir as text +% str2double(get(hObject,'String')) returns contents of output_dir as a double + + +% --- Executes during object creation, after setting all properties. +function output_dir_CreateFcn(hObject, eventdata, handles) +% hObject handle to output_dir (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles empty - handles not created until after all CreateFcns called + +% Hint: edit controls usually have a white background on Windows. +% See ISPC and COMPUTER. +if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) + set(hObject,'BackgroundColor','white'); +end + + +% --- Executes on button press in radiobutton1. +function radiobutton1_Callback(hObject, eventdata, handles) +% hObject handle to radiobutton1 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of radiobutton1 + +if get(hObject,'Value') + set(handles.radiobutton0,'Value',0) + set(handles.radiobutton2,'Value',0) +else + set(hObject,'Value',1) +end + + +% --- Executes on button press in radiobutton2. +function radiobutton2_Callback(hObject, eventdata, handles) +% hObject handle to radiobutton2 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of radiobutton2 + +if get(hObject,'Value') + set(handles.radiobutton0,'Value',0) + set(handles.radiobutton1,'Value',0) +else + set(hObject,'Value',1) +end + + +% --- Executes on button press in radiobutton0. +function radiobutton0_Callback(hObject, eventdata, handles) +% hObject handle to radiobutton0 (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of radiobutton0 + +if get(hObject,'Value') + set(handles.radiobutton1,'Value',0) + set(handles.radiobutton2,'Value',0) +else + set(hObject,'Value',1) +end diff --git a/CT/dicominfo2.m b/CT/dicominfo2.m index 297c52c..70b8a64 100644 --- a/CT/dicominfo2.m +++ b/CT/dicominfo2.m @@ -1,712 +1,712 @@ -function metadata = dicominfo2(filename, varargin) -%DICOMINFO Read metadata from DICOM message. -% INFO = DICOMINFO(FILENAME) reads the metadata from the compliant -% DICOM file specified in the string FILENAME. -% -% INFO = DICOMINFO(FILENAME, 'dictionary', D) uses the data dictionary -% file given in the string D to read the DICOM message. The file in D -% must be on the MATLAB search path. The default value is dicom-dict.mat. -% -% Example: -% -% info = dicominfo('CT-MONO2-16-ankle.dcm'); -% -% See also DICOMREAD, DICOMWRITE, DICOMDISP, DICOMDICT, DICOMUID. - -% Copyright 1993-2014 The MathWorks, Inc. -% MODIFIED by Raphaël Obrist (line 54) - - -% Parse input arguments. -if (nargin < 1) - - error(message('images:dicominfo:tooFewInputs')) - -end - -% Set the dictionary. -args = parseInputs(varargin{:}); -dicomdict('set_current', args.Dictionary) -dictionary = dicomdict('get_current'); - -% Get the metadata. -try - - % Get details about the file to read. - fileDetails = getFileDetails(filename); - - % Ensure the file is actually DICOM. - if (~isdicom(fileDetails.name)) - error(message('images:dicominfo:notDICOM')) - end - - % Parse the DICOM file. - attrs = dicomparse(fileDetails.name, ... - fileDetails.bytes, ... - getMachineEndian, ... - false, ... - dictionary); - - % Process the raw attributes. - [metadata,attrNames] = processMetadata(attrs, true, dictionary); - metadata = dicom_set_imfinfo_values(metadata); - metadata = setMoreImfinfoValues(metadata, fileDetails); - %metadata = processOverlays(metadata, dictionary); - metadata = processCurveData(metadata, attrs, attrNames); - - pixelDataField = dicomlookup_actions('7fe0', '0010', dictionary); - if (isfield(metadata, pixelDataField)) - metadata = rmfield(metadata, pixelDataField); - end - -catch ME - - dicomdict('reset_current'); - rethrow(ME) - -end - -% Reset the dictionary. -dicomdict('reset_current'); - - - -function [metadata,attrNames] = processMetadata(attrs, isTopLevel, dictionary) - -if (isempty(attrs)) - metadata = []; - return -end - -% Create a structure for the output and get the names of attributes. -[metadata, attrNames] = createMetadataStruct(attrs, isTopLevel, dictionary); - -% Fill the metadata structure, converting data along the way. -for currentAttr = 1:numel(attrNames) - - this = attrs(currentAttr); - metadata.(attrNames{currentAttr}) = convertRawAttr(this, dictionary); - -end - - - -function processedAttr = convertRawAttr(rawAttr, dictionary) - -% Information about whether to swap is contained in the attribute. -swap = needToSwap(rawAttr); - -% Determine the correct output encoding. -if (isempty(rawAttr.VR)) - - % Look up VR for implicit VR files. Use 'UN' for unknown - % tags. (See PS 3.5 Sec. 6.2.2.) - vr = findVRFromTag(rawAttr.Group, rawAttr.Element, dictionary); - if (~isempty(vr)) - - % Some attributes have a conditional VR. Pick the first. - rawAttr.VR = vr; - if (numel(rawAttr.VR) > 2) - rawAttr.VR = rawAttr.VR(1:2); - end - - else - rawAttr.VR = 'UN'; - end - -end - -% Convert raw data. (See PS 3.5 Sec. 6.2 for full VR details.) -switch (rawAttr.VR) -case {'AE','AS','CS','DA','DT','LO','LT','SH','ST','TM','UI','UT'} - - processedAttr = deblankAndStripNulls(char(rawAttr.Data)); - -case {'AT'} - - % For historical reasons don't transpose AT. - processedAttr = dicom_typecast(rawAttr.Data, 'uint16', swap); - -case {'DS', 'IS'} - - processedAttr = sscanf(char(rawAttr.Data), '%f\\'); - -case {'FL', 'OF'} - - processedAttr = dicom_typecast(rawAttr.Data, 'single', swap)'; - -case 'FD' - - processedAttr = dicom_typecast(rawAttr.Data, 'double', swap)'; - -case 'OB' - - processedAttr = rawAttr.Data'; - -case {'OW', 'US'} - - processedAttr = dicom_typecast(rawAttr.Data, 'uint16', swap)'; - -case 'PN' - - processedAttr = parsePerson(deblankAndStripNulls(char(rawAttr.Data))); - -case 'SL' - - processedAttr = dicom_typecast(rawAttr.Data, 'int32', swap)'; - -case 'SQ' - - processedAttr = parseSequence(rawAttr.Data, dictionary); - -case 'SS' - - processedAttr = dicom_typecast(rawAttr.Data, 'int16', swap)'; - -case 'UL' - - processedAttr = dicom_typecast(rawAttr.Data, 'uint32', swap)'; - -case 'UN' - - % It's possible that the attribute contains a private sequence - % with implicit VR; in which case the Data field contains the - % parsed sequence. - if (isstruct(rawAttr.Data)) - processedAttr = parseSequence(rawAttr.Data, dictionary); - else - processedAttr = rawAttr.Data'; - end - -otherwise - - % PS 3.5-1999 Sec. 6.2 indicates that all unknown VRs can be - % interpretted as UN. - processedAttr = rawAttr.Data'; - -end - -% Change empty arrays to 0-by-0. -if isempty(processedAttr) - processedAttr = reshape(processedAttr, [0 0]); -end - - - -function byteOrder = getMachineEndian - -persistent endian - -if (~isempty(endian)) - byteOrder = endian; - return -end - -[~, ~, endian] = computer; -byteOrder = endian; - - - -function args = parseInputs(varargin) - -% Set default values -args.Dictionary = dicomdict('get'); - -% Parse arguments based on their number. -if (nargin > 1) - - paramStrings = {'dictionary'}; - - % For each pair - for k = 1:2:length(varargin) - param = lower(varargin{k}); - - - if (~ischar(param)) - error(message('images:dicominfo:parameterNameNotString')); - end - - idx = strmatch(param, paramStrings); - - if (isempty(idx)) - error(message('images:dicominfo:unrecognizedParameterName', param)); - elseif (length(idx) > 1) - error(message('images:dicominfo:ambiguousParameterName', param)); - end - - switch (paramStrings{idx}) - case 'dictionary' - - if (k == length(varargin)) - error(message('images:dicominfo:missingDictionary')); - else - args.Dictionary = varargin{k + 1}; - end - - end % switch - - end % for - -end - - - -function personName = parsePerson(personString) -%PARSEPERSON Get the various parts of a person name - -% A description and examples of PN values is in PS 3.5-2000 Table 6.2-1. - -pnParts = {'FamilyName' - 'GivenName' - 'MiddleName' - 'NamePrefix' - 'NameSuffix'}; - -if (isempty(personString)) - personName = makePerson(pnParts); - return -end - -people = tokenize(personString, '\\'); % Must quote '\' for calls to STRREAD. - -personName = struct([]); - -for p = 1:length(people) - - % ASCII, ideographic, and phonetic characters are separated by '='. - components = tokenize(people{p}, '='); - - if (isempty(components)) - personName = makePerson(pnParts); - return - end - - - % Only use ASCII parts. - - if (~isempty(components{1})) - - % Get the separate parts of the person's name from the component. - componentParts = tokenize(components{1}, '^'); - - % The DICOM standard requires that PN values have five or fewer - % values separated by "^". Some vendors produce files with more - % than these person name parts. - if (numel(componentParts) <= 5) - - % If there are the correct numbers, put them in separate fields. - for q = 1:length(componentParts) - - personName(p).(pnParts{q}) = componentParts{q}; - - end - - else - - % If there are more, just return the whole string. - personName(p).FamilyName = people{p}; - - end - - else - - % Use full string as value if no ASCII is present. - if (~isempty(components)) - personName(p).FamilyName = people{p}; - end - - end - -end - - - -function personStruct = makePerson(pnParts) -%MAKEPERSON Make an empty struct containing the PN fields. - -for p = 1:numel(pnParts) - personStruct.(pnParts{p}) = ''; -end - - - -function processedStruct = parseSequence(attrs, dictionary) - -numItems = countItems(attrs); -itemNames = getItemNames(numItems); - -% Initialize the structure to contain this structure. -structInitializer = cat(1, itemNames, cell(1, numItems)); -processedStruct = struct(structInitializer{:}); - -% Process each item (but not delimiters). -item = 0; -for idx = 1:numel(attrs) - - this = attrs(idx); - if (~isDelimiter(this)) - item = item + 1; - processedStruct.(itemNames{item}) = processMetadata(this.Data, false, dictionary); - end - -end - - - -function header = getImfinfoFields - -header = {'Filename', '' - 'FileModDate', '' - 'FileSize', [] - 'Format', 'DICOM' - 'FormatVersion', 3.0 - 'Width', [] - 'Height', [] - 'BitDepth', [] - 'ColorType', ''}'; - - - -function metadata = setMoreImfinfoValues(metadata, d) - -metadata.Filename = d.name; -metadata.FileModDate = d.date; -metadata.FileSize = d.bytes; - - - -function details = getFileDetails(filename) - -% Get the fully qualified path to the file. -fid = fopen(filename); -if (fid > 0) - - fullPathFilename = fopen(fid); - fclose(fid); - -else - - % Look for the file with a different extension. - file = dicom_create_file_struct; - file.Filename = filename; - file = dicom_get_msg(file); - - if (isempty(file.Filename)) - error(message('images:dicominfo:noFileOrMessagesFound', filename)); - end - - % dicom_get_msg looks up the fully qualified path. - fullPathFilename = file.Filename; - -end - -details = dir(fullPathFilename); -details.name = fullPathFilename; - - - -function [metadata, attrNames] = createMetadataStruct(attrs, isTopLevel, dictionary) - -% Get the attribute names. -totalAttrs = numel(attrs); -attrNames = cell(1, totalAttrs); - -for currentAttr = 1:totalAttrs - attrNames{currentAttr} = ... - dicomlookup_actions(attrs(currentAttr).Group, ... - attrs(currentAttr).Element, ... - dictionary); - - % Empty attributes indicate that a public/retired attribute was - % not found in the data dictionary. This used to be an error - % condition, but is easily resolved by providing a special - % attribute name. - if (isempty(attrNames{currentAttr})) - attrNames{currentAttr} = sprintf('Unknown_%04X_%04X', ... - attrs(currentAttr).Group, ... - attrs(currentAttr).Element); - end -end - -% Remove duplicate attribute names. Keep the last appearance of the attribute. -[tmp, reorderIdx] = unique(attrNames); -if (numel(tmp) ~= totalAttrs) - warning(message('images:dicominfo:attrWithSameName')) -end - -uniqueAttrNames = attrNames(sort(reorderIdx)); -uniqueTotalAttrs = numel(uniqueAttrNames); - -% Create a metadata structure to hold the parsed attributes. Use a -% cell array initializer, which has a populated section for IMFINFO -% data and an unitialized section for the attributes from the DICOM -% file. -if (isTopLevel) - structInitializer = cat(2, getImfinfoFields(), ... - cat(1, uniqueAttrNames, cell(1, uniqueTotalAttrs))); -else - structInitializer = cat(1, uniqueAttrNames, cell(1, uniqueTotalAttrs)); -end - -metadata = struct(structInitializer{:}); - - - -function str = deblankAndStripNulls(str) -%DEBLANKANDDENULL Deblank a string, treating char(0) as a blank. - -if (isempty(str)) - return -end - -while (~isempty(str) && (str(end) == 0)) - str(end) = ''; -end - -str = deblank(str); - - - -function vr = findVRFromTag(group, element, dictionary) - -% Look up the attribute. -attr = dicomlookup_helper(group, element, dictionary); - -% Get the vr. -if (~isempty(attr)) - - vr = attr.VR; - -else - - % Private creator attributes should be treated as CS. - if ((rem(group, 2) == 1) && (element == 0)) - vr = 'UL'; - elseif ((rem(group, 2) == 1) && (element < 256)) - vr = 'CS'; - else - vr = 'UN'; - end - -end - - - -function out = processOverlays(in, dictionary) - -out = in; - -% Look for overlays. -allFields = fieldnames(in); -idx = strmatch('OverlayData', allFields); - -if (isempty(idx)) - return -end - -% Convert each overlay data attribute. -for p = 1:numel(idx) - - olName = allFields{idx(p)}; - - % The overlay fields can be present but empty. - if (isempty(in.(olName))) - continue; - end - - % Which repeating group is this? - [group, element] = dicomlookup_actions(olName, dictionary); - - % Get relevant details. All overlays are in groups 6000 - 60FE. - overlay.Rows = double(in.(dicomlookup_actions(group, '0010', dictionary))); - overlay.Columns = double(in.(dicomlookup_actions(group, '0011', dictionary))); - - sppTag = dicomlookup_actions(group, '0012', dictionary); - if (isfield(in, sppTag)) - overlay.SamplesPerPixel = double(in.(sppTag)); - else - overlay.SamplesPerPixel = 1; - end - - bitsTag = dicomlookup_actions(group, '0100', dictionary); - if (isfield(in, bitsTag)) - overlay.BitsAllocated = double(in.(bitsTag)); - else - overlay.BitsAllocated = 1; - end - - numTag = dicomlookup_actions(group, '0015', dictionary); - if (isfield(in, numTag)) - overlay.NumberOfFrames = double(in.(numTag)); - else - overlay.NumberOfFrames = 1; - end - - % We could potential support more overlays later. - if ((overlay.BitsAllocated > 1) || (overlay.SamplesPerPixel > 1)) - - warning(message('images:dicominfo:unsupportedOverlay', sprintf( '(%04X,%04X)', group, element ))); - continue; - - end - - % Process the overlay. - for frame = 1:(overlay.NumberOfFrames) - - overlayData = tobits(in.(olName)); - numSamples = overlay.Columns * overlay.Rows * overlay.NumberOfFrames; - out.(olName) = permute(reshape(overlayData(1:numSamples), ... - overlay.Columns, ... - overlay.Rows, ... - overlay.NumberOfFrames), ... - [2 1 3]); - - end - -end - - - -function out = processCurveData(in, attrs, attrNames) -% Reference - PS 3.3 - 2003 C 10.2. The Curve Data's final data type -% depends on DataValueRepresentation. Process those attributes here. - -% Passing in attrs because we may need to swap the data depending on the -% endianness of the machine and the attribute. - -% default -out = in; - -% Look for Curve Data. -allFields = fieldnames(in); -idx = strmatch('CurveData', allFields); - -if (isempty(idx)) - return -end - -% All the Curve Data attributes will have the same endianness, so we just need -% to check one attribute and apply that setting to the rest. - -curveDataName = allFields{idx(1)}; -curveDataLoc = strncmp(curveDataName,attrNames, length(curveDataName)); -swap = needToSwap(attrs(curveDataLoc)); - -for p = 1 : numel(idx) - - curveDataName = allFields{idx(p)}; - - underscore = strfind(curveDataName,'_'); - % The data type of the Curve Data comes from the - % DataValueRepresentation attribute. - numOfRepeatedAttr = curveDataName(underscore+1:end); - dvrName = strcat('DataValueRepresentation','_',numOfRepeatedAttr); - - if ~isfield(in,dvrName) - % do nothing - continue; - else - - dataType = in.(dvrName); - % See PS 3.3-2003, C 10.2.1.2 - switch dataType - case 0 - expDataType = 'uint16'; - case 1 - expDataType = 'int16'; - case 2 - expDataType = 'single'; - case 3 - expDataType = 'double'; - case 4 - expDataType = 'int32'; - otherwise - warning(message('images:dicominfo:unknownDataType', dataType)); - end - - % We need to undo any previous swapping before calling typecast, and do the - % final swapping afterwards. - - if swap - out.(curveDataName) = ... - swapbytes(typecast(swapbytes(in.(curveDataName)), ... - expDataType)); - else - out.(curveDataName) = typecast(in.(curveDataName), expDataType); - end - end -end - - - -function itemNames = getItemNames(numberOfItems) - -% Create a cell array of item names, which can be quickly used. -persistent namesCell -if (isempty(namesCell)) - namesCell = generateItemNames(50); -end - -% If the number of cached names is too small, expand it and recache. -if (numberOfItems > numel(namesCell)) - namesCell = generateItemNames(numberOfItems); -end - -% Return the first n item names. -itemNames = namesCell(1:numberOfItems); - - - -function namesCell = generateItemNames(numberOfItems) - -namesCell = cell(1, numberOfItems); -for idx = 1:numberOfItems - namesCell{idx} = sprintf('Item_%d', idx); -end - - - -function tf = needToSwap(currentAttr) - -switch (getMachineEndian) -case 'L' - if (currentAttr.IsLittleEndian) - tf = false; - else - tf = true; - end - -case 'B' - if (currentAttr.IsLittleEndian) - tf = true; - else - tf = false; - end - -otherwise - error(message('images:dicominfo:unknownEndian', getMachineEndian)) - -end - - - -function tf = isDelimiter(attr) - -% True if (FFFE,E00D) or (FFFE,E0DD). -tf = (attr.Group == 65534) && ... - ((attr.Element == 57357) || (attr.Element == 57565)); - - - -function count = countItems(attrs) - -if (isempty(attrs)) - count = 0; -else - % Find the items (FFFE,E000) in the array of attributes (all of - % which are item tags or delimiters; no normal attributes - % appear in attrs here). - idx = find(([attrs(:).Group] == 65534) & ... - ([attrs(:).Element] == 57344)); - count = numel(idx); +function metadata = dicominfo2(filename, varargin) +%DICOMINFO Read metadata from DICOM message. +% INFO = DICOMINFO(FILENAME) reads the metadata from the compliant +% DICOM file specified in the string FILENAME. +% +% INFO = DICOMINFO(FILENAME, 'dictionary', D) uses the data dictionary +% file given in the string D to read the DICOM message. The file in D +% must be on the MATLAB search path. The default value is dicom-dict.mat. +% +% Example: +% +% info = dicominfo('CT-MONO2-16-ankle.dcm'); +% +% See also DICOMREAD, DICOMWRITE, DICOMDISP, DICOMDICT, DICOMUID. + +% Copyright 1993-2014 The MathWorks, Inc. +% MODIFIED by Raphaël Obrist (line 54) + + +% Parse input arguments. +if (nargin < 1) + + error(message('images:dicominfo:tooFewInputs')) + +end + +% Set the dictionary. +args = parseInputs(varargin{:}); +dicomdict('set_current', args.Dictionary) +dictionary = dicomdict('get_current'); + +% Get the metadata. +try + + % Get details about the file to read. + fileDetails = getFileDetails(filename); + + % Ensure the file is actually DICOM. + if (~isdicom(fileDetails.name)) + error(message('images:dicominfo:notDICOM')) + end + + % Parse the DICOM file. + attrs = dicomparse(fileDetails.name, ... + fileDetails.bytes, ... + getMachineEndian, ... + false, ... + dictionary); + + % Process the raw attributes. + [metadata,attrNames] = processMetadata(attrs, true, dictionary); + metadata = dicom_set_imfinfo_values(metadata); + metadata = setMoreImfinfoValues(metadata, fileDetails); + %metadata = processOverlays(metadata, dictionary); + metadata = processCurveData(metadata, attrs, attrNames); + + pixelDataField = dicomlookup_actions('7fe0', '0010', dictionary); + if (isfield(metadata, pixelDataField)) + metadata = rmfield(metadata, pixelDataField); + end + +catch ME + + dicomdict('reset_current'); + rethrow(ME) + +end + +% Reset the dictionary. +dicomdict('reset_current'); + + + +function [metadata,attrNames] = processMetadata(attrs, isTopLevel, dictionary) + +if (isempty(attrs)) + metadata = []; + return +end + +% Create a structure for the output and get the names of attributes. +[metadata, attrNames] = createMetadataStruct(attrs, isTopLevel, dictionary); + +% Fill the metadata structure, converting data along the way. +for currentAttr = 1:numel(attrNames) + + this = attrs(currentAttr); + metadata.(attrNames{currentAttr}) = convertRawAttr(this, dictionary); + +end + + + +function processedAttr = convertRawAttr(rawAttr, dictionary) + +% Information about whether to swap is contained in the attribute. +swap = needToSwap(rawAttr); + +% Determine the correct output encoding. +if (isempty(rawAttr.VR)) + + % Look up VR for implicit VR files. Use 'UN' for unknown + % tags. (See PS 3.5 Sec. 6.2.2.) + vr = findVRFromTag(rawAttr.Group, rawAttr.Element, dictionary); + if (~isempty(vr)) + + % Some attributes have a conditional VR. Pick the first. + rawAttr.VR = vr; + if (numel(rawAttr.VR) > 2) + rawAttr.VR = rawAttr.VR(1:2); + end + + else + rawAttr.VR = 'UN'; + end + +end + +% Convert raw data. (See PS 3.5 Sec. 6.2 for full VR details.) +switch (rawAttr.VR) +case {'AE','AS','CS','DA','DT','LO','LT','SH','ST','TM','UI','UT'} + + processedAttr = deblankAndStripNulls(char(rawAttr.Data)); + +case {'AT'} + + % For historical reasons don't transpose AT. + processedAttr = dicom_typecast(rawAttr.Data, 'uint16', swap); + +case {'DS', 'IS'} + + processedAttr = sscanf(char(rawAttr.Data), '%f\\'); + +case {'FL', 'OF'} + + processedAttr = dicom_typecast(rawAttr.Data, 'single', swap)'; + +case 'FD' + + processedAttr = dicom_typecast(rawAttr.Data, 'double', swap)'; + +case 'OB' + + processedAttr = rawAttr.Data'; + +case {'OW', 'US'} + + processedAttr = dicom_typecast(rawAttr.Data, 'uint16', swap)'; + +case 'PN' + + processedAttr = parsePerson(deblankAndStripNulls(char(rawAttr.Data))); + +case 'SL' + + processedAttr = dicom_typecast(rawAttr.Data, 'int32', swap)'; + +case 'SQ' + + processedAttr = parseSequence(rawAttr.Data, dictionary); + +case 'SS' + + processedAttr = dicom_typecast(rawAttr.Data, 'int16', swap)'; + +case 'UL' + + processedAttr = dicom_typecast(rawAttr.Data, 'uint32', swap)'; + +case 'UN' + + % It's possible that the attribute contains a private sequence + % with implicit VR; in which case the Data field contains the + % parsed sequence. + if (isstruct(rawAttr.Data)) + processedAttr = parseSequence(rawAttr.Data, dictionary); + else + processedAttr = rawAttr.Data'; + end + +otherwise + + % PS 3.5-1999 Sec. 6.2 indicates that all unknown VRs can be + % interpretted as UN. + processedAttr = rawAttr.Data'; + +end + +% Change empty arrays to 0-by-0. +if isempty(processedAttr) + processedAttr = reshape(processedAttr, [0 0]); +end + + + +function byteOrder = getMachineEndian + +persistent endian + +if (~isempty(endian)) + byteOrder = endian; + return +end + +[~, ~, endian] = computer; +byteOrder = endian; + + + +function args = parseInputs(varargin) + +% Set default values +args.Dictionary = dicomdict('get'); + +% Parse arguments based on their number. +if (nargin > 1) + + paramStrings = {'dictionary'}; + + % For each pair + for k = 1:2:length(varargin) + param = lower(varargin{k}); + + + if (~ischar(param)) + error(message('images:dicominfo:parameterNameNotString')); + end + + idx = strmatch(param, paramStrings); + + if (isempty(idx)) + error(message('images:dicominfo:unrecognizedParameterName', param)); + elseif (length(idx) > 1) + error(message('images:dicominfo:ambiguousParameterName', param)); + end + + switch (paramStrings{idx}) + case 'dictionary' + + if (k == length(varargin)) + error(message('images:dicominfo:missingDictionary')); + else + args.Dictionary = varargin{k + 1}; + end + + end % switch + + end % for + +end + + + +function personName = parsePerson(personString) +%PARSEPERSON Get the various parts of a person name + +% A description and examples of PN values is in PS 3.5-2000 Table 6.2-1. + +pnParts = {'FamilyName' + 'GivenName' + 'MiddleName' + 'NamePrefix' + 'NameSuffix'}; + +if (isempty(personString)) + personName = makePerson(pnParts); + return +end + +people = tokenize(personString, '\\'); % Must quote '\' for calls to STRREAD. + +personName = struct([]); + +for p = 1:length(people) + + % ASCII, ideographic, and phonetic characters are separated by '='. + components = tokenize(people{p}, '='); + + if (isempty(components)) + personName = makePerson(pnParts); + return + end + + + % Only use ASCII parts. + + if (~isempty(components{1})) + + % Get the separate parts of the person's name from the component. + componentParts = tokenize(components{1}, '^'); + + % The DICOM standard requires that PN values have five or fewer + % values separated by "^". Some vendors produce files with more + % than these person name parts. + if (numel(componentParts) <= 5) + + % If there are the correct numbers, put them in separate fields. + for q = 1:length(componentParts) + + personName(p).(pnParts{q}) = componentParts{q}; + + end + + else + + % If there are more, just return the whole string. + personName(p).FamilyName = people{p}; + + end + + else + + % Use full string as value if no ASCII is present. + if (~isempty(components)) + personName(p).FamilyName = people{p}; + end + + end + +end + + + +function personStruct = makePerson(pnParts) +%MAKEPERSON Make an empty struct containing the PN fields. + +for p = 1:numel(pnParts) + personStruct.(pnParts{p}) = ''; +end + + + +function processedStruct = parseSequence(attrs, dictionary) + +numItems = countItems(attrs); +itemNames = getItemNames(numItems); + +% Initialize the structure to contain this structure. +structInitializer = cat(1, itemNames, cell(1, numItems)); +processedStruct = struct(structInitializer{:}); + +% Process each item (but not delimiters). +item = 0; +for idx = 1:numel(attrs) + + this = attrs(idx); + if (~isDelimiter(this)) + item = item + 1; + processedStruct.(itemNames{item}) = processMetadata(this.Data, false, dictionary); + end + +end + + + +function header = getImfinfoFields + +header = {'Filename', '' + 'FileModDate', '' + 'FileSize', [] + 'Format', 'DICOM' + 'FormatVersion', 3.0 + 'Width', [] + 'Height', [] + 'BitDepth', [] + 'ColorType', ''}'; + + + +function metadata = setMoreImfinfoValues(metadata, d) + +metadata.Filename = d.name; +metadata.FileModDate = d.date; +metadata.FileSize = d.bytes; + + + +function details = getFileDetails(filename) + +% Get the fully qualified path to the file. +fid = fopen(filename); +if (fid > 0) + + fullPathFilename = fopen(fid); + fclose(fid); + +else + + % Look for the file with a different extension. + file = dicom_create_file_struct; + file.Filename = filename; + file = dicom_get_msg(file); + + if (isempty(file.Filename)) + error(message('images:dicominfo:noFileOrMessagesFound', filename)); + end + + % dicom_get_msg looks up the fully qualified path. + fullPathFilename = file.Filename; + +end + +details = dir(fullPathFilename); +details.name = fullPathFilename; + + + +function [metadata, attrNames] = createMetadataStruct(attrs, isTopLevel, dictionary) + +% Get the attribute names. +totalAttrs = numel(attrs); +attrNames = cell(1, totalAttrs); + +for currentAttr = 1:totalAttrs + attrNames{currentAttr} = ... + dicomlookup_actions(attrs(currentAttr).Group, ... + attrs(currentAttr).Element, ... + dictionary); + + % Empty attributes indicate that a public/retired attribute was + % not found in the data dictionary. This used to be an error + % condition, but is easily resolved by providing a special + % attribute name. + if (isempty(attrNames{currentAttr})) + attrNames{currentAttr} = sprintf('Unknown_%04X_%04X', ... + attrs(currentAttr).Group, ... + attrs(currentAttr).Element); + end +end + +% Remove duplicate attribute names. Keep the last appearance of the attribute. +[tmp, reorderIdx] = unique(attrNames); +if (numel(tmp) ~= totalAttrs) + warning(message('images:dicominfo:attrWithSameName')) +end + +uniqueAttrNames = attrNames(sort(reorderIdx)); +uniqueTotalAttrs = numel(uniqueAttrNames); + +% Create a metadata structure to hold the parsed attributes. Use a +% cell array initializer, which has a populated section for IMFINFO +% data and an unitialized section for the attributes from the DICOM +% file. +if (isTopLevel) + structInitializer = cat(2, getImfinfoFields(), ... + cat(1, uniqueAttrNames, cell(1, uniqueTotalAttrs))); +else + structInitializer = cat(1, uniqueAttrNames, cell(1, uniqueTotalAttrs)); +end + +metadata = struct(structInitializer{:}); + + + +function str = deblankAndStripNulls(str) +%DEBLANKANDDENULL Deblank a string, treating char(0) as a blank. + +if (isempty(str)) + return +end + +while (~isempty(str) && (str(end) == 0)) + str(end) = ''; +end + +str = deblank(str); + + + +function vr = findVRFromTag(group, element, dictionary) + +% Look up the attribute. +attr = dicomlookup_helper(group, element, dictionary); + +% Get the vr. +if (~isempty(attr)) + + vr = attr.VR; + +else + + % Private creator attributes should be treated as CS. + if ((rem(group, 2) == 1) && (element == 0)) + vr = 'UL'; + elseif ((rem(group, 2) == 1) && (element < 256)) + vr = 'CS'; + else + vr = 'UN'; + end + +end + + + +function out = processOverlays(in, dictionary) + +out = in; + +% Look for overlays. +allFields = fieldnames(in); +idx = strmatch('OverlayData', allFields); + +if (isempty(idx)) + return +end + +% Convert each overlay data attribute. +for p = 1:numel(idx) + + olName = allFields{idx(p)}; + + % The overlay fields can be present but empty. + if (isempty(in.(olName))) + continue; + end + + % Which repeating group is this? + [group, element] = dicomlookup_actions(olName, dictionary); + + % Get relevant details. All overlays are in groups 6000 - 60FE. + overlay.Rows = double(in.(dicomlookup_actions(group, '0010', dictionary))); + overlay.Columns = double(in.(dicomlookup_actions(group, '0011', dictionary))); + + sppTag = dicomlookup_actions(group, '0012', dictionary); + if (isfield(in, sppTag)) + overlay.SamplesPerPixel = double(in.(sppTag)); + else + overlay.SamplesPerPixel = 1; + end + + bitsTag = dicomlookup_actions(group, '0100', dictionary); + if (isfield(in, bitsTag)) + overlay.BitsAllocated = double(in.(bitsTag)); + else + overlay.BitsAllocated = 1; + end + + numTag = dicomlookup_actions(group, '0015', dictionary); + if (isfield(in, numTag)) + overlay.NumberOfFrames = double(in.(numTag)); + else + overlay.NumberOfFrames = 1; + end + + % We could potential support more overlays later. + if ((overlay.BitsAllocated > 1) || (overlay.SamplesPerPixel > 1)) + + warning(message('images:dicominfo:unsupportedOverlay', sprintf( '(%04X,%04X)', group, element ))); + continue; + + end + + % Process the overlay. + for frame = 1:(overlay.NumberOfFrames) + + overlayData = tobits(in.(olName)); + numSamples = overlay.Columns * overlay.Rows * overlay.NumberOfFrames; + out.(olName) = permute(reshape(overlayData(1:numSamples), ... + overlay.Columns, ... + overlay.Rows, ... + overlay.NumberOfFrames), ... + [2 1 3]); + + end + +end + + + +function out = processCurveData(in, attrs, attrNames) +% Reference - PS 3.3 - 2003 C 10.2. The Curve Data's final data type +% depends on DataValueRepresentation. Process those attributes here. + +% Passing in attrs because we may need to swap the data depending on the +% endianness of the machine and the attribute. + +% default +out = in; + +% Look for Curve Data. +allFields = fieldnames(in); +idx = strmatch('CurveData', allFields); + +if (isempty(idx)) + return +end + +% All the Curve Data attributes will have the same endianness, so we just need +% to check one attribute and apply that setting to the rest. + +curveDataName = allFields{idx(1)}; +curveDataLoc = strncmp(curveDataName,attrNames, length(curveDataName)); +swap = needToSwap(attrs(curveDataLoc)); + +for p = 1 : numel(idx) + + curveDataName = allFields{idx(p)}; + + underscore = strfind(curveDataName,'_'); + % The data type of the Curve Data comes from the + % DataValueRepresentation attribute. + numOfRepeatedAttr = curveDataName(underscore+1:end); + dvrName = strcat('DataValueRepresentation','_',numOfRepeatedAttr); + + if ~isfield(in,dvrName) + % do nothing + continue; + else + + dataType = in.(dvrName); + % See PS 3.3-2003, C 10.2.1.2 + switch dataType + case 0 + expDataType = 'uint16'; + case 1 + expDataType = 'int16'; + case 2 + expDataType = 'single'; + case 3 + expDataType = 'double'; + case 4 + expDataType = 'int32'; + otherwise + warning(message('images:dicominfo:unknownDataType', dataType)); + end + + % We need to undo any previous swapping before calling typecast, and do the + % final swapping afterwards. + + if swap + out.(curveDataName) = ... + swapbytes(typecast(swapbytes(in.(curveDataName)), ... + expDataType)); + else + out.(curveDataName) = typecast(in.(curveDataName), expDataType); + end + end +end + + + +function itemNames = getItemNames(numberOfItems) + +% Create a cell array of item names, which can be quickly used. +persistent namesCell +if (isempty(namesCell)) + namesCell = generateItemNames(50); +end + +% If the number of cached names is too small, expand it and recache. +if (numberOfItems > numel(namesCell)) + namesCell = generateItemNames(numberOfItems); +end + +% Return the first n item names. +itemNames = namesCell(1:numberOfItems); + + + +function namesCell = generateItemNames(numberOfItems) + +namesCell = cell(1, numberOfItems); +for idx = 1:numberOfItems + namesCell{idx} = sprintf('Item_%d', idx); +end + + + +function tf = needToSwap(currentAttr) + +switch (getMachineEndian) +case 'L' + if (currentAttr.IsLittleEndian) + tf = false; + else + tf = true; + end + +case 'B' + if (currentAttr.IsLittleEndian) + tf = true; + else + tf = false; + end + +otherwise + error(message('images:dicominfo:unknownEndian', getMachineEndian)) + +end + + + +function tf = isDelimiter(attr) + +% True if (FFFE,E00D) or (FFFE,E0DD). +tf = (attr.Group == 65534) && ... + ((attr.Element == 57357) || (attr.Element == 57565)); + + + +function count = countItems(attrs) + +if (isempty(attrs)) + count = 0; +else + % Find the items (FFFE,E000) in the array of attributes (all of + % which are item tags or delimiters; no normal attributes + % appear in attrs here). + idx = find(([attrs(:).Group] == 65534) & ... + ([attrs(:).Element] == 57344)); + count = numel(idx); end \ No newline at end of file diff --git a/CT/infoCT.m b/CT/infoCT.m index 7aa43af..357888f 100644 --- a/CT/infoCT.m +++ b/CT/infoCT.m @@ -1,319 +1,321 @@ -%% infoCT - -% This script lists the technical and the personal informations linked to the dicoms for -% "Pathologic" and "Normal" patients and returns the excel file "InfoCT". -% Test -% - -% INPUT: 'normal' or 'pathologic' - -% Author: RO-EPFL-LBO -% Date: 2016-09-05 -%% -function infoCT - -addpath(genpath('CT')); -addpath(genpath('XLS_MySQL_DB')); -addpath(genpath('muscles')); -addpath(genpath('anatomy')); -addpath(genpath('Generated_Amira_TCL_Scripts')); - -XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase - -list{1,1} = 'sCase.id'; -list{1,2} = 'patient.IPP'; -list{1,3} = 'patient.initials'; -list{1,4} = 'patient.gender'; -list{1,5} = 'patient.birth_date'; -%list{1,6} = 'patient.age'; -%list{1,7} = 'patient.ethnicity'; -list{1,6} = 'patient.height'; -list{1,7} = 'patient.weight'; -%list{1,10} = 'patient.BMI'; -list{1,8} = 'shoulder.side'; -list{1,9} = 'CT.date'; -list{1,10} = 'CT.kernel'; -list{1,11} = 'CT.pixel_size'; -list{1,12} = 'CT.slice_spacing'; -list{1,13} = 'CT.slice_thickness'; -list{1,14} = 'CT.tension'; -list{1,15} = 'CT.current'; -list{1,16} = 'CT.institution'; -list{1,17} = 'CT.manufacturer'; -list{1,18} = 'CT.model'; -list{1,19} = 'CT.protocol'; - -directory = '../../../data'; - - -%% Loop over all the normal and pathologic cases - - location = 'N'; - l=1; - rootDir = '../../../data/N'; - for levelDir1 = 0:9 - for levelDir2 = 0:9 - curentDir = sprintf('%s/%d/%d/N*', rootDir, levelDir1, levelDir2); - folderInfo = dir(curentDir); - for i=1:1:numel(folderInfo) - name = folderInfo(i).name; - sCases_normal{l,1} = folderInfo(i).name; - sCases_normal{l,2} = levelDir1; - sCases_normal{l,3} = levelDir2; - sCases_normal{l,4} = 'N'; - l=l+1; - end - end - end - - location = 'P'; - l=1; - rootDir = '../../../data/P'; - for levelDir1 = 0:9 - for levelDir2 = 0:9 - curentDir = sprintf('%s/%d/%d/P*', rootDir, levelDir1, levelDir2); - folderInfo = dir(curentDir); - - for i=1:1:numel(folderInfo) - name = folderInfo(i).name; - sCases_pathologic{l,1} = folderInfo(i).name; - sCases_pathologic{l,2} = levelDir1; - sCases_pathologic{l,3} = levelDir2; - sCases_pathologic{l,4} = 'P'; - l=l+1; - end - end - end - - sCases = [sCases_normal;sCases_pathologic]; - -%% Data exportation from dicom -for j=2:1:numel(sCases(:,1))+1 - - patient = char(sCases(j-1,1)) - levelDir1 = cell2mat(sCases(j-1,2)); - levelDir2 = cell2mat(sCases(j-1,3)); - location = char(sCases(j-1,4)); - finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); - - if exist(sprintf('%s/%s/CT-%s-1/dicom', finalDirectory,patient,patient),'dir') == 7 %if a folder "dicom" exist, the patient name is added to the "measured" column -% directoryin=list(j,1); -% directoryin=char(directoryin); - - ctdirectoryin = sprintf('%s/%s/CT-%s-1/dicom',finalDirectory, patient, patient); - images = dir(ctdirectoryin); - ct=images(10).name; - ct2=images(11).name; - inpath=[ctdirectoryin,'/',ct]; - inpath2=[ctdirectoryin,'/',ct2]; - info=dicominfo2(inpath); - info2=dicominfo2(inpath2); - - - - %Patient# - list{j,1} = strtok(patient,'-'); - - %Weight - tf = isfield(info,'PatientWeight'); % Test si le champ "PatientWeight" existe - - if tf==1 - weight=info.PatientWeight; - else - weight=' '; - end - list{j,7} = weight; - - %Height - tf = isfield(info,'PatientSize'); % Test si le champ "PatientSize" existe - - if tf==1 - Size=info.PatientSize; - Size=Size*100; - Size=round(Size,0); - else - Size=' '; - end - list{j,6} = Size; - - %Gender - tf = isfield(info,'PatientSex'); % Test si le champ "PatientSex" existe - - if tf==1 - gender=info.PatientSex; - else - gender=' '; - end - list{j,4} = gender; - - %Birth date - tf = isfield(info,'PatientBirthDate'); % Test si le champ "PatientBirthDate" existe - - if tf==1 - date=info.PatientBirthDate; - date=[date(1:4) '.' date(5:6) '.' date(7:8)]; - else - date=' '; - end - list{j,5} = date; - - %CT date - tf = isfield(info,'AcquisitionDate'); % Test si le champ "AcquisitionDate" existe - - if tf==1 - ctdate=info.AcquisitionDate; - ctdate=[ctdate(1:4) '.' ctdate(5:6) '.' ctdate(7:8)]; - else - ctdate=' '; - end - list{j,9} = ctdate; - - %IPP - tf = isfield(info,'PatientID'); % Test si le champ "PatientID" existe - - if tf==1 - - id=info.PatientID; - else - - id=' '; - end - list{j,2} = id; - - %Kernel - tf = isfield(info,'ConvolutionKernel'); % Test si le champ "ConvolutionKernel" existe - - if tf==1 - kernel=info.ConvolutionKernel; - else - kernel=' '; - end - list{j,10} = kernel; - - %Pixel Size - tf = isfield(info,'PixelSpacing'); % Test si le champ "PixelSpacing" existe - - if tf==1 - pixel1=info.PixelSpacing(1); - %pixel2=info.PixelSpacing(2); - pixel1=num2str(pixel1); - %pixel2=num2str(pixel2); - %pixel= ['[',pixel1,',',pixel2,']']; - pixel= [,pixel1,]; - else - pixel=' '; - end - list{j,11} = pixel; - - %Slice Thickness - tf = isfield(info,'SliceThickness'); % Test si le champ "SliceThickness" existe - - if tf==1 - thickness=info.SliceThickness; - else - thickness=' '; - end - list{j,13} = thickness; - - %Tension - tf = isfield(info,'KVP'); % Test si le champ "KVP" existe - - if tf==1 - tension=info.KVP; - else - tension=' '; - end - list{j,14} = tension; - - - %Current - tf = isfield(info,'XrayTubeCurrent'); % Test si le champ "XrayTubeCurrent" existe - - if tf==1 - current=info.XrayTubeCurrent; - else - current=' '; - end - list{j,15} = current; - - - %Institution Name - tf = isfield(info,'InstitutionName'); % Test si le champ "InstitutionName" existe - - if tf==1 - instname=info.InstitutionName; - else - instname=' '; - end - list{j,16} = instname; - - %Manufacturer - tf = isfield(info,'Manufacturer'); % Test si le champ "Manufacturer" existe - - if tf==1 - manufacturer=info.Manufacturer; - else - manufacturer=' '; - end - list{j,17} = manufacturer; - - %Model Name - tf = isfield(info,'ManufacturerModelName'); % Test si le champ "ManufacturerModelName" existe - - if tf==1 - manufacturermn=info.ManufacturerModelName; - else - manufacturermn=' '; - end - list{j,18} = manufacturermn; - - %Protocol Name - tf = isfield(info,'ProtocolName'); % Test si le champ "ProtocolName" existe - - if tf==1 - protocol=info.ProtocolName; - else - protocol=' '; - end - list{j,19} = protocol; - - %Spacing Between Slices - tf = isfield(info,'ImagePositionPatient'); % Test si le champ "ImagePositionPatient" existe - - if tf==1 - pos1 = info.ImagePositionPatient(3); - pos2 = info2.ImagePositionPatient(3); - %pixel1=num2str(pixel1); - %pixel2=num2str(pixel2); - %pixel= ['[',pixel1,',',pixel2,']']; - space2= abs(pos1-pos2); - else - space2=' '; - end - list{j,12} = space2; - - % %Patient Name - % tf = isfield(info,'PatientName'); % Test si le champ "PatientName" existe - % - % if tf==1 - % name = info.PatientName.FamilyName; - % - % %pixel1=num2str(pixel1); - % - % else - % name=' '; - % end - % list{j,8} = name; - else - list{j,1} = strtok(patient,'-'); - end - if strcmp(patient,'P410-95295') - end -end -delete([XLSShoulderDatabaseLocation 'xlsFromMatlab/CT.xls']); -xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/CT'], list) - -addpath('XLS_MySQL_DB'); -MainExcel2SQL; -end - +%% infoCT + +% This script lists the technical and the personal informations linked to the dicoms for +% "Pathologic" and "Normal" patients and returns the excel file "InfoCT". +% +% Alex Terrier comment: This scipt only works with matlab R2015a from a Windows system. It is +% using a modified version of dicominfo.m (dicominfo2.m) and a modified +% private function directory. This script should be updated and abandoned. + +% INPUT: 'normal' or 'pathologic' + +% Author: RO-EPFL-LBO +% Date: 2016-09-05 +%% +function infoCT + +addpath(genpath('CT')); +addpath(genpath('XLS_MySQL_DB')); +addpath(genpath('muscles')); +addpath(genpath('anatomy')); +addpath(genpath('Generated_Amira_TCL_Scripts')); + +XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase + +list{1,1} = 'sCase.id'; +list{1,2} = 'patient.IPP'; +list{1,3} = 'patient.initials'; +list{1,4} = 'patient.gender'; +list{1,5} = 'patient.birth_date'; +%list{1,6} = 'patient.age'; +%list{1,7} = 'patient.ethnicity'; +list{1,6} = 'patient.height'; +list{1,7} = 'patient.weight'; +%list{1,10} = 'patient.BMI'; +list{1,8} = 'shoulder.side'; +list{1,9} = 'CT.date'; +list{1,10} = 'CT.kernel'; +list{1,11} = 'CT.pixel_size'; +list{1,12} = 'CT.slice_spacing'; +list{1,13} = 'CT.slice_thickness'; +list{1,14} = 'CT.tension'; +list{1,15} = 'CT.current'; +list{1,16} = 'CT.institution'; +list{1,17} = 'CT.manufacturer'; +list{1,18} = 'CT.model'; +list{1,19} = 'CT.protocol'; + +directory = '../../../data'; + + +%% Loop over all the normal and pathologic cases + + location = 'N'; + l=1; + rootDir = '../../../data/N'; + for levelDir1 = 0:9 + for levelDir2 = 0:9 + curentDir = sprintf('%s/%d/%d/N*', rootDir, levelDir1, levelDir2); + folderInfo = dir(curentDir); + for i=1:1:numel(folderInfo) + name = folderInfo(i).name; + sCases_normal{l,1} = folderInfo(i).name; + sCases_normal{l,2} = levelDir1; + sCases_normal{l,3} = levelDir2; + sCases_normal{l,4} = 'N'; + l=l+1; + end + end + end + + location = 'P'; + l=1; + rootDir = '../../../data/P'; + for levelDir1 = 0:9 + for levelDir2 = 0:9 + curentDir = sprintf('%s/%d/%d/P*', rootDir, levelDir1, levelDir2); + folderInfo = dir(curentDir); + + for i=1:1:numel(folderInfo) + name = folderInfo(i).name; + sCases_pathologic{l,1} = folderInfo(i).name; + sCases_pathologic{l,2} = levelDir1; + sCases_pathologic{l,3} = levelDir2; + sCases_pathologic{l,4} = 'P'; + l=l+1; + end + end + end + + sCases = [sCases_normal;sCases_pathologic]; + +%% Data exportation from dicom +for j=2:1:numel(sCases(:,1))+1 + + patient = char(sCases(j-1,1)) + levelDir1 = cell2mat(sCases(j-1,2)); + levelDir2 = cell2mat(sCases(j-1,3)); + location = char(sCases(j-1,4)); + finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); + + if exist(sprintf('%s/%s/CT-%s-1/dicom', finalDirectory,patient,patient),'dir') == 7 %if a folder "dicom" exist, the patient name is added to the "measured" column +% directoryin=list(j,1); +% directoryin=char(directoryin); + + ctdirectoryin = sprintf('%s/%s/CT-%s-1/dicom',finalDirectory, patient, patient); + images = dir(ctdirectoryin); + ct=images(10).name; + ct2=images(11).name; + inpath=[ctdirectoryin,'/',ct]; + inpath2=[ctdirectoryin,'/',ct2]; + info=dicominfo2(inpath); + info2=dicominfo2(inpath2); + + + + %Patient# + list{j,1} = strtok(patient,'-'); + + %Weight + tf = isfield(info,'PatientWeight'); % Test si le champ "PatientWeight" existe + + if tf==1 + weight=info.PatientWeight; + else + weight=' '; + end + list{j,7} = weight; + + %Height + tf = isfield(info,'PatientSize'); % Test si le champ "PatientSize" existe + + if tf==1 + Size=info.PatientSize; + Size=Size*100; + Size=round(Size,0); + else + Size=' '; + end + list{j,6} = Size; + + %Gender + tf = isfield(info,'PatientSex'); % Test si le champ "PatientSex" existe + + if tf==1 + gender=info.PatientSex; + else + gender=' '; + end + list{j,4} = gender; + + %Birth date + tf = isfield(info,'PatientBirthDate'); % Test si le champ "PatientBirthDate" existe + + if tf==1 + date=info.PatientBirthDate; + date=[date(1:4) '.' date(5:6) '.' date(7:8)]; + else + date=' '; + end + list{j,5} = date; + + %CT date + tf = isfield(info,'AcquisitionDate'); % Test si le champ "AcquisitionDate" existe + + if tf==1 + ctdate=info.AcquisitionDate; + ctdate=[ctdate(1:4) '.' ctdate(5:6) '.' ctdate(7:8)]; + else + ctdate=' '; + end + list{j,9} = ctdate; + + %IPP + tf = isfield(info,'PatientID'); % Test si le champ "PatientID" existe + + if tf==1 + + id=info.PatientID; + else + + id=' '; + end + list{j,2} = id; + + %Kernel + tf = isfield(info,'ConvolutionKernel'); % Test si le champ "ConvolutionKernel" existe + + if tf==1 + kernel=info.ConvolutionKernel; + else + kernel=' '; + end + list{j,10} = kernel; + + %Pixel Size + tf = isfield(info,'PixelSpacing'); % Test si le champ "PixelSpacing" existe + + if tf==1 + pixel1=info.PixelSpacing(1); + %pixel2=info.PixelSpacing(2); + pixel1=num2str(pixel1); + %pixel2=num2str(pixel2); + %pixel= ['[',pixel1,',',pixel2,']']; + pixel= [,pixel1,]; + else + pixel=' '; + end + list{j,11} = pixel; + + %Slice Thickness + tf = isfield(info,'SliceThickness'); % Test si le champ "SliceThickness" existe + + if tf==1 + thickness=info.SliceThickness; + else + thickness=' '; + end + list{j,13} = thickness; + + %Tension + tf = isfield(info,'KVP'); % Test si le champ "KVP" existe + + if tf==1 + tension=info.KVP; + else + tension=' '; + end + list{j,14} = tension; + + + %Current + tf = isfield(info,'XrayTubeCurrent'); % Test si le champ "XrayTubeCurrent" existe + + if tf==1 + current=info.XrayTubeCurrent; + else + current=' '; + end + list{j,15} = current; + + + %Institution Name + tf = isfield(info,'InstitutionName'); % Test si le champ "InstitutionName" existe + + if tf==1 + instname=info.InstitutionName; + else + instname=' '; + end + list{j,16} = instname; + + %Manufacturer + tf = isfield(info,'Manufacturer'); % Test si le champ "Manufacturer" existe + + if tf==1 + manufacturer=info.Manufacturer; + else + manufacturer=' '; + end + list{j,17} = manufacturer; + + %Model Name + tf = isfield(info,'ManufacturerModelName'); % Test si le champ "ManufacturerModelName" existe + + if tf==1 + manufacturermn=info.ManufacturerModelName; + else + manufacturermn=' '; + end + list{j,18} = manufacturermn; + + %Protocol Name + tf = isfield(info,'ProtocolName'); % Test si le champ "ProtocolName" existe + + if tf==1 + protocol=info.ProtocolName; + else + protocol=' '; + end + list{j,19} = protocol; + + %Spacing Between Slices + tf = isfield(info,'ImagePositionPatient'); % Test si le champ "ImagePositionPatient" existe + + if tf==1 + pos1 = info.ImagePositionPatient(3); + pos2 = info2.ImagePositionPatient(3); + %pixel1=num2str(pixel1); + %pixel2=num2str(pixel2); + %pixel= ['[',pixel1,',',pixel2,']']; + space2= abs(pos1-pos2); + else + space2=' '; + end + list{j,12} = space2; + + % %Patient Name + % tf = isfield(info,'PatientName'); % Test si le champ "PatientName" existe + % + % if tf==1 + % name = info.PatientName.FamilyName; + % + % %pixel1=num2str(pixel1); + % + % else + % name=' '; + % end + % list{j,8} = name; + else + list{j,1} = strtok(patient,'-'); + end + if strcmp(patient,'P410-95295') + end +end +delete([XLSShoulderDatabaseLocation 'xlsFromMatlab/CT.xls']); +xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/CT'], list) + +addpath('XLS_MySQL_DB'); +MainExcel2SQL; +end + diff --git a/CT/private/analyze75open.m b/CT/private/analyze75open.m index c451f82..1b6a4b9 100644 --- a/CT/private/analyze75open.m +++ b/CT/private/analyze75open.m @@ -1,38 +1,38 @@ -function fid = analyze75open(filename, ext, mode, defaultByteOrder) -% Open an Analyze 7.5 file. - -% Copyright 2006-2011 The MathWorks, Inc. - - - -% Ensure that filename has a .hdr extension -[pname, fname, passedExt] = fileparts(filename); - -if ~isempty(passedExt) - switch lower(passedExt) - case {'.hdr','.img'} - % The file has the correct extension. - - otherwise - error(message('images:analyze75info:invalidFileFormat', passedExt)); - - end % switch -end % if - -filename = fullfile(pname, [fname '.' ext]); - -if (nargin < 4) - defaultByteOrder = 'ieee-be'; -end - -% Open the file with the default ByteOrder. -fid = fopen(filename, mode, defaultByteOrder); - -if (fid == -1) - if ~isempty(dir(filename)) - error(message('images:isanalyze75:hdrFileOpen', filename)) - else - error(message('images:isanalyze75:hdrFileExist', filename)) - end % if - -end +function fid = analyze75open(filename, ext, mode, defaultByteOrder) +% Open an Analyze 7.5 file. + +% Copyright 2006-2011 The MathWorks, Inc. + + + +% Ensure that filename has a .hdr extension +[pname, fname, passedExt] = fileparts(filename); + +if ~isempty(passedExt) + switch lower(passedExt) + case {'.hdr','.img'} + % The file has the correct extension. + + otherwise + error(message('images:analyze75info:invalidFileFormat', passedExt)); + + end % switch +end % if + +filename = fullfile(pname, [fname '.' ext]); + +if (nargin < 4) + defaultByteOrder = 'ieee-be'; +end + +% Open the file with the default ByteOrder. +fid = fopen(filename, mode, defaultByteOrder); + +if (fid == -1) + if ~isempty(dir(filename)) + error(message('images:isanalyze75:hdrFileOpen', filename)) + else + error(message('images:isanalyze75:hdrFileExist', filename)) + end % if + +end diff --git a/CT/private/dicom_add_attr.m b/CT/private/dicom_add_attr.m index 250983c..8dc6fac 100644 --- a/CT/private/dicom_add_attr.m +++ b/CT/private/dicom_add_attr.m @@ -1,976 +1,976 @@ -function attr_str = dicom_add_attr(attr_str, group, element, dictionary, varargin) -%DICOM_ADD_ATTR Add an attribute to a structure of attributes. -% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY, DATA, VR) add -% the attribute (GROUP,ELEMENT) with DATA and value representation -% VR to the structure IN. -% -% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY, DATA) add the -% attribute (GROUP,ELEMENT) with DATA to the structure IN. The -% value representation for this attribute is inferred from the data -% dictionary; if the VR value is not unique, an error will be -% issued. -% -% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY) add the empty -% attribute specified by (GROUP, ELEMENT) to IN. The VR value for -% the attribute will be picked from the data dictionary. -% -% See also DICOM_ADD_ITEM. - -% Copyright 1993-2011 The MathWorks, Inc. - - -pos = length(attr_str) + 1; - -% -% Group and Element. -% - -attr_str(pos).Group = get_group_or_element(group); -attr_str(pos).Element = get_group_or_element(element); - - -% Get attribute's properties from dictionary. -attr_dict = dicom_dict_lookup(attr_str(pos).Group, ... - attr_str(pos).Element, ... - dictionary); - -if ((isempty(attr_dict)) && (rem(group, 2) == 1)) - - % Private data needs certain values to be set. - if (nargin < 6) - attr_dict(1).VR = 'UN'; - end - - attr_dict(1).VM = [0 inf]; - -end - - -% -% VR. -% - -if (nargin == 6) - - % VR provided. - attr_str(pos).VR = varargin{2}; - -elseif ((nargin == 5) && ... - ((iscell(attr_dict.VR)) && (length(attr_dict.VR) > 1))) - - % Data provided, but multiple possible VR values. - error(message('images:dicom_add_attr:attributeRequiresVRArg', sprintf( '(%04X,%04X)', attr_str( pos ).Group, attr_str( pos ).Element ))) - -else - - % Single VR value or empty data. Use first VR value. - if (iscell(attr_dict.VR)) - - attr_str(pos).VR = attr_dict.VR{1}; - - else - - attr_str(pos).VR = attr_dict.VR; - - end - -end - - -% -% VM -% - -attr_str(pos).VM = attr_dict.VM; - - -% -% Data. -% - -% Get data from varargin or create default empty data. -if (nargin > 4) - data = varargin{1}; -else - data = []; -end - - -% -% Convert MATLAB data to DICOM's type and style. -% - -attr_str(pos).Data = validate_data(data, attr_str(pos)); - - - -function data_out = validate_data(data_in, attr_str) -%VALIDATE_DATA Validate user-provided data and massage to acceptable form. - -vr_details = get_vr_details(attr_str.VR); - -if (~isempty(data_in)) - - attr_tag = sprintf('(%04X,%04X)', attr_str.Group, attr_str.Element); - - % Check type. - if (~has_correct_data_type(class(data_in), vr_details.Types_accept)) - error(message('images:dicom_add_attr:attributeHasWrongType', attr_tag)) - end - - % Convert data. - data_out = massage_data(data_in, attr_str); - - % Check VM. - if (~has_correct_vm(data_out, attr_str.VM, vr_details)) - - warning(message('images:dicom_add_attr:wrongAttribNum', attr_tag)); - - end - - % Check lengths. - if (~has_correct_lengths(data_out, vr_details)) - - warning(message('images:dicom_add_attr:wrongAttribData', attr_tag)); - end - - % Validate data, if appropriate. - if (ischar(data_out)) - - if (~isempty(find_invalid_chars(data_out, vr_details))) - - warning(message('images:dicom_add_attr:invalidAttribChar', attr_tag)); - end - end - - % Pad data, if necessary. - switch (class(data_out)) - case {'uint8', 'int8', 'char'} - if (rem(numel(data_out), 2) == 1) - data_out = pad_data(data_out, vr_details); - end - end - -else - - data_out = data_in; - -end - - - -function val = get_group_or_element(in) - -if (isempty(in)) - - error(message('images:dicom_add_attr:groupElementNotHexOrInt')) - -elseif (ischar(in)) - - val = sscanf(in, '%x'); - -elseif (isnumeric(in)) - - val = in; - -else - - error(message('images:dicom_add_attr:groupElementNotHexOrInt')) - -end - - - -function details = get_vr_details(vr_in) -%GET_VR_DETAILS Get properties of a Value Representation. - -persistent vr_hash; -persistent vr_details; - -if (isempty(vr_hash)) - - % Build up the hash table and details structures. - [vr_hash, vr_details] = build_vr_details; - - % Get the details from the new hash. - details = get_vr_details(vr_in); - -else - - % Find the VR passed in. - idx = strmatch(vr_in, vr_hash); - - if (isempty(idx)) - - details = []; - - else - - details = vr_details(idx); - - end - -end - - - -function [vr_hash, vr_details] = build_vr_details -%BUILD_VR_DETAILS Create a hash table of VR properties. - -% Character categories. -ASCII_UPPER = 65:90; -ASCII_NUMS = 48:57; -ASCII_LF = 10; -ASCII_FF = 12; -ASCII_CR = 13; -ASCII_ESC = 27; -DICOM_CTRL = [ASCII_LF, ASCII_FF, ASCII_CR, ASCII_ESC]; -DICOM_NONCTRL = 32:126; -DICOM_DEFAULT = [DICOM_CTRL, DICOM_NONCTRL]; -DICOM_EXTENDED = 127:255; -DICOM_ALL = [DICOM_DEFAULT, DICOM_EXTENDED]; - -% Datatype categories. -TYPES_SIGNED = {'int8', 'int16', 'int32'}; -TYPES_UNSIGNED = {'uint8', 'uint16', 'uint32'}; -TYPES_FLOATING = {'double', 'single'}; -TYPES_INTEGRAL = [TYPES_SIGNED, TYPES_UNSIGNED]; -TYPES_NUMERIC = [TYPES_INTEGRAL, TYPES_FLOATING, {'logical'}]; - -% Hash of VRs. -vr_hash = {'AE' - 'AS' - 'AT' - 'CS' - 'DA' - 'DS' - 'DT' - 'FD' - 'FL' - 'IS' - 'LO' - 'LT' - 'OB' - 'OW' - 'PN' - 'SH' - 'SL' - 'SQ' - 'SS' - 'ST' - 'TM' - 'UI' - 'UL' - 'UN' - 'US' - 'UT'}; - -% Struct of VR details. - -vr_details(1).VR_name = 'AE'; -vr_details(1).Types_accept = {'char'}; -vr_details(1).Types_output = 'char'; -vr_details(1).Size_range = [0 16]; -vr_details(1).Separator = '\'; -vr_details(1).Char_repertoire = DICOM_NONCTRL; - -vr_details(2).VR_name = 'AS'; -vr_details(2).Types_accept = {'char'}; -vr_details(2).Types_output = 'char'; -vr_details(2).Size_range = [0 4]; -vr_details(2).Separator = '\'; -vr_details(2).Char_repertoire = [ASCII_NUMS, 'D', 'W', 'M', 'Y']; - -vr_details(3).VR_name = 'AT'; -vr_details(3).Types_accept = TYPES_NUMERIC; -vr_details(3).Types_output = 'uint16'; -vr_details(3).Size_range = [2 2]; -vr_details(3).Separator = []; -vr_details(3).Char_repertoire = ''; - -vr_details(4).VR_name = 'CS'; -vr_details(4).Types_accept = {'char'}; -vr_details(4).Types_output = 'char'; -vr_details(4).Size_range = [0 16]; -vr_details(4).Separator = '\'; -vr_details(4).Char_repertoire = [ASCII_UPPER, ASCII_NUMS, ' ', '_']; - -vr_details(5).VR_name = 'DA'; -vr_details(5).Types_accept = {'char', 'double'}; -vr_details(5).Types_output = 'char'; -vr_details(5).Size_range = [8 10]; % 10 ALLOWS FOR PRE 3.0. -vr_details(5).Separator = '\'; -vr_details(5).Char_repertoire = [ASCII_NUMS, '.']; - -vr_details(6).VR_name = 'DS'; -vr_details(6).Types_accept = [{'char'}, TYPES_NUMERIC]; -vr_details(6).Types_output = 'char'; -vr_details(6).Size_range = [0 16]; -vr_details(6).Separator = '\'; -vr_details(6).Char_repertoire = [ASCII_NUMS, '+', '-', 'E', 'e', '.', ' ']; - -vr_details(7).VR_name = 'DT'; -vr_details(7).Types_accept = {'char', 'double'}; -vr_details(7).Types_output = 'char'; -vr_details(7).Size_range = [0 26]; -vr_details(7).Separator = '\'; -vr_details(7).Char_repertoire = [ASCII_NUMS, '+', '-', '.']; - -vr_details(8).VR_name = 'FD'; -vr_details(8).Types_accept = TYPES_NUMERIC; -vr_details(8).Types_output = 'double'; -vr_details(8).Size_range = [1 1]; -vr_details(8).Separator = []; -vr_details(8).Char_repertoire = ''; - -vr_details(9).VR_name = 'FL'; -vr_details(9).Types_accept = TYPES_NUMERIC; -vr_details(9).Types_output = 'single'; -vr_details(9).Size_range = [1 1]; -vr_details(9).Separator = []; -vr_details(9).Char_repertoire = ''; - -vr_details(10).VR_name = 'IS'; -vr_details(10).Types_accept = TYPES_NUMERIC; -vr_details(10).Types_output = 'char'; -vr_details(10).Size_range = [0 12]; -vr_details(10).Separator = '\'; -vr_details(10).Char_repertoire = [ASCII_NUMS, '-', ' ']; - -vr_details(11).VR_name = 'LO'; -vr_details(11).Types_accept = {'char'}; -vr_details(11).Types_output = 'char'; -vr_details(11).Size_range = [0 64]; -vr_details(11).Separator = '\'; -vr_details(11).Char_repertoire = [DICOM_NONCTRL, DICOM_EXTENDED, ASCII_ESC]; - -vr_details(12).VR_name = 'LT'; -vr_details(12).Types_accept = {'char'}; -vr_details(12).Types_output = 'char'; -vr_details(12).Size_range = [0 10240]; -vr_details(12).Separator = ''; -vr_details(12).Char_repertoire = DICOM_ALL; - -vr_details(13).VR_name = 'OB'; -vr_details(13).Types_accept = {'uint8', 'int8', 'logical'}; -vr_details(13).Types_output = 'uint8'; -vr_details(13).Size_range = [0 inf]; -vr_details(13).Separator = []; -vr_details(13).Char_repertoire = ''; - -vr_details(14).VR_name = 'OW'; -vr_details(14).Types_accept = [TYPES_INTEGRAL, {'logical'}]; -vr_details(14).Types_output = 'uint16'; -vr_details(14).Size_range = [0 inf]; -vr_details(14).Separator = []; -vr_details(14).Char_repertoire = ''; - -vr_details(15).VR_name = 'PN'; -vr_details(15).Types_accept = {'char', 'struct'}; -vr_details(15).Types_output = 'char'; -vr_details(15).Size_range = [0 (64 * 3)]; % 64 chars / component group. -vr_details(15).Separator = '\'; -vr_details(15).Char_repertoire = [DICOM_NONCTRL, ASCII_ESC, DICOM_EXTENDED]; - -vr_details(16).VR_name = 'SH'; -vr_details(16).Types_accept = {'char'}; -vr_details(16).Types_output = 'char'; -vr_details(16).Size_range = [0 16]; -vr_details(16).Separator = '\'; -vr_details(16).Char_repertoire = [DICOM_NONCTRL, ASCII_ESC, DICOM_EXTENDED]; - -vr_details(17).VR_name = 'SL'; -vr_details(17).Types_accept = TYPES_NUMERIC; -vr_details(17).Types_output = 'int32'; -vr_details(17).Size_range = [1 1]; -vr_details(17).Separator = []; -vr_details(17).Char_repertoire = ''; - -vr_details(18).VR_name = 'SQ'; -vr_details(18).Types_accept = {'struct'}; -vr_details(18).Types_output = 'struct'; -vr_details(18).Size_range = [1 1]; % All SQ attributes have VM of 1. -vr_details(18).Separator = []; -vr_details(18).Char_repertoire = ''; - -vr_details(19).VR_name = 'SS'; -vr_details(19).Types_accept = TYPES_NUMERIC; -vr_details(19).Types_output = 'int16'; -vr_details(19).Size_range = [1 1]; -vr_details(19).Separator = []; -vr_details(19).Char_repertoire = ''; - -vr_details(20).VR_name = 'ST'; -vr_details(20).Types_accept = {'char'}; -vr_details(20).Types_output = 'char'; -vr_details(20).Size_range = [0 1024]; -vr_details(20).Separator = ''; -vr_details(20).Char_repertoire = DICOM_ALL; - -% ':' allowed for backward compatibility. -% See note for TM in PS-3.5 Sec. 6.2.0. -vr_details(21).VR_name = 'TM'; -vr_details(21).Types_accept = {'char', 'double'}; -vr_details(21).Types_output = 'char'; -vr_details(21).Size_range = [0 16]; -vr_details(21).Separator = '\'; -vr_details(21).Char_repertoire = [ASCII_NUMS, '.', ' ', ':']; - -vr_details(22).VR_name = 'UI'; -vr_details(22).Types_accept = {'char'}; -vr_details(22).Types_output = 'char'; -vr_details(22).Size_range = [0 64]; -vr_details(22).Separator = '\'; -vr_details(22).Char_repertoire = [ASCII_NUMS, '.', 0]; - -vr_details(23).VR_name = 'UL'; -vr_details(23).Types_accept = TYPES_NUMERIC; -vr_details(23).Types_output = 'uint32'; -vr_details(23).Size_range = [1 1]; -vr_details(23).Separator = []; -vr_details(23).Char_repertoire = ''; - -vr_details(24).VR_name = 'UN'; -vr_details(24).Types_accept = [TYPES_NUMERIC, {'char'}, {'struct'}]; -vr_details(24).Types_output = 'uint8'; % Don't convert raw data. -vr_details(24).Size_range = [0 inf]; -vr_details(24).Separator = []; -vr_details(24).Char_repertoire = 0:255; % Anything is allowed. - -vr_details(25).VR_name = 'US'; -vr_details(25).Types_accept = TYPES_NUMERIC; -vr_details(25).Types_output = 'uint16'; -vr_details(25).Size_range = [1 1]; -vr_details(25).Separator = []; -vr_details(25).Char_repertoire = ''; - -vr_details(26).VR_name = 'UT'; -vr_details(26).Types_accept = {'char'}; -vr_details(26).Types_output = 'char'; -vr_details(26).Size_range = [0 (2^32 - 2)]; -vr_details(26).Separator = ''; -vr_details(26).Char_repertoire = DICOM_ALL; - - - -function tf = has_correct_data_type(datatype, acceptable) -%HAS_CORRECT_DATA_TYPE Verify that the data is of the right type. - -switch (datatype) -case acceptable - tf = true; -otherwise - tf = false; -end - - - -function tf = has_correct_vm(data, vm, vr_details) -%HAS_CORRECT_VM Verify that data has correct number of components. - -switch (class(data)) -case 'char' - - if (isempty(vr_details.Separator)) - tf = true; - else - idx = find(data == vr_details.Separator); - - tf = ((length(idx) + 1) >= vm(1)) & ... - ((length(idx) + 1) <= vm(2)); - end - -case 'struct' - - tf = true; - -case {'uint8', 'int8', 'uint16', 'int16', 'uint32', 'int32', ... - 'double', 'single'} - - tf = (numel(data) >= vm(1) * vr_details.Size_range(1)) & ... - (numel(data) <= vm(2) * vr_details.Size_range(2)); - -otherwise - - tf = false; - -end - - - -function data_out = massage_data(data_in, attr_str) -%MASSAGE_DATA Convert data to its DICOM type. - -% We assume that this won't be called on data that can't be converted. -% The function HAS_CORRECT_DATA_TYPE permits this assumption. - -switch (attr_str.VR) -case 'AT' - - % Attribute tags must be stored as UINT16 pairs. - data_out = uint16(data_in); - - if (numel(data_out) ~= length(data_in)) - - if (size(data_out, 2) ~= 2) - error(message('images:dicom_add_attr:AttributeNeedsPairsOfUint16Data', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) - - end - - data_out = data_out'; - - end - - data_out = data_out(:); - -case 'DA' - - % Convert a MATLAB serial date to a string. - if (isa(data_in, 'double')) - - % - warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) - - - tmp = datestr(data_in, 30); % yyyymmddTHHMMSS - data_out = tmp(1:8); - - else - - data_out = data_in; - - end - -case 'DS' - - % Convert numeric values to strings. - if (~ischar(data_in)) - - data_out = convertNumericToString(data_in); - - else - - data_out = data_in; - - end - -case 'DT' - - % Convert a MATLAB serial date to a string. - if (isa(data_in, 'double')) - - - % - warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) - - - - data_out = ''; - - for p = 1:length(data_in) - - tmp_base = datestr(data_in, 30); % yyyymmddTHHMMSS - tmp_base(9) = ''; - - v = datevec(data_in); - tmp_fraction = sprintf('%0.6f', (v(end) - round(v(end)))); - tmp_fraction(1) = ''; % Remove leading 0. - - data_out = [data_out '\' tmp_base tmp_fraction(2:end)]; %#ok - - end - - else - - data_out = data_in; - - end - -case 'FD' - - data_out = double(data_in); - -case 'FL' - - data_out = single(data_in); - -case 'IS' - - % Convert numeric values to strings. - if (~ischar(data_in)) - - data_out = sprintf('%d\\', round(data_in)); - data_out(end) = ''; - - else - - data_out = data_in; - - end - -case 'OB' - - % Convert logical values to packed UINT8 arrays. - if (islogical(data_in)) - - data_out = pack_logical(data_in, 8); - - else - data_out = data_in; - end - -case 'OW' - - if (islogical(data_in)) - - % Convert logical values to packed UINT8 arrays. - data_out = pack_logical(data_in, 16); - - elseif (isa(data_in, 'uint32') || isa(data_in, 'uint32')) - - % 32-bit values need to be swapped as 16-bit short words not - % 32-bit words (e.g., "1234" byte order on LE should become - % "2143" on BE machines, and vice versa). - data_out = dicom_typecast(data_in, 'uint16'); - - else - data_out = data_in; - end - -case 'PN' - - % Convert person structures to strings. - if (isstruct(data_in)) - - data_out = struct_to_pn(data_in); - - else - - data_out = data_in; - - end - -case 'SL' - - data_out = int32(data_in); - -case 'SS' - - data_out = int16(data_in); - -case 'TM' - - % Convert a MATLAB serial date to a string. - if (isa(data_in, 'double')) - - % - warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) - - - tmp = datestr(data_in, 30); % yyyymmddTHHMMSS - data_out = tmp(10:end); - - else - - data_out = data_in; - end - -case 'UL' - - data_out = uint32(data_in); - -case 'UN' - - if (isnumeric(data_in)) - data_out = dicom_typecast(data_in, 'uint8'); - else - data_out = data_in; - end - -case 'US' - - data_out = uint16(data_in); - -otherwise - - data_out = data_in; - -end - - - -function pn_string = struct_to_pn(pn_struct) -%STRUCT_TO_PN Convert a person name in a struct to a character string. - -pn_string = ''; - -for p = 1:length(pn_struct) - - % Build up the PN string for this value. - tmp = ''; - - if (isfield(pn_struct, 'FamilyName')) - tmp = [tmp, pn_struct.FamilyName, '^']; %#ok - else - tmp = [tmp, '^']; %#ok - end - - if (isfield(pn_struct, 'GivenName')) - tmp = [tmp, pn_struct.GivenName, '^']; %#ok - else - tmp = [tmp, '^']; %#ok - end - - if (isfield(pn_struct, 'MiddleName')) - tmp = [tmp, pn_struct.MiddleName, '^']; %#ok - else - tmp = [tmp, '^']; %#ok - end - - if (isfield(pn_struct, 'NamePrefix')) - tmp = [tmp, pn_struct.NamePrefix, '^']; %#ok - else - tmp = [tmp, '^']; %#ok - end - - if (isfield(pn_struct, 'NameSuffix')) - tmp = [tmp, pn_struct.NameSuffix, '^']; %#ok - else - tmp = [tmp, '^']; %#ok - end - - % Remove trailing null components. - while ((~isempty(tmp)) && (isequal(tmp(end), '^'))) - tmp(end) = ''; - end - - % Add this value to the output string. - pn_string = [pn_string, tmp, '\']; %#ok - -end - -% Remove trailing delimiter ('\'). -pn_string(end) = ''; - - - -function bad_chars = find_invalid_chars(data, vr_details) -%FIND_INVALID_CHARS Look for invalid characters - -bad_chars = setdiff(data, [vr_details.Char_repertoire, vr_details.Separator]); - - - -function out = pad_data(in, vr_details) -%PAD_DATA Pad data to an even length - -switch (vr_details.VR_name) -case {'AE', 'CS', 'DS' 'DT', 'IS', 'LO', 'LT', 'PN', 'SH', 'ST', 'TM', 'UT'} - - out = in; - out(end + 1) = ' '; - -case {'OB', 'UI', 'UN'} - - out = in; - out(end + 1) = 0; - -otherwise - - % If it's numeric, it's even-byte aligned unless its 8-bit. - if ((isa(in, 'uint8')) || (isa(in, 'int8'))) - - out = in; - out(end + 1) = 0; - - else - - out = in; - - end - -end - - - -function tf = has_correct_lengths(data, vr_details) -%HAS_CORRECT_LENGTHS Determine if data components are correctly sized. - -if (isempty(vr_details.Separator)) - - % If there's no separator, then data lengths are unimportant (except - % for attributes with a VR of "AT", which have two UINT16 components.) - - if (isnumeric(data)) - - if (isequal(vr_details.VR_name, 'AT')) - - if (rem(numel(data), 2) == 1) - tf = 0; - else - tf = 1; - end - - else - - tf = 1; - - end - - elseif (isstruct(data)) - - tf = 1; - - else - - tf = ((numel(data) >= vr_details.Size_range(1)) & ... - (numel(data) <= vr_details.Size_range(2))); - - end - -else - - % Find the separators. - idx = find(data == vr_details.Separator); - component_start = 1; - - tf = 1; - - % Test lengths for all but last component. - for p = 1:length(idx) - - component_end = idx(p) - 1; - - data_length = component_end - component_start + 1; - - tf = tf * ... - ((data_length >= vr_details.Size_range(1)) & ... - (data_length <= vr_details.Size_range(2))); - - component_start = component_end + 2; - - end - - % Test length of the last (or only) component. - data_length = numel(data) - component_start + 1; - - tf = tf * ... - ((data_length >= vr_details.Size_range(1)) & ... - (data_length <= vr_details.Size_range(2))); - -end - - - -function strData = convertNumericToString(numData) -% Convert numeric data to character strings representing that -% data. DICOM number strings can be at most 16-bytes long and -% should use exponential notation where necessary. -% -% We use a complicated set of rules involving the sign of the data -% and its base-10 logarithm to pick a precision value for SPRINTF -% that maximizes the significant digits and prints at most 16 -% characters. -% -% When viewed on a number line, the state transitions for the -% precision value are listed below. (That is, where do the rules -% for determining the format specifier change?) -% -% * -1E+14 -% * -1E+0 (aka -1) -% * -1E-5 -% * 0 -% * 1E-5 -% * 1E+0 (aka 1) -% * 1E+14 - -strData = ''; -for idx = 1:numel(numData) - - if (numData(idx) == 0) - - % Avoid "log of 0" warnings and needless computations. - fmtString = '%d'; - - else - - power10 = floor(log10(abs(double(numData(idx))))); - - if (numData >= 0) - - % The precision is: - % 0 <= x < 1E-5 --> %.10G - % 1E-5 <= x < 1 --> varies - % 1 <= x < 1E14 --> %.15G - % 1E14 <= x --> %.10G - if (power10 >= 14) - fmtString = '%.10G'; - else - precision = max(10, min(15, power10 + 15)); - fmtString = sprintf('%%.%dG', precision); - end - - else - - % The precision is: - % x < -1E14 --> %.9G - % -1E14 <= x < -1 --> %.14G - % -1 <= x < -1E-5 --> varies - % -1E-5 <= x < 0 --> %.9G - if (power10 >= 14) - fmtString = '%.9G'; - else - precision = max(9, min(14, power10 + 14)); - fmtString = sprintf('%%.%dG', precision); - end - end - - end - - strData = [strData, '\', sprintf(fmtString, numData(idx))]; %#ok - -end - -% Remove the leading '\' from the first iteration. -strData(1) = ''; - - - -function data_out = pack_logical(data_in, bits) - -% Transpose the data to row-major format. -data_in = data_in'; -data_in = data_in(:); - -% The easiest way to pack the data is to perform matrix -% multiplication after reshaping the data to match the -% bit positions in the output data. (Pad if necessary.) -padded_output_length = ceil(numel(data_in) / bits); -if ((numel(data_in) / bits) ~= padded_output_length) - - data_in(padded_output_length * bits) = 0; - -end - -% MATLAB doesn't support matrix multiplication of integral types, -% so use double. (Reshape the input data to be n-by-bits and -% multiply by the bits-by-1 mask to make an n-by-1 packed array.) -data_in = reshape(double(data_in), bits, [])'; - -mask = zeros(bits, 1); -for idx = 1:bits - mask(idx) = 2^(idx - 1); -end - -% Pack the bits via matrix multiplication. -data_out = data_in * mask; - -% Convert double data back to the correct type. -switch (bits) -case 8 - data_out = uint8(data_out); -case 16 - data_out = uint16(data_out); -otherwise - error(message('images:dicom_add_attr:badPackBits')) -end +function attr_str = dicom_add_attr(attr_str, group, element, dictionary, varargin) +%DICOM_ADD_ATTR Add an attribute to a structure of attributes. +% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY, DATA, VR) add +% the attribute (GROUP,ELEMENT) with DATA and value representation +% VR to the structure IN. +% +% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY, DATA) add the +% attribute (GROUP,ELEMENT) with DATA to the structure IN. The +% value representation for this attribute is inferred from the data +% dictionary; if the VR value is not unique, an error will be +% issued. +% +% OUT = DICOM_ADD_ATTR(IN, GROUP, ELEMENT, DICTIONARY) add the empty +% attribute specified by (GROUP, ELEMENT) to IN. The VR value for +% the attribute will be picked from the data dictionary. +% +% See also DICOM_ADD_ITEM. + +% Copyright 1993-2011 The MathWorks, Inc. + + +pos = length(attr_str) + 1; + +% +% Group and Element. +% + +attr_str(pos).Group = get_group_or_element(group); +attr_str(pos).Element = get_group_or_element(element); + + +% Get attribute's properties from dictionary. +attr_dict = dicom_dict_lookup(attr_str(pos).Group, ... + attr_str(pos).Element, ... + dictionary); + +if ((isempty(attr_dict)) && (rem(group, 2) == 1)) + + % Private data needs certain values to be set. + if (nargin < 6) + attr_dict(1).VR = 'UN'; + end + + attr_dict(1).VM = [0 inf]; + +end + + +% +% VR. +% + +if (nargin == 6) + + % VR provided. + attr_str(pos).VR = varargin{2}; + +elseif ((nargin == 5) && ... + ((iscell(attr_dict.VR)) && (length(attr_dict.VR) > 1))) + + % Data provided, but multiple possible VR values. + error(message('images:dicom_add_attr:attributeRequiresVRArg', sprintf( '(%04X,%04X)', attr_str( pos ).Group, attr_str( pos ).Element ))) + +else + + % Single VR value or empty data. Use first VR value. + if (iscell(attr_dict.VR)) + + attr_str(pos).VR = attr_dict.VR{1}; + + else + + attr_str(pos).VR = attr_dict.VR; + + end + +end + + +% +% VM +% + +attr_str(pos).VM = attr_dict.VM; + + +% +% Data. +% + +% Get data from varargin or create default empty data. +if (nargin > 4) + data = varargin{1}; +else + data = []; +end + + +% +% Convert MATLAB data to DICOM's type and style. +% + +attr_str(pos).Data = validate_data(data, attr_str(pos)); + + + +function data_out = validate_data(data_in, attr_str) +%VALIDATE_DATA Validate user-provided data and massage to acceptable form. + +vr_details = get_vr_details(attr_str.VR); + +if (~isempty(data_in)) + + attr_tag = sprintf('(%04X,%04X)', attr_str.Group, attr_str.Element); + + % Check type. + if (~has_correct_data_type(class(data_in), vr_details.Types_accept)) + error(message('images:dicom_add_attr:attributeHasWrongType', attr_tag)) + end + + % Convert data. + data_out = massage_data(data_in, attr_str); + + % Check VM. + if (~has_correct_vm(data_out, attr_str.VM, vr_details)) + + warning(message('images:dicom_add_attr:wrongAttribNum', attr_tag)); + + end + + % Check lengths. + if (~has_correct_lengths(data_out, vr_details)) + + warning(message('images:dicom_add_attr:wrongAttribData', attr_tag)); + end + + % Validate data, if appropriate. + if (ischar(data_out)) + + if (~isempty(find_invalid_chars(data_out, vr_details))) + + warning(message('images:dicom_add_attr:invalidAttribChar', attr_tag)); + end + end + + % Pad data, if necessary. + switch (class(data_out)) + case {'uint8', 'int8', 'char'} + if (rem(numel(data_out), 2) == 1) + data_out = pad_data(data_out, vr_details); + end + end + +else + + data_out = data_in; + +end + + + +function val = get_group_or_element(in) + +if (isempty(in)) + + error(message('images:dicom_add_attr:groupElementNotHexOrInt')) + +elseif (ischar(in)) + + val = sscanf(in, '%x'); + +elseif (isnumeric(in)) + + val = in; + +else + + error(message('images:dicom_add_attr:groupElementNotHexOrInt')) + +end + + + +function details = get_vr_details(vr_in) +%GET_VR_DETAILS Get properties of a Value Representation. + +persistent vr_hash; +persistent vr_details; + +if (isempty(vr_hash)) + + % Build up the hash table and details structures. + [vr_hash, vr_details] = build_vr_details; + + % Get the details from the new hash. + details = get_vr_details(vr_in); + +else + + % Find the VR passed in. + idx = strmatch(vr_in, vr_hash); + + if (isempty(idx)) + + details = []; + + else + + details = vr_details(idx); + + end + +end + + + +function [vr_hash, vr_details] = build_vr_details +%BUILD_VR_DETAILS Create a hash table of VR properties. + +% Character categories. +ASCII_UPPER = 65:90; +ASCII_NUMS = 48:57; +ASCII_LF = 10; +ASCII_FF = 12; +ASCII_CR = 13; +ASCII_ESC = 27; +DICOM_CTRL = [ASCII_LF, ASCII_FF, ASCII_CR, ASCII_ESC]; +DICOM_NONCTRL = 32:126; +DICOM_DEFAULT = [DICOM_CTRL, DICOM_NONCTRL]; +DICOM_EXTENDED = 127:255; +DICOM_ALL = [DICOM_DEFAULT, DICOM_EXTENDED]; + +% Datatype categories. +TYPES_SIGNED = {'int8', 'int16', 'int32'}; +TYPES_UNSIGNED = {'uint8', 'uint16', 'uint32'}; +TYPES_FLOATING = {'double', 'single'}; +TYPES_INTEGRAL = [TYPES_SIGNED, TYPES_UNSIGNED]; +TYPES_NUMERIC = [TYPES_INTEGRAL, TYPES_FLOATING, {'logical'}]; + +% Hash of VRs. +vr_hash = {'AE' + 'AS' + 'AT' + 'CS' + 'DA' + 'DS' + 'DT' + 'FD' + 'FL' + 'IS' + 'LO' + 'LT' + 'OB' + 'OW' + 'PN' + 'SH' + 'SL' + 'SQ' + 'SS' + 'ST' + 'TM' + 'UI' + 'UL' + 'UN' + 'US' + 'UT'}; + +% Struct of VR details. + +vr_details(1).VR_name = 'AE'; +vr_details(1).Types_accept = {'char'}; +vr_details(1).Types_output = 'char'; +vr_details(1).Size_range = [0 16]; +vr_details(1).Separator = '\'; +vr_details(1).Char_repertoire = DICOM_NONCTRL; + +vr_details(2).VR_name = 'AS'; +vr_details(2).Types_accept = {'char'}; +vr_details(2).Types_output = 'char'; +vr_details(2).Size_range = [0 4]; +vr_details(2).Separator = '\'; +vr_details(2).Char_repertoire = [ASCII_NUMS, 'D', 'W', 'M', 'Y']; + +vr_details(3).VR_name = 'AT'; +vr_details(3).Types_accept = TYPES_NUMERIC; +vr_details(3).Types_output = 'uint16'; +vr_details(3).Size_range = [2 2]; +vr_details(3).Separator = []; +vr_details(3).Char_repertoire = ''; + +vr_details(4).VR_name = 'CS'; +vr_details(4).Types_accept = {'char'}; +vr_details(4).Types_output = 'char'; +vr_details(4).Size_range = [0 16]; +vr_details(4).Separator = '\'; +vr_details(4).Char_repertoire = [ASCII_UPPER, ASCII_NUMS, ' ', '_']; + +vr_details(5).VR_name = 'DA'; +vr_details(5).Types_accept = {'char', 'double'}; +vr_details(5).Types_output = 'char'; +vr_details(5).Size_range = [8 10]; % 10 ALLOWS FOR PRE 3.0. +vr_details(5).Separator = '\'; +vr_details(5).Char_repertoire = [ASCII_NUMS, '.']; + +vr_details(6).VR_name = 'DS'; +vr_details(6).Types_accept = [{'char'}, TYPES_NUMERIC]; +vr_details(6).Types_output = 'char'; +vr_details(6).Size_range = [0 16]; +vr_details(6).Separator = '\'; +vr_details(6).Char_repertoire = [ASCII_NUMS, '+', '-', 'E', 'e', '.', ' ']; + +vr_details(7).VR_name = 'DT'; +vr_details(7).Types_accept = {'char', 'double'}; +vr_details(7).Types_output = 'char'; +vr_details(7).Size_range = [0 26]; +vr_details(7).Separator = '\'; +vr_details(7).Char_repertoire = [ASCII_NUMS, '+', '-', '.']; + +vr_details(8).VR_name = 'FD'; +vr_details(8).Types_accept = TYPES_NUMERIC; +vr_details(8).Types_output = 'double'; +vr_details(8).Size_range = [1 1]; +vr_details(8).Separator = []; +vr_details(8).Char_repertoire = ''; + +vr_details(9).VR_name = 'FL'; +vr_details(9).Types_accept = TYPES_NUMERIC; +vr_details(9).Types_output = 'single'; +vr_details(9).Size_range = [1 1]; +vr_details(9).Separator = []; +vr_details(9).Char_repertoire = ''; + +vr_details(10).VR_name = 'IS'; +vr_details(10).Types_accept = TYPES_NUMERIC; +vr_details(10).Types_output = 'char'; +vr_details(10).Size_range = [0 12]; +vr_details(10).Separator = '\'; +vr_details(10).Char_repertoire = [ASCII_NUMS, '-', ' ']; + +vr_details(11).VR_name = 'LO'; +vr_details(11).Types_accept = {'char'}; +vr_details(11).Types_output = 'char'; +vr_details(11).Size_range = [0 64]; +vr_details(11).Separator = '\'; +vr_details(11).Char_repertoire = [DICOM_NONCTRL, DICOM_EXTENDED, ASCII_ESC]; + +vr_details(12).VR_name = 'LT'; +vr_details(12).Types_accept = {'char'}; +vr_details(12).Types_output = 'char'; +vr_details(12).Size_range = [0 10240]; +vr_details(12).Separator = ''; +vr_details(12).Char_repertoire = DICOM_ALL; + +vr_details(13).VR_name = 'OB'; +vr_details(13).Types_accept = {'uint8', 'int8', 'logical'}; +vr_details(13).Types_output = 'uint8'; +vr_details(13).Size_range = [0 inf]; +vr_details(13).Separator = []; +vr_details(13).Char_repertoire = ''; + +vr_details(14).VR_name = 'OW'; +vr_details(14).Types_accept = [TYPES_INTEGRAL, {'logical'}]; +vr_details(14).Types_output = 'uint16'; +vr_details(14).Size_range = [0 inf]; +vr_details(14).Separator = []; +vr_details(14).Char_repertoire = ''; + +vr_details(15).VR_name = 'PN'; +vr_details(15).Types_accept = {'char', 'struct'}; +vr_details(15).Types_output = 'char'; +vr_details(15).Size_range = [0 (64 * 3)]; % 64 chars / component group. +vr_details(15).Separator = '\'; +vr_details(15).Char_repertoire = [DICOM_NONCTRL, ASCII_ESC, DICOM_EXTENDED]; + +vr_details(16).VR_name = 'SH'; +vr_details(16).Types_accept = {'char'}; +vr_details(16).Types_output = 'char'; +vr_details(16).Size_range = [0 16]; +vr_details(16).Separator = '\'; +vr_details(16).Char_repertoire = [DICOM_NONCTRL, ASCII_ESC, DICOM_EXTENDED]; + +vr_details(17).VR_name = 'SL'; +vr_details(17).Types_accept = TYPES_NUMERIC; +vr_details(17).Types_output = 'int32'; +vr_details(17).Size_range = [1 1]; +vr_details(17).Separator = []; +vr_details(17).Char_repertoire = ''; + +vr_details(18).VR_name = 'SQ'; +vr_details(18).Types_accept = {'struct'}; +vr_details(18).Types_output = 'struct'; +vr_details(18).Size_range = [1 1]; % All SQ attributes have VM of 1. +vr_details(18).Separator = []; +vr_details(18).Char_repertoire = ''; + +vr_details(19).VR_name = 'SS'; +vr_details(19).Types_accept = TYPES_NUMERIC; +vr_details(19).Types_output = 'int16'; +vr_details(19).Size_range = [1 1]; +vr_details(19).Separator = []; +vr_details(19).Char_repertoire = ''; + +vr_details(20).VR_name = 'ST'; +vr_details(20).Types_accept = {'char'}; +vr_details(20).Types_output = 'char'; +vr_details(20).Size_range = [0 1024]; +vr_details(20).Separator = ''; +vr_details(20).Char_repertoire = DICOM_ALL; + +% ':' allowed for backward compatibility. +% See note for TM in PS-3.5 Sec. 6.2.0. +vr_details(21).VR_name = 'TM'; +vr_details(21).Types_accept = {'char', 'double'}; +vr_details(21).Types_output = 'char'; +vr_details(21).Size_range = [0 16]; +vr_details(21).Separator = '\'; +vr_details(21).Char_repertoire = [ASCII_NUMS, '.', ' ', ':']; + +vr_details(22).VR_name = 'UI'; +vr_details(22).Types_accept = {'char'}; +vr_details(22).Types_output = 'char'; +vr_details(22).Size_range = [0 64]; +vr_details(22).Separator = '\'; +vr_details(22).Char_repertoire = [ASCII_NUMS, '.', 0]; + +vr_details(23).VR_name = 'UL'; +vr_details(23).Types_accept = TYPES_NUMERIC; +vr_details(23).Types_output = 'uint32'; +vr_details(23).Size_range = [1 1]; +vr_details(23).Separator = []; +vr_details(23).Char_repertoire = ''; + +vr_details(24).VR_name = 'UN'; +vr_details(24).Types_accept = [TYPES_NUMERIC, {'char'}, {'struct'}]; +vr_details(24).Types_output = 'uint8'; % Don't convert raw data. +vr_details(24).Size_range = [0 inf]; +vr_details(24).Separator = []; +vr_details(24).Char_repertoire = 0:255; % Anything is allowed. + +vr_details(25).VR_name = 'US'; +vr_details(25).Types_accept = TYPES_NUMERIC; +vr_details(25).Types_output = 'uint16'; +vr_details(25).Size_range = [1 1]; +vr_details(25).Separator = []; +vr_details(25).Char_repertoire = ''; + +vr_details(26).VR_name = 'UT'; +vr_details(26).Types_accept = {'char'}; +vr_details(26).Types_output = 'char'; +vr_details(26).Size_range = [0 (2^32 - 2)]; +vr_details(26).Separator = ''; +vr_details(26).Char_repertoire = DICOM_ALL; + + + +function tf = has_correct_data_type(datatype, acceptable) +%HAS_CORRECT_DATA_TYPE Verify that the data is of the right type. + +switch (datatype) +case acceptable + tf = true; +otherwise + tf = false; +end + + + +function tf = has_correct_vm(data, vm, vr_details) +%HAS_CORRECT_VM Verify that data has correct number of components. + +switch (class(data)) +case 'char' + + if (isempty(vr_details.Separator)) + tf = true; + else + idx = find(data == vr_details.Separator); + + tf = ((length(idx) + 1) >= vm(1)) & ... + ((length(idx) + 1) <= vm(2)); + end + +case 'struct' + + tf = true; + +case {'uint8', 'int8', 'uint16', 'int16', 'uint32', 'int32', ... + 'double', 'single'} + + tf = (numel(data) >= vm(1) * vr_details.Size_range(1)) & ... + (numel(data) <= vm(2) * vr_details.Size_range(2)); + +otherwise + + tf = false; + +end + + + +function data_out = massage_data(data_in, attr_str) +%MASSAGE_DATA Convert data to its DICOM type. + +% We assume that this won't be called on data that can't be converted. +% The function HAS_CORRECT_DATA_TYPE permits this assumption. + +switch (attr_str.VR) +case 'AT' + + % Attribute tags must be stored as UINT16 pairs. + data_out = uint16(data_in); + + if (numel(data_out) ~= length(data_in)) + + if (size(data_out, 2) ~= 2) + error(message('images:dicom_add_attr:AttributeNeedsPairsOfUint16Data', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) + + end + + data_out = data_out'; + + end + + data_out = data_out(:); + +case 'DA' + + % Convert a MATLAB serial date to a string. + if (isa(data_in, 'double')) + + % + warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) + + + tmp = datestr(data_in, 30); % yyyymmddTHHMMSS + data_out = tmp(1:8); + + else + + data_out = data_in; + + end + +case 'DS' + + % Convert numeric values to strings. + if (~ischar(data_in)) + + data_out = convertNumericToString(data_in); + + else + + data_out = data_in; + + end + +case 'DT' + + % Convert a MATLAB serial date to a string. + if (isa(data_in, 'double')) + + + % + warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) + + + + data_out = ''; + + for p = 1:length(data_in) + + tmp_base = datestr(data_in, 30); % yyyymmddTHHMMSS + tmp_base(9) = ''; + + v = datevec(data_in); + tmp_fraction = sprintf('%0.6f', (v(end) - round(v(end)))); + tmp_fraction(1) = ''; % Remove leading 0. + + data_out = [data_out '\' tmp_base tmp_fraction(2:end)]; %#ok + + end + + else + + data_out = data_in; + + end + +case 'FD' + + data_out = double(data_in); + +case 'FL' + + data_out = single(data_in); + +case 'IS' + + % Convert numeric values to strings. + if (~ischar(data_in)) + + data_out = sprintf('%d\\', round(data_in)); + data_out(end) = ''; + + else + + data_out = data_in; + + end + +case 'OB' + + % Convert logical values to packed UINT8 arrays. + if (islogical(data_in)) + + data_out = pack_logical(data_in, 8); + + else + data_out = data_in; + end + +case 'OW' + + if (islogical(data_in)) + + % Convert logical values to packed UINT8 arrays. + data_out = pack_logical(data_in, 16); + + elseif (isa(data_in, 'uint32') || isa(data_in, 'uint32')) + + % 32-bit values need to be swapped as 16-bit short words not + % 32-bit words (e.g., "1234" byte order on LE should become + % "2143" on BE machines, and vice versa). + data_out = dicom_typecast(data_in, 'uint16'); + + else + data_out = data_in; + end + +case 'PN' + + % Convert person structures to strings. + if (isstruct(data_in)) + + data_out = struct_to_pn(data_in); + + else + + data_out = data_in; + + end + +case 'SL' + + data_out = int32(data_in); + +case 'SS' + + data_out = int16(data_in); + +case 'TM' + + % Convert a MATLAB serial date to a string. + if (isa(data_in, 'double')) + + % + warning(message('images:dicom_add_attr:serialDateToString', sprintf( '(%04X,%04X)', attr_str.Group, attr_str.Element ))) + + + tmp = datestr(data_in, 30); % yyyymmddTHHMMSS + data_out = tmp(10:end); + + else + + data_out = data_in; + end + +case 'UL' + + data_out = uint32(data_in); + +case 'UN' + + if (isnumeric(data_in)) + data_out = dicom_typecast(data_in, 'uint8'); + else + data_out = data_in; + end + +case 'US' + + data_out = uint16(data_in); + +otherwise + + data_out = data_in; + +end + + + +function pn_string = struct_to_pn(pn_struct) +%STRUCT_TO_PN Convert a person name in a struct to a character string. + +pn_string = ''; + +for p = 1:length(pn_struct) + + % Build up the PN string for this value. + tmp = ''; + + if (isfield(pn_struct, 'FamilyName')) + tmp = [tmp, pn_struct.FamilyName, '^']; %#ok + else + tmp = [tmp, '^']; %#ok + end + + if (isfield(pn_struct, 'GivenName')) + tmp = [tmp, pn_struct.GivenName, '^']; %#ok + else + tmp = [tmp, '^']; %#ok + end + + if (isfield(pn_struct, 'MiddleName')) + tmp = [tmp, pn_struct.MiddleName, '^']; %#ok + else + tmp = [tmp, '^']; %#ok + end + + if (isfield(pn_struct, 'NamePrefix')) + tmp = [tmp, pn_struct.NamePrefix, '^']; %#ok + else + tmp = [tmp, '^']; %#ok + end + + if (isfield(pn_struct, 'NameSuffix')) + tmp = [tmp, pn_struct.NameSuffix, '^']; %#ok + else + tmp = [tmp, '^']; %#ok + end + + % Remove trailing null components. + while ((~isempty(tmp)) && (isequal(tmp(end), '^'))) + tmp(end) = ''; + end + + % Add this value to the output string. + pn_string = [pn_string, tmp, '\']; %#ok + +end + +% Remove trailing delimiter ('\'). +pn_string(end) = ''; + + + +function bad_chars = find_invalid_chars(data, vr_details) +%FIND_INVALID_CHARS Look for invalid characters + +bad_chars = setdiff(data, [vr_details.Char_repertoire, vr_details.Separator]); + + + +function out = pad_data(in, vr_details) +%PAD_DATA Pad data to an even length + +switch (vr_details.VR_name) +case {'AE', 'CS', 'DS' 'DT', 'IS', 'LO', 'LT', 'PN', 'SH', 'ST', 'TM', 'UT'} + + out = in; + out(end + 1) = ' '; + +case {'OB', 'UI', 'UN'} + + out = in; + out(end + 1) = 0; + +otherwise + + % If it's numeric, it's even-byte aligned unless its 8-bit. + if ((isa(in, 'uint8')) || (isa(in, 'int8'))) + + out = in; + out(end + 1) = 0; + + else + + out = in; + + end + +end + + + +function tf = has_correct_lengths(data, vr_details) +%HAS_CORRECT_LENGTHS Determine if data components are correctly sized. + +if (isempty(vr_details.Separator)) + + % If there's no separator, then data lengths are unimportant (except + % for attributes with a VR of "AT", which have two UINT16 components.) + + if (isnumeric(data)) + + if (isequal(vr_details.VR_name, 'AT')) + + if (rem(numel(data), 2) == 1) + tf = 0; + else + tf = 1; + end + + else + + tf = 1; + + end + + elseif (isstruct(data)) + + tf = 1; + + else + + tf = ((numel(data) >= vr_details.Size_range(1)) & ... + (numel(data) <= vr_details.Size_range(2))); + + end + +else + + % Find the separators. + idx = find(data == vr_details.Separator); + component_start = 1; + + tf = 1; + + % Test lengths for all but last component. + for p = 1:length(idx) + + component_end = idx(p) - 1; + + data_length = component_end - component_start + 1; + + tf = tf * ... + ((data_length >= vr_details.Size_range(1)) & ... + (data_length <= vr_details.Size_range(2))); + + component_start = component_end + 2; + + end + + % Test length of the last (or only) component. + data_length = numel(data) - component_start + 1; + + tf = tf * ... + ((data_length >= vr_details.Size_range(1)) & ... + (data_length <= vr_details.Size_range(2))); + +end + + + +function strData = convertNumericToString(numData) +% Convert numeric data to character strings representing that +% data. DICOM number strings can be at most 16-bytes long and +% should use exponential notation where necessary. +% +% We use a complicated set of rules involving the sign of the data +% and its base-10 logarithm to pick a precision value for SPRINTF +% that maximizes the significant digits and prints at most 16 +% characters. +% +% When viewed on a number line, the state transitions for the +% precision value are listed below. (That is, where do the rules +% for determining the format specifier change?) +% +% * -1E+14 +% * -1E+0 (aka -1) +% * -1E-5 +% * 0 +% * 1E-5 +% * 1E+0 (aka 1) +% * 1E+14 + +strData = ''; +for idx = 1:numel(numData) + + if (numData(idx) == 0) + + % Avoid "log of 0" warnings and needless computations. + fmtString = '%d'; + + else + + power10 = floor(log10(abs(double(numData(idx))))); + + if (numData >= 0) + + % The precision is: + % 0 <= x < 1E-5 --> %.10G + % 1E-5 <= x < 1 --> varies + % 1 <= x < 1E14 --> %.15G + % 1E14 <= x --> %.10G + if (power10 >= 14) + fmtString = '%.10G'; + else + precision = max(10, min(15, power10 + 15)); + fmtString = sprintf('%%.%dG', precision); + end + + else + + % The precision is: + % x < -1E14 --> %.9G + % -1E14 <= x < -1 --> %.14G + % -1 <= x < -1E-5 --> varies + % -1E-5 <= x < 0 --> %.9G + if (power10 >= 14) + fmtString = '%.9G'; + else + precision = max(9, min(14, power10 + 14)); + fmtString = sprintf('%%.%dG', precision); + end + end + + end + + strData = [strData, '\', sprintf(fmtString, numData(idx))]; %#ok + +end + +% Remove the leading '\' from the first iteration. +strData(1) = ''; + + + +function data_out = pack_logical(data_in, bits) + +% Transpose the data to row-major format. +data_in = data_in'; +data_in = data_in(:); + +% The easiest way to pack the data is to perform matrix +% multiplication after reshaping the data to match the +% bit positions in the output data. (Pad if necessary.) +padded_output_length = ceil(numel(data_in) / bits); +if ((numel(data_in) / bits) ~= padded_output_length) + + data_in(padded_output_length * bits) = 0; + +end + +% MATLAB doesn't support matrix multiplication of integral types, +% so use double. (Reshape the input data to be n-by-bits and +% multiply by the bits-by-1 mask to make an n-by-1 packed array.) +data_in = reshape(double(data_in), bits, [])'; + +mask = zeros(bits, 1); +for idx = 1:bits + mask(idx) = 2^(idx - 1); +end + +% Pack the bits via matrix multiplication. +data_out = data_in * mask; + +% Convert double data back to the correct type. +switch (bits) +case 8 + data_out = uint8(data_out); +case 16 + data_out = uint16(data_out); +otherwise + error(message('images:dicom_add_attr:badPackBits')) +end diff --git a/CT/private/dicom_add_item.m b/CT/private/dicom_add_item.m index 734fb0a..0768d47 100644 --- a/CT/private/dicom_add_item.m +++ b/CT/private/dicom_add_item.m @@ -1,101 +1,101 @@ -function attr_str = dicom_add_item(attr_str, group, element, varargin) -%DICOM_ADD_ITEM Add an item/delimiter to a structure of attributes. -% OUT = DICOM_ADD_ITEM(IN, GROUP, ELEMENT) -% OUT = DICOM_ADD_ITEM(IN, GROUP, ELEMENT, DATA) -% -% This function is similar to DICOM_ADD_ATTR, but it doesn't allow -% specifying VR values and can only be used for attributes of group -% FFFE. -% -% See also DICOM_ADD_ATTR, DICOM_ADD_PIXEL_DATA. - -% Copyright 1993-2010 The MathWorks, Inc. - - -% See PS-3.5 Sec. 7.5 for details on sequence and item encoding. - - -% Get group and element. -tmp.Group = get_group_or_element(group); -tmp.Element = get_group_or_element(element); - -% Get data value. -if (nargin > 4) - error(message('images:dicom_add_item:tooManyInputArgs')); -elseif (nargin == 3) - tmp.Data = []; -else - tmp.Data = varargin{1}; -end - -tmp.VR = 'UN'; - -% Check the group and element values. -if (tmp.Group ~= 65534) % 0xFFFE == 65534 - error(message('images:dicom_add_item:groupNotAccepted', sprintf( '%X', tmp.Group ))) -end - -switch (tmp.Element) -case {57344} % 0xE000 == 57344 - - % Data is okay for (FFFE,E000). - -case {57357, 57565} % 0xE00D == 57357, 0XE0DD == 57565 - - if (~isempty(tmp.Data)) - error(message('images:dicom_add_item:AttributeCannotHaveData', sprintf( '(%04X,%04X)', tmp.Group, tmp.Element ))) - end - -otherwise - error(message('images:dicom_add_item:attributeNotSupported', sprintf( '(%04X,%04X)', tmp.Group, tmp.Element ))) - -end - -% Pad the data to an even byte boundary. -if (rem(getSizeInBytes(tmp.Data), 2) == 1) - tmp.Data(end + 1) = uint8(0); -end - -% Store the data. -attr_str = cat(2, attr_str, tmp); - - -function numBytes = getSizeInBytes(data) - -switch (class(data)) -case {'uint8', 'int8'} - multiplier = 1; - -case {'uint16', 'int16'} - multiplier = 2; - -case {'uint32', 'int32', 'single'} - multiplier = 4; - -case {'double'} - multiplier = 8; - -end - -numBytes = multiplier * numel(data); - - -function val = get_group_or_element(in) - -if (isempty(in)) - - error(message('images:dicom_add_item:groupElementNotHexOrInt')) - -elseif (ischar(in)) - - val = sscanf(in, '%x'); - -elseif (isnumeric(in)) - - val = in; - -else - - error(message('images:dicom_add_item:groupElementNotHexOrInt')) - -end +function attr_str = dicom_add_item(attr_str, group, element, varargin) +%DICOM_ADD_ITEM Add an item/delimiter to a structure of attributes. +% OUT = DICOM_ADD_ITEM(IN, GROUP, ELEMENT) +% OUT = DICOM_ADD_ITEM(IN, GROUP, ELEMENT, DATA) +% +% This function is similar to DICOM_ADD_ATTR, but it doesn't allow +% specifying VR values and can only be used for attributes of group +% FFFE. +% +% See also DICOM_ADD_ATTR, DICOM_ADD_PIXEL_DATA. + +% Copyright 1993-2010 The MathWorks, Inc. + + +% See PS-3.5 Sec. 7.5 for details on sequence and item encoding. + + +% Get group and element. +tmp.Group = get_group_or_element(group); +tmp.Element = get_group_or_element(element); + +% Get data value. +if (nargin > 4) + error(message('images:dicom_add_item:tooManyInputArgs')); +elseif (nargin == 3) + tmp.Data = []; +else + tmp.Data = varargin{1}; +end + +tmp.VR = 'UN'; + +% Check the group and element values. +if (tmp.Group ~= 65534) % 0xFFFE == 65534 + error(message('images:dicom_add_item:groupNotAccepted', sprintf( '%X', tmp.Group ))) +end + +switch (tmp.Element) +case {57344} % 0xE000 == 57344 + + % Data is okay for (FFFE,E000). + +case {57357, 57565} % 0xE00D == 57357, 0XE0DD == 57565 + + if (~isempty(tmp.Data)) + error(message('images:dicom_add_item:AttributeCannotHaveData', sprintf( '(%04X,%04X)', tmp.Group, tmp.Element ))) + end + +otherwise + error(message('images:dicom_add_item:attributeNotSupported', sprintf( '(%04X,%04X)', tmp.Group, tmp.Element ))) + +end + +% Pad the data to an even byte boundary. +if (rem(getSizeInBytes(tmp.Data), 2) == 1) + tmp.Data(end + 1) = uint8(0); +end + +% Store the data. +attr_str = cat(2, attr_str, tmp); + + +function numBytes = getSizeInBytes(data) + +switch (class(data)) +case {'uint8', 'int8'} + multiplier = 1; + +case {'uint16', 'int16'} + multiplier = 2; + +case {'uint32', 'int32', 'single'} + multiplier = 4; + +case {'double'} + multiplier = 8; + +end + +numBytes = multiplier * numel(data); + + +function val = get_group_or_element(in) + +if (isempty(in)) + + error(message('images:dicom_add_item:groupElementNotHexOrInt')) + +elseif (ischar(in)) + + val = sscanf(in, '%x'); + +elseif (isnumeric(in)) + + val = in; + +else + + error(message('images:dicom_add_item:groupElementNotHexOrInt')) + +end diff --git a/CT/private/dicom_close_msg.m b/CT/private/dicom_close_msg.m index dceab55..2b9f43e 100644 --- a/CT/private/dicom_close_msg.m +++ b/CT/private/dicom_close_msg.m @@ -1,22 +1,22 @@ -function file = dicom_close_msg(file) -%DICOM_CLOSE_MSG Close a DICOM message. -% FILE = DICOM_CLOSE_MSG(FILE) closes the DICOM message pointed to in -% FILE.FID. The returned value, FILE, is the updated file structure. - -% Copyright 1993-2010 The MathWorks, Inc. - -if (file.FID > 0) - - result = fclose(file.FID); - - if (result == -1) - - error(message('images:dicom_close_msg:unableToClose', file.Filename, ferror( file.FID ))) - - end - -else - - error(message('images:dicom_close_msg:invalidFID')) - -end +function file = dicom_close_msg(file) +%DICOM_CLOSE_MSG Close a DICOM message. +% FILE = DICOM_CLOSE_MSG(FILE) closes the DICOM message pointed to in +% FILE.FID. The returned value, FILE, is the updated file structure. + +% Copyright 1993-2010 The MathWorks, Inc. + +if (file.FID > 0) + + result = fclose(file.FID); + + if (result == -1) + + error(message('images:dicom_close_msg:unableToClose', file.Filename, ferror( file.FID ))) + + end + +else + + error(message('images:dicom_close_msg:invalidFID')) + +end diff --git a/CT/private/dicom_compress_pixel_cells.m b/CT/private/dicom_compress_pixel_cells.m index e27fe42..15e7732 100644 --- a/CT/private/dicom_compress_pixel_cells.m +++ b/CT/private/dicom_compress_pixel_cells.m @@ -1,133 +1,133 @@ -function attrs = dicom_compress_pixel_cells(pixel_cells, txfr, bits_allocated, dims) -%DICOM_COMPRESS_PIXEL_CELLS Compress pixel cells into fragments. -% ATTRS = DICOM_COMPRESS_PIXEL_CELLS(CELLS, TXFR, BITS, DIMS) compress -% the pixel cells CELLS using the transfer syntax TXFR. BITS is the -% size of each pixel cell, and DIMS is the MATLAB dimensions of the -% data contained in the pixel cells. -% -% The result is a structure of attributes containing the compressed -% pixel cell fragments and item delimiters. -% -% See also DICOM_ADD_ITEM. - -% Copyright 1993-2010 The MathWorks, Inc. - - -% Encapsulated (compressed) pixel cells have the following structure: -% -% (7FE0,0010) with undefined length (handled elsewhere) -% (FFFE,0000) containing off-set table or 0 length data -% (FFFE,0000) for each fragment (a collection of UINT8 data) -% (FFFE,00DD) with 0 length data - -% Create an empty container for the "sequence." -attrs = struct([]); - -% Compress the pixel_cells. -[fragments, frames] = compress_cells(pixel_cells, txfr, bits_allocated, dims); - -% Create the Basic Offset Table. -offsets = build_offset_table(fragments, frames); -attrs = dicom_add_item(attrs, 'FFFE', 'E000', offsets); - -% Add the fragments. -for p = 1:length(fragments) - - attrs = dicom_add_item(attrs, 'FFFE', 'E000', fragments{p}); - -end - -% Add the sequence delimiter. -attrs = dicom_add_item(attrs, 'FFFE', 'E0DD'); - - - -function [fragments, frames] = compress_cells(pixel_cells, txfr, ... - bits_allocated, dims) -%COMPRESS_CELLS Return a cell array of encoded pixel cell fragments. - -switch (txfr) -case '1.2.840.10008.1.2.5' - - [fragments, frames] = dicom_encode_rle(pixel_cells, bits_allocated, dims); - fragments = padFragments(fragments); - -case '1.2.840.10008.1.2.4.50' - - [fragments, frames] = dicom_encode_jpeg_lossy(pixel_cells, bits_allocated); - fragments = padFragments(fragments); - -case '1.2.840.10008.1.2.4.70' - - [fragments, frames] = dicom_encode_jpeg_lossless(pixel_cells, bits_allocated); - fragments = padFragments(fragments); - -case '1.2.840.10008.1.2.4.90' - - [fragments, frames] = dicom_encode_jpeg2000_lossless(pixel_cells, bits_allocated); - fragments = padFragments(fragments); - -case '1.2.840.10008.1.2.4.91' - - [fragments, frames] = dicom_encode_jpeg2000_lossy(pixel_cells, bits_allocated); - fragments = padFragments(fragments); - -otherwise - error(message('images:dicom_compress_pixel_cells:cannotCompress', txfr)) - -end - - - -function offset_table = build_offset_table(fragments, frames) -%BUILD_OFFSET_TABLE Create a vector of offsets to the start of the frames. - -% Don't store an offset table for just one frame. -if (length(frames) == 1) - offset_table = uint32([]); - return -end - -% Build an offset table, a UINT32 vector with byte offsets to the -% beginning of the next frame. The offsets are relative to the end of -% the offset table item, which is always followed by the first fragment. -offset_table = repmat(uint32(0), size(frames)); - -current_offset = 0; -current_frame = 1; - -for p = 1:length(fragments) - - if (p == frames(current_frame)) - - % Beginning of frame starts at current position. - offset_table(current_frame) = current_offset; - current_frame = current_frame + 1; - - % Offset to next fragment is 8 bytes for tag and length plus size - % of fragment. - current_offset = current_offset + 8 + numel(fragments{p}); - - else - - % Not the beginning of a new frame. - current_offset = current_offset + 8 + numel(fragments{p}); - - end - -end - - - -function fragments = padFragments(fragments) -%padFragments Add null bytes to odd-length fragments. - -for p = 1:numel(fragments) - - if (rem(numel(fragments{p}), 2) == 1) - - fragments{p}(end+1) = 0; - - end - -end +function attrs = dicom_compress_pixel_cells(pixel_cells, txfr, bits_allocated, dims) +%DICOM_COMPRESS_PIXEL_CELLS Compress pixel cells into fragments. +% ATTRS = DICOM_COMPRESS_PIXEL_CELLS(CELLS, TXFR, BITS, DIMS) compress +% the pixel cells CELLS using the transfer syntax TXFR. BITS is the +% size of each pixel cell, and DIMS is the MATLAB dimensions of the +% data contained in the pixel cells. +% +% The result is a structure of attributes containing the compressed +% pixel cell fragments and item delimiters. +% +% See also DICOM_ADD_ITEM. + +% Copyright 1993-2010 The MathWorks, Inc. + + +% Encapsulated (compressed) pixel cells have the following structure: +% +% (7FE0,0010) with undefined length (handled elsewhere) +% (FFFE,0000) containing off-set table or 0 length data +% (FFFE,0000) for each fragment (a collection of UINT8 data) +% (FFFE,00DD) with 0 length data + +% Create an empty container for the "sequence." +attrs = struct([]); + +% Compress the pixel_cells. +[fragments, frames] = compress_cells(pixel_cells, txfr, bits_allocated, dims); + +% Create the Basic Offset Table. +offsets = build_offset_table(fragments, frames); +attrs = dicom_add_item(attrs, 'FFFE', 'E000', offsets); + +% Add the fragments. +for p = 1:length(fragments) + + attrs = dicom_add_item(attrs, 'FFFE', 'E000', fragments{p}); + +end + +% Add the sequence delimiter. +attrs = dicom_add_item(attrs, 'FFFE', 'E0DD'); + + + +function [fragments, frames] = compress_cells(pixel_cells, txfr, ... + bits_allocated, dims) +%COMPRESS_CELLS Return a cell array of encoded pixel cell fragments. + +switch (txfr) +case '1.2.840.10008.1.2.5' + + [fragments, frames] = dicom_encode_rle(pixel_cells, bits_allocated, dims); + fragments = padFragments(fragments); + +case '1.2.840.10008.1.2.4.50' + + [fragments, frames] = dicom_encode_jpeg_lossy(pixel_cells, bits_allocated); + fragments = padFragments(fragments); + +case '1.2.840.10008.1.2.4.70' + + [fragments, frames] = dicom_encode_jpeg_lossless(pixel_cells, bits_allocated); + fragments = padFragments(fragments); + +case '1.2.840.10008.1.2.4.90' + + [fragments, frames] = dicom_encode_jpeg2000_lossless(pixel_cells, bits_allocated); + fragments = padFragments(fragments); + +case '1.2.840.10008.1.2.4.91' + + [fragments, frames] = dicom_encode_jpeg2000_lossy(pixel_cells, bits_allocated); + fragments = padFragments(fragments); + +otherwise + error(message('images:dicom_compress_pixel_cells:cannotCompress', txfr)) + +end + + + +function offset_table = build_offset_table(fragments, frames) +%BUILD_OFFSET_TABLE Create a vector of offsets to the start of the frames. + +% Don't store an offset table for just one frame. +if (length(frames) == 1) + offset_table = uint32([]); + return +end + +% Build an offset table, a UINT32 vector with byte offsets to the +% beginning of the next frame. The offsets are relative to the end of +% the offset table item, which is always followed by the first fragment. +offset_table = repmat(uint32(0), size(frames)); + +current_offset = 0; +current_frame = 1; + +for p = 1:length(fragments) + + if (p == frames(current_frame)) + + % Beginning of frame starts at current position. + offset_table(current_frame) = current_offset; + current_frame = current_frame + 1; + + % Offset to next fragment is 8 bytes for tag and length plus size + % of fragment. + current_offset = current_offset + 8 + numel(fragments{p}); + + else + + % Not the beginning of a new frame. + current_offset = current_offset + 8 + numel(fragments{p}); + + end + +end + + + +function fragments = padFragments(fragments) +%padFragments Add null bytes to odd-length fragments. + +for p = 1:numel(fragments) + + if (rem(numel(fragments{p}), 2) == 1) + + fragments{p}(end+1) = 0; + + end + +end diff --git a/CT/private/dicom_convert_meta_to_attr.m b/CT/private/dicom_convert_meta_to_attr.m index baa6d64..22957cf 100644 --- a/CT/private/dicom_convert_meta_to_attr.m +++ b/CT/private/dicom_convert_meta_to_attr.m @@ -1,166 +1,166 @@ -function attr = dicom_convert_meta_to_attr(attr_name, metadata, dictionary, txfr) -%DICOM_CONVERT_META_TO_ATTR Convert a metadata field to an attr struct. - -% Copyright 1993-2011 The MathWorks, Inc. - -% Look up the attribute tag. -tag = dicom_tag_lookup(attr_name, dictionary); - -if (isempty(tag)) - - attr = []; - return - -end - -% Get the VR. -VR = determine_VR(tag, metadata, dictionary, txfr); - -% Process struct data - Person Names (PN) and sequences (SQ). -if (isequal(VR, 'PN') || isPersonName(metadata.(attr_name))) - - data = dicom_encode_pn(metadata.(attr_name)); - -elseif (isequal(VR, 'SQ') || isstruct(metadata.(attr_name))) - - data = encode_SQ(metadata.(attr_name), dictionary, txfr); - -else - - data = metadata.(attr_name); - -end - - -% Add the attribute. -if (isempty(VR)) - attr = dicom_add_attr([], tag(1), tag(2), dictionary, data); -else - attr = dicom_add_attr([], tag(1), tag(2), dictionary, data, VR); -end - - - -function VR = determine_VR(tag, metadata, dictionary, txfr) -%DETERMINE_VR Find an attribute's value representation (VR). - -attr_details = dicom_dict_lookup(tag(1), tag(2), dictionary); - -if (isempty(attr_details)) - - if (tag(2) == 0) - VR = 'UL'; - else - VR = []; - end - -else - - VR = attr_details.VR; - - if (iscell(VR)) - - % If it's US/SS, look at Pixel Representation (0028,0103). - % If it's OB/OW, look at whether it's compressed. - - - if (~isempty(strfind([VR{:}], 'US'))) - - PixRep = dicomlookup('0028','0103'); - if (isfield(metadata, PixRep) && (metadata.(PixRep) == 1)) - VR = 'SS'; - else - VR = 'US'; - end - - elseif (isequal(tag, uint16([sscanf('7fe0', '%x'), ... - sscanf('0010', '%x')]))) - - uidDetails = dicom_uid_decode(txfr); - bitDepth = metadata.(dicomlookup('0028', '0100')); - - if (uidDetails.Compressed) - - VR = 'OB'; - - elseif (isequal(uidDetails.VR, 'IMPLICIT')) - - VR = 'OW'; - - elseif (bitDepth > 8) - - VR = 'OW'; - - else - - VR = 'OB'; - - end - - else - VR = VR{1}; - end - - end - -end - - - -function attrs = encode_SQ(SQ_struct, dictionary, txfr) -%ENCODE_SQ Turn a structure of sequence data into attributes. - -attrs = []; - -if (isempty(SQ_struct)) - return -end - -% Don't worry about encoding rules yet. Just convert the MATLAB struct -% containing item and data fields into an array of attribute structs. - -items = fieldnames(SQ_struct); -for p = 1:numel(items) - - data = encode_item(SQ_struct.(items{p}), dictionary, txfr); - attrs = dicom_add_attr(attrs, 'fffe', 'e000', dictionary, data); - -end - - - -function attrs = encode_item(item_struct, dictionary, txfr) -%ENCODE_ITEM Turn one item of a sequence into attributes. - -attrs = []; - -if (isempty(item_struct)) - return -end - -attr_names = fieldnames(item_struct); -for p = 1:numel(attr_names) - - new_attr = dicom_convert_meta_to_attr(attr_names{p}, item_struct, dictionary, txfr); - attrs = cat(2, attrs, new_attr); - -end - - - -function tf = isPersonName(attr) - -if (isstruct(attr)) - - tf = isfield(attr, 'FamilyName') || ... - isfield(attr, 'GivenName') || ... - isfield(attr, 'MiddleName') || ... - isfield(attr, 'NamePrefix') || ... - isfield(attr, 'NameSuffix'); - -else - - tf = false; - -end - +function attr = dicom_convert_meta_to_attr(attr_name, metadata, dictionary, txfr) +%DICOM_CONVERT_META_TO_ATTR Convert a metadata field to an attr struct. + +% Copyright 1993-2011 The MathWorks, Inc. + +% Look up the attribute tag. +tag = dicom_tag_lookup(attr_name, dictionary); + +if (isempty(tag)) + + attr = []; + return + +end + +% Get the VR. +VR = determine_VR(tag, metadata, dictionary, txfr); + +% Process struct data - Person Names (PN) and sequences (SQ). +if (isequal(VR, 'PN') || isPersonName(metadata.(attr_name))) + + data = dicom_encode_pn(metadata.(attr_name)); + +elseif (isequal(VR, 'SQ') || isstruct(metadata.(attr_name))) + + data = encode_SQ(metadata.(attr_name), dictionary, txfr); + +else + + data = metadata.(attr_name); + +end + + +% Add the attribute. +if (isempty(VR)) + attr = dicom_add_attr([], tag(1), tag(2), dictionary, data); +else + attr = dicom_add_attr([], tag(1), tag(2), dictionary, data, VR); +end + + + +function VR = determine_VR(tag, metadata, dictionary, txfr) +%DETERMINE_VR Find an attribute's value representation (VR). + +attr_details = dicom_dict_lookup(tag(1), tag(2), dictionary); + +if (isempty(attr_details)) + + if (tag(2) == 0) + VR = 'UL'; + else + VR = []; + end + +else + + VR = attr_details.VR; + + if (iscell(VR)) + + % If it's US/SS, look at Pixel Representation (0028,0103). + % If it's OB/OW, look at whether it's compressed. + + + if (~isempty(strfind([VR{:}], 'US'))) + + PixRep = dicomlookup('0028','0103'); + if (isfield(metadata, PixRep) && (metadata.(PixRep) == 1)) + VR = 'SS'; + else + VR = 'US'; + end + + elseif (isequal(tag, uint16([sscanf('7fe0', '%x'), ... + sscanf('0010', '%x')]))) + + uidDetails = dicom_uid_decode(txfr); + bitDepth = metadata.(dicomlookup('0028', '0100')); + + if (uidDetails.Compressed) + + VR = 'OB'; + + elseif (isequal(uidDetails.VR, 'IMPLICIT')) + + VR = 'OW'; + + elseif (bitDepth > 8) + + VR = 'OW'; + + else + + VR = 'OB'; + + end + + else + VR = VR{1}; + end + + end + +end + + + +function attrs = encode_SQ(SQ_struct, dictionary, txfr) +%ENCODE_SQ Turn a structure of sequence data into attributes. + +attrs = []; + +if (isempty(SQ_struct)) + return +end + +% Don't worry about encoding rules yet. Just convert the MATLAB struct +% containing item and data fields into an array of attribute structs. + +items = fieldnames(SQ_struct); +for p = 1:numel(items) + + data = encode_item(SQ_struct.(items{p}), dictionary, txfr); + attrs = dicom_add_attr(attrs, 'fffe', 'e000', dictionary, data); + +end + + + +function attrs = encode_item(item_struct, dictionary, txfr) +%ENCODE_ITEM Turn one item of a sequence into attributes. + +attrs = []; + +if (isempty(item_struct)) + return +end + +attr_names = fieldnames(item_struct); +for p = 1:numel(attr_names) + + new_attr = dicom_convert_meta_to_attr(attr_names{p}, item_struct, dictionary, txfr); + attrs = cat(2, attrs, new_attr); + +end + + + +function tf = isPersonName(attr) + +if (isstruct(attr)) + + tf = isfield(attr, 'FamilyName') || ... + isfield(attr, 'GivenName') || ... + isfield(attr, 'MiddleName') || ... + isfield(attr, 'NamePrefix') || ... + isfield(attr, 'NameSuffix'); + +else + + tf = false; + +end + diff --git a/CT/private/dicom_copy_IOD.m b/CT/private/dicom_copy_IOD.m index 9c63acd..33ed893 100644 --- a/CT/private/dicom_copy_IOD.m +++ b/CT/private/dicom_copy_IOD.m @@ -1,141 +1,141 @@ -function [all_attrs, status] = dicom_copy_IOD(X, map, metadata, options) -%DICOM_COPY_IOD Copy attributes from metadata to an arbitrary IOD. -% [ATTRS, STATUS] = DICOM_COPY_IOD(X, MAP, METADATA, OPTIONS) creates -% a structure array of DICOM attributes for an arbitrary SOP class -% corresponding to the class contained in the metadata structure. The -% value of image pixel attributes are derived from the image X and the -% colormap MAP. Non-image attributes are derived from the METADATA -% struct (typically given by DICOMINFO) and the transfer syntax UID -% (OPTIONS.txfr). -% -% NOTE: This routine does not verify that attributes in METADATA belong -% in the information object. A risk exists that invalid data passed to -% this routine will lead to formally correct DICOM files that contain -% incomplete or nonsensical data. -% -% See also: DICOMWRITE, DICOM_CREATE_IOD. - -% Copyright 1993-2014 The MathWorks, Inc. - -dictionary = dicomdict('get_current'); - -all_attrs = []; -status = []; - -% Determine what kind of object to write. -IOD_UID = getIOD(metadata, options, dictionary); - -% Update the instance-specific and required metadata. -metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); -metadata = dicom_prep_FileMetadata(metadata, IOD_UID, options.txfr, dictionary); -metadata = dicom_prep_ImagePixel(metadata, X, map, options.txfr, options.usemetadatabitdepths, dictionary); - -% Get the metadata fields that need to be processed. -metadata_fields = fieldnames(metadata); -fields_to_write = remove_dicominfo_fields(metadata_fields); - -% Process all of the remaining metadata -for p = 1:numel(fields_to_write) - - attr_name = fields_to_write{p}; - - % Private tags have an odd group number. Only write them if the - % 'WritePrivate' option was true. - tag = dicom_tag_lookup(attr_name, dictionary); - if (isempty(tag)) || ((~options.writeprivate) && (rem(tag(1), 2) == 1)) - continue; - end - - new_attr = dicom_convert_meta_to_attr(attr_name, metadata, dictionary, options.txfr); - all_attrs = cat(2, all_attrs, new_attr); - -end - - - -function fields_out = remove_dicominfo_fields(metadata_fields) -%REMOVE_DICOMINFO_FIELDS Strip DICOMINFO-specific fields from metadata. - -dicominfo_fields = get_dicominfo_fields; -fields_out = setdiff(metadata_fields, dicominfo_fields); - - - -function fields = get_dicominfo_fields -%GET_DICOMINFO_FIELDS Get a cell array of field names specific to DICOMINFO. - -fields = {'Filename' - 'FileModDate' - 'FileSize' - 'Format' - 'FormatVersion' - 'Width' - 'Height' - 'BitDepth' - 'ColorType' - 'SelectedFrames' - 'FileStruct' - 'StartOfPixelData'}; - - - -function uidValue = getIOD(metadata, options, dictionary) - -% A field containing the SOP Class UID is necessary for writing. -% Look for the fields (0002,0002) and/or (0008,0016) in the metadata -% and/or options. - -% (0002,0002) is usually called "Media Storage SOP Class UID." It's the -% file metadata version of (0008,0016). -MediaStorageUID_name = dicom_name_lookup('0002', '0002', dictionary); - -if (isfield(metadata, MediaStorageUID_name)) - uidValue_0002_0002 = metadata.(MediaStorageUID_name); -else - uidValue_0002_0002 = ''; -end - -% (0008,0016) is usually known as "SOP Class UID." -SOPClassUID_name = dicom_name_lookup('0008', '0016', dictionary); -if (isfield(options, 'sopclassuid')) - - uidValue_0008_0016 = options.sopclassuid; - -else - - % Look for the SOP Class UID in the metadata under a different name. - if (isfield(metadata, SOPClassUID_name)) - uidValue_0008_0016 = metadata.(SOPClassUID_name); - else - uidValue_0008_0016 = ''; - end - -end - -% Pick the value of the UID. -if (~isempty(uidValue_0002_0002) && isempty(uidValue_0008_0016)) - - % Use the value of (0002,0002). - uidValue = uidValue_0002_0002; - -elseif (isempty(uidValue_0002_0002) && ~isempty(uidValue_0008_0016)) - - % Use the value of (0008, 0016). - uidValue = uidValue_0008_0016; - -elseif (~isempty(uidValue_0002_0002) && ~isempty(uidValue_0008_0016)) - - % If both Media Storage Class UID and SOP Class UID are present, they - % must match. - if isequal(uidValue_0002_0002, uidValue_0008_0016) - uidValue = uidValue_0002_0002; - else - error(message('images:dicom_copy_IOD:iodMismatch', SOPClassUID_name, MediaStorageUID_name)) - end - -else - - error(message('images:dicom_copy_IOD:missingSOPClassUID', SOPClassUID_name)); - -end - +function [all_attrs, status] = dicom_copy_IOD(X, map, metadata, options) +%DICOM_COPY_IOD Copy attributes from metadata to an arbitrary IOD. +% [ATTRS, STATUS] = DICOM_COPY_IOD(X, MAP, METADATA, OPTIONS) creates +% a structure array of DICOM attributes for an arbitrary SOP class +% corresponding to the class contained in the metadata structure. The +% value of image pixel attributes are derived from the image X and the +% colormap MAP. Non-image attributes are derived from the METADATA +% struct (typically given by DICOMINFO) and the transfer syntax UID +% (OPTIONS.txfr). +% +% NOTE: This routine does not verify that attributes in METADATA belong +% in the information object. A risk exists that invalid data passed to +% this routine will lead to formally correct DICOM files that contain +% incomplete or nonsensical data. +% +% See also: DICOMWRITE, DICOM_CREATE_IOD. + +% Copyright 1993-2014 The MathWorks, Inc. + +dictionary = dicomdict('get_current'); + +all_attrs = []; +status = []; + +% Determine what kind of object to write. +IOD_UID = getIOD(metadata, options, dictionary); + +% Update the instance-specific and required metadata. +metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); +metadata = dicom_prep_FileMetadata(metadata, IOD_UID, options.txfr, dictionary); +metadata = dicom_prep_ImagePixel(metadata, X, map, options.txfr, options.usemetadatabitdepths, dictionary); + +% Get the metadata fields that need to be processed. +metadata_fields = fieldnames(metadata); +fields_to_write = remove_dicominfo_fields(metadata_fields); + +% Process all of the remaining metadata +for p = 1:numel(fields_to_write) + + attr_name = fields_to_write{p}; + + % Private tags have an odd group number. Only write them if the + % 'WritePrivate' option was true. + tag = dicom_tag_lookup(attr_name, dictionary); + if (isempty(tag)) || ((~options.writeprivate) && (rem(tag(1), 2) == 1)) + continue; + end + + new_attr = dicom_convert_meta_to_attr(attr_name, metadata, dictionary, options.txfr); + all_attrs = cat(2, all_attrs, new_attr); + +end + + + +function fields_out = remove_dicominfo_fields(metadata_fields) +%REMOVE_DICOMINFO_FIELDS Strip DICOMINFO-specific fields from metadata. + +dicominfo_fields = get_dicominfo_fields; +fields_out = setdiff(metadata_fields, dicominfo_fields); + + + +function fields = get_dicominfo_fields +%GET_DICOMINFO_FIELDS Get a cell array of field names specific to DICOMINFO. + +fields = {'Filename' + 'FileModDate' + 'FileSize' + 'Format' + 'FormatVersion' + 'Width' + 'Height' + 'BitDepth' + 'ColorType' + 'SelectedFrames' + 'FileStruct' + 'StartOfPixelData'}; + + + +function uidValue = getIOD(metadata, options, dictionary) + +% A field containing the SOP Class UID is necessary for writing. +% Look for the fields (0002,0002) and/or (0008,0016) in the metadata +% and/or options. + +% (0002,0002) is usually called "Media Storage SOP Class UID." It's the +% file metadata version of (0008,0016). +MediaStorageUID_name = dicom_name_lookup('0002', '0002', dictionary); + +if (isfield(metadata, MediaStorageUID_name)) + uidValue_0002_0002 = metadata.(MediaStorageUID_name); +else + uidValue_0002_0002 = ''; +end + +% (0008,0016) is usually known as "SOP Class UID." +SOPClassUID_name = dicom_name_lookup('0008', '0016', dictionary); +if (isfield(options, 'sopclassuid')) + + uidValue_0008_0016 = options.sopclassuid; + +else + + % Look for the SOP Class UID in the metadata under a different name. + if (isfield(metadata, SOPClassUID_name)) + uidValue_0008_0016 = metadata.(SOPClassUID_name); + else + uidValue_0008_0016 = ''; + end + +end + +% Pick the value of the UID. +if (~isempty(uidValue_0002_0002) && isempty(uidValue_0008_0016)) + + % Use the value of (0002,0002). + uidValue = uidValue_0002_0002; + +elseif (isempty(uidValue_0002_0002) && ~isempty(uidValue_0008_0016)) + + % Use the value of (0008, 0016). + uidValue = uidValue_0008_0016; + +elseif (~isempty(uidValue_0002_0002) && ~isempty(uidValue_0008_0016)) + + % If both Media Storage Class UID and SOP Class UID are present, they + % must match. + if isequal(uidValue_0002_0002, uidValue_0008_0016) + uidValue = uidValue_0002_0002; + else + error(message('images:dicom_copy_IOD:iodMismatch', SOPClassUID_name, MediaStorageUID_name)) + end + +else + + error(message('images:dicom_copy_IOD:missingSOPClassUID', SOPClassUID_name)); + +end + diff --git a/CT/private/dicom_create_IOD.m b/CT/private/dicom_create_IOD.m index 472762b..b311c5d 100644 --- a/CT/private/dicom_create_IOD.m +++ b/CT/private/dicom_create_IOD.m @@ -1,603 +1,603 @@ -function [attrs, status] = dicom_create_IOD(IOD_UID, X, map, metadata, options) -%DICOM_CREATE_IOD Create the attributes for a given IOD. -% [ATTRS, STATUS] = DICOM_CREATE_IOD(UID, X, MAP, METADATA, TXFR) -% creates a structure array of DICOM attributes for the SOP Class -% corresponding to UID. The attributes' values are bassed on the image -% X, colormap MAP, the METADATA struct (as given by DICOMINFO, for -% example), and the transfer syntax UID (TXFR) used to encode the -% file. -% -% This is the principal function of DICOM information object creation. -% -% See also DICOMWRITE, DICOM_PREP_METADATA, DICOM_IODS, DICOM_MODULES. - -% Copyright 1993-2014 The MathWorks, Inc. - -dictionary = dicomdict('get_current'); - -% Find modules and other details. -iod_details = get_iod_details(IOD_UID); - -if (isempty(iod_details)) - error(message('images:dicom_create_IOD:unimplementedIOD', IOD_UID)); -end - -% Set necessary values for this IOD. -metadata = dicom_prep_metadata(IOD_UID, metadata, X, map, options.txfr, options.usemetadatabitdepths, dictionary); - -% Look for all of the private metadata. -if (isfield(options, 'writeprivate')) - - if (islogical(options.writeprivate) || ... - isnumeric(options.writeprivate)) - - if (options.writeprivate) - - [private_tags, private_names] = find_private_metadata(metadata, dictionary); - - end - - else - - error(message('images:dicom_create_IOD:invalidWritePrivateValue')) - - end - -end - -% A module definition function must be registered for unsupported or -% private modules. Private IODs need only use a custom definition -% function for unimplemented modules, but they must have a definition -% function listed. (Registration not yet supported.) -if (isempty(iod_details.Def_fcn)) - error(message('images:dicom_create_IOD:noModuleDefinitionFcn', iod_details.Name, IOD_UID)); -end - -attrs = []; -status = []; -for p = 1:numel(iod_details.Modules(:,1)) - - % Determine whether to encode this module. - if (test_module_condition(X, metadata, iod_details.Modules(p,:), dictionary)) - - % Find the attributes in the module. - module_details = dicom_modules(iod_details.Modules{p,1}); - - if (isempty(module_details)) - error(message('images:dicom_create_IOD:undefinedModule', iod_details.Modules{ p, 1 })); - end - - % Process attributes. - [new_attrs, new_status] = process_modules(module_details.Attrs, ... - metadata, ... - dictionary); - attrs = add_to_IOD(attrs, new_attrs); - status = add_to_status(status, new_status); - - end - -end - -% Process any private metadata. -if ((options.writeprivate) && (~isempty(private_tags))) - - new_attrs = process_private(private_tags, private_names, metadata, dictionary, options.txfr); - attrs = add_to_IOD(attrs, new_attrs); - -end - - - -function iod_details = get_iod_details(IOD_UID) -%GET_IOD_DETAILS Find the details of a given IOD. - -% Load IOD definitions. -iod_directory = dicom_iods; - -% Look for a particular UID. -idx = strmatch(IOD_UID, {iod_directory.UID}, 'exact'); - -if (isempty(idx)) - iod_details = []; -else - iod_details = iod_directory(idx); -end - - - -function attrs = add_to_IOD(attrs, new_attrs) -%ADD_TO_IOD Add attributes to an existing IOD. -attrs = cat(2, attrs, new_attrs); - - - -function status = add_to_status(status, new_status) -%ADD_TO_STATUS Add status values to an existing status struct. - -fnames = fieldnames(new_status); -for p = 1:numel(fnames) - if (isfield(status, fnames{p})) - status(1).(fnames{p}) = [status(1).(fnames{p}) ... - new_status(1).(fnames{p})]; - else - status(1).(fnames{p}) = new_status(1).(fnames{p}); - end -end - - - -function [attrs, status] = process_modules(attr_details, metadata, dictionary) -%PROCESS_MODULES Create attributes from a module definition. - -attrs = []; -status.BadAttribute = {}; -status.MissingCondition = {}; -status.MissingData = {}; -status.SuspectAttribute = {}; - -p = 0; -numAttrs = numel(attr_details(:,1)); -% For each attribute in the module... -while (p < numAttrs) - - p = p + 1; - - level = attr_details{p, 1}; - group = attr_details{p, 2}; - element = attr_details{p, 3}; - attr_type = attr_details{p, 4}; - vr_map = attr_details{p, 5}; - enum_values = attr_details{p, 6}; - condition = attr_details{p, 7}; - - % Skip all sequence attributes for now. - % - % NOTE: At this point, all sequences in supported modules are - % type 3 (optional). We can, and will, safely skip all - % attributes in a sequence (including the top level attribute). - - nextAttr = p + 1; - if ((p < numAttrs) && ... - (attr_details{nextAttr, 1} > level)) - - % The "level" determines nesting, with 0 as the - % non-sequence elements - while ((nextAttr <= numAttrs) && ... - (attr_details{nextAttr, 1} > level)) - - nextAttr = nextAttr + 1; - - end - - % Point to the next attribute after the sequence. - p = nextAttr - 1; - continue - - end - - % - % Look for the attribute from the module. - % - try - name = dicom_name_lookup(group, element, dictionary); - catch - status.BadAttribute{end + 1} = sprintf('(%s,%s)', ... - group, element); - continue; - end - - if (isempty(name)) - status.BadAttribute{end + 1} = sprintf('(%s,%s)', ... - group, element); - continue; - end - - % - % Determine how to handle the attribute. - % - switch (attr_type) - case {'1', '2', '3'} - % Must process the attribute. No special action. - - case {'1C', '2C'} - % Conditionally process the attribute. - if (isempty(condition)) - - status.MissingCondition{end + 1} = sprintf('(%s,%s)', ... - group, element); - continue; - - else - - if (~check_condition(condition, metadata, dictionary)) - continue; - end - - end - - otherwise - status.BadAttribute{end + 1} = sprintf('(%s,%s)', group, element); - continue; - - end - - % Process special VR mapping. - if ((isequal(group, '7FE0')) && (isequal(element, '0010'))) - VR = get_pixelData_VR(metadata, dictionary); - else - if (numel(vr_map) == 0) - VR = ''; - else - VR = remap_vr(group, element, vr_map, metadata, dictionary); - end - end - - % - % Add the attribute. - % - if (isfield(metadata, name)) - - % Process acceptable values. - if ((~isempty(enum_values)) && (~isempty(metadata.(name)))) - if (~check_values(metadata.(name), enum_values)) - status.SuspectAttribute{end + 1} = sprintf('(%s,%s)', ... - group, element); - end - end - - % Add the attribute. - if (isempty(VR)) - attrs = dicom_add_attr(attrs, group, element, dictionary, ... - metadata.(name)); - else - attrs = dicom_add_attr(attrs, group, element, dictionary, ... - metadata.(name), VR); - end - - else - - switch (attr_type) - case {'1', '1C'} - - % The attribute should exist. - status.MissingData{end + 1} = sprintf('(%s,%s)', ... - group, element); - continue; - - case {'2', '2C'} - - % Nonexistent attributes have empty data. - attrs = dicom_add_attr(attrs, group, element, dictionary); - - end - - % Nonexistent type 3 is ignored. - - end - -end - - - -function tf = check_condition(expr, metadata, dictionary) -%CHECK_CONDITION Determine whether a particular condition is true. -% -% Conditions are LISP-style cell arrays. The first element is a -% conditional operator, remaining cells are arguments to the operator. -% -% Conditions can be nested. Each cell array in expr indicates a new -% conditional expression. - -% -% Error checking -% -if (~iscell(expr)) - error(message('images:dicom_create_IOD:conditionalsMustBeCellArrays')) -end - -if (numel(expr) == 1) - operator = expr{1}; - operands = {}; -else - operator = expr{1}; - operands = expr(2:end); -end - - -% -% Process conditional expressions recursively. -% -switch (lower(operator)) -case 'and' - - % This AND short circuits. - for p = 1:numel(operands) - tf = check_condition(operands{p}, metadata, dictionary); - - if (~tf) - return - end - end - -case 'equal' - - if (numel(operands) ~= 2) - error(message('images:dicom_create_IOD:presentOpNeeds2Args')) - end - - % Get the group and element from the first operand. - group = operands{1}(2:5); - element = operands{1}(7:10); - - % Look up the metadata value. - name = dicom_name_lookup(group, element, dictionary); - - if (isfield(metadata, name)) - tf = isequal(metadata.(name), operands{2}); - else - tf = false; - end - -case 'false' - - tf = false; - -case 'not' - - if (numel(operands) ~= 1) - error(message('images:dicom_create_IOD:tooManyArgsToNot')) - else - tf = ~check_condition(operands{1}, metadata, dictionary); - end - -case 'or' - - % This OR short circuits. - for p = 1:numel(operands) - tf = check_condition(operands{p}, metadata, dictionary); - - if (tf) - return - end - end - -case 'present' - - if (numel(operands) ~= 1) - error(message('images:dicom_create_IOD:presentNeeds1Arg')) - end - - % Get the group and element from the first operand. - group = operands{1}(2:5); - element = operands{1}(7:10); - - % Look up the metadata value. - name = dicom_name_lookup(group, element, dictionary); - - tf = isfield(metadata, name); - -case 'true' - - tf = true; - -otherwise - error(message('images:dicom_create_IOD:badConditionalOp', operator)) -end - - - -function tf = test_module_condition(X, metadata, module, dictionary) -%TEST_MODULE_CONDITIONS Test whether a module should be created. - -moduleType = module{2}; -moduleCondition = module{3}; - -switch (moduleType) -case {'M'} - - tf = true; - -case {'U', 'C'} - - if (isempty(moduleCondition)) - tf = false; - return - end - - switch (moduleCondition{1}) - case 'HAS_FILEMETADATA' - tf = true; - - case 'HAS_BOLUS' - tf = isfield(metadata, dicom_name_lookup('0018', '0010', dictionary)); - - case 'HAS_OVERLAY' - tf = isfield(metadata, dicom_name_lookup('6000', '0010', dictionary)); - - case 'HAS_VOILUT' - tf = ((isfield(metadata, dicom_name_lookup('0028', '3010', dictionary))) || ... - (isfield(metadata, dicom_name_lookup('0028', '1050', dictionary)))); - - case 'IS_MULTIFRAME' - tf = size(X,4) > 1; - - case 'NEEDS_SC_CINE' - - framePointerAttr = dicom_name_lookup('0028', '0009', dictionary); - if (isfield(metadata, framePointerAttr)) - - framePointer = metadata.(framePointerAttr); - tf = (isequal(framePointer(:), sscanf('0018 1063', '%x')) || ... - isequal(framePointer(:), sscanf('0018 1065', '%x'))); - - else - - tf = false; - - end - - case 'TRUE' - tf = true; - - case 'FALSE' - tf = false; - - otherwise - tf = false; - - end - -otherwise - error(message('images:dicom_create_IOD:unknownModuleType', moduleType)) - -end - - - -function VR = remap_vr(group, element, vr_map, metadata, dictionary) -%REMAP_VR Translate a VR mapping into a VR value. - -% Default behavior is an empty VR. -VR = ''; - -if (numel(vr_map) > 1) - - % First item in VR map is an attribute tag. - cond_name = dicom_name_lookup(vr_map{1}(2:5), vr_map{1}(7:10), dictionary); - - % Remaining items map the conditional attribute's value to a VR. - if (isfield(metadata, cond_name)) - - for p = 2:numel(vr_map) - - if (isequal(metadata.(cond_name), vr_map{p}{1})) - - VR = vr_map{p}{2}; - return - - end - - end - - end - - % If the condition isn't met (or the attribute isn't present), - % use the first entry in the attribute's VR list. - if (isempty(VR)) - - attr = dicom_dict_lookup(group, element); - VR = attr.VR; - - end - -elseif (numel(vr_map) == 1) - - % Only item in VR map is the VR. - VR = vr_map{1}; - -end - - - -function VR = get_pixelData_VR(metadata, dictionary) -%GET_PIXELDATA_VR Get the VR for (7FE0,0010). -% -% PS 3.5 Sec. 8 contains the rules for picking the VR of (7FE0,0010). - -uid_details = dicom_uid_decode(metadata.(dicom_name_lookup('0002','0010', dictionary))); - -if (uid_details.Compressed) - - % Compressed pixels are always stored OB. - VR = 'OB'; - -elseif (isequal(uid_details.VR, 'Implicit')) - - % Implicit VR transfer syntaxes are always OW. - VR = 'OW'; - -else - - % The VR of other Explicit VR transfter syntaxes depends on the bit - % depth of the pixels. - if (metadata.(dicom_name_lookup('0028','0100', dictionary)) <= 8) - VR = 'OB'; - else - VR = 'OW'; - end - -end - - - -function tf = check_values(attr_data, enum_values) -%CHECK_VALUE Verify an attribute's data against required values. - -if (iscellstr(enum_values)) - - tf = any(strcmp(attr_data, enum_values)); - -else - - tmp = 0; - for p = 1:numel(enum_values) - tmp = tmp + isequal(attr_data, enum_values{p}); - end - - tf = (tmp > 0); - -end - - - -function [private_attrs, private_names] = find_private_metadata(metadata, dictionary) -%FIND_PRIVATE_METADATA Find user-contributed private metadata. - -% Private attributes have odd element values in their tag. -% -% By default these values come back from DICOMINFO with field names like -% "Private_0029_1004" and "Private_0029_10xx_Creator". -% -% If a custom data dictionary is provided, the names will look like -% regular DICOM attributes. - -private_attrs = {}; -private_names = {}; - -% Look through metadata for private attributes. -fields = fieldnames(metadata); - -for p = 1:numel(fields) - - tag = dicom_tag_lookup(fields{p}, dictionary); - - if ((~isempty(tag)) && (rem(tag(1), 2) ~= 0)) - private_attrs{end + 1} = tag; %#ok - private_names{end + 1} = fields{p}; %#ok - end - -end - - - -function [attrs, status] = process_private(private_tags, private_names, metadata, dictionary, txfr) -%PROCESS_PRIVATE Create attributes from the private data list. - -% Delay judgement of Private attributes. -status.BadAttribute = {}; -status.MissingCondition = {}; -status.MissingData = {}; -status.SuspectAttribute = {}; - -% Preallocate the attribute structure. -attrs(numel(private_tags)).Group = []; -attrs(end).Element = []; -attrs(end).VR = ''; -attrs(end).VM = []; -attrs(end).Data = []; - -% Convert the private data to a writable attribute. -for p = 1:numel(private_tags) - attrs(p) = dicom_convert_meta_to_attr(private_names{p}, metadata, dictionary, txfr); -end - +function [attrs, status] = dicom_create_IOD(IOD_UID, X, map, metadata, options) +%DICOM_CREATE_IOD Create the attributes for a given IOD. +% [ATTRS, STATUS] = DICOM_CREATE_IOD(UID, X, MAP, METADATA, TXFR) +% creates a structure array of DICOM attributes for the SOP Class +% corresponding to UID. The attributes' values are bassed on the image +% X, colormap MAP, the METADATA struct (as given by DICOMINFO, for +% example), and the transfer syntax UID (TXFR) used to encode the +% file. +% +% This is the principal function of DICOM information object creation. +% +% See also DICOMWRITE, DICOM_PREP_METADATA, DICOM_IODS, DICOM_MODULES. + +% Copyright 1993-2014 The MathWorks, Inc. + +dictionary = dicomdict('get_current'); + +% Find modules and other details. +iod_details = get_iod_details(IOD_UID); + +if (isempty(iod_details)) + error(message('images:dicom_create_IOD:unimplementedIOD', IOD_UID)); +end + +% Set necessary values for this IOD. +metadata = dicom_prep_metadata(IOD_UID, metadata, X, map, options.txfr, options.usemetadatabitdepths, dictionary); + +% Look for all of the private metadata. +if (isfield(options, 'writeprivate')) + + if (islogical(options.writeprivate) || ... + isnumeric(options.writeprivate)) + + if (options.writeprivate) + + [private_tags, private_names] = find_private_metadata(metadata, dictionary); + + end + + else + + error(message('images:dicom_create_IOD:invalidWritePrivateValue')) + + end + +end + +% A module definition function must be registered for unsupported or +% private modules. Private IODs need only use a custom definition +% function for unimplemented modules, but they must have a definition +% function listed. (Registration not yet supported.) +if (isempty(iod_details.Def_fcn)) + error(message('images:dicom_create_IOD:noModuleDefinitionFcn', iod_details.Name, IOD_UID)); +end + +attrs = []; +status = []; +for p = 1:numel(iod_details.Modules(:,1)) + + % Determine whether to encode this module. + if (test_module_condition(X, metadata, iod_details.Modules(p,:), dictionary)) + + % Find the attributes in the module. + module_details = dicom_modules(iod_details.Modules{p,1}); + + if (isempty(module_details)) + error(message('images:dicom_create_IOD:undefinedModule', iod_details.Modules{ p, 1 })); + end + + % Process attributes. + [new_attrs, new_status] = process_modules(module_details.Attrs, ... + metadata, ... + dictionary); + attrs = add_to_IOD(attrs, new_attrs); + status = add_to_status(status, new_status); + + end + +end + +% Process any private metadata. +if ((options.writeprivate) && (~isempty(private_tags))) + + new_attrs = process_private(private_tags, private_names, metadata, dictionary, options.txfr); + attrs = add_to_IOD(attrs, new_attrs); + +end + + + +function iod_details = get_iod_details(IOD_UID) +%GET_IOD_DETAILS Find the details of a given IOD. + +% Load IOD definitions. +iod_directory = dicom_iods; + +% Look for a particular UID. +idx = strmatch(IOD_UID, {iod_directory.UID}, 'exact'); + +if (isempty(idx)) + iod_details = []; +else + iod_details = iod_directory(idx); +end + + + +function attrs = add_to_IOD(attrs, new_attrs) +%ADD_TO_IOD Add attributes to an existing IOD. +attrs = cat(2, attrs, new_attrs); + + + +function status = add_to_status(status, new_status) +%ADD_TO_STATUS Add status values to an existing status struct. + +fnames = fieldnames(new_status); +for p = 1:numel(fnames) + if (isfield(status, fnames{p})) + status(1).(fnames{p}) = [status(1).(fnames{p}) ... + new_status(1).(fnames{p})]; + else + status(1).(fnames{p}) = new_status(1).(fnames{p}); + end +end + + + +function [attrs, status] = process_modules(attr_details, metadata, dictionary) +%PROCESS_MODULES Create attributes from a module definition. + +attrs = []; +status.BadAttribute = {}; +status.MissingCondition = {}; +status.MissingData = {}; +status.SuspectAttribute = {}; + +p = 0; +numAttrs = numel(attr_details(:,1)); +% For each attribute in the module... +while (p < numAttrs) + + p = p + 1; + + level = attr_details{p, 1}; + group = attr_details{p, 2}; + element = attr_details{p, 3}; + attr_type = attr_details{p, 4}; + vr_map = attr_details{p, 5}; + enum_values = attr_details{p, 6}; + condition = attr_details{p, 7}; + + % Skip all sequence attributes for now. + % + % NOTE: At this point, all sequences in supported modules are + % type 3 (optional). We can, and will, safely skip all + % attributes in a sequence (including the top level attribute). + + nextAttr = p + 1; + if ((p < numAttrs) && ... + (attr_details{nextAttr, 1} > level)) + + % The "level" determines nesting, with 0 as the + % non-sequence elements + while ((nextAttr <= numAttrs) && ... + (attr_details{nextAttr, 1} > level)) + + nextAttr = nextAttr + 1; + + end + + % Point to the next attribute after the sequence. + p = nextAttr - 1; + continue + + end + + % + % Look for the attribute from the module. + % + try + name = dicom_name_lookup(group, element, dictionary); + catch + status.BadAttribute{end + 1} = sprintf('(%s,%s)', ... + group, element); + continue; + end + + if (isempty(name)) + status.BadAttribute{end + 1} = sprintf('(%s,%s)', ... + group, element); + continue; + end + + % + % Determine how to handle the attribute. + % + switch (attr_type) + case {'1', '2', '3'} + % Must process the attribute. No special action. + + case {'1C', '2C'} + % Conditionally process the attribute. + if (isempty(condition)) + + status.MissingCondition{end + 1} = sprintf('(%s,%s)', ... + group, element); + continue; + + else + + if (~check_condition(condition, metadata, dictionary)) + continue; + end + + end + + otherwise + status.BadAttribute{end + 1} = sprintf('(%s,%s)', group, element); + continue; + + end + + % Process special VR mapping. + if ((isequal(group, '7FE0')) && (isequal(element, '0010'))) + VR = get_pixelData_VR(metadata, dictionary); + else + if (numel(vr_map) == 0) + VR = ''; + else + VR = remap_vr(group, element, vr_map, metadata, dictionary); + end + end + + % + % Add the attribute. + % + if (isfield(metadata, name)) + + % Process acceptable values. + if ((~isempty(enum_values)) && (~isempty(metadata.(name)))) + if (~check_values(metadata.(name), enum_values)) + status.SuspectAttribute{end + 1} = sprintf('(%s,%s)', ... + group, element); + end + end + + % Add the attribute. + if (isempty(VR)) + attrs = dicom_add_attr(attrs, group, element, dictionary, ... + metadata.(name)); + else + attrs = dicom_add_attr(attrs, group, element, dictionary, ... + metadata.(name), VR); + end + + else + + switch (attr_type) + case {'1', '1C'} + + % The attribute should exist. + status.MissingData{end + 1} = sprintf('(%s,%s)', ... + group, element); + continue; + + case {'2', '2C'} + + % Nonexistent attributes have empty data. + attrs = dicom_add_attr(attrs, group, element, dictionary); + + end + + % Nonexistent type 3 is ignored. + + end + +end + + + +function tf = check_condition(expr, metadata, dictionary) +%CHECK_CONDITION Determine whether a particular condition is true. +% +% Conditions are LISP-style cell arrays. The first element is a +% conditional operator, remaining cells are arguments to the operator. +% +% Conditions can be nested. Each cell array in expr indicates a new +% conditional expression. + +% +% Error checking +% +if (~iscell(expr)) + error(message('images:dicom_create_IOD:conditionalsMustBeCellArrays')) +end + +if (numel(expr) == 1) + operator = expr{1}; + operands = {}; +else + operator = expr{1}; + operands = expr(2:end); +end + + +% +% Process conditional expressions recursively. +% +switch (lower(operator)) +case 'and' + + % This AND short circuits. + for p = 1:numel(operands) + tf = check_condition(operands{p}, metadata, dictionary); + + if (~tf) + return + end + end + +case 'equal' + + if (numel(operands) ~= 2) + error(message('images:dicom_create_IOD:presentOpNeeds2Args')) + end + + % Get the group and element from the first operand. + group = operands{1}(2:5); + element = operands{1}(7:10); + + % Look up the metadata value. + name = dicom_name_lookup(group, element, dictionary); + + if (isfield(metadata, name)) + tf = isequal(metadata.(name), operands{2}); + else + tf = false; + end + +case 'false' + + tf = false; + +case 'not' + + if (numel(operands) ~= 1) + error(message('images:dicom_create_IOD:tooManyArgsToNot')) + else + tf = ~check_condition(operands{1}, metadata, dictionary); + end + +case 'or' + + % This OR short circuits. + for p = 1:numel(operands) + tf = check_condition(operands{p}, metadata, dictionary); + + if (tf) + return + end + end + +case 'present' + + if (numel(operands) ~= 1) + error(message('images:dicom_create_IOD:presentNeeds1Arg')) + end + + % Get the group and element from the first operand. + group = operands{1}(2:5); + element = operands{1}(7:10); + + % Look up the metadata value. + name = dicom_name_lookup(group, element, dictionary); + + tf = isfield(metadata, name); + +case 'true' + + tf = true; + +otherwise + error(message('images:dicom_create_IOD:badConditionalOp', operator)) +end + + + +function tf = test_module_condition(X, metadata, module, dictionary) +%TEST_MODULE_CONDITIONS Test whether a module should be created. + +moduleType = module{2}; +moduleCondition = module{3}; + +switch (moduleType) +case {'M'} + + tf = true; + +case {'U', 'C'} + + if (isempty(moduleCondition)) + tf = false; + return + end + + switch (moduleCondition{1}) + case 'HAS_FILEMETADATA' + tf = true; + + case 'HAS_BOLUS' + tf = isfield(metadata, dicom_name_lookup('0018', '0010', dictionary)); + + case 'HAS_OVERLAY' + tf = isfield(metadata, dicom_name_lookup('6000', '0010', dictionary)); + + case 'HAS_VOILUT' + tf = ((isfield(metadata, dicom_name_lookup('0028', '3010', dictionary))) || ... + (isfield(metadata, dicom_name_lookup('0028', '1050', dictionary)))); + + case 'IS_MULTIFRAME' + tf = size(X,4) > 1; + + case 'NEEDS_SC_CINE' + + framePointerAttr = dicom_name_lookup('0028', '0009', dictionary); + if (isfield(metadata, framePointerAttr)) + + framePointer = metadata.(framePointerAttr); + tf = (isequal(framePointer(:), sscanf('0018 1063', '%x')) || ... + isequal(framePointer(:), sscanf('0018 1065', '%x'))); + + else + + tf = false; + + end + + case 'TRUE' + tf = true; + + case 'FALSE' + tf = false; + + otherwise + tf = false; + + end + +otherwise + error(message('images:dicom_create_IOD:unknownModuleType', moduleType)) + +end + + + +function VR = remap_vr(group, element, vr_map, metadata, dictionary) +%REMAP_VR Translate a VR mapping into a VR value. + +% Default behavior is an empty VR. +VR = ''; + +if (numel(vr_map) > 1) + + % First item in VR map is an attribute tag. + cond_name = dicom_name_lookup(vr_map{1}(2:5), vr_map{1}(7:10), dictionary); + + % Remaining items map the conditional attribute's value to a VR. + if (isfield(metadata, cond_name)) + + for p = 2:numel(vr_map) + + if (isequal(metadata.(cond_name), vr_map{p}{1})) + + VR = vr_map{p}{2}; + return + + end + + end + + end + + % If the condition isn't met (or the attribute isn't present), + % use the first entry in the attribute's VR list. + if (isempty(VR)) + + attr = dicom_dict_lookup(group, element); + VR = attr.VR; + + end + +elseif (numel(vr_map) == 1) + + % Only item in VR map is the VR. + VR = vr_map{1}; + +end + + + +function VR = get_pixelData_VR(metadata, dictionary) +%GET_PIXELDATA_VR Get the VR for (7FE0,0010). +% +% PS 3.5 Sec. 8 contains the rules for picking the VR of (7FE0,0010). + +uid_details = dicom_uid_decode(metadata.(dicom_name_lookup('0002','0010', dictionary))); + +if (uid_details.Compressed) + + % Compressed pixels are always stored OB. + VR = 'OB'; + +elseif (isequal(uid_details.VR, 'Implicit')) + + % Implicit VR transfer syntaxes are always OW. + VR = 'OW'; + +else + + % The VR of other Explicit VR transfter syntaxes depends on the bit + % depth of the pixels. + if (metadata.(dicom_name_lookup('0028','0100', dictionary)) <= 8) + VR = 'OB'; + else + VR = 'OW'; + end + +end + + + +function tf = check_values(attr_data, enum_values) +%CHECK_VALUE Verify an attribute's data against required values. + +if (iscellstr(enum_values)) + + tf = any(strcmp(attr_data, enum_values)); + +else + + tmp = 0; + for p = 1:numel(enum_values) + tmp = tmp + isequal(attr_data, enum_values{p}); + end + + tf = (tmp > 0); + +end + + + +function [private_attrs, private_names] = find_private_metadata(metadata, dictionary) +%FIND_PRIVATE_METADATA Find user-contributed private metadata. + +% Private attributes have odd element values in their tag. +% +% By default these values come back from DICOMINFO with field names like +% "Private_0029_1004" and "Private_0029_10xx_Creator". +% +% If a custom data dictionary is provided, the names will look like +% regular DICOM attributes. + +private_attrs = {}; +private_names = {}; + +% Look through metadata for private attributes. +fields = fieldnames(metadata); + +for p = 1:numel(fields) + + tag = dicom_tag_lookup(fields{p}, dictionary); + + if ((~isempty(tag)) && (rem(tag(1), 2) ~= 0)) + private_attrs{end + 1} = tag; %#ok + private_names{end + 1} = fields{p}; %#ok + end + +end + + + +function [attrs, status] = process_private(private_tags, private_names, metadata, dictionary, txfr) +%PROCESS_PRIVATE Create attributes from the private data list. + +% Delay judgement of Private attributes. +status.BadAttribute = {}; +status.MissingCondition = {}; +status.MissingData = {}; +status.SuspectAttribute = {}; + +% Preallocate the attribute structure. +attrs(numel(private_tags)).Group = []; +attrs(end).Element = []; +attrs(end).VR = ''; +attrs(end).VM = []; +attrs(end).Data = []; + +% Convert the private data to a writable attribute. +for p = 1:numel(private_tags) + attrs(p) = dicom_convert_meta_to_attr(private_names{p}, metadata, dictionary, txfr); +end + diff --git a/CT/private/dicom_create_attr.m b/CT/private/dicom_create_attr.m index c7825d8..4c63cae 100644 --- a/CT/private/dicom_create_attr.m +++ b/CT/private/dicom_create_attr.m @@ -1,13 +1,13 @@ -function attr = dicom_create_attr -%DICOM_CREATE_ATTR Create a structure to contain an attribute. -% ATTR = DICOM_CREATE_ATTR create a structure ATTR to contain an -% attribute's metadata. The fields are filled with empty values. - -% Copyright 1993-2005 The MathWorks, Inc. - -attr = struct('Name', '', ... - 'Group', [], ... - 'Element', [], ... - 'VR', '', ... - 'VM', [], ... - 'Length', []); +function attr = dicom_create_attr +%DICOM_CREATE_ATTR Create a structure to contain an attribute. +% ATTR = DICOM_CREATE_ATTR create a structure ATTR to contain an +% attribute's metadata. The fields are filled with empty values. + +% Copyright 1993-2005 The MathWorks, Inc. + +attr = struct('Name', '', ... + 'Group', [], ... + 'Element', [], ... + 'VR', '', ... + 'VM', [], ... + 'Length', []); diff --git a/CT/private/dicom_create_file_struct.m b/CT/private/dicom_create_file_struct.m index 5e50b66..12c41b1 100644 --- a/CT/private/dicom_create_file_struct.m +++ b/CT/private/dicom_create_file_struct.m @@ -1,16 +1,16 @@ -function file = dicom_create_file_struct -%CREATE_FILE_STRUCT Create a file structure with default values. -% FILE = DICOM_CREATE_FILE create a structure FILE to contain -% information about the DICOM message pool. On creation, the fields -% are filled with values to indicate that no files have been chosen. - -% Copyright 1993-2010 The MathWorks, Inc. - -file.Filename = ''; -file.Current_Message = 0; -file.FID = -1; -file.Current_Endian = ''; -file.Pixel_Endian = ''; -file.Current_VR = ''; -file.Warn.Current = 0; -file.Warn.Max = inf; +function file = dicom_create_file_struct +%CREATE_FILE_STRUCT Create a file structure with default values. +% FILE = DICOM_CREATE_FILE create a structure FILE to contain +% information about the DICOM message pool. On creation, the fields +% are filled with values to indicate that no files have been chosen. + +% Copyright 1993-2010 The MathWorks, Inc. + +file.Filename = ''; +file.Current_Message = 0; +file.FID = -1; +file.Current_Endian = ''; +file.Pixel_Endian = ''; +file.Current_VR = ''; +file.Warn.Current = 0; +file.Warn.Max = inf; diff --git a/CT/private/dicom_decode_jpg8.m b/CT/private/dicom_decode_jpg8.m index 8fa00d0..3662a4a 100644 --- a/CT/private/dicom_decode_jpg8.m +++ b/CT/private/dicom_decode_jpg8.m @@ -1,49 +1,49 @@ -function pixel_cells = dicom_decode_jpg8(comp_fragment) -%DICOM_DECODE_JPG8 Decode a JPEG compressed bytestream. -% PIXEL_CELLS = DICOM_DECODE_JPG8(COMP_FRAGMENT) decompresses the 8-bit -% to 16-bit lossy or lossless JPEG compressed fragment COMP_FRAGMENT and -% returns the decompressed PIXEL_CELLS. PIXEL_CELLS is an m-by-n -% rectangular array of image data returned by IMREAD and contains a -% complete, correctly shaped and oriented image. -% -% PIXEL_CELLS is a UINT8 array is the JPEG file contains 8 or fewer bytes -% per sample. Otherwise PIXEL_CELLS is a UINT16 array. PIXEL_CELLS will -% need to be cast to a signed type if the DICOM file's Pixel Representation -% is 1. -% -% See also: IMREAD, IMFINFO. - -% Copyright 1993-2010 The MathWorks, Inc. - -% Open a temporary file for the JPEG data. -tmp_file = tempname; -fid = fopen(tmp_file, 'w'); - -if (fid < 3) - error(message('images:dicom_decode_jpg8:CouldNotOpenFile', tmp_file)) -end - -% Write the JPEG bytestream to the file. -count = fwrite(fid, comp_fragment, 'uint8'); - -if (count ~= length(comp_fragment)) - warning(message('images:dicom_decode_jpg8:dataTruncatedWhenWritingToTempFile')) -end - -% Close the temporary file. -fclose(fid); - -% Reread the file. -try - pixel_cells = imread(tmp_file, 'jpeg'); -catch ME - delete(tmp_file); - rethrow(ME) -end - -% Remove the temporary file. -try - delete(tmp_file); -catch - warning(message('images:dicom_decode_jpg8:CouldNotDeleteTempFile', tmp_file)) -end +function pixel_cells = dicom_decode_jpg8(comp_fragment) +%DICOM_DECODE_JPG8 Decode a JPEG compressed bytestream. +% PIXEL_CELLS = DICOM_DECODE_JPG8(COMP_FRAGMENT) decompresses the 8-bit +% to 16-bit lossy or lossless JPEG compressed fragment COMP_FRAGMENT and +% returns the decompressed PIXEL_CELLS. PIXEL_CELLS is an m-by-n +% rectangular array of image data returned by IMREAD and contains a +% complete, correctly shaped and oriented image. +% +% PIXEL_CELLS is a UINT8 array is the JPEG file contains 8 or fewer bytes +% per sample. Otherwise PIXEL_CELLS is a UINT16 array. PIXEL_CELLS will +% need to be cast to a signed type if the DICOM file's Pixel Representation +% is 1. +% +% See also: IMREAD, IMFINFO. + +% Copyright 1993-2010 The MathWorks, Inc. + +% Open a temporary file for the JPEG data. +tmp_file = tempname; +fid = fopen(tmp_file, 'w'); + +if (fid < 3) + error(message('images:dicom_decode_jpg8:CouldNotOpenFile', tmp_file)) +end + +% Write the JPEG bytestream to the file. +count = fwrite(fid, comp_fragment, 'uint8'); + +if (count ~= length(comp_fragment)) + warning(message('images:dicom_decode_jpg8:dataTruncatedWhenWritingToTempFile')) +end + +% Close the temporary file. +fclose(fid); + +% Reread the file. +try + pixel_cells = imread(tmp_file, 'jpeg'); +catch ME + delete(tmp_file); + rethrow(ME) +end + +% Remove the temporary file. +try + delete(tmp_file); +catch + warning(message('images:dicom_decode_jpg8:CouldNotDeleteTempFile', tmp_file)) +end diff --git a/CT/private/dicom_decode_pixel_cells.m b/CT/private/dicom_decode_pixel_cells.m index 0baca63..d072990 100644 --- a/CT/private/dicom_decode_pixel_cells.m +++ b/CT/private/dicom_decode_pixel_cells.m @@ -1,139 +1,139 @@ -function [pixels, overlays] = dicom_decode_pixel_cells(data, info, ol_bits) -%DICOM_DECODE_PIXEL_CELLS Get pixel values and overlay bits from pixel cells. -% [PIXELS, OVERLAYS] = DICOM_DECODE_PIXEL_CELLS(DATA, INFO, BITS) -% extracts the pixel and overlay data from the pixel cells DATA. INFO -% is the metadata structure for the pixel data array DATA. OL_BITS is -% a vector of bit positions in DATA which contain overlay data, which -% are always single bits. -% -% The PIXELS returned are a vector of integer values without overlay -% data. The values in PIXELS have been shifted to start at the -% least-significant byte for each element, so that they actually -% represent the correct pixel values without further modification. -% -% The OVERLAYS array is an m-by-n-by-1-by-p logical array, where p is -% the number of overlays in the DATA. - -% Copyright 1993-2005 The MathWorks, Inc. - - -% -% Read overlays. -% - -if (isempty(ol_bits)) - - overlays = logical([]); - -else - - overlays = repmat(false, [info.Rows info.Columns info.NumberOfFrames ... - length(ol_bits)]); - -end - -level = 0; - -for p = 1:length(ol_bits) - - level = level + 1; - - plane = bitget(data, (ol_bits(p) + 1)); - plane = reshape(plane, [size(overlays,1), size(overlays,2), ... - size(overlays,3)]); - - plane = permute(plane, [2 1 3]); - overlays(:, :, :, level) = plane; - -end - -% -% Strip out overlays. -% - -% Convert signed data temporarily to unsigned. -if (info.PixelRepresentation == 1) - - switch (class(data)) - case 'int8' - data = dicom_typecast(data, 'uint8'); - case 'int16' - data = dicom_typecast(data, 'uint16'); - case 'int32' - data = dicom_typecast(data, 'uint32'); - end - -end - -% Remove higher order overlays. -mask = (2^(info.HighBit + 1) - 1); -data = bitand(data, mask); - -% Remove lower order overlays. -bit_offset = (info.HighBit + 1) - info.BitsStored; -data = bitshift(data, -bit_offset); - -% Fit the data into an appropriate sized storage unit. -container_size = ceil(info.BitsStored / 8) * 8; - -if (info.PixelRepresentation == 1) - - sign_bit = info.HighBit - bit_offset + 1; - has_negative = any(bitget(data, sign_bit)); - - % Move the size bit to the end of the data container (e.g., the 8th - % or 16th bit) and fill old sign bit with 0. - if ((sign_bit ~= container_size) && (has_negative)) - - mask = bitget(data, sign_bit); - data = bitor(data, bitshift(mask, container_size)); - data = bitset(data, sign_bit, 0); - - end - -end - -% Convert signed data back from unsigned. -if (info.PixelRepresentation == 1) - - switch (class(data)) - case 'uint8' - data = dicom_typecast(data, 'int8'); - case 'uint16' - data = dicom_typecast(data, 'int16'); - case 'uint32' - data = dicom_typecast(data, 'int32'); - end - -end - -% -% Store pixel bits. -% - -switch (container_size) -case 8 - - if (info.PixelRepresentation == 0) - pixels = uint8(data); - else - pixels = int8(data); - end - -case 16 - - if (info.PixelRepresentation == 0) - pixels = uint16(data); - else - pixels = int16(data); - end - -case 32 - - if (info.PixelRepresentation == 0) - pixels = uint32(data); - else - pixels = int32(data); - end - -end +function [pixels, overlays] = dicom_decode_pixel_cells(data, info, ol_bits) +%DICOM_DECODE_PIXEL_CELLS Get pixel values and overlay bits from pixel cells. +% [PIXELS, OVERLAYS] = DICOM_DECODE_PIXEL_CELLS(DATA, INFO, BITS) +% extracts the pixel and overlay data from the pixel cells DATA. INFO +% is the metadata structure for the pixel data array DATA. OL_BITS is +% a vector of bit positions in DATA which contain overlay data, which +% are always single bits. +% +% The PIXELS returned are a vector of integer values without overlay +% data. The values in PIXELS have been shifted to start at the +% least-significant byte for each element, so that they actually +% represent the correct pixel values without further modification. +% +% The OVERLAYS array is an m-by-n-by-1-by-p logical array, where p is +% the number of overlays in the DATA. + +% Copyright 1993-2005 The MathWorks, Inc. + + +% +% Read overlays. +% + +if (isempty(ol_bits)) + + overlays = logical([]); + +else + + overlays = repmat(false, [info.Rows info.Columns info.NumberOfFrames ... + length(ol_bits)]); + +end + +level = 0; + +for p = 1:length(ol_bits) + + level = level + 1; + + plane = bitget(data, (ol_bits(p) + 1)); + plane = reshape(plane, [size(overlays,1), size(overlays,2), ... + size(overlays,3)]); + + plane = permute(plane, [2 1 3]); + overlays(:, :, :, level) = plane; + +end + +% +% Strip out overlays. +% + +% Convert signed data temporarily to unsigned. +if (info.PixelRepresentation == 1) + + switch (class(data)) + case 'int8' + data = dicom_typecast(data, 'uint8'); + case 'int16' + data = dicom_typecast(data, 'uint16'); + case 'int32' + data = dicom_typecast(data, 'uint32'); + end + +end + +% Remove higher order overlays. +mask = (2^(info.HighBit + 1) - 1); +data = bitand(data, mask); + +% Remove lower order overlays. +bit_offset = (info.HighBit + 1) - info.BitsStored; +data = bitshift(data, -bit_offset); + +% Fit the data into an appropriate sized storage unit. +container_size = ceil(info.BitsStored / 8) * 8; + +if (info.PixelRepresentation == 1) + + sign_bit = info.HighBit - bit_offset + 1; + has_negative = any(bitget(data, sign_bit)); + + % Move the size bit to the end of the data container (e.g., the 8th + % or 16th bit) and fill old sign bit with 0. + if ((sign_bit ~= container_size) && (has_negative)) + + mask = bitget(data, sign_bit); + data = bitor(data, bitshift(mask, container_size)); + data = bitset(data, sign_bit, 0); + + end + +end + +% Convert signed data back from unsigned. +if (info.PixelRepresentation == 1) + + switch (class(data)) + case 'uint8' + data = dicom_typecast(data, 'int8'); + case 'uint16' + data = dicom_typecast(data, 'int16'); + case 'uint32' + data = dicom_typecast(data, 'int32'); + end + +end + +% +% Store pixel bits. +% + +switch (container_size) +case 8 + + if (info.PixelRepresentation == 0) + pixels = uint8(data); + else + pixels = int8(data); + end + +case 16 + + if (info.PixelRepresentation == 0) + pixels = uint16(data); + else + pixels = int16(data); + end + +case 32 + + if (info.PixelRepresentation == 0) + pixels = uint32(data); + else + pixels = int32(data); + end + +end diff --git a/CT/private/dicom_dict_lookup.m b/CT/private/dicom_dict_lookup.m index 589b2e8..6f8f38c 100644 --- a/CT/private/dicom_dict_lookup.m +++ b/CT/private/dicom_dict_lookup.m @@ -1,71 +1,71 @@ -function attr = dicom_dict_lookup(group, element, dictionary) -%DICOM_DICT_LOOKUP Lookup an attribute in the data dictionary. -% ATTRIBUTE = DICOM_DICT_LOOKUP(GROUP, ELEMENT, DICTIONARY) searches for -% the attribute (GROUP,ELEMENT) in the data dictionary, DICTIONARY. A -% structure containing the attribute's properties from the dictionary -% is returned. ATTRIBUTE is empty if (GROUP,ELEMENT) is not in -% DICTIONARY. -% -% Note: GROUP and ELEMENT can be either decimal values or hexadecimal -% strings. - -% Copyright 1993-2010 The MathWorks, Inc. - - -% IMPORTANT NOTE: -% -% This function must be wrapped inside of a try-catch-end block in order -% to prevent the DICOM file from being left open after an error. - - -MAX_GROUP = 65535; % 0xFFFF -MAX_ELEMENT = 65535; % 0xFFFF - -% -% Load the data dictionary. -% - -persistent tags values prev_dictionary; -mlock; - -% Load dictionary for the first time or if dictionary has changed. -if ((isempty(values)) || (~isequal(prev_dictionary, dictionary))) - - [tags, values] = dicom_load_dictionary(dictionary); - prev_dictionary = dictionary; - -end - -% -% Convert hex strings to decimals. -% - -if (ischar(group)) - group = sscanf(group, '%x'); -end - -if (ischar(element)) - element = sscanf(element, '%x'); -end - -if (group > MAX_GROUP) - error(message('images:dicom_dict_lookup:groupOutOfRange', sprintf( '%x', group ), sprintf( '(%x,%04x)', group, element ))) -end - - -if (element > MAX_ELEMENT) - error(message('images:dicom_dict_lookup:elementOutOfRange', sprintf( '%x', element ), sprintf( '(%04x,%x)', group, element ))) -end - -% -% Look up the attribute. -% - -% Group and Element values in the published data dictionary are 0-based. -index = tags((group + 1), (element + 1)); - -if (index == 0) - attr = struct([]); -else - attr = values(index); -end +function attr = dicom_dict_lookup(group, element, dictionary) +%DICOM_DICT_LOOKUP Lookup an attribute in the data dictionary. +% ATTRIBUTE = DICOM_DICT_LOOKUP(GROUP, ELEMENT, DICTIONARY) searches for +% the attribute (GROUP,ELEMENT) in the data dictionary, DICTIONARY. A +% structure containing the attribute's properties from the dictionary +% is returned. ATTRIBUTE is empty if (GROUP,ELEMENT) is not in +% DICTIONARY. +% +% Note: GROUP and ELEMENT can be either decimal values or hexadecimal +% strings. + +% Copyright 1993-2010 The MathWorks, Inc. + + +% IMPORTANT NOTE: +% +% This function must be wrapped inside of a try-catch-end block in order +% to prevent the DICOM file from being left open after an error. + + +MAX_GROUP = 65535; % 0xFFFF +MAX_ELEMENT = 65535; % 0xFFFF + +% +% Load the data dictionary. +% + +persistent tags values prev_dictionary; +mlock; + +% Load dictionary for the first time or if dictionary has changed. +if ((isempty(values)) || (~isequal(prev_dictionary, dictionary))) + + [tags, values] = dicom_load_dictionary(dictionary); + prev_dictionary = dictionary; + +end + +% +% Convert hex strings to decimals. +% + +if (ischar(group)) + group = sscanf(group, '%x'); +end + +if (ischar(element)) + element = sscanf(element, '%x'); +end + +if (group > MAX_GROUP) + error(message('images:dicom_dict_lookup:groupOutOfRange', sprintf( '%x', group ), sprintf( '(%x,%04x)', group, element ))) +end + + +if (element > MAX_ELEMENT) + error(message('images:dicom_dict_lookup:elementOutOfRange', sprintf( '%x', element ), sprintf( '(%04x,%x)', group, element ))) +end + +% +% Look up the attribute. +% + +% Group and Element values in the published data dictionary are 0-based. +index = tags((group + 1), (element + 1)); + +if (index == 0) + attr = struct([]); +else + attr = values(index); +end diff --git a/CT/private/dicom_encode_attrs.m b/CT/private/dicom_encode_attrs.m index 3444b8f..3386628 100644 --- a/CT/private/dicom_encode_attrs.m +++ b/CT/private/dicom_encode_attrs.m @@ -1,320 +1,320 @@ -function data_streams = dicom_encode_attrs(attrs, txfr_syntax, uid_details) -%DICOM_ENCODE_ATTRS Encode a collection of attributes to a data stream. -% STREAMS = DICOM_ENCODE_ATTRS(ATTRS, TXFR) encode the attributes in -% the structure ATTRS according to the transfer syntax TXFR. The -% result is a cell array containing vectors of UINT8 values, which -% represent the values to be written to the output device. Each cell -% contains the encoded data stream for all of the attributes which -% share a common group number. -% -% Note: It is assumed that the attribute's data is already in the -% corresponding data type for the VR (e.g., if the VR is UL, the data -% is already UINT32). -% -% See also DICOM_ADD_ATTR, DICOM_ENCODE_PIXEL_CELLS. - -% Copyright 1993-2010 The MathWorks, Inc. - - -% -% Determine whether data will need to be byte swapped. -% - -[swap_file, swap_meta, swap_pixel] = determine_swap(txfr_syntax, uid_details); -PIXEL_GROUP = sscanf('7fe0', '%x'); -PIXEL_ELEMENT = sscanf('0010', '%x'); - -% -% Process the attributes. -% - -total_attrs = length(attrs); -current_attr = 1; -current_stream = 1; - -data_streams = {}; - -while (current_attr <= total_attrs) - - current_group = attrs(current_attr).Group; - stream = uint8([]); - - % Process each attribute of the group. - while (attrs(current_attr).Group == current_group) - - % Get the current attribute. - attr = attrs(current_attr); - - % Create the UINT8 output stream. - if (attr.Element == 0) - - % Skip group lengths. - encoded_attr = uint8([]); - - elseif (current_group == 2) - - encoded_attr = create_encoded_attr(attr, uid_details, swap_file); - - elseif ((attr.Group == PIXEL_GROUP) && ... - (attr.Element == PIXEL_ELEMENT)) - - encoded_attr = create_encoded_attr(attr, uid_details, swap_pixel); - - % GE format has different endianness within the PixelData - % attribute. Fix it. - if (isequal(txfr_syntax, '1.2.840.113619.5.2')) - encoded_attr = fix_pixel_attr(encoded_attr); - end - - else - - encoded_attr = create_encoded_attr(attr, uid_details, swap_meta); - - end - - % Add to the output stream. - stream = [stream encoded_attr]; %#ok - current_attr = current_attr + 1; - - % Don't index past the end of the attrs array. - if (current_attr > total_attrs) - break - end - - end - - % Prepend group length attribute. - if (current_group == 2) - - len_attr.Group = current_group; - len_attr.Element = 0; - len_attr.VR = 'UL'; - len_attr.Data = uint32(length(stream)); - - encoded_attr = create_encoded_attr(len_attr, uid_details, swap_file); - - stream = [encoded_attr stream]; - - end - - % Store the stream. - data_streams{current_stream} = stream; - - current_stream = current_stream + 1; - -end - - - -function segment = create_encoded_attr(attr, uid_details, swap) - -% If it's a sequence, recursively enocde the items and attributes. -if (isstruct(attr.Data)) - - fnames = fieldnames(attr.Data); - - if ((isequal(attr.VR, 'PN')) || ... - (~isempty(strfind(lower(fnames{1}), 'name')))) - - attr.Data = dicom_encode_pn(attr.Data); - - else - - attr.Data = encode_structure(attr.Data, uid_details, swap); - - end -end - -% Determine size of data. -switch (class(attr.Data)) -case {'uint8', 'int8', 'char'} - data_size = 1; - -case {'uint16', 'int16'} - data_size = 2; - -case {'uint32', 'int32', 'single'} - data_size = 4; - -case {'double'} - data_size = 8; - -end - -% Group and Element -segment = dicom_typecast(uint16(attr.Group), 'uint8', swap); -segment = [segment dicom_typecast(uint16(attr.Element), 'uint8', swap)]; - -% VR and Length -if ((isequal(uid_details.VR, 'Implicit')) && (attr.Group > 2)) - - % VR does not appear in the file. - - len = uint32(data_size * length(attr.Data)); - -else - - % VR. - segment = [segment uint8(attr.VR)]; - - % Determine length. - switch (attr.VR) - case {'OB', 'OW', 'SQ'} - - segment = [segment uint8([0 0])]; % Padding. - - len = uint32(data_size * length(attr.Data)); - - case {'UN'} - - if (attr.Group == 65534) % 0xfffe - - % Items/delimiters don't have VR or two-byte padding. - segment((end - 1):end) = []; - - else - - segment = [segment uint8([0 0])]; % Padding. - - end - - len = uint32(data_size * length(attr.Data)); - - case {'UT'} - - % Syntactically this is read the same as OB/OW/etc., but it - % cannot have undefined length. - - segment = [segment uint8([0 0])]; % Padding. - - len = uint32(data_size * length(attr.Data)); - - case {'AE','AS','AT','CS','DA','DS','DT','FD','FL','IS', ... - 'LO','LT', 'PN','SH','SL','SS','ST','TM','UI','UL','US'} - - len = uint16(data_size * length(attr.Data)); - - otherwise - - % PS 3.5-1999 Sec. 6.2 indicates that all unknown VRs can be - % interpretted as being the same as OB, OW, SQ, or UN. The - % size of data is not known but, the reading structure is. - - segment = [segment uint8([0 0])]; % Padding. - - len = uint32(data_size * length(attr.Data)); - - end - - % Special case for length of encapsulated (7FE0,0010). - if (((attr.Group == 32736) && (attr.Element == 16)) && ... - (uid_details.Compressed == 1)) - - % Undefined length. - len = dicom_undefined_length(); - - end - -end - -% If the data length is odd, then we will have to pad it. Add one to the -% length of the attribute. -if ((len ~= dicom_undefined_length()) && (rem(len, 2) ~= 0)) - len = len + 1; -end - -% Add the length and data to the segment. -segment = [segment dicom_typecast(len, 'uint8', swap)]; - -if (ischar(attr.Data)) - segment = [segment uint8(attr.Data(:)')]; -else - tmp = dicom_typecast(attr.Data, 'uint8', swap); - segment = [segment tmp(:)']; -end - -% Pad the data (if necessary) by adding a null byte. -if (rem(numel(segment), 2) ~= 0) - segment(end + 1) = 0; %#ok -end - - - -function [tf_file, tf_meta, tf_pixel] = determine_swap(txfr_syntax, uid_details) - -[a, b, endian] = computer; - -switch (endian) -case 'B' - - if (isequal(txfr_syntax, '1.2.840.113619.5.2')) - tf_file = 1; - tf_meta = 1; - tf_pixel = 0; - elseif (isequal(uid_details.Endian, 'ieee-be')) - tf_file = 1; - tf_meta = 0; - tf_pixel = 0; - else - tf_file = 1; - tf_meta = 1; - tf_pixel = 1; - end - -case 'L' - - if (isequal(txfr_syntax, '1.2.840.113619.5.2')) - tf_file = 0; - tf_meta = 0; - tf_pixel = 1; - elseif (isequal(uid_details.Endian, 'ieee-le')) - tf_file = 0; - tf_meta = 0; - tf_pixel = 0; - else - tf_file = 0; - tf_meta = 1; - tf_pixel = 1; - end - -end - - - -function data = fix_pixel_attr(data) - -% GE's special transfer syntax has different endianness within the -% attribute. Swap the bytes for the tag and length. Leave the rest -% alone. -% -% Swapping always needs to be done regardless of the endianness of the -% processor. It undoes a prior, unwanted swap of the tag and length. - -tag = data(1:4); -tag = dicom_typecast(dicom_typecast(tag, 'uint16'), 'uint8', 1); - -len = data(5:8); -len = dicom_typecast(dicom_typecast(len, 'uint32'), 'uint8', 1); - -data(1:4) = tag; -data(5:8) = len; - - - -function encoded_data = encode_structure(struct_data, uid_details, swap) -%ENCODE_STRUCTURE Turn a structure of data into a byte stream. - -% If it's a sequence, the structure will contain fields named 'Item_n'. -% To encode the sequence: -% (1) Add an item tag. -% (2) Encode the attributes within the item. - -encoded_data = uint8([]); - -for p = 1:numel(struct_data) - - tmp = dicom_encode_attrs(struct_data(p), uid_details.Value, uid_details); - encoded_data = [encoded_data ... - tmp{1}]; - -end +function data_streams = dicom_encode_attrs(attrs, txfr_syntax, uid_details) +%DICOM_ENCODE_ATTRS Encode a collection of attributes to a data stream. +% STREAMS = DICOM_ENCODE_ATTRS(ATTRS, TXFR) encode the attributes in +% the structure ATTRS according to the transfer syntax TXFR. The +% result is a cell array containing vectors of UINT8 values, which +% represent the values to be written to the output device. Each cell +% contains the encoded data stream for all of the attributes which +% share a common group number. +% +% Note: It is assumed that the attribute's data is already in the +% corresponding data type for the VR (e.g., if the VR is UL, the data +% is already UINT32). +% +% See also DICOM_ADD_ATTR, DICOM_ENCODE_PIXEL_CELLS. + +% Copyright 1993-2010 The MathWorks, Inc. + + +% +% Determine whether data will need to be byte swapped. +% + +[swap_file, swap_meta, swap_pixel] = determine_swap(txfr_syntax, uid_details); +PIXEL_GROUP = sscanf('7fe0', '%x'); +PIXEL_ELEMENT = sscanf('0010', '%x'); + +% +% Process the attributes. +% + +total_attrs = length(attrs); +current_attr = 1; +current_stream = 1; + +data_streams = {}; + +while (current_attr <= total_attrs) + + current_group = attrs(current_attr).Group; + stream = uint8([]); + + % Process each attribute of the group. + while (attrs(current_attr).Group == current_group) + + % Get the current attribute. + attr = attrs(current_attr); + + % Create the UINT8 output stream. + if (attr.Element == 0) + + % Skip group lengths. + encoded_attr = uint8([]); + + elseif (current_group == 2) + + encoded_attr = create_encoded_attr(attr, uid_details, swap_file); + + elseif ((attr.Group == PIXEL_GROUP) && ... + (attr.Element == PIXEL_ELEMENT)) + + encoded_attr = create_encoded_attr(attr, uid_details, swap_pixel); + + % GE format has different endianness within the PixelData + % attribute. Fix it. + if (isequal(txfr_syntax, '1.2.840.113619.5.2')) + encoded_attr = fix_pixel_attr(encoded_attr); + end + + else + + encoded_attr = create_encoded_attr(attr, uid_details, swap_meta); + + end + + % Add to the output stream. + stream = [stream encoded_attr]; %#ok + current_attr = current_attr + 1; + + % Don't index past the end of the attrs array. + if (current_attr > total_attrs) + break + end + + end + + % Prepend group length attribute. + if (current_group == 2) + + len_attr.Group = current_group; + len_attr.Element = 0; + len_attr.VR = 'UL'; + len_attr.Data = uint32(length(stream)); + + encoded_attr = create_encoded_attr(len_attr, uid_details, swap_file); + + stream = [encoded_attr stream]; + + end + + % Store the stream. + data_streams{current_stream} = stream; + + current_stream = current_stream + 1; + +end + + + +function segment = create_encoded_attr(attr, uid_details, swap) + +% If it's a sequence, recursively enocde the items and attributes. +if (isstruct(attr.Data)) + + fnames = fieldnames(attr.Data); + + if ((isequal(attr.VR, 'PN')) || ... + (~isempty(strfind(lower(fnames{1}), 'name')))) + + attr.Data = dicom_encode_pn(attr.Data); + + else + + attr.Data = encode_structure(attr.Data, uid_details, swap); + + end +end + +% Determine size of data. +switch (class(attr.Data)) +case {'uint8', 'int8', 'char'} + data_size = 1; + +case {'uint16', 'int16'} + data_size = 2; + +case {'uint32', 'int32', 'single'} + data_size = 4; + +case {'double'} + data_size = 8; + +end + +% Group and Element +segment = dicom_typecast(uint16(attr.Group), 'uint8', swap); +segment = [segment dicom_typecast(uint16(attr.Element), 'uint8', swap)]; + +% VR and Length +if ((isequal(uid_details.VR, 'Implicit')) && (attr.Group > 2)) + + % VR does not appear in the file. + + len = uint32(data_size * length(attr.Data)); + +else + + % VR. + segment = [segment uint8(attr.VR)]; + + % Determine length. + switch (attr.VR) + case {'OB', 'OW', 'SQ'} + + segment = [segment uint8([0 0])]; % Padding. + + len = uint32(data_size * length(attr.Data)); + + case {'UN'} + + if (attr.Group == 65534) % 0xfffe + + % Items/delimiters don't have VR or two-byte padding. + segment((end - 1):end) = []; + + else + + segment = [segment uint8([0 0])]; % Padding. + + end + + len = uint32(data_size * length(attr.Data)); + + case {'UT'} + + % Syntactically this is read the same as OB/OW/etc., but it + % cannot have undefined length. + + segment = [segment uint8([0 0])]; % Padding. + + len = uint32(data_size * length(attr.Data)); + + case {'AE','AS','AT','CS','DA','DS','DT','FD','FL','IS', ... + 'LO','LT', 'PN','SH','SL','SS','ST','TM','UI','UL','US'} + + len = uint16(data_size * length(attr.Data)); + + otherwise + + % PS 3.5-1999 Sec. 6.2 indicates that all unknown VRs can be + % interpretted as being the same as OB, OW, SQ, or UN. The + % size of data is not known but, the reading structure is. + + segment = [segment uint8([0 0])]; % Padding. + + len = uint32(data_size * length(attr.Data)); + + end + + % Special case for length of encapsulated (7FE0,0010). + if (((attr.Group == 32736) && (attr.Element == 16)) && ... + (uid_details.Compressed == 1)) + + % Undefined length. + len = dicom_undefined_length(); + + end + +end + +% If the data length is odd, then we will have to pad it. Add one to the +% length of the attribute. +if ((len ~= dicom_undefined_length()) && (rem(len, 2) ~= 0)) + len = len + 1; +end + +% Add the length and data to the segment. +segment = [segment dicom_typecast(len, 'uint8', swap)]; + +if (ischar(attr.Data)) + segment = [segment uint8(attr.Data(:)')]; +else + tmp = dicom_typecast(attr.Data, 'uint8', swap); + segment = [segment tmp(:)']; +end + +% Pad the data (if necessary) by adding a null byte. +if (rem(numel(segment), 2) ~= 0) + segment(end + 1) = 0; %#ok +end + + + +function [tf_file, tf_meta, tf_pixel] = determine_swap(txfr_syntax, uid_details) + +[a, b, endian] = computer; + +switch (endian) +case 'B' + + if (isequal(txfr_syntax, '1.2.840.113619.5.2')) + tf_file = 1; + tf_meta = 1; + tf_pixel = 0; + elseif (isequal(uid_details.Endian, 'ieee-be')) + tf_file = 1; + tf_meta = 0; + tf_pixel = 0; + else + tf_file = 1; + tf_meta = 1; + tf_pixel = 1; + end + +case 'L' + + if (isequal(txfr_syntax, '1.2.840.113619.5.2')) + tf_file = 0; + tf_meta = 0; + tf_pixel = 1; + elseif (isequal(uid_details.Endian, 'ieee-le')) + tf_file = 0; + tf_meta = 0; + tf_pixel = 0; + else + tf_file = 0; + tf_meta = 1; + tf_pixel = 1; + end + +end + + + +function data = fix_pixel_attr(data) + +% GE's special transfer syntax has different endianness within the +% attribute. Swap the bytes for the tag and length. Leave the rest +% alone. +% +% Swapping always needs to be done regardless of the endianness of the +% processor. It undoes a prior, unwanted swap of the tag and length. + +tag = data(1:4); +tag = dicom_typecast(dicom_typecast(tag, 'uint16'), 'uint8', 1); + +len = data(5:8); +len = dicom_typecast(dicom_typecast(len, 'uint32'), 'uint8', 1); + +data(1:4) = tag; +data(5:8) = len; + + + +function encoded_data = encode_structure(struct_data, uid_details, swap) +%ENCODE_STRUCTURE Turn a structure of data into a byte stream. + +% If it's a sequence, the structure will contain fields named 'Item_n'. +% To encode the sequence: +% (1) Add an item tag. +% (2) Encode the attributes within the item. + +encoded_data = uint8([]); + +for p = 1:numel(struct_data) + + tmp = dicom_encode_attrs(struct_data(p), uid_details.Value, uid_details); + encoded_data = [encoded_data ... + tmp{1}]; + +end diff --git a/CT/private/dicom_encode_jpeg2000_lossless.m b/CT/private/dicom_encode_jpeg2000_lossless.m index 94f3172..423770d 100644 --- a/CT/private/dicom_encode_jpeg2000_lossless.m +++ b/CT/private/dicom_encode_jpeg2000_lossless.m @@ -1,38 +1,38 @@ -function [fragments, frames] = dicom_encode_jpeg2000_lossless(X, bits) -%DICOM_ENCODE_JPEG2000_LOSSLESS Encode pixel cells using lossless JPEG. -% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG2000_LOSSLESS(X) compresses and -% encodes the image X using lossless JPEG compression. FRAGMENTS is -% a cell array containing the encoded frames (as UINT 8data) from the -% compressor. LIST is a vector of indices to the first fragment of -% each compressed frame of a multiframe image. -% -% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG2000_LOSSY. - -% Copyright 2010 The MathWorks, Inc. - - -% Use IMWRITE to create a JPEG image. - -numFrames = size(X,4); -fragments = cell(numFrames, 1); -frames = 1:numFrames; - -for p = 1:numFrames - - tempfile = tempname; - imwrite(X(:,:,:,p), tempfile, 'j2c', 'mode', 'lossless'); - - % Read the image from the temporary file. - fid = fopen(tempfile, 'r'); - fragments{p} = fread(fid, inf, 'uint8=>uint8'); - fclose(fid); - - % Remove the temporary file. - try - delete(tempfile) - catch - warning(message('images:dicom_encode_jpeg2000_lossless:tempFileDelete', tempfile)); - end - -end - +function [fragments, frames] = dicom_encode_jpeg2000_lossless(X, bits) +%DICOM_ENCODE_JPEG2000_LOSSLESS Encode pixel cells using lossless JPEG. +% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG2000_LOSSLESS(X) compresses and +% encodes the image X using lossless JPEG compression. FRAGMENTS is +% a cell array containing the encoded frames (as UINT 8data) from the +% compressor. LIST is a vector of indices to the first fragment of +% each compressed frame of a multiframe image. +% +% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG2000_LOSSY. + +% Copyright 2010 The MathWorks, Inc. + + +% Use IMWRITE to create a JPEG image. + +numFrames = size(X,4); +fragments = cell(numFrames, 1); +frames = 1:numFrames; + +for p = 1:numFrames + + tempfile = tempname; + imwrite(X(:,:,:,p), tempfile, 'j2c', 'mode', 'lossless'); + + % Read the image from the temporary file. + fid = fopen(tempfile, 'r'); + fragments{p} = fread(fid, inf, 'uint8=>uint8'); + fclose(fid); + + % Remove the temporary file. + try + delete(tempfile) + catch + warning(message('images:dicom_encode_jpeg2000_lossless:tempFileDelete', tempfile)); + end + +end + diff --git a/CT/private/dicom_encode_jpeg2000_lossy.m b/CT/private/dicom_encode_jpeg2000_lossy.m index aff294e..df135dd 100644 --- a/CT/private/dicom_encode_jpeg2000_lossy.m +++ b/CT/private/dicom_encode_jpeg2000_lossy.m @@ -1,38 +1,38 @@ -function [fragments, frames] = dicom_encode_jpeg2000_lossy(X, bits) -%DICOM_ENCODE_JPEG2000_LOSSY Encode pixel cells using lossy JPEG. -% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG2000_LOSSY(X) compresses and -% encodes the image X using lossy JPEG2000 compression. FRAGMENTS is -% a cell array containing the encoded frames (as UINT8 data) from the -% compressor. LIST is a vector of indices to the first fragment of -% each compressed frame of a multiframe image. -% -% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG2000_LOSSY. - -% Copyright 2010 The MathWorks, Inc. - - -% Use IMWRITE to create a JPEG2000 image. - -numFrames = size(X,4); -fragments = cell(numFrames, 1); -frames = 1:numFrames; - -for p = 1:numFrames - - tempfile = tempname; - imwrite(X(:,:,:,p), tempfile, 'j2c', 'mode', 'lossy'); - - % Read the image from the temporary file. - fid = fopen(tempfile, 'r'); - fragments{p} = fread(fid, inf, 'uint8=>uint8'); - fclose(fid); - - % Remove the temporary file. - try - delete(tempfile) - catch - warning(message('images:dicom_encode_jpeg2000_lossy:tempFileDelete', tempfile)); - end - -end - +function [fragments, frames] = dicom_encode_jpeg2000_lossy(X, bits) +%DICOM_ENCODE_JPEG2000_LOSSY Encode pixel cells using lossy JPEG. +% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG2000_LOSSY(X) compresses and +% encodes the image X using lossy JPEG2000 compression. FRAGMENTS is +% a cell array containing the encoded frames (as UINT8 data) from the +% compressor. LIST is a vector of indices to the first fragment of +% each compressed frame of a multiframe image. +% +% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG2000_LOSSY. + +% Copyright 2010 The MathWorks, Inc. + + +% Use IMWRITE to create a JPEG2000 image. + +numFrames = size(X,4); +fragments = cell(numFrames, 1); +frames = 1:numFrames; + +for p = 1:numFrames + + tempfile = tempname; + imwrite(X(:,:,:,p), tempfile, 'j2c', 'mode', 'lossy'); + + % Read the image from the temporary file. + fid = fopen(tempfile, 'r'); + fragments{p} = fread(fid, inf, 'uint8=>uint8'); + fclose(fid); + + % Remove the temporary file. + try + delete(tempfile) + catch + warning(message('images:dicom_encode_jpeg2000_lossy:tempFileDelete', tempfile)); + end + +end + diff --git a/CT/private/dicom_encode_jpeg_lossless.m b/CT/private/dicom_encode_jpeg_lossless.m index 8ba8f2e..a883d69 100644 --- a/CT/private/dicom_encode_jpeg_lossless.m +++ b/CT/private/dicom_encode_jpeg_lossless.m @@ -1,46 +1,46 @@ -function [fragments, frames] = dicom_encode_jpeg_lossless(X, bits) -%DICOM_ENCODE_JPEG_LOSSLESS Encode pixel cells using lossless JPEG. -% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG_LOSSLES(X) compresses and -% encodes the image X using baseline lossles JPEG compression. -% FRAGMENTS is a cell array containing the encoded frames (as UINT8 -% data) from the compressor. LIST is a vector of indices to the first -% fragment of each compressed frame of a multiframe image. -% -% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG_LOSSY. - -% Copyright 1993-2010 The MathWorks, Inc. - - -% Use IMWRITE to create a JPEG image. - -classname = class(X); - -switch (classname) -case {'int8', 'int16'} - tmp = dicom_typecast(X(:), ['u' classname]); - X = reshape(tmp, size(X)); -end - -numFrames = size(X,4); -fragments = cell(numFrames, 1); -frames = 1:numFrames; - -for p = 1:numFrames - - tempfile = tempname; - imwrite(X(:,:,:,p), tempfile, 'jpeg', 'mode', 'lossless', 'bitdepth', bits); - - % Read the image from the temporary file. - fid = fopen(tempfile, 'r'); - fragments{p} = fread(fid, inf, 'uint8=>uint8'); - fclose(fid); - - % Remove the temporary file. - try - delete(tempfile) - catch - warning(message('images:dicom_encode_jpeg_lossless:tempFileDelete', tempfile)); - end - -end - +function [fragments, frames] = dicom_encode_jpeg_lossless(X, bits) +%DICOM_ENCODE_JPEG_LOSSLESS Encode pixel cells using lossless JPEG. +% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG_LOSSLES(X) compresses and +% encodes the image X using baseline lossles JPEG compression. +% FRAGMENTS is a cell array containing the encoded frames (as UINT8 +% data) from the compressor. LIST is a vector of indices to the first +% fragment of each compressed frame of a multiframe image. +% +% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG_LOSSY. + +% Copyright 1993-2010 The MathWorks, Inc. + + +% Use IMWRITE to create a JPEG image. + +classname = class(X); + +switch (classname) +case {'int8', 'int16'} + tmp = dicom_typecast(X(:), ['u' classname]); + X = reshape(tmp, size(X)); +end + +numFrames = size(X,4); +fragments = cell(numFrames, 1); +frames = 1:numFrames; + +for p = 1:numFrames + + tempfile = tempname; + imwrite(X(:,:,:,p), tempfile, 'jpeg', 'mode', 'lossless', 'bitdepth', bits); + + % Read the image from the temporary file. + fid = fopen(tempfile, 'r'); + fragments{p} = fread(fid, inf, 'uint8=>uint8'); + fclose(fid); + + % Remove the temporary file. + try + delete(tempfile) + catch + warning(message('images:dicom_encode_jpeg_lossless:tempFileDelete', tempfile)); + end + +end + diff --git a/CT/private/dicom_encode_jpeg_lossy.m b/CT/private/dicom_encode_jpeg_lossy.m index 2ce7c0d..e5f1822 100644 --- a/CT/private/dicom_encode_jpeg_lossy.m +++ b/CT/private/dicom_encode_jpeg_lossy.m @@ -1,90 +1,90 @@ -function [fragments, frames] = dicom_encode_jpeg_lossy(X, bits) -%DICOM_ENCODE_JPEG_LOSSY Encode pixel cells using lossy JPEG compression. -% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG_LOSSY(X) compresses and encodes -% the image X using baseline lossy JPEG compression. FRAGMENTS is a -% cell array containing the encoded frames (as UINT8 data) from the -% compressor. LIST is a vector of indices to the first fragment of -% each compressed frame of a multiframe image. -% -% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG_LOSSLESS. - -% Copyright 1993-2011 The MathWorks, Inc. - -% Because lossy JPEG compression of signed data doesn't make sense, -% error. See PS 3.5 Sec. 8.2.1. -X = convertSigned(X); - -% Use IMWRITE to create a JPEG image, but don't warn about signed data. - -numFrames = size(X,4); -fragments = cell(numFrames, 1); -frames = 1:numFrames; - -% The maximum bit-depth for lossy JPEG is 12 bits/sample. -if (bits > 12) - bits = 12; -end - -if (max(X(:)) >= 4096) - warning(message('images:dicom_encode_jpeg_lossy:sampleTooLarge', sprintf( '%ld', max( X( : ) ) ))) -end - -% Write each frame. -for p = 1:numFrames - - tempfile = tempname; - imwrite(X(:,:,:,p), tempfile, 'jpeg', 'bitdepth', bits); - - % Read the image from the temporary file. - fid = fopen(tempfile, 'r'); - fragments{p} = fread(fid, inf, 'uint8=>uint8'); - fclose(fid); - - % Remove the temporary file. - try - delete(tempfile) - catch %#ok - warning(message('images:dicom_encode_jpeg_lossy:tempFileDelete', tempfile)); - end - -end - - -function X = convertSigned(X) -% Image has signed data. - -if (~isConvertible(X)) - - error(message('images:dicom_encode_jpeg_lossy:signedData')) - -end - -switch (class(X)) -case 'int8' - - X = uint8(X); - -case 'int16' - - X = uint16(X); - -case 'int32' - - X = uint32(X); - -case 'int64' - - X = uint8(X); - -otherwise - - % No op. - -end - - - -function tf = isConvertible(X) - -%We can silently convert any type where all of the values are nonnegative. +function [fragments, frames] = dicom_encode_jpeg_lossy(X, bits) +%DICOM_ENCODE_JPEG_LOSSY Encode pixel cells using lossy JPEG compression. +% [FRAGMENTS, LIST] = DICOM_ENCODE_JPEG_LOSSY(X) compresses and encodes +% the image X using baseline lossy JPEG compression. FRAGMENTS is a +% cell array containing the encoded frames (as UINT8 data) from the +% compressor. LIST is a vector of indices to the first fragment of +% each compressed frame of a multiframe image. +% +% See also DICOM_ENCODE_RLE, DICOM_ENCODE_JPEG_LOSSLESS. + +% Copyright 1993-2011 The MathWorks, Inc. + +% Because lossy JPEG compression of signed data doesn't make sense, +% error. See PS 3.5 Sec. 8.2.1. +X = convertSigned(X); + +% Use IMWRITE to create a JPEG image, but don't warn about signed data. + +numFrames = size(X,4); +fragments = cell(numFrames, 1); +frames = 1:numFrames; + +% The maximum bit-depth for lossy JPEG is 12 bits/sample. +if (bits > 12) + bits = 12; +end + +if (max(X(:)) >= 4096) + warning(message('images:dicom_encode_jpeg_lossy:sampleTooLarge', sprintf( '%ld', max( X( : ) ) ))) +end + +% Write each frame. +for p = 1:numFrames + + tempfile = tempname; + imwrite(X(:,:,:,p), tempfile, 'jpeg', 'bitdepth', bits); + + % Read the image from the temporary file. + fid = fopen(tempfile, 'r'); + fragments{p} = fread(fid, inf, 'uint8=>uint8'); + fclose(fid); + + % Remove the temporary file. + try + delete(tempfile) + catch %#ok + warning(message('images:dicom_encode_jpeg_lossy:tempFileDelete', tempfile)); + end + +end + + +function X = convertSigned(X) +% Image has signed data. + +if (~isConvertible(X)) + + error(message('images:dicom_encode_jpeg_lossy:signedData')) + +end + +switch (class(X)) +case 'int8' + + X = uint8(X); + +case 'int16' + + X = uint16(X); + +case 'int32' + + X = uint32(X); + +case 'int64' + + X = uint8(X); + +otherwise + + % No op. + +end + + + +function tf = isConvertible(X) + +%We can silently convert any type where all of the values are nonnegative. tf = ~(any(X(:) < 0)); \ No newline at end of file diff --git a/CT/private/dicom_encode_pixel_cells.m b/CT/private/dicom_encode_pixel_cells.m index 157c285..a00ebe9 100644 --- a/CT/private/dicom_encode_pixel_cells.m +++ b/CT/private/dicom_encode_pixel_cells.m @@ -1,125 +1,125 @@ -function pixelCells = dicom_encode_pixel_cells(X, map, ba, bs, hb) -%DICOM_ENCODE_PIXEL_CELLS Convert an image to pixel cells. -% PIXCELLS = DICOM_ENCODE_PIXEL_CELLS(X, MAP) convert the image X with -% colormap MAP to a sequence of DICOM pixel cells. - -% Copyright 1993-2014 The MathWorks, Inc. - -% Images are stored across then down. If there are multiple samples, -% keep all samples for each pixel contiguously stored. -X = permute(X, [3 2 1 4]); - -pixelCells = basicEncoding(X, map, ba); - -% For floating point pixels, basicEncoding() is sufficient. -switch (class(X)) -case {'single', 'double', 'logical'} - return -case {'uint8', 'int8'} - maxBitsAlloc = 8; -case {'uint16', 'int16'} - maxBitsAlloc = 16; -case {'uint32', 'int32'} - maxBitsAlloc = 32; -case {'uint64', 'int64'} - maxBitsAlloc = 64; -otherwise - assert(false, 'Internal error: Unsupported data type') -end - -% Find out whether to shift the pixels within the cell. Pixels that fully -% span have a "high bit" value one less than "bits stored" (0-based). -if (hb ~= (bs - 1)) - pixelCells = shiftPixels(pixelCells, bs, hb); -end - -% "Squeeze" the extra bits out of the pixel stream if BitsAllocated is less -% than the number of bits used by the MATLAB datatype. -if (ba ~= maxBitsAlloc) - pixelCells = advancedEncoding(pixelCells, ba); -end - -%-------------------------------------------------------------------------- -function pixelCells = basicEncoding(X, map, ba) - -% Convert to correct output type. -switch (class(X)) -case 'logical' - - warning(message('images:dicom_encode_pixel_cells:scalingLogicalData')); - - tmp = uint8(X); - tmp(X) = 255; - - X = tmp; - -case {'single', 'double'} - - maxValue = 2^ba - 1; - - if (isempty(map)) - - % RGB or Grayscale. - X = uint16(maxValue * X); - - else - - if (size(X, 1) == 1) - - % Indexed. - X = uint16(X - 1); - - elseif (size(X, 1) == 4) - - % RGBA - X(1:3, :, :) = X(1:3, :, :) * maxValue; - X(4, :, :) = X(4, :, :) - 1; - X = uint16(X); - - end - end -end - -pixelCells = X(:); - -%-------------------------------------------------------------------------- -function pixelCells = advancedEncoding(pixelCells, ba) -%advancedEncoding Encode pixel cells while writing to a temporary file and -%then reading the encoded pixels back to the buffer. This truncates "short" -%pixel cells that don't span the full datatype. - -pixelCellClass = class(pixelCells); - -% Write the data to the temporary file, squeezing out extra bits. The last -% pixel cell will be padded to end on a byte boundary. -filename = tempname(); -fid = fopen(filename, 'wb'); -if (fid < 0) - error(message('images:dicomwrite:tempFileNotCreated')) -end - -fileCleaner = onCleanup(@() delete(filename)); - -switch (pixelCellClass) - case {'int8', 'int16', 'int32', 'int64'} - precision = sprintf('bit%d', ba); - case {'uint8', 'uint16', 'uint32', 'uint64'} - precision = sprintf('ubit%d', ba); -end -fwrite(fid, pixelCells, precision); -fclose(fid); - -% Read all of the data back into the original encoded class type. Each -% "pixel cell" read will incorporate more than one pixel, but this is done -% to ensure that any potential byte-swapping happens correctly. -precision = sprintf('%s=>%s', pixelCellClass, pixelCellClass); - -fid = fopen(filename, 'rb'); -pixelCells = fread(fid, inf, precision); -fclose(fid); - -%-------------------------------------------------------------------------- -function pixelCells = shiftPixels(pixelCells, bs, hb) - -bitsToShift = hb - bs + 1; -pixelCells = bitshift(pixelCells, bitsToShift); +function pixelCells = dicom_encode_pixel_cells(X, map, ba, bs, hb) +%DICOM_ENCODE_PIXEL_CELLS Convert an image to pixel cells. +% PIXCELLS = DICOM_ENCODE_PIXEL_CELLS(X, MAP) convert the image X with +% colormap MAP to a sequence of DICOM pixel cells. + +% Copyright 1993-2014 The MathWorks, Inc. + +% Images are stored across then down. If there are multiple samples, +% keep all samples for each pixel contiguously stored. +X = permute(X, [3 2 1 4]); + +pixelCells = basicEncoding(X, map, ba); + +% For floating point pixels, basicEncoding() is sufficient. +switch (class(X)) +case {'single', 'double', 'logical'} + return +case {'uint8', 'int8'} + maxBitsAlloc = 8; +case {'uint16', 'int16'} + maxBitsAlloc = 16; +case {'uint32', 'int32'} + maxBitsAlloc = 32; +case {'uint64', 'int64'} + maxBitsAlloc = 64; +otherwise + assert(false, 'Internal error: Unsupported data type') +end + +% Find out whether to shift the pixels within the cell. Pixels that fully +% span have a "high bit" value one less than "bits stored" (0-based). +if (hb ~= (bs - 1)) + pixelCells = shiftPixels(pixelCells, bs, hb); +end + +% "Squeeze" the extra bits out of the pixel stream if BitsAllocated is less +% than the number of bits used by the MATLAB datatype. +if (ba ~= maxBitsAlloc) + pixelCells = advancedEncoding(pixelCells, ba); +end + +%-------------------------------------------------------------------------- +function pixelCells = basicEncoding(X, map, ba) + +% Convert to correct output type. +switch (class(X)) +case 'logical' + + warning(message('images:dicom_encode_pixel_cells:scalingLogicalData')); + + tmp = uint8(X); + tmp(X) = 255; + + X = tmp; + +case {'single', 'double'} + + maxValue = 2^ba - 1; + + if (isempty(map)) + + % RGB or Grayscale. + X = uint16(maxValue * X); + + else + + if (size(X, 1) == 1) + + % Indexed. + X = uint16(X - 1); + + elseif (size(X, 1) == 4) + + % RGBA + X(1:3, :, :) = X(1:3, :, :) * maxValue; + X(4, :, :) = X(4, :, :) - 1; + X = uint16(X); + + end + end +end + +pixelCells = X(:); + +%-------------------------------------------------------------------------- +function pixelCells = advancedEncoding(pixelCells, ba) +%advancedEncoding Encode pixel cells while writing to a temporary file and +%then reading the encoded pixels back to the buffer. This truncates "short" +%pixel cells that don't span the full datatype. + +pixelCellClass = class(pixelCells); + +% Write the data to the temporary file, squeezing out extra bits. The last +% pixel cell will be padded to end on a byte boundary. +filename = tempname(); +fid = fopen(filename, 'wb'); +if (fid < 0) + error(message('images:dicomwrite:tempFileNotCreated')) +end + +fileCleaner = onCleanup(@() delete(filename)); + +switch (pixelCellClass) + case {'int8', 'int16', 'int32', 'int64'} + precision = sprintf('bit%d', ba); + case {'uint8', 'uint16', 'uint32', 'uint64'} + precision = sprintf('ubit%d', ba); +end +fwrite(fid, pixelCells, precision); +fclose(fid); + +% Read all of the data back into the original encoded class type. Each +% "pixel cell" read will incorporate more than one pixel, but this is done +% to ensure that any potential byte-swapping happens correctly. +precision = sprintf('%s=>%s', pixelCellClass, pixelCellClass); + +fid = fopen(filename, 'rb'); +pixelCells = fread(fid, inf, precision); +fclose(fid); + +%-------------------------------------------------------------------------- +function pixelCells = shiftPixels(pixelCells, bs, hb) + +bitsToShift = hb - bs + 1; +pixelCells = bitshift(pixelCells, bitsToShift); diff --git a/CT/private/dicom_encode_pn.m b/CT/private/dicom_encode_pn.m index 9388c7f..e5b4bd0 100644 --- a/CT/private/dicom_encode_pn.m +++ b/CT/private/dicom_encode_pn.m @@ -1,59 +1,59 @@ -function PN_chars = dicom_encode_pn(PN_struct) -%DICOM_ENCODE_PN Turn a structure of name info into a formatted string. - -% Copyright 1993-2006 The MathWorks, Inc. - -% Empty values and undecorated PN strings should fall through. -if ((~isstruct(PN_struct)) || (isempty(PN_struct))) - PN_chars = PN_struct; - return -else - PN_chars = ''; -end - -% Encode a decorated PN struct. -for p = 1:numel(PN_struct) - - % Add each of the components to the output string. - if (isfield(PN_struct, 'FamilyName')) - PN_chars = [PN_chars PN_struct.FamilyName '^']; - else - PN_chars = [PN_chars '^']; - end - - if (isfield(PN_struct, 'GivenName')) - PN_chars = [PN_chars PN_struct.GivenName '^']; - else - PN_chars = [PN_chars '^']; - end - - if (isfield(PN_struct, 'MiddleName')) - PN_chars = [PN_chars PN_struct.MiddleName '^']; - else - PN_chars = [PN_chars '^']; - end - - if (isfield(PN_struct, 'NamePrefix')) - PN_chars = [PN_chars PN_struct.NamePrefix '^']; - else - PN_chars = [PN_chars '^']; - end - - if (isfield(PN_struct, 'NameSuffix')) - PN_chars = [PN_chars PN_struct.NameSuffix '^']; - else - PN_chars = [PN_chars '^']; - end - - % Remove extraneous '^' separators. - while ((~isempty(PN_chars)) && (PN_chars(end) == '^')) - PN_chars(end) = ''; - end - - % Separate multiple values. - PN_chars = [PN_chars '\']; - -end - -% Remove extra value delimiter '\'. -PN_chars(end) = ''; +function PN_chars = dicom_encode_pn(PN_struct) +%DICOM_ENCODE_PN Turn a structure of name info into a formatted string. + +% Copyright 1993-2006 The MathWorks, Inc. + +% Empty values and undecorated PN strings should fall through. +if ((~isstruct(PN_struct)) || (isempty(PN_struct))) + PN_chars = PN_struct; + return +else + PN_chars = ''; +end + +% Encode a decorated PN struct. +for p = 1:numel(PN_struct) + + % Add each of the components to the output string. + if (isfield(PN_struct, 'FamilyName')) + PN_chars = [PN_chars PN_struct.FamilyName '^']; + else + PN_chars = [PN_chars '^']; + end + + if (isfield(PN_struct, 'GivenName')) + PN_chars = [PN_chars PN_struct.GivenName '^']; + else + PN_chars = [PN_chars '^']; + end + + if (isfield(PN_struct, 'MiddleName')) + PN_chars = [PN_chars PN_struct.MiddleName '^']; + else + PN_chars = [PN_chars '^']; + end + + if (isfield(PN_struct, 'NamePrefix')) + PN_chars = [PN_chars PN_struct.NamePrefix '^']; + else + PN_chars = [PN_chars '^']; + end + + if (isfield(PN_struct, 'NameSuffix')) + PN_chars = [PN_chars PN_struct.NameSuffix '^']; + else + PN_chars = [PN_chars '^']; + end + + % Remove extraneous '^' separators. + while ((~isempty(PN_chars)) && (PN_chars(end) == '^')) + PN_chars(end) = ''; + end + + % Separate multiple values. + PN_chars = [PN_chars '\']; + +end + +% Remove extra value delimiter '\'. +PN_chars(end) = ''; diff --git a/CT/private/dicom_encode_rle.m b/CT/private/dicom_encode_rle.m index 060645c..286401e 100644 --- a/CT/private/dicom_encode_rle.m +++ b/CT/private/dicom_encode_rle.m @@ -1,271 +1,271 @@ -function [fragments, frames] = dicom_encode_rle(pixel_cells, bits_allocated, dims) -%DICOM_ENCODE_RLE Run-length encode pixel cells. -% [FRAGMENTS, LIST] = DICOM_ENCODE_RLE(PIXCELLS, BITS, DIMS) compresses -% the pixel cells PIXCELLS using run-length encoding. BITS is the -% number of bits allocated for each pixel cell, and DIMS is the -% original dimensions of the image stored in PIXCELLS, which is now a -% vector. FRAGMENTS is a cell array containing the encoded frames (as -% UINT8 data) from the compressor. LIST is a vector of indices to the -% first fragment of each compressed frame of a multiframe image. -% -% See also DICOM_ENCODE_JPEG_LOSSY. - -% Copyright 1993-2011 The MathWorks, Inc. - - -% Run-length encoded pixel cells have the following form: -% -% RLE Header (16 UINT32 components) -% - The first component is number of RLE segments (0 - 15) -% - The remaining 15 components are offsets to the beginning of each -% segment (or 0 if there are no more segments). -% RLE Segment 1 -% RLE Segment 2 -% etc. -% -% The segments are generated by stripping off successive bytes from the -% composite pixel code, starting with the most significant byte [1]. -% The stripped bytes are passed through the RLE coder to create the -% compressed segment. -% -% Individual image rows must be sent through the coder separately even if -% they are part of the same segment. This prevents a run from spanning -% multiple rows. The compressed rows are concatenated within each -% segment. -% -% If a composite pixel cell does not end on byte boundaries, 0 bits are -% appended to the least significant byte. This will happen if the number -% of bits allocated for pixels and overlays (0028,0100) is not divisible -% by 8. -% -% Sequences of bytes can be compressed into an arbitrary number of -% fragments. This allows for encoding the image into a set of "strips". -% The only restriction is that each fragment must contain the same number -% of pixel cell elements in each segment (e.g., the same number of red, -% green, and blue samples, etc.). -% -% [1] - A composite pixel code is one pixel cell in the sequence of pixel -% cells. The unstated conclusion of using composite pixel codes is -% that planar configuration (0028,0006) must be 0 if more than one -% sample is present and RLE coding is used. -% -% See Annex G in part 5 of the DICOM spec for more details. - -% DICOM_ENCODE_PIXEL_CELLS explicitly creates pixel cells that are byte -% aligned. -if (rem(bits_allocated, 8) ~= 0) - error(message('images:dicom_encode_rle:badBitsAllocated')) -end - -% Reshape data to make segment creation easy. -if (length(dims) >= 3) - num_segments = (bits_allocated / 8) * dims(3); -else - num_segments = (bits_allocated / 8); -end - -if (num_segments > 15) - error(message('images:dicom_encode_rle:tooManySegments')) -end - -if (numel(dims) == 4) - numFrames = dims(4); -else - numFrames = 1; -end - -swap = determine_swap; - -pixel_cells = dicom_typecast(pixel_cells, 'uint8', swap); -pixel_cells = reshape(pixel_cells, [num_segments, ... - (numel(pixel_cells) / num_segments)]); - -offset = 0; - -fragments = cell(1, numFrames); -for f = 1:numFrames - - encoded_data = []; - - % Encode each segment. - segment_lengths = zeros(1, num_segments); - for p = 1:num_segments - - segment = []; - - % Encode each row. - for q = 1:dims(1) - - start_idx = offset + (q - 1) * dims(2) + 1; - end_idx = offset + q * dims(2); - - encoded_row = dicom_encode_rle_segment(pixel_cells(p, start_idx:end_idx)); - - segment = [segment; encoded_row]; %#ok - - end - - % Store segment. - encoded_data = [encoded_data; segment]; %#ok - segment_lengths(p) = numel(segment); - - end - - offset = end_idx; - - % Create RLE header. - header = repmat(uint32(0), [16 1]); - header(1) = uint32(num_segments); - header(2) = uint32(64); - - for p = 2:(num_segments) - - header(p + 1) = uint32(64 + sum(segment_lengths(1:(p - 1)))); - - end - - encoded_data = [dicom_typecast(header, 'uint8', swap); encoded_data]; %#ok - - % Create output from fragments. - fragments{f} = encoded_data; - -end - -frames = 1:numFrames; - - - -% This is essentially how dicom_rle_encode_segment() works. -% function coded_bytes = rle_coder(X) -% -% X = double(X(:)); -% -% current_pos = 1; -% max_pos = numel(X); -% -% % Find runs. Where the diff is 0, there is a repeated value. The first run -% % is not included. -% X_diff = diff(X); -% X_reps = find(X_diff == 0); -% rep_pix = [X_reps(1); X_reps(find(diff(X_reps) > 1) + 1); (max_pos + 1)]; -% -% all_runs = find(X_diff ~= 0) + 1; -% -% rep_number = 1; -% next_rep = rep_pix(rep_number); -% -% coded_values = []; -% more_data = 1; -% -% while (more_data) -% -% if (current_pos ~= next_rep) -% -% % Handle literal values between runs. -% -% % Only 128 literal values can be encoded in one literal run. -% while ((next_rep - current_pos) > 128) -% -% coded_values = [coded_values -% 127 -% X(current_pos:(current_pos + 127))]; -% current_pos = current_pos + 128; -% -% end -% -% run_length = next_rep - current_pos; -% coded_values = [coded_values -% (run_length - 1) -% X(current_pos:(next_rep - 1))]; -% -% % All literal values until the next repetitive run are encoded. -% current_pos = next_rep; -% -% else -% -% % Handle repetitive runs. -% -% % Find next run of different values. -% idx = find(all_runs == current_pos); -% -% if (isempty(idx)) -% -% % This run appeared at the very beginning of the data. -% if (isempty(all_runs)) -% -% % There is only one run. -% next_run = max_pos + 1; -% -% else -% -% next_run = all_runs(1); -% -% end -% -% elseif (idx == numel(all_runs)) -% -% % This is the last run. -% next_run = max_pos + 1; -% -% else -% -% next_run = all_runs(idx + 1); -% -% end -% -% while ((next_run - current_pos) > 128) -% -% coded_values = [coded_values -% -127 -% X(current_pos)]; -% current_pos = current_pos + 128; -% -% end -% -% run_length = next_run - current_pos; -% coded_values = [coded_values -% (-run_length + 1) -% X(current_pos)]; -% -% current_pos = current_pos + run_length; -% -% rep_number = rep_number + 1; -% next_rep = rep_pix(rep_number); -% -% end -% -% if (current_pos > max_pos) -% -% more_data = 0; -% -% end -% -% end -% -% % Convert double data to bytestream. -% % -% % The next statement maps the signed values in the range [-128, 255] to -% % [0, 255], the valid range for UINT8. Values in the range [0, 255] are -% % unchanged; values in [-128, -1] are mapped to [128, 255]. -% % -% % Converting negative values is okay because the bit pattern for a -% % converted value as a UINT8 is the same as the negative value's bit -% % pattern as an INT8. The RLE decoder algorithm will read the next byte -% % as a signed or unsigned value based on its own logic; it suffices for -% % this function to write indistinguishable bytes (signed or unsigned). -% coded_bytes = uint8(rem((coded_values + 256), 256)); - - - -function swap = determine_swap -% Determine if endianness of data needs swapped. - -[~, ~, mach] = fopen(1); - -% RLE compressed data must be written to file as IEEE-LE. -if (isempty(strfind(lower(mach), 'ieee-le'))) - swap = 1; -else - swap = 0; -end - +function [fragments, frames] = dicom_encode_rle(pixel_cells, bits_allocated, dims) +%DICOM_ENCODE_RLE Run-length encode pixel cells. +% [FRAGMENTS, LIST] = DICOM_ENCODE_RLE(PIXCELLS, BITS, DIMS) compresses +% the pixel cells PIXCELLS using run-length encoding. BITS is the +% number of bits allocated for each pixel cell, and DIMS is the +% original dimensions of the image stored in PIXCELLS, which is now a +% vector. FRAGMENTS is a cell array containing the encoded frames (as +% UINT8 data) from the compressor. LIST is a vector of indices to the +% first fragment of each compressed frame of a multiframe image. +% +% See also DICOM_ENCODE_JPEG_LOSSY. + +% Copyright 1993-2011 The MathWorks, Inc. + + +% Run-length encoded pixel cells have the following form: +% +% RLE Header (16 UINT32 components) +% - The first component is number of RLE segments (0 - 15) +% - The remaining 15 components are offsets to the beginning of each +% segment (or 0 if there are no more segments). +% RLE Segment 1 +% RLE Segment 2 +% etc. +% +% The segments are generated by stripping off successive bytes from the +% composite pixel code, starting with the most significant byte [1]. +% The stripped bytes are passed through the RLE coder to create the +% compressed segment. +% +% Individual image rows must be sent through the coder separately even if +% they are part of the same segment. This prevents a run from spanning +% multiple rows. The compressed rows are concatenated within each +% segment. +% +% If a composite pixel cell does not end on byte boundaries, 0 bits are +% appended to the least significant byte. This will happen if the number +% of bits allocated for pixels and overlays (0028,0100) is not divisible +% by 8. +% +% Sequences of bytes can be compressed into an arbitrary number of +% fragments. This allows for encoding the image into a set of "strips". +% The only restriction is that each fragment must contain the same number +% of pixel cell elements in each segment (e.g., the same number of red, +% green, and blue samples, etc.). +% +% [1] - A composite pixel code is one pixel cell in the sequence of pixel +% cells. The unstated conclusion of using composite pixel codes is +% that planar configuration (0028,0006) must be 0 if more than one +% sample is present and RLE coding is used. +% +% See Annex G in part 5 of the DICOM spec for more details. + +% DICOM_ENCODE_PIXEL_CELLS explicitly creates pixel cells that are byte +% aligned. +if (rem(bits_allocated, 8) ~= 0) + error(message('images:dicom_encode_rle:badBitsAllocated')) +end + +% Reshape data to make segment creation easy. +if (length(dims) >= 3) + num_segments = (bits_allocated / 8) * dims(3); +else + num_segments = (bits_allocated / 8); +end + +if (num_segments > 15) + error(message('images:dicom_encode_rle:tooManySegments')) +end + +if (numel(dims) == 4) + numFrames = dims(4); +else + numFrames = 1; +end + +swap = determine_swap; + +pixel_cells = dicom_typecast(pixel_cells, 'uint8', swap); +pixel_cells = reshape(pixel_cells, [num_segments, ... + (numel(pixel_cells) / num_segments)]); + +offset = 0; + +fragments = cell(1, numFrames); +for f = 1:numFrames + + encoded_data = []; + + % Encode each segment. + segment_lengths = zeros(1, num_segments); + for p = 1:num_segments + + segment = []; + + % Encode each row. + for q = 1:dims(1) + + start_idx = offset + (q - 1) * dims(2) + 1; + end_idx = offset + q * dims(2); + + encoded_row = dicom_encode_rle_segment(pixel_cells(p, start_idx:end_idx)); + + segment = [segment; encoded_row]; %#ok + + end + + % Store segment. + encoded_data = [encoded_data; segment]; %#ok + segment_lengths(p) = numel(segment); + + end + + offset = end_idx; + + % Create RLE header. + header = repmat(uint32(0), [16 1]); + header(1) = uint32(num_segments); + header(2) = uint32(64); + + for p = 2:(num_segments) + + header(p + 1) = uint32(64 + sum(segment_lengths(1:(p - 1)))); + + end + + encoded_data = [dicom_typecast(header, 'uint8', swap); encoded_data]; %#ok + + % Create output from fragments. + fragments{f} = encoded_data; + +end + +frames = 1:numFrames; + + + +% This is essentially how dicom_rle_encode_segment() works. +% function coded_bytes = rle_coder(X) +% +% X = double(X(:)); +% +% current_pos = 1; +% max_pos = numel(X); +% +% % Find runs. Where the diff is 0, there is a repeated value. The first run +% % is not included. +% X_diff = diff(X); +% X_reps = find(X_diff == 0); +% rep_pix = [X_reps(1); X_reps(find(diff(X_reps) > 1) + 1); (max_pos + 1)]; +% +% all_runs = find(X_diff ~= 0) + 1; +% +% rep_number = 1; +% next_rep = rep_pix(rep_number); +% +% coded_values = []; +% more_data = 1; +% +% while (more_data) +% +% if (current_pos ~= next_rep) +% +% % Handle literal values between runs. +% +% % Only 128 literal values can be encoded in one literal run. +% while ((next_rep - current_pos) > 128) +% +% coded_values = [coded_values +% 127 +% X(current_pos:(current_pos + 127))]; +% current_pos = current_pos + 128; +% +% end +% +% run_length = next_rep - current_pos; +% coded_values = [coded_values +% (run_length - 1) +% X(current_pos:(next_rep - 1))]; +% +% % All literal values until the next repetitive run are encoded. +% current_pos = next_rep; +% +% else +% +% % Handle repetitive runs. +% +% % Find next run of different values. +% idx = find(all_runs == current_pos); +% +% if (isempty(idx)) +% +% % This run appeared at the very beginning of the data. +% if (isempty(all_runs)) +% +% % There is only one run. +% next_run = max_pos + 1; +% +% else +% +% next_run = all_runs(1); +% +% end +% +% elseif (idx == numel(all_runs)) +% +% % This is the last run. +% next_run = max_pos + 1; +% +% else +% +% next_run = all_runs(idx + 1); +% +% end +% +% while ((next_run - current_pos) > 128) +% +% coded_values = [coded_values +% -127 +% X(current_pos)]; +% current_pos = current_pos + 128; +% +% end +% +% run_length = next_run - current_pos; +% coded_values = [coded_values +% (-run_length + 1) +% X(current_pos)]; +% +% current_pos = current_pos + run_length; +% +% rep_number = rep_number + 1; +% next_rep = rep_pix(rep_number); +% +% end +% +% if (current_pos > max_pos) +% +% more_data = 0; +% +% end +% +% end +% +% % Convert double data to bytestream. +% % +% % The next statement maps the signed values in the range [-128, 255] to +% % [0, 255], the valid range for UINT8. Values in the range [0, 255] are +% % unchanged; values in [-128, -1] are mapped to [128, 255]. +% % +% % Converting negative values is okay because the bit pattern for a +% % converted value as a UINT8 is the same as the negative value's bit +% % pattern as an INT8. The RLE decoder algorithm will read the next byte +% % as a signed or unsigned value based on its own logic; it suffices for +% % this function to write indistinguishable bytes (signed or unsigned). +% coded_bytes = uint8(rem((coded_values + 256), 256)); + + + +function swap = determine_swap +% Determine if endianness of data needs swapped. + +[~, ~, mach] = fopen(1); + +% RLE compressed data must be written to file as IEEE-LE. +if (isempty(strfind(lower(mach), 'ieee-le'))) + swap = 1; +else + swap = 0; +end + diff --git a/CT/private/dicom_generate_uid.m b/CT/private/dicom_generate_uid.m index ce25711..520d062 100644 --- a/CT/private/dicom_generate_uid.m +++ b/CT/private/dicom_generate_uid.m @@ -1,151 +1,151 @@ -function uid = dicom_generate_uid(uid_type) -%DICOM_GENERATE_UID Create a globally unique ID. -% UID = DICOM_GENERATE_UID(TYPE) creates a unique identifier (UID) of -% the specified type. TYPE must be one of the following: -% -% 'instance' - A UID for any arbitrary DICOM object -% 'ipt_root' - The root of the Image Processing Toolbox's UID -% 'series' - A UID for an arbitrary series of DICOM images -% 'study' - A UID for an arbitrary study of DICOM series -% -% See also MWGUIDGEN. - -% Copyright 1993-2012 The MathWorks, Inc. - -% This is the UID root assigned to us. It prevents collisions with UID -% generation schemes from other vendors. -ipt_root = '1.3.6.1.4.1.9590.100.1'; - -switch (uid_type) -case {'ipt_root'} - - uid = ipt_root; - -case {'instance', 'series', 'study'} - - switch (lower(computer())) - case {'pcwin', 'pcwin64'} - - guid_32bit = create_guid_windows(); - uid = guid_to_uid(ipt_root, guid_32bit); - - case {'glnxa64', 'glnx86'} - - guid_32bit = create_guid_linux(); - uid = guid_to_uid(ipt_root, guid_32bit); - - case {'maci', 'maci64'} - - guid_32bit = create_guid_mac(); - uid = guid_to_uid(ipt_root, guid_32bit); - - otherwise - - error(message('images:dicom_generate_uid:unsupportedPlatform')) - - end - -otherwise - - error(message('images:dicom_generate_uid:inputValue', uid_type)); - -end - - - -function guid_32bit = create_guid_linux - -[status, raw_guid] = system('uuidgen'); -if (status ~= 0) - - [status, raw_guid] = system('cat /proc/sys/kernel/random/uuid'); - if (status ~= 0) - - error(message('images:dicom_generate_uid:linuxSystemProblem')) - - end - -end - -tmp_guid = strip_unix_messages(raw_guid); -guid_32bit = sscanf(strrep(tmp_guid, '-', ''), '%08x'); - - - -function guid_32bit = create_guid_mac - -[status, raw_guid] = system('uuidgen'); -if (status ~= 0) - - error(message('images:dicom_generate_uid:macSystemProblem')) - -end - -tmp_guid = strip_unix_messages(raw_guid); -guid_32bit = sscanf(strrep(tmp_guid, '-', ''), '%08x'); - - - -function guid_32bit = create_guid_windows - -% Generate a GUID as a series of 16 UINT8 values. -guid_8bit = mwguidgen; - -% Convert the bytes to four UINT32 values. -guid_32bit = dicom_typecast(guid_8bit, 'uint32'); - - - -function uid = guid_to_uid(ipt_root, guid_32bit) - -% Convert a group of numeric values into a concatenated string. -guid = ''; -for p = 1:length(guid_32bit) - - guid = [guid sprintf('%010.0f', double(guid_32bit(p)))]; %#ok - -end - -% The maximum decimal representation of four concatenated 32-bit values -% is 40 digits long, which is one digit too many for the UID container in -% DICOM (after you add in the UID root). Shorten it to fit in DICOM's -% length requirements by removing a value from the middle. (As a result, -% 1 in every 10^39 values will be a duplicate.) -guid(13) = ''; - -% The DICOM standard requires the digit that follows a dot to be -% nonzero. Removing the leading zeros does not cause duplication. -guid = remove_leading_zeros(guid); - -% Append the GUID to the UID root. The intervening digit is the version -% number of the UID generation scheme. Increment the version number if -% the rule/mechanism for generating "guid" changes. (The next scheme version -% number should be 4; skip 3, which is being used for implementation UIDs.) -uid = [ipt_root '.2.' guid]; - - - -function out = remove_leading_zeros(in) - -out = in; -while (out(1) == '0') - out(1) = ''; -end - - - -function out = strip_unix_messages(in) - -% It's possible that sourcing a shell configuration file (or -% another part of the shell launch) causes a message to appear in -% the system() output. Strip out everything but the GUID value, -% which we must assume to be the last full line. - -out = in; - -newLine = sprintf('\n'); -idx = find(in == newLine); - -if (numel(idx) > 1) - out(1:idx(end-1)) = ''; -end +function uid = dicom_generate_uid(uid_type) +%DICOM_GENERATE_UID Create a globally unique ID. +% UID = DICOM_GENERATE_UID(TYPE) creates a unique identifier (UID) of +% the specified type. TYPE must be one of the following: +% +% 'instance' - A UID for any arbitrary DICOM object +% 'ipt_root' - The root of the Image Processing Toolbox's UID +% 'series' - A UID for an arbitrary series of DICOM images +% 'study' - A UID for an arbitrary study of DICOM series +% +% See also MWGUIDGEN. + +% Copyright 1993-2012 The MathWorks, Inc. + +% This is the UID root assigned to us. It prevents collisions with UID +% generation schemes from other vendors. +ipt_root = '1.3.6.1.4.1.9590.100.1'; + +switch (uid_type) +case {'ipt_root'} + + uid = ipt_root; + +case {'instance', 'series', 'study'} + + switch (lower(computer())) + case {'pcwin', 'pcwin64'} + + guid_32bit = create_guid_windows(); + uid = guid_to_uid(ipt_root, guid_32bit); + + case {'glnxa64', 'glnx86'} + + guid_32bit = create_guid_linux(); + uid = guid_to_uid(ipt_root, guid_32bit); + + case {'maci', 'maci64'} + + guid_32bit = create_guid_mac(); + uid = guid_to_uid(ipt_root, guid_32bit); + + otherwise + + error(message('images:dicom_generate_uid:unsupportedPlatform')) + + end + +otherwise + + error(message('images:dicom_generate_uid:inputValue', uid_type)); + +end + + + +function guid_32bit = create_guid_linux + +[status, raw_guid] = system('uuidgen'); +if (status ~= 0) + + [status, raw_guid] = system('cat /proc/sys/kernel/random/uuid'); + if (status ~= 0) + + error(message('images:dicom_generate_uid:linuxSystemProblem')) + + end + +end + +tmp_guid = strip_unix_messages(raw_guid); +guid_32bit = sscanf(strrep(tmp_guid, '-', ''), '%08x'); + + + +function guid_32bit = create_guid_mac + +[status, raw_guid] = system('uuidgen'); +if (status ~= 0) + + error(message('images:dicom_generate_uid:macSystemProblem')) + +end + +tmp_guid = strip_unix_messages(raw_guid); +guid_32bit = sscanf(strrep(tmp_guid, '-', ''), '%08x'); + + + +function guid_32bit = create_guid_windows + +% Generate a GUID as a series of 16 UINT8 values. +guid_8bit = mwguidgen; + +% Convert the bytes to four UINT32 values. +guid_32bit = dicom_typecast(guid_8bit, 'uint32'); + + + +function uid = guid_to_uid(ipt_root, guid_32bit) + +% Convert a group of numeric values into a concatenated string. +guid = ''; +for p = 1:length(guid_32bit) + + guid = [guid sprintf('%010.0f', double(guid_32bit(p)))]; %#ok + +end + +% The maximum decimal representation of four concatenated 32-bit values +% is 40 digits long, which is one digit too many for the UID container in +% DICOM (after you add in the UID root). Shorten it to fit in DICOM's +% length requirements by removing a value from the middle. (As a result, +% 1 in every 10^39 values will be a duplicate.) +guid(13) = ''; + +% The DICOM standard requires the digit that follows a dot to be +% nonzero. Removing the leading zeros does not cause duplication. +guid = remove_leading_zeros(guid); + +% Append the GUID to the UID root. The intervening digit is the version +% number of the UID generation scheme. Increment the version number if +% the rule/mechanism for generating "guid" changes. (The next scheme version +% number should be 4; skip 3, which is being used for implementation UIDs.) +uid = [ipt_root '.2.' guid]; + + + +function out = remove_leading_zeros(in) + +out = in; +while (out(1) == '0') + out(1) = ''; +end + + + +function out = strip_unix_messages(in) + +% It's possible that sourcing a shell configuration file (or +% another part of the shell launch) causes a message to appear in +% the system() output. Strip out everything but the GUID value, +% which we must assume to be the last full line. + +out = in; + +newLine = sprintf('\n'); +idx = find(in == newLine); + +if (numel(idx) > 1) + out(1:idx(end-1)) = ''; +end diff --git a/CT/private/dicom_get_msg.m b/CT/private/dicom_get_msg.m index fb38e65..fab9fa2 100644 --- a/CT/private/dicom_get_msg.m +++ b/CT/private/dicom_get_msg.m @@ -1,58 +1,58 @@ -function file = dicom_get_msg(file) -%DICOM_GET_MSG Get a pool of potential messages to load later. -% FILE = DICOM_GET_MSG(FILE) processes FILE.Filename to obtain a pool -% of potential DICOM messages to read. After execution, FILE.Filename -% will contain a cell array of messages to read. -% -% Note: When loading a locally stored file, this function just checks -% for the existence of the file. When network contexts are supported, -% the message pool will contain the results of a QUERY operation. -% -% See also DICOM_OPEN_MSG and DICOM_CREATE_FILE_STRUCT. - -% Copyright 1993-2011 The MathWorks, Inc. - -% Verify that file exists. - -if (exist(file.Filename) ~= 2) - - % Look for file with common extensions. - if (exist([file.Filename '.dcm'])) - - file.Filename = [file.Filename '.dcm']; - - elseif (exist([file.Filename '.dic'])) - - file.Filename = [file.Filename '.dic']; - - elseif (exist([file.Filename '.dicom'])) - - file.Filename = [file.Filename '.dicom']; - - elseif (exist([file.Filename '.img'])) - - file.Filename = [file.Filename '.img']; - - else - - file.Filename = ''; - return - - end - -end - -% Get full filename. -fid = fopen(file.Filename); - -if (fid < 0) - - error(message('images:dicom_get_msg:fileOpen', file.Filename)) - -else - - file.Filename = fopen(fid); - -end - -fclose(fid); +function file = dicom_get_msg(file) +%DICOM_GET_MSG Get a pool of potential messages to load later. +% FILE = DICOM_GET_MSG(FILE) processes FILE.Filename to obtain a pool +% of potential DICOM messages to read. After execution, FILE.Filename +% will contain a cell array of messages to read. +% +% Note: When loading a locally stored file, this function just checks +% for the existence of the file. When network contexts are supported, +% the message pool will contain the results of a QUERY operation. +% +% See also DICOM_OPEN_MSG and DICOM_CREATE_FILE_STRUCT. + +% Copyright 1993-2011 The MathWorks, Inc. + +% Verify that file exists. + +if (exist(file.Filename) ~= 2) + + % Look for file with common extensions. + if (exist([file.Filename '.dcm'])) + + file.Filename = [file.Filename '.dcm']; + + elseif (exist([file.Filename '.dic'])) + + file.Filename = [file.Filename '.dic']; + + elseif (exist([file.Filename '.dicom'])) + + file.Filename = [file.Filename '.dicom']; + + elseif (exist([file.Filename '.img'])) + + file.Filename = [file.Filename '.img']; + + else + + file.Filename = ''; + return + + end + +end + +% Get full filename. +fid = fopen(file.Filename); + +if (fid < 0) + + error(message('images:dicom_get_msg:fileOpen', file.Filename)) + +else + + file.Filename = fopen(fid); + +end + +fclose(fid); diff --git a/CT/private/dicom_get_next_tag.m b/CT/private/dicom_get_next_tag.m index 090cbae..ba23430 100644 --- a/CT/private/dicom_get_next_tag.m +++ b/CT/private/dicom_get_next_tag.m @@ -1,35 +1,35 @@ -function [group, element] = dicom_get_next_tag(file) -%DICOM_GET_NEXT_TAG Get the group and element values for the next attribute. -% [GROUP,ELEMENT] = DICOM_GET_NEXT_TAG(MESSAGE) returns the GROUP and -% ELEMENT of the next attribute in MESSAGE. -% -% Note: This function reads the tag and then rewinds to the beginning -% of it. It is then possible to call DICOM_READ_ATTR without changing -% the position in the file. - -% Copyright 1993-2005 The MathWorks, Inc. - -if (~feof(file.FID)) - - [tag, count] = fread(file.FID, 2, 'uint16', file.Current_Endian); - - if (count ~= 2) - - group = -1; - element = -1; - - return - - end - - group = tag(1); - element = tag(2); - - fseek(file.FID, -4, 'cof'); - -else - - group = -1; - element = -1; - -end +function [group, element] = dicom_get_next_tag(file) +%DICOM_GET_NEXT_TAG Get the group and element values for the next attribute. +% [GROUP,ELEMENT] = DICOM_GET_NEXT_TAG(MESSAGE) returns the GROUP and +% ELEMENT of the next attribute in MESSAGE. +% +% Note: This function reads the tag and then rewinds to the beginning +% of it. It is then possible to call DICOM_READ_ATTR without changing +% the position in the file. + +% Copyright 1993-2005 The MathWorks, Inc. + +if (~feof(file.FID)) + + [tag, count] = fread(file.FID, 2, 'uint16', file.Current_Endian); + + if (count ~= 2) + + group = -1; + element = -1; + + return + + end + + group = tag(1); + element = tag(2); + + fseek(file.FID, -4, 'cof'); + +else + + group = -1; + element = -1; + +end diff --git a/CT/private/dicom_has_fmeta.m b/CT/private/dicom_has_fmeta.m index 237c5f0..5791e5e 100644 --- a/CT/private/dicom_has_fmeta.m +++ b/CT/private/dicom_has_fmeta.m @@ -1,39 +1,39 @@ -function has_fmeta = dicom_has_fmeta(file) -%DICOM_HAS_FMETA Determines whether a message contains file metadata. -% TF = DICOM_HAS_FMETA(FILE) Returns 1 if FILE has file metadata, 0 -% otherwise. File metadata are attributes whose groups are 2 - 7. - -% Copyright 1993-2010 The MathWorks, Inc. - -fseek(file.FID, 128, 'bof'); - -if (ftell(file.FID) ~= 128) - % File is too short to have 128 byte preamble: no file metadata. - has_fmeta = false; - fseek(file.FID, 0, 'bof'); - - return - -end - -% Look for the format string 'DICM'. -fmt_string = fread(file.FID, 4, 'uchar'); - -has_fmeta = isequal(fmt_string, [68 73 67 77]'); - -% Very rarely a file will have file metadata but not a preamble. -if (~has_fmeta) - - has_fmeta = has_group0002_without_preamble(file); - fseek(file.FID, 0, 'bof'); - -end - - - -function tf = has_group0002_without_preamble(file) - -fseek(file.FID, 0, 'bof'); -group = fread(file.FID, 1, 'uint16=>uint16'); - -tf = ((group == 2) || (swapbytes(group) == 2)); +function has_fmeta = dicom_has_fmeta(file) +%DICOM_HAS_FMETA Determines whether a message contains file metadata. +% TF = DICOM_HAS_FMETA(FILE) Returns 1 if FILE has file metadata, 0 +% otherwise. File metadata are attributes whose groups are 2 - 7. + +% Copyright 1993-2010 The MathWorks, Inc. + +fseek(file.FID, 128, 'bof'); + +if (ftell(file.FID) ~= 128) + % File is too short to have 128 byte preamble: no file metadata. + has_fmeta = false; + fseek(file.FID, 0, 'bof'); + + return + +end + +% Look for the format string 'DICM'. +fmt_string = fread(file.FID, 4, 'uchar'); + +has_fmeta = isequal(fmt_string, [68 73 67 77]'); + +% Very rarely a file will have file metadata but not a preamble. +if (~has_fmeta) + + has_fmeta = has_group0002_without_preamble(file); + fseek(file.FID, 0, 'bof'); + +end + + + +function tf = has_group0002_without_preamble(file) + +fseek(file.FID, 0, 'bof'); +group = fread(file.FID, 1, 'uint16=>uint16'); + +tf = ((group == 2) || (swapbytes(group) == 2)); diff --git a/CT/private/dicom_has_overlay_bits.m b/CT/private/dicom_has_overlay_bits.m index 4b8a221..6d4d2bf 100644 --- a/CT/private/dicom_has_overlay_bits.m +++ b/CT/private/dicom_has_overlay_bits.m @@ -1,46 +1,46 @@ -function overlay_bits = dicom_has_overlay_bits(info) -%DICOM_HAS_OVERLAY_BITS Return overlay bit positions in pixel cells. - -% Copyright 1993-2005 The MathWorks, Inc. - -% Overlay data requires one or more OverlayBitsAllocated attributes to be -% set (60xx,0100). - -info_fields = fieldnames(info); -overlay_fields = strmatch('OverlayBitsAllocated', info_fields); - -% Determine if overlay data is with the pixel data. -if (~isempty(overlay_fields)) - - % Overlay pixels are with Image pixels iff OverlayBitsAllocated and - % BitsAllocated are the same. - - overlay_bits = []; - count = 0; - - % Find overlay bit location for each overlay in pixel cell. - for p = overlay_fields' - - OverlayBitsAllocated = info.(info_fields{p}); - - if (isequal(OverlayBitsAllocated, info.BitsAllocated)) - - % Find position in pixel cell: OverlayBitPosition (60xx,0102). - - count = count + 1; - - OBP_field = strrep(info_fields{p}, 'BitsAllocated', ... - 'BitPosition'); - - overlay_bits(count) = info.(OBP_field); - - end - - end - -else - - % No overlays. - overlay_bits = []; - -end +function overlay_bits = dicom_has_overlay_bits(info) +%DICOM_HAS_OVERLAY_BITS Return overlay bit positions in pixel cells. + +% Copyright 1993-2005 The MathWorks, Inc. + +% Overlay data requires one or more OverlayBitsAllocated attributes to be +% set (60xx,0100). + +info_fields = fieldnames(info); +overlay_fields = strmatch('OverlayBitsAllocated', info_fields); + +% Determine if overlay data is with the pixel data. +if (~isempty(overlay_fields)) + + % Overlay pixels are with Image pixels iff OverlayBitsAllocated and + % BitsAllocated are the same. + + overlay_bits = []; + count = 0; + + % Find overlay bit location for each overlay in pixel cell. + for p = overlay_fields' + + OverlayBitsAllocated = info.(info_fields{p}); + + if (isequal(OverlayBitsAllocated, info.BitsAllocated)) + + % Find position in pixel cell: OverlayBitPosition (60xx,0102). + + count = count + 1; + + OBP_field = strrep(info_fields{p}, 'BitsAllocated', ... + 'BitPosition'); + + overlay_bits(count) = info.(OBP_field); + + end + + end + +else + + % No overlays. + overlay_bits = []; + +end diff --git a/CT/private/dicom_iods.m b/CT/private/dicom_iods.m index 2f06b39..4989f32 100644 --- a/CT/private/dicom_iods.m +++ b/CT/private/dicom_iods.m @@ -1,1376 +1,1376 @@ -function iods = dicom_iods -%DICOM_IODS Repository of information object definitions. -% IODS = DICOM_IODS returns a structure array containing various DICOM -% information object definitions (IODS), which are used to create -% Service-Object Pair (SOP) classes. SOP instances comprise the high- -% level description of the information stored in a DICOM file. -% -% The IODS struct array contains the following fields: -% - Name: An abbreviated name of the IOD -% - UID: The unique identifier of the storage class for this IOD -% - Def_fcn: The function containing the modules' definitions -% - Prep_fcn: The function used to process the raw metadata needed for -% these modules -% - Modules: A cell array describing the modules needed for this IOD. -% -% The Modules cell array contains the name of the modules, the type of -% the module ('M' = mandatory, 'C' = conditional, 'U' = user optional), -% and the condition constant indicating whether the module should be -% built. -% -% See also DICOM_CREATE_IOD, DICOM_MODULES. - -% Copyright 1993-2010 The MathWorks, Inc. - -% The lexicon has changed since we named some of the modules. -% Here's a translation: -% * SpecimenIdentification --> "Clinical Trial Subject" - - -iods = []; - -iods(end+1).Name = 'CRImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'CRSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'CRImage' 'M', {} - 'OverlayPlane' 'U', {} - 'Curve' 'U', {} - 'ModalityLUT' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'CTImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.3'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePlane' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {'HAS_BOLUS'} - 'CTImage' 'M', {} - 'OverlayPlane' 'U', {'HAS_OVERLAY'} - 'VOILUT' 'U', {'HAS_VOILUT'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MRImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.4'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.4'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePlane' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {'HAS_BOLUS'} - 'MRImage' 'M', {} - 'OverlayPlane' 'U', {'HAS_OVERLAY'} - 'VOILUT' 'U', {'HAS_VOILUT'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'NMImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.20'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'NMPETPatientOrientation' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'NMImagePixel' 'M', {} - 'MultiFrame' 'M', {} - 'NMMultiFrame' 'M', {} - 'NMImage' 'M', {} - 'NMIsotope' 'M', {} - 'NMDetector' 'M', {} - 'NMTomoAcquisition' 'C', {} - 'NMMultiGatedAcquisition' 'C', {} - 'NMPhase' 'C', {} - 'NMReconstruction' 'C', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'U', {} - 'Curve' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'NMImageRetired'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.5'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'NMSeriesRetired' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'NMEquipmentRetired' 'U', {} - 'GeneralImage' 'M', {} - 'ImagePlane' 'U', {} - 'ImagePixel' 'M', {} - 'Cine' 'C', {} - 'MultiFrame' 'C', {} - 'NMImageRetired' 'M', {} - 'NMSPECTAcquisitionImageRetired' 'C', {} - 'NMMultiGatedAcquisitionImageRetired' 'C', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'U', {} - 'Curve' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'USImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.6.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'USFrameOfReference' 'C', {} - 'Synchronization' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'PaletteColorLookupTable' 'C', {} - 'USRegionCalibration' 'U', {} - 'USImage' 'M', {} - 'OverlayPlane' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'USMultiFrameImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.3.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'USFrameOfReference' 'C', {} - 'Synchronization' 'C', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'M', {} - 'MultiFrame' 'M', {} - 'PaletteColorLookupTable' 'C', {} - 'USRegionCalibration' 'U', {} - 'USImage' 'M', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'USImageRetired'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.6'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'USFrameOfReference' 'C', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'USRegionCalibration' 'U', {} - 'USImage' 'M', {} - 'OverlayPlane' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'USMultiFrameImageRetired'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'USFrameOfReference' 'C', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'C', {} - 'MultiFrame' 'M', {} - 'USRegionCalibration' 'U', {} - 'USImage' 'M', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'SCImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.7'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.8.1'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'U', {} - 'SCImageEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'SCImage' 'M', {} - 'OverlayPlane' 'U', {} - 'ModalityLUT' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MultiframeSingleBitSCImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.8.2'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'U', {} - 'SCImageEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'Cine' 'C', {'NEEDS_SC_CINE'} - 'MultiFrame' 'M', {} - 'FramePointers' 'U', {} - 'SCImage' 'U', {} - 'SCMultiFrameImage' 'M', {} - 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MultiframeGrayscaleByteSCImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.8.3'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'U', {} - 'SCImageEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'Cine' 'C', {'NEEDS_SC_CINE'} - 'MultiFrame' 'M', {} - 'FramePointers' 'U', {} - 'SCImage' 'U', {} - 'SCMultiFrameImage' 'M', {} - 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} - 'VOILUT' 'C', {'HAS_VOILUT'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MultiframeGrayscaleWordSCImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.8.4'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'U', {} - 'SCImageEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'Cine' 'C', {'NEEDS_SC_CINE'} - 'MultiFrame' 'M', {} - 'FramePointers' 'U', {} - 'SCImage' 'U', {} - 'SCMultiFrameImage' 'M', {} - 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} - 'VOILUT' 'C', {'HAS_VOILUT'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MultiframeTrueColorSCImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.4'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = 'PS 3.3 Sec. A.8.5'; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'U', {} - 'SCImageEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'Cine' 'C', {'NEEDS_SC_CINE'} - 'MultiFrame' 'M', {} - 'FramePointers' 'U', {} - 'SCImage' 'U', {} - 'SCMultiFrameImage' 'M', {} - 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'StandaloneOverlay'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.8'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'OverlayIdentification' 'M', {} - 'OverlayPlane' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'StandaloneCurve'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'CurveIdentification' 'M', {} - 'Curve' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'StandaloneModalityLUT'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.10'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'ModalityLUT' 'M', {} - 'LUTIdentification' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'StandaloneVOILUT'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.11'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'VOILUT' 'M', {} - 'LUTIdentification' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'XAImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'C', {} - 'MultiFrame' 'C', {} - 'FramePointers' 'U', {} - 'Mask' 'C', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'XRayImage' 'M', {} - 'XRayAcquisition' 'M', {} - 'XRayCollimator' 'U', {} - 'XRayTable' 'C', {} - 'XAPositioner' 'M', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'C', {} - 'Curve' 'U', {} - 'ModalityLUT' 'C', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'XABiplaneImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'C', {} - 'MultiFrame' 'C', {} - 'FramePointers' 'U', {} - 'Mask' 'C', {} - 'DisplayShutter' 'U', {} - 'XRayAcquisition' 'M', {} - 'XRayCollimator' 'U', {} - 'XAPositioner' 'M', {} - 'VOILUT' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'XRayImage' 'M', {} - 'XRayTable' 'C', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'C', {} - 'BiplaneOverlay' 'C', {} - 'Curve' 'U', {} - 'ModalityLUT' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'XRFImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'C', {} - 'MultiFrame' 'C', {} - 'FramePointers' 'U', {} - 'Mask' 'C', {} - 'XRayImage' 'M', {} - 'XRayAcquisition' 'M', {} - 'XRayCollimator' 'U', {} - 'DisplayShutter' 'U', {} - 'Therapy' 'U', {} - 'Device' 'U', {} - 'XRayTable' 'C', {} - 'XRFPositioner' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'C', {} - 'Curve' 'U', {} - 'ModalityLUT' 'C', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'PETImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.128'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'PETSeries' 'M', {} - 'PETIsotope' 'M', {} - 'PETMultigatedAcquisition' 'C', {} - 'NMPETPatientOrientation' 'M', {} - 'FrameOfReference' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePlane' 'M', {} - 'ImagePixel' 'M', {} - 'PETImage' 'M', {} - 'OverlayPlane' 'U', {} - 'VOILUT' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'StandalonePETCurve'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.129'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'PETSeries' 'M', {} - 'PETIsotope' 'M', {} - 'PETMultigatedAcquisition' 'C', {} - 'GeneralEquipment' 'M', {} - 'CurveIdentification' 'M', {} - 'Curve' 'M', {} - 'PETCurve' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'C', {} - 'Cine' 'C', {} - 'MultiFrame' 'C', {} - 'RTImage' 'M', {} - 'ModalityLUT' 'U', {} - 'VOILUT' 'U', {} - 'Approval' 'U', {} - 'Curve' 'U', {} - 'Audio' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTDose'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'FrameOfReference' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'C', {} - 'ImagePlane' 'C', {} - 'ImagePixel' 'C', {} - 'MultiFrame' 'C', {} - 'OverlayPlane' 'U', {} - 'MultiFrameOverlay' 'U', {} - 'ModalityLUT' 'U', {} - 'RTDose' 'M', {} - 'RTDVH' 'U', {} - 'StructureSet' 'C', {} - 'ROIContour' 'C', {} - 'RTDoseROI' 'C', {} - 'Audio' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTStructureSet'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'StructureSet' 'M', {} - 'ROIContour' 'M', {} - 'RTROIObservations' 'M', {} - 'Approval' 'U', {} - 'Audio' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTPlan'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.5'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'RTGeneralPlan' 'M', {} - 'RTPrescription' 'U', {} - 'RTToleranceTables' 'U', {} - 'RTPatientSetup' 'U', {} - 'RTFractionScheme' 'U', {} - 'RTBeams' 'C', {} - 'RTBrachyApplicationSetups' 'C', {} - 'Approval' 'U', {} - 'Audio' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTBeamsTreatmentRecord'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.4'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'RTGeneralTreatmentRecord' 'M', {} - 'RTPatientSetup' 'U', {} - 'RTTreatmentMachineRecord' 'M', {} - 'MeasuredDoseReferenceRecord' 'U', {} - 'CalculatedDoseReferenceRecord' 'U', {} - 'RTBeamsSessionRecord' 'M', {} - 'RTTreatmentSummaryRecord' 'U', {} - 'Curve' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTBrachyTreatmentRecord'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.6'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'RTGeneralTreatmentRecord' 'M', {} - 'RTPatientSetup' 'U', {} - 'RTTreatmentMachineRecord' 'M', {} - 'MeasuredDoseReferenceRecord' 'U', {} - 'CalculatedDoseReferenceRecord' 'U', {} - 'RTBrachySessionRecord' 'M', {} - 'RTTreatmentSummaryRecord' 'U', {} - 'Curve' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'RTTreatmentSummaryRecord'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.7'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'RTSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'RTGeneralTreatmentRecord' 'M', {} - 'RTTreatmentSummaryRecord' 'U', {} - 'Curve' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'DXImageForProcessing'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.1.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'DXImageForPresentation'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MammographyImageForProcessing'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.2.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'MammographySeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'MammographyImage' 'M', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MammographyImageForPresentation'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'MammographySeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'MammographyImage' 'M', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'IntraoralImageForProcessing'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.3.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'IntraoralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'IntraoralImage' 'M', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'IntraoralImageForPresentation'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'DXSeries' 'M', {} - 'IntraoralSeries' 'M', {} - 'FrameOfReference' 'U', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'ContrastBolus' 'U', {} - 'DisplayShutter' 'U', {} - 'Device' 'U', {} - 'Therapy' 'U', {} - 'DXAnatomyImaged' 'M', {} - 'DXImage' 'M', {} - 'DXDetector' 'M', {} - 'XRayCollimator' 'U', {} - 'DXPositioning' 'U', {} - 'XRayTomoAcquisition' 'U', {} - 'XRayAcquisitionDose' 'U', {} - 'XRayGeneration' 'U', {} - 'XRayFiltration' 'U', {} - 'XRayGrid' 'U', {} - 'IntraoralImage' 'M', {} - 'OverlayPlane' 'C', {} - 'Curve' 'U', {} - 'VOILUT' 'C', {} - 'ImageHistogram' 'U', {} - 'AcquisitionContext' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'VLEndoscopicImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'VLEndoscopicSeriesDummy' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'AcquisitionContext' 'M', {} - 'VLImage' 'M', {} - 'OverlayPlane' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'VLMicroscopicImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'VLMicroscopicSeriesDummy' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'AcquisitionContext' 'M', {} - 'VLImage' 'M', {} - 'OverlayPlane' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'VLSlideCoordinatesMicroscopicImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'VLSlideCoordinatesMicroscopicSeriesDummy' 'M', {} - 'FrameOfReference' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'AcquisitionContext' 'M', {} - 'VLImage' 'M', {} - 'SlideCoordinates' 'M', {} - 'OverlayPlane' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'VLPhotographicImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.4'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'VLPhotographicSeriesDummy' 'M', {} - 'GeneralEquipment' 'M', {} - 'GeneralImage' 'M', {} - 'ImagePixel' 'M', {} - 'AcquisitionContext' 'M', {} - 'VLImage' 'M', {} - 'OverlayPlane' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'BasicVoice'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.4.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'U', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'U', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'TwelveLeadECG'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'U', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'GeneralECG'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.2'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'U', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'AmbulatoryECG'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.3'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'U', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'HemodynamicWaveform'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.2.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'C', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'CardiacElectrophysiologyWaveform'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.3.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'Synchronization' 'C', {} - 'GeneralEquipment' 'M', {} - 'WaveformIdentification' 'M', {} - 'Waveform' 'M', {} - 'AcquisitionContext' 'M', {} - 'WaveformAnnotation' 'C', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'BasicTextSR'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.11'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'SRDocumentSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'SRDocumentGeneral' 'M', {} - 'SRDocumentContent' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'EnhancedSR'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.22'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'SRDocumentSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'SRDocumentGeneral' 'M', {} - 'SRDocumentContent' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'ComprehensiveSR'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.33'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'SRDocumentSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'SRDocumentGeneral' 'M', {} - 'SRDocumentContent' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'MammographyCADSR'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.50'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'SRDocumentSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'SRDocumentGeneral' 'M', {} - 'SRDocumentContent' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'KeyObjectSelectionDocument'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.59'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'C', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'KeyObjectDocumentSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'KeyObjectDocument' 'M', {} - 'SRDocumentContent' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'GrayscaleSoftcopyPresentationState'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.11.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'GeneralSeries' 'M', {} - 'PresentationSeries' 'M', {} - 'GeneralEquipment' 'M', {} - 'PresentationState' 'M', {} - 'Mask' 'C', {} - 'DisplayShutter' 'C', {} - 'BitmapDisplayShutter' 'C', {} - 'OverlayPlane' 'C', {} - 'OverlayCurveActivation' 'C', {} - 'DisplayedArea' 'M', {} - 'GraphicAnnotation' 'C', {} - 'SpatialTransformation' 'C', {} - 'GraphicLayer' 'C', {} - 'ModalityLUT' 'C', {} - 'SoftcopyVOILUT' 'C', {} - 'SoftcopyPresentationLUT' 'M', {} - 'SOPCommon' 'M', {} -}; - -iods(end+1).Name = 'EnhancedMRImage'; -iods(end).UID = '1.2.840.10008.5.1.4.1.1.4.1'; -iods(end).Def_fcn = 'dicom_modules'; -iods(end).Prep_fcn = 'dicom_prep_metadata'; -iods(end).Spec_part = ''; -iods(end).Modules = { - 'FileMetadata', 'C', {'HAS_FILEMETADATA'} - 'Patient' 'M', {} - 'ClinicalTrialSubject' 'U', {} - 'GeneralStudy' 'M', {} - 'PatientStudy' 'U', {'TRUE'} - 'ClinicalTrialStudy' 'U', {} - 'GeneralSeries' 'M', {} - 'ClinicalTrialSeries' 'U', {} - 'MRSeries' 'M', {} - 'FrameOfReference' 'M', {} - 'Synchronization' 'C', {'IS_TIME_SYNCHRONIZED'} - 'GeneralEquipment' 'M', {} - 'EnhancedGeneralEquipment' 'M', {} - 'ImagePixel' 'M', {} - 'EnhancedContrastBolus' 'C', {} - 'MultiFrameFunctionalGroups' 'M', {} - 'MultiFrameDimension' 'M', {} - 'CardiacSynchronization' 'C', {} - 'RespiratorySynchronization' 'C', {} - 'BulkMotionSynchronization' 'C', {} - 'SupplementalPaletteColorLUT' 'C', {} - 'AcquisitionContext' 'M', {} - 'Device' 'U',{} - 'Specimen' 'U', {} - 'EnhancedMRImage' 'M', {} - 'MRPulseSequence' 'C', {} - 'SOPCommon' 'M', {} -}; +function iods = dicom_iods +%DICOM_IODS Repository of information object definitions. +% IODS = DICOM_IODS returns a structure array containing various DICOM +% information object definitions (IODS), which are used to create +% Service-Object Pair (SOP) classes. SOP instances comprise the high- +% level description of the information stored in a DICOM file. +% +% The IODS struct array contains the following fields: +% - Name: An abbreviated name of the IOD +% - UID: The unique identifier of the storage class for this IOD +% - Def_fcn: The function containing the modules' definitions +% - Prep_fcn: The function used to process the raw metadata needed for +% these modules +% - Modules: A cell array describing the modules needed for this IOD. +% +% The Modules cell array contains the name of the modules, the type of +% the module ('M' = mandatory, 'C' = conditional, 'U' = user optional), +% and the condition constant indicating whether the module should be +% built. +% +% See also DICOM_CREATE_IOD, DICOM_MODULES. + +% Copyright 1993-2010 The MathWorks, Inc. + +% The lexicon has changed since we named some of the modules. +% Here's a translation: +% * SpecimenIdentification --> "Clinical Trial Subject" + + +iods = []; + +iods(end+1).Name = 'CRImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'CRSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'CRImage' 'M', {} + 'OverlayPlane' 'U', {} + 'Curve' 'U', {} + 'ModalityLUT' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'CTImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.3'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePlane' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {'HAS_BOLUS'} + 'CTImage' 'M', {} + 'OverlayPlane' 'U', {'HAS_OVERLAY'} + 'VOILUT' 'U', {'HAS_VOILUT'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MRImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.4'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.4'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePlane' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {'HAS_BOLUS'} + 'MRImage' 'M', {} + 'OverlayPlane' 'U', {'HAS_OVERLAY'} + 'VOILUT' 'U', {'HAS_VOILUT'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'NMImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.20'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'NMPETPatientOrientation' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'NMImagePixel' 'M', {} + 'MultiFrame' 'M', {} + 'NMMultiFrame' 'M', {} + 'NMImage' 'M', {} + 'NMIsotope' 'M', {} + 'NMDetector' 'M', {} + 'NMTomoAcquisition' 'C', {} + 'NMMultiGatedAcquisition' 'C', {} + 'NMPhase' 'C', {} + 'NMReconstruction' 'C', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'U', {} + 'Curve' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'NMImageRetired'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.5'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'NMSeriesRetired' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'NMEquipmentRetired' 'U', {} + 'GeneralImage' 'M', {} + 'ImagePlane' 'U', {} + 'ImagePixel' 'M', {} + 'Cine' 'C', {} + 'MultiFrame' 'C', {} + 'NMImageRetired' 'M', {} + 'NMSPECTAcquisitionImageRetired' 'C', {} + 'NMMultiGatedAcquisitionImageRetired' 'C', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'U', {} + 'Curve' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'USImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.6.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'USFrameOfReference' 'C', {} + 'Synchronization' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'PaletteColorLookupTable' 'C', {} + 'USRegionCalibration' 'U', {} + 'USImage' 'M', {} + 'OverlayPlane' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'USMultiFrameImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.3.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'USFrameOfReference' 'C', {} + 'Synchronization' 'C', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'M', {} + 'MultiFrame' 'M', {} + 'PaletteColorLookupTable' 'C', {} + 'USRegionCalibration' 'U', {} + 'USImage' 'M', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'USImageRetired'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.6'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'USFrameOfReference' 'C', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'USRegionCalibration' 'U', {} + 'USImage' 'M', {} + 'OverlayPlane' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'USMultiFrameImageRetired'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'USFrameOfReference' 'C', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'C', {} + 'MultiFrame' 'M', {} + 'USRegionCalibration' 'U', {} + 'USImage' 'M', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'SCImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.7'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.8.1'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'U', {} + 'SCImageEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'SCImage' 'M', {} + 'OverlayPlane' 'U', {} + 'ModalityLUT' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MultiframeSingleBitSCImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.8.2'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'U', {} + 'SCImageEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'Cine' 'C', {'NEEDS_SC_CINE'} + 'MultiFrame' 'M', {} + 'FramePointers' 'U', {} + 'SCImage' 'U', {} + 'SCMultiFrameImage' 'M', {} + 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MultiframeGrayscaleByteSCImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.8.3'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'U', {} + 'SCImageEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'Cine' 'C', {'NEEDS_SC_CINE'} + 'MultiFrame' 'M', {} + 'FramePointers' 'U', {} + 'SCImage' 'U', {} + 'SCMultiFrameImage' 'M', {} + 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} + 'VOILUT' 'C', {'HAS_VOILUT'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MultiframeGrayscaleWordSCImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.8.4'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'U', {} + 'SCImageEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'Cine' 'C', {'NEEDS_SC_CINE'} + 'MultiFrame' 'M', {} + 'FramePointers' 'U', {} + 'SCImage' 'U', {} + 'SCMultiFrameImage' 'M', {} + 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} + 'VOILUT' 'C', {'HAS_VOILUT'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MultiframeTrueColorSCImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.7.4'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = 'PS 3.3 Sec. A.8.5'; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'U', {} + 'SCImageEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'Cine' 'C', {'NEEDS_SC_CINE'} + 'MultiFrame' 'M', {} + 'FramePointers' 'U', {} + 'SCImage' 'U', {} + 'SCMultiFrameImage' 'M', {} + 'SCMultiFrameVector' 'C', {'IS_MULTIFRAME'} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'StandaloneOverlay'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.8'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'OverlayIdentification' 'M', {} + 'OverlayPlane' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'StandaloneCurve'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'CurveIdentification' 'M', {} + 'Curve' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'StandaloneModalityLUT'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.10'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'ModalityLUT' 'M', {} + 'LUTIdentification' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'StandaloneVOILUT'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.11'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'VOILUT' 'M', {} + 'LUTIdentification' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'XAImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'C', {} + 'MultiFrame' 'C', {} + 'FramePointers' 'U', {} + 'Mask' 'C', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'XRayImage' 'M', {} + 'XRayAcquisition' 'M', {} + 'XRayCollimator' 'U', {} + 'XRayTable' 'C', {} + 'XAPositioner' 'M', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'C', {} + 'Curve' 'U', {} + 'ModalityLUT' 'C', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'XABiplaneImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'C', {} + 'MultiFrame' 'C', {} + 'FramePointers' 'U', {} + 'Mask' 'C', {} + 'DisplayShutter' 'U', {} + 'XRayAcquisition' 'M', {} + 'XRayCollimator' 'U', {} + 'XAPositioner' 'M', {} + 'VOILUT' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'XRayImage' 'M', {} + 'XRayTable' 'C', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'C', {} + 'BiplaneOverlay' 'C', {} + 'Curve' 'U', {} + 'ModalityLUT' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'XRFImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.12.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'C', {} + 'MultiFrame' 'C', {} + 'FramePointers' 'U', {} + 'Mask' 'C', {} + 'XRayImage' 'M', {} + 'XRayAcquisition' 'M', {} + 'XRayCollimator' 'U', {} + 'DisplayShutter' 'U', {} + 'Therapy' 'U', {} + 'Device' 'U', {} + 'XRayTable' 'C', {} + 'XRFPositioner' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'C', {} + 'Curve' 'U', {} + 'ModalityLUT' 'C', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'PETImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.128'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'PETSeries' 'M', {} + 'PETIsotope' 'M', {} + 'PETMultigatedAcquisition' 'C', {} + 'NMPETPatientOrientation' 'M', {} + 'FrameOfReference' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePlane' 'M', {} + 'ImagePixel' 'M', {} + 'PETImage' 'M', {} + 'OverlayPlane' 'U', {} + 'VOILUT' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'StandalonePETCurve'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.129'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'PETSeries' 'M', {} + 'PETIsotope' 'M', {} + 'PETMultigatedAcquisition' 'C', {} + 'GeneralEquipment' 'M', {} + 'CurveIdentification' 'M', {} + 'Curve' 'M', {} + 'PETCurve' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'C', {} + 'Cine' 'C', {} + 'MultiFrame' 'C', {} + 'RTImage' 'M', {} + 'ModalityLUT' 'U', {} + 'VOILUT' 'U', {} + 'Approval' 'U', {} + 'Curve' 'U', {} + 'Audio' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTDose'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'FrameOfReference' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'C', {} + 'ImagePlane' 'C', {} + 'ImagePixel' 'C', {} + 'MultiFrame' 'C', {} + 'OverlayPlane' 'U', {} + 'MultiFrameOverlay' 'U', {} + 'ModalityLUT' 'U', {} + 'RTDose' 'M', {} + 'RTDVH' 'U', {} + 'StructureSet' 'C', {} + 'ROIContour' 'C', {} + 'RTDoseROI' 'C', {} + 'Audio' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTStructureSet'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'StructureSet' 'M', {} + 'ROIContour' 'M', {} + 'RTROIObservations' 'M', {} + 'Approval' 'U', {} + 'Audio' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTPlan'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.5'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'RTGeneralPlan' 'M', {} + 'RTPrescription' 'U', {} + 'RTToleranceTables' 'U', {} + 'RTPatientSetup' 'U', {} + 'RTFractionScheme' 'U', {} + 'RTBeams' 'C', {} + 'RTBrachyApplicationSetups' 'C', {} + 'Approval' 'U', {} + 'Audio' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTBeamsTreatmentRecord'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.4'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'RTGeneralTreatmentRecord' 'M', {} + 'RTPatientSetup' 'U', {} + 'RTTreatmentMachineRecord' 'M', {} + 'MeasuredDoseReferenceRecord' 'U', {} + 'CalculatedDoseReferenceRecord' 'U', {} + 'RTBeamsSessionRecord' 'M', {} + 'RTTreatmentSummaryRecord' 'U', {} + 'Curve' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTBrachyTreatmentRecord'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.6'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'RTGeneralTreatmentRecord' 'M', {} + 'RTPatientSetup' 'U', {} + 'RTTreatmentMachineRecord' 'M', {} + 'MeasuredDoseReferenceRecord' 'U', {} + 'CalculatedDoseReferenceRecord' 'U', {} + 'RTBrachySessionRecord' 'M', {} + 'RTTreatmentSummaryRecord' 'U', {} + 'Curve' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'RTTreatmentSummaryRecord'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.481.7'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'RTSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'RTGeneralTreatmentRecord' 'M', {} + 'RTTreatmentSummaryRecord' 'U', {} + 'Curve' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'DXImageForProcessing'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.1.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'DXImageForPresentation'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MammographyImageForProcessing'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.2.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'MammographySeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'MammographyImage' 'M', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MammographyImageForPresentation'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'MammographySeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'MammographyImage' 'M', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'IntraoralImageForProcessing'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.3.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'IntraoralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'IntraoralImage' 'M', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'IntraoralImageForPresentation'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.1.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'DXSeries' 'M', {} + 'IntraoralSeries' 'M', {} + 'FrameOfReference' 'U', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'ContrastBolus' 'U', {} + 'DisplayShutter' 'U', {} + 'Device' 'U', {} + 'Therapy' 'U', {} + 'DXAnatomyImaged' 'M', {} + 'DXImage' 'M', {} + 'DXDetector' 'M', {} + 'XRayCollimator' 'U', {} + 'DXPositioning' 'U', {} + 'XRayTomoAcquisition' 'U', {} + 'XRayAcquisitionDose' 'U', {} + 'XRayGeneration' 'U', {} + 'XRayFiltration' 'U', {} + 'XRayGrid' 'U', {} + 'IntraoralImage' 'M', {} + 'OverlayPlane' 'C', {} + 'Curve' 'U', {} + 'VOILUT' 'C', {} + 'ImageHistogram' 'U', {} + 'AcquisitionContext' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'VLEndoscopicImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'VLEndoscopicSeriesDummy' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'AcquisitionContext' 'M', {} + 'VLImage' 'M', {} + 'OverlayPlane' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'VLMicroscopicImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'VLMicroscopicSeriesDummy' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'AcquisitionContext' 'M', {} + 'VLImage' 'M', {} + 'OverlayPlane' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'VLSlideCoordinatesMicroscopicImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'VLSlideCoordinatesMicroscopicSeriesDummy' 'M', {} + 'FrameOfReference' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'AcquisitionContext' 'M', {} + 'VLImage' 'M', {} + 'SlideCoordinates' 'M', {} + 'OverlayPlane' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'VLPhotographicImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.77.1.4'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'VLPhotographicSeriesDummy' 'M', {} + 'GeneralEquipment' 'M', {} + 'GeneralImage' 'M', {} + 'ImagePixel' 'M', {} + 'AcquisitionContext' 'M', {} + 'VLImage' 'M', {} + 'OverlayPlane' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'BasicVoice'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.4.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'U', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'U', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'TwelveLeadECG'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'U', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'GeneralECG'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.2'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'U', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'AmbulatoryECG'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.1.3'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'U', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'HemodynamicWaveform'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.2.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'C', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'CardiacElectrophysiologyWaveform'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.9.3.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'Synchronization' 'C', {} + 'GeneralEquipment' 'M', {} + 'WaveformIdentification' 'M', {} + 'Waveform' 'M', {} + 'AcquisitionContext' 'M', {} + 'WaveformAnnotation' 'C', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'BasicTextSR'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.11'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'SRDocumentSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'SRDocumentGeneral' 'M', {} + 'SRDocumentContent' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'EnhancedSR'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.22'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'SRDocumentSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'SRDocumentGeneral' 'M', {} + 'SRDocumentContent' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'ComprehensiveSR'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.33'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'SRDocumentSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'SRDocumentGeneral' 'M', {} + 'SRDocumentContent' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'MammographyCADSR'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.50'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'SRDocumentSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'SRDocumentGeneral' 'M', {} + 'SRDocumentContent' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'KeyObjectSelectionDocument'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.88.59'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'C', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'KeyObjectDocumentSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'KeyObjectDocument' 'M', {} + 'SRDocumentContent' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'GrayscaleSoftcopyPresentationState'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.11.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'GeneralSeries' 'M', {} + 'PresentationSeries' 'M', {} + 'GeneralEquipment' 'M', {} + 'PresentationState' 'M', {} + 'Mask' 'C', {} + 'DisplayShutter' 'C', {} + 'BitmapDisplayShutter' 'C', {} + 'OverlayPlane' 'C', {} + 'OverlayCurveActivation' 'C', {} + 'DisplayedArea' 'M', {} + 'GraphicAnnotation' 'C', {} + 'SpatialTransformation' 'C', {} + 'GraphicLayer' 'C', {} + 'ModalityLUT' 'C', {} + 'SoftcopyVOILUT' 'C', {} + 'SoftcopyPresentationLUT' 'M', {} + 'SOPCommon' 'M', {} +}; + +iods(end+1).Name = 'EnhancedMRImage'; +iods(end).UID = '1.2.840.10008.5.1.4.1.1.4.1'; +iods(end).Def_fcn = 'dicom_modules'; +iods(end).Prep_fcn = 'dicom_prep_metadata'; +iods(end).Spec_part = ''; +iods(end).Modules = { + 'FileMetadata', 'C', {'HAS_FILEMETADATA'} + 'Patient' 'M', {} + 'ClinicalTrialSubject' 'U', {} + 'GeneralStudy' 'M', {} + 'PatientStudy' 'U', {'TRUE'} + 'ClinicalTrialStudy' 'U', {} + 'GeneralSeries' 'M', {} + 'ClinicalTrialSeries' 'U', {} + 'MRSeries' 'M', {} + 'FrameOfReference' 'M', {} + 'Synchronization' 'C', {'IS_TIME_SYNCHRONIZED'} + 'GeneralEquipment' 'M', {} + 'EnhancedGeneralEquipment' 'M', {} + 'ImagePixel' 'M', {} + 'EnhancedContrastBolus' 'C', {} + 'MultiFrameFunctionalGroups' 'M', {} + 'MultiFrameDimension' 'M', {} + 'CardiacSynchronization' 'C', {} + 'RespiratorySynchronization' 'C', {} + 'BulkMotionSynchronization' 'C', {} + 'SupplementalPaletteColorLUT' 'C', {} + 'AcquisitionContext' 'M', {} + 'Device' 'U',{} + 'Specimen' 'U', {} + 'EnhancedMRImage' 'M', {} + 'MRPulseSequence' 'C', {} + 'SOPCommon' 'M', {} +}; diff --git a/CT/private/dicom_load_dictionary.m b/CT/private/dicom_load_dictionary.m index 86b41a1..e6f21bc 100644 --- a/CT/private/dicom_load_dictionary.m +++ b/CT/private/dicom_load_dictionary.m @@ -1,257 +1,257 @@ -function [tags, values] = dicom_load_dictionary(dictionary) -%DICOM_LOAD_DICTIONARY Load the DICOM data dictionary into MATLAB - -% Lines in the data dictionary file have the form: -% -% # Comment -% (ABCD,EFO1) \t VR1/VR2/... \t BlanklessName \t VM \n -% -% VM can be a scalar or range (e.g., 1, 3, 1-3, 3-n) - -% The data dictionary in memory is stored as a structure array with one -% entry in the struct for each attribute in the data dictionary. -% Attributes in repeating classes are expanded into the 128 attributes for -% the class. -% -% The entries of the associated 65536-by-65536 sparse lookup table are -% nonzero if a group and element combination correspond to an attribute -% in the data dictionary. The DICOM data dictionary is zero-based, so -% the indices for tag (0x0008,0x0010) are (9,17). Thus, entry (9,17) in -% the sparse array contains the index for attribute (0008,0010) in the -% struct, or 0 if it isn't in the dictionary. - -% Copyright 1993-2011 The MathWorks, Inc. - -if (strfind(dictionary, '.mat')) - - load(dictionary); - return - -end - -MAX_GROUP = 65535; % 0xFFFF -MAX_ELEMENT = 65535; % 0xFFFF - -values = struct([]); - -count = 0; - -% -% Read the dictionary. -% -fmt = '(%4c,%4c)\t%s%s%s'; -[group, element, vr, name, vm] = textread(dictionary, fmt, ... - 'commentstyle', 'shell'); - -% -% Remove duplicate entries from the list. Keep the last. -% - -% Concatenate group and element to make the elements be sorted uniquely. -ge = [group element]; - -% UNIQUE has the property of sorting rows and keeping the last -% duplicate. This is yields the desired behavior of removing entries -% from the data dictionary which have been superceded by later entries. -[ge_sorted, idx] = unique(ge, 'rows'); - -% Separate the sorted group and element values. -group = ge_sorted(:, 1:4); -element = ge_sorted(:, 5:8); - -% Sort and remove duplicates from the value fields using the index from -% UNIQUE. -name = name(idx); -vr = vr(idx); -vm = vm(idx); - -% -% Process VR, VM -% - -for p = 1:length(vr) - - % VR. - - if (~isempty(strfind(vr{p}, '/'))) - % Multiple potential VR's. - - tmp_vr = tokenize(vr{p}, '/'); - - for q = 1:length(tmp_vr) - if (length(tmp_vr{q}) ~= 2) - error(message('images:dicom_load_dictionary:invalidVR', vr{ p }, group( p, : ), element( p, : ))) - end - end - - vr{p} = tmp_vr'; - - else - % Single VR value. - - if (length(vr{p}) ~= 2) - error(message('images:dicom_load_dictionary:invalidVR', vr{ p }, group( p, : ), element( p, : ))) - end - - end - - % - % VM. - % - - % Look for range. - h_pos = strfind(vm{p}, '-'); - - if (~isempty(h_pos)) - % VM is a range. - - if ((length(h_pos) > 1) || ... - (h_pos(1) == 1) || ... - (h_pos(end) == length(vm{p}))) - - error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) - end - - vm_low = vm{p}(1:(h_pos - 1)); - vm_low = sscanf(vm_low, '%d'); - - vm_high = vm{p}((h_pos + 1):end); - - if (strfind(lower(vm_high), 'n')) - vm_high = inf; - else - vm_high = sscanf(vm_high, '%d'); - end - - if (isempty(vm_low) || isempty(vm_high)) - - error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) - - end - - vm{p} = [vm_low vm_high]; - - else - % VM is scalar. - tmp = sscanf(vm{p}, '%d'); - - if (isnan(tmp)) - - error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) - - else - vm{p} = [tmp tmp]; - end - - end - -end % for p ... - -% -% Handle the repeating classes before storing the rest. -% - All of these classes will have a group or element ending in 'xx'. -% - The spec says that new repeating classes are not to be introduced. -% Sequences will be used instead. -% - The range for these values is always 00 to ff (e.g., 5000 - 50ff). -% - Only even group values are part of the repeating group (odd ones are -% private, of course). Repeating elements, on the other hand, can be -% either odd or even. -% - Technically these classes have the same name in the data dictionary, -% but we need to uniquely identify them when placing them in the -% structure. -% - See PS 3.5-2000 Sec. 7.6 for information on repeating groups. -% - Repeating elements are an evil construction present in ACR-NEMA only. -% - -% Find repeating groups and elements. -rep_group = strmatch('xx', fliplr(group)); -rep_element = strmatch('xx', fliplr(element)); - -% Number of all attributes is (regular + repeating). -count_rep_classes = length(rep_group) + length(rep_element); -count_expanded = 128*length(rep_group) + 256*length(rep_element); -total_attr = length(vr) + count_expanded - count_rep_classes; - -% Preallocate variables. -values(total_attr).Name = 'TEMP PLACEHOLDER'; -values(total_attr).VR = {''}; -values(total_attr).VM = 0; - -rows = zeros(total_attr, 1); -cols = zeros(total_attr, 1); - -% Assign values for the Repeating groups. - -for p = 1:length(rep_group) - - % Find the offset of the repeating group (e.g., 6000 or 5000). - offset = sscanf([group(rep_group(p), 1:2) '00'], '%x'); - cur_element = sscanf(element(rep_group(p), 1:4), '%x'); - - % Place the repeating values in the struct. - for q = 1:128 - - count = count + 1; - - values(count).Name = [name{rep_group(p)} '_' sprintf('%X', 2*(q-1))]; - values(count).VR = vr{rep_group(p)}; - values(count).VM = vm{rep_group(p)}; - - end - - % Keep track of group and element values for the allocated attributes. - rows((count - 127):(count)) = (offset + 1):2:(offset + 256); - cols((count - 127):(count)) = cur_element + 1; - -end - -% Repeating elements. - -for p = 1:length(rep_element) - - % Find the offset of the repeating element. - offset = sscanf([element(rep_element(p), 1:2) '00'], '%x'); - cur_group = sscanf(group(rep_element(p), 1:4), '%x'); - - % Place the repeating values in the struct. - for q = 1:256 - - count = count + 1; - - values(count).Name = [name{rep_element(p)} '_' sprintf('%X', (q-1))]; - values(count).VR = vr{rep_group(p)}; - values(count).VM = vm{rep_group(p)}; - - end - - % Keep track of group and element values for the allocated attributes. - rows((count - 255):(count)) = cur_group + 1; - cols((count - 255):(count)) = (offset + 1):(offset + 256); - -end - -% Don't process the repeating values twice. -group([rep_group; rep_element],:) = []; -element([rep_group; rep_element],:) = []; - -name([rep_group; rep_element]) = []; -vr([rep_group; rep_element]) = []; -vm([rep_group; rep_element]) = []; - -% -% Store the remainder of the values. -% - -group = hex2dec(group); -element = hex2dec(element); - -count = count + 1; - -rows(count:end) = group + 1; -cols(count:end) = element + 1; - -tags = sparse(rows, cols, 1:total_attr, MAX_GROUP + 1, MAX_ELEMENT + 1); - -[values(count:total_attr).Name] = deal(name{:}); -[values(count:total_attr).VR] = deal(vr{:}); -[values(count:total_attr).VM] = deal(vm{:}); +function [tags, values] = dicom_load_dictionary(dictionary) +%DICOM_LOAD_DICTIONARY Load the DICOM data dictionary into MATLAB + +% Lines in the data dictionary file have the form: +% +% # Comment +% (ABCD,EFO1) \t VR1/VR2/... \t BlanklessName \t VM \n +% +% VM can be a scalar or range (e.g., 1, 3, 1-3, 3-n) + +% The data dictionary in memory is stored as a structure array with one +% entry in the struct for each attribute in the data dictionary. +% Attributes in repeating classes are expanded into the 128 attributes for +% the class. +% +% The entries of the associated 65536-by-65536 sparse lookup table are +% nonzero if a group and element combination correspond to an attribute +% in the data dictionary. The DICOM data dictionary is zero-based, so +% the indices for tag (0x0008,0x0010) are (9,17). Thus, entry (9,17) in +% the sparse array contains the index for attribute (0008,0010) in the +% struct, or 0 if it isn't in the dictionary. + +% Copyright 1993-2011 The MathWorks, Inc. + +if (strfind(dictionary, '.mat')) + + load(dictionary); + return + +end + +MAX_GROUP = 65535; % 0xFFFF +MAX_ELEMENT = 65535; % 0xFFFF + +values = struct([]); + +count = 0; + +% +% Read the dictionary. +% +fmt = '(%4c,%4c)\t%s%s%s'; +[group, element, vr, name, vm] = textread(dictionary, fmt, ... + 'commentstyle', 'shell'); + +% +% Remove duplicate entries from the list. Keep the last. +% + +% Concatenate group and element to make the elements be sorted uniquely. +ge = [group element]; + +% UNIQUE has the property of sorting rows and keeping the last +% duplicate. This is yields the desired behavior of removing entries +% from the data dictionary which have been superceded by later entries. +[ge_sorted, idx] = unique(ge, 'rows'); + +% Separate the sorted group and element values. +group = ge_sorted(:, 1:4); +element = ge_sorted(:, 5:8); + +% Sort and remove duplicates from the value fields using the index from +% UNIQUE. +name = name(idx); +vr = vr(idx); +vm = vm(idx); + +% +% Process VR, VM +% + +for p = 1:length(vr) + + % VR. + + if (~isempty(strfind(vr{p}, '/'))) + % Multiple potential VR's. + + tmp_vr = tokenize(vr{p}, '/'); + + for q = 1:length(tmp_vr) + if (length(tmp_vr{q}) ~= 2) + error(message('images:dicom_load_dictionary:invalidVR', vr{ p }, group( p, : ), element( p, : ))) + end + end + + vr{p} = tmp_vr'; + + else + % Single VR value. + + if (length(vr{p}) ~= 2) + error(message('images:dicom_load_dictionary:invalidVR', vr{ p }, group( p, : ), element( p, : ))) + end + + end + + % + % VM. + % + + % Look for range. + h_pos = strfind(vm{p}, '-'); + + if (~isempty(h_pos)) + % VM is a range. + + if ((length(h_pos) > 1) || ... + (h_pos(1) == 1) || ... + (h_pos(end) == length(vm{p}))) + + error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) + end + + vm_low = vm{p}(1:(h_pos - 1)); + vm_low = sscanf(vm_low, '%d'); + + vm_high = vm{p}((h_pos + 1):end); + + if (strfind(lower(vm_high), 'n')) + vm_high = inf; + else + vm_high = sscanf(vm_high, '%d'); + end + + if (isempty(vm_low) || isempty(vm_high)) + + error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) + + end + + vm{p} = [vm_low vm_high]; + + else + % VM is scalar. + tmp = sscanf(vm{p}, '%d'); + + if (isnan(tmp)) + + error(message('images:dicom_load_dictionary:invalidVM', vm{ p }, group( p, : ), element( p, : ))) + + else + vm{p} = [tmp tmp]; + end + + end + +end % for p ... + +% +% Handle the repeating classes before storing the rest. +% - All of these classes will have a group or element ending in 'xx'. +% - The spec says that new repeating classes are not to be introduced. +% Sequences will be used instead. +% - The range for these values is always 00 to ff (e.g., 5000 - 50ff). +% - Only even group values are part of the repeating group (odd ones are +% private, of course). Repeating elements, on the other hand, can be +% either odd or even. +% - Technically these classes have the same name in the data dictionary, +% but we need to uniquely identify them when placing them in the +% structure. +% - See PS 3.5-2000 Sec. 7.6 for information on repeating groups. +% - Repeating elements are an evil construction present in ACR-NEMA only. +% + +% Find repeating groups and elements. +rep_group = strmatch('xx', fliplr(group)); +rep_element = strmatch('xx', fliplr(element)); + +% Number of all attributes is (regular + repeating). +count_rep_classes = length(rep_group) + length(rep_element); +count_expanded = 128*length(rep_group) + 256*length(rep_element); +total_attr = length(vr) + count_expanded - count_rep_classes; + +% Preallocate variables. +values(total_attr).Name = 'TEMP PLACEHOLDER'; +values(total_attr).VR = {''}; +values(total_attr).VM = 0; + +rows = zeros(total_attr, 1); +cols = zeros(total_attr, 1); + +% Assign values for the Repeating groups. + +for p = 1:length(rep_group) + + % Find the offset of the repeating group (e.g., 6000 or 5000). + offset = sscanf([group(rep_group(p), 1:2) '00'], '%x'); + cur_element = sscanf(element(rep_group(p), 1:4), '%x'); + + % Place the repeating values in the struct. + for q = 1:128 + + count = count + 1; + + values(count).Name = [name{rep_group(p)} '_' sprintf('%X', 2*(q-1))]; + values(count).VR = vr{rep_group(p)}; + values(count).VM = vm{rep_group(p)}; + + end + + % Keep track of group and element values for the allocated attributes. + rows((count - 127):(count)) = (offset + 1):2:(offset + 256); + cols((count - 127):(count)) = cur_element + 1; + +end + +% Repeating elements. + +for p = 1:length(rep_element) + + % Find the offset of the repeating element. + offset = sscanf([element(rep_element(p), 1:2) '00'], '%x'); + cur_group = sscanf(group(rep_element(p), 1:4), '%x'); + + % Place the repeating values in the struct. + for q = 1:256 + + count = count + 1; + + values(count).Name = [name{rep_element(p)} '_' sprintf('%X', (q-1))]; + values(count).VR = vr{rep_group(p)}; + values(count).VM = vm{rep_group(p)}; + + end + + % Keep track of group and element values for the allocated attributes. + rows((count - 255):(count)) = cur_group + 1; + cols((count - 255):(count)) = (offset + 1):(offset + 256); + +end + +% Don't process the repeating values twice. +group([rep_group; rep_element],:) = []; +element([rep_group; rep_element],:) = []; + +name([rep_group; rep_element]) = []; +vr([rep_group; rep_element]) = []; +vm([rep_group; rep_element]) = []; + +% +% Store the remainder of the values. +% + +group = hex2dec(group); +element = hex2dec(element); + +count = count + 1; + +rows(count:end) = group + 1; +cols(count:end) = element + 1; + +tags = sparse(rows, cols, 1:total_attr, MAX_GROUP + 1, MAX_ELEMENT + 1); + +[values(count:total_attr).Name] = deal(name{:}); +[values(count:total_attr).VR] = deal(vr{:}); +[values(count:total_attr).VM] = deal(vm{:}); diff --git a/CT/private/dicom_modules.m b/CT/private/dicom_modules.m index a44a540..1a053e5 100644 --- a/CT/private/dicom_modules.m +++ b/CT/private/dicom_modules.m @@ -1,1835 +1,1835 @@ -function module_details = dicom_modules(module_name) -%DICOM_MODULES Repository of DICOM module attributes and details. -% DETAILS = DICOM_MODULES(NAME) returns a structure array of details -% about the module NAME. DETAILS is a structure with fields -% -% - Name The module's name. -% - SpecPart Where this module is defined in the DICOM spec. -% - Attrs The attributes that define a module. A cell array with -% the following meanings attributed to the columns: -% -% (1) Depth in the module. Nonzero indicates a sequence. -% (2) Group -% (3) Element -% (4) Attribute type (see PS 3.3 Sec. 5.4) -% (5) VR mapping -% (6) Enumerated values. If this is present, an attribute's value -% must be one or more of the items in the cell array (or empty if -% type 2 or 2C). -% (7) LISP-like condition if type 1C or 2C. -% -% See also DICOM_IODS, dicom-dict.txt. - -% Copyright 1993-2010 The MathWorks, Inc. -% - -switch (module_name) -case 'FileMetadata' - module_details = build_FileMetadata; -case 'Patient' - module_details = build_Patient; -case 'GeneralStudy' - module_details = build_GeneralStudy; -case 'PatientStudy' - module_details = build_PatientStudy; -case 'GeneralSeries' - module_details = build_GeneralSeries; -case 'FrameOfReference' - module_details = build_FrameOfReference; -case 'GeneralEquipment' - module_details = build_GeneralEquipment; -case 'GeneralImage' - module_details = build_GeneralImage; -case 'ImagePlane' - module_details = build_ImagePlane; -case 'ImagePixel' - module_details = build_ImagePixel; -case 'ContrastBolus' - module_details = build_ContrastBolus; -case 'MRImage' - module_details = build_MRImage; -case 'CTImage' - module_details = build_CTImage; -case 'OverlayPlane' - module_details = build_OverlayPlane; -case 'VOILUT' - module_details = build_VOILUT; -case 'SOPCommon' - module_details = build_SOPCommon; -case 'SCImageEquipment' - module_details = build_SCImageEquipment; -case 'SCImage' - module_details = build_SCImage; -case 'USFrameOfReference' - module_details = build_USFrameOfReference; -case 'PaletteColorLookupTable' - module_details = build_PaletteColorLookupTable; -case 'USRegionCalibration' - module_details = build_USRegionCalibration; -case 'USImage' - module_details = build_USImage; -case 'Cine' - module_details = build_Cine; -case 'MultiFrame' - module_details = build_MultiFrame; -case 'ModalityLUT' - module_details = build_ModalityLUT; -case 'FramePointers' - module_details = build_FramePointers; -case 'Mask' - module_details = build_Mask; -case 'DisplayShutter' - module_details = build_DisplayShutter; -case 'Device' - module_details = build_Device; -case 'Therapy' - module_details = build_Therapy; -case 'XRayImage' - module_details = build_XRayImage; -case 'XRayAcquisition' - module_details = build_XRayAcquisition; -case 'XRayCollimator' - module_details = build_XRayCollimator; -case 'XRayTable' - module_details = build_XRayTable; -case 'XAPositioner' - module_details = build_XAPositioner; -case 'MultiFrameOverlay' - module_details = build_MultiFrameOverlay; -case 'Curve' - module_details = build_Curve; -case 'SCMultiFrameImage' - module_details = build_SCMultiFrameImage; -case 'SCMultiFrameVector' - module_details = build_SCMultiFrameVector; -case 'MRSeries' - module_details = build_MRSeries; -case 'Synchronization' - module_details = build_Synchronization; -case 'EnhancedGeneralEquipment' - module_details = build_EnhancedGeneralEquipment; -case 'ClinicalTrialSeries' - module_details = build_ClinicalTrialSeries; -case 'ClinicalTrialStudy' - module_details = build_ClinicalTrialStudy; -case 'ClinicalTrialSubject' - module_details = build_ClinicalTrialSubject; -case 'EnhancedContrastBolus' - module_details = build_EnhancedContrastBolus; -case 'MultiFrameDimension' - module_details = build_MultiFrameDimension; -case 'CardiacSynchronization' - module_details = build_CardiacSynchronization; -case 'RespiratorySynchronization' - module_details = build_RespiratorySynchronization; -case 'BulkMotionSynchronization' - module_details = build_BulkMotionSynchronization; -case 'SupplementalPaletteColorLUT' - module_details = build_SupplementalPaletteColorLUT; -case 'AcquisitionContext' - module_details = build_AcquisitionContext; -case 'MultiFrameFunctionalGroups' - module_details = build_MultiFrameFunctionalGroups; -case 'Specimen' - module_details = build_Specimen; -case 'MRPulseSequence' - module_details = build_MRPulseSequence; -case 'EnhancedMRImage' - module_details = build_EnhancedMRImage; -case '' - module_details = build_; -otherwise - module_details = []; -end - - - -function details = build_FileMetadata - -details.Name = 'FileMetadata'; -details.SpecPart = 'PS 3.10 Sec. 7.1'; -details.Attrs = { - 0, '0002', '0001', '1', {}, {uint8([0 1])}, {} - 0, '0002', '0002', '1', {}, {}, {} - 0, '0002', '0003', '1', {}, {}, {} - 0, '0002', '0010', '1', {}, {}, {} - 0, '0002', '0012', '1', {}, {}, {} - 0, '0002', '0013', '3', {}, {}, {} - 0, '0002', '0016', '3', {}, {}, {} - 0, '0002', '0100', '3', {}, {}, {} - 0, '0002', '0102', '1C', {}, {}, {'present', '(0002,0100)'} - }; - - -function details = build_Patient - -details.Name = 'Patient'; -details.SpecPart = 'PS 3.3 Sec. C.7.1.1'; -details.Attrs = { - 0, '0010', '0010', '2', {}, {}, {} - 0, '0010', '0020', '2', {}, {}, {} - 0, '0010', '0030', '2', {}, {}, {} - 0, '0010', '0040', '2', {}, {'M' 'F' '0'}, {} - 0, '0008', '1120', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1120)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1120)'} - 0, '0010', '0032', '3', {}, {}, {} - 0, '0010', '1000', '3', {}, {}, {} - 0, '0010', '1001', '3', {}, {}, {} - 0, '0010', '2160', '3', {}, {}, {} - 0, '0010', '4000', '3', {}, {}, {} - }; - - -function details = build_GeneralStudy - -details.Name = 'GeneralStudy'; -details.SpecPart = 'PS 3.3 Sec. C.7.2.1'; -details.Attrs = { - 0, '0020', '000D', '1', {}, {}, {} - 0, '0008', '0020', '2', {}, {}, {} - 0, '0008', '0030', '2', {}, {}, {} - 0, '0008', '0090', '2', {}, {}, {} - 0, '0020', '0010', '2', {}, {}, {} - 0, '0008', '0050', '2', {}, {}, {} - 0, '0008', '1030', '3', {}, {}, {} - 0, '0008', '1048', '3', {}, {}, {} - 0, '0008', '1060', '3', {}, {}, {} - 0, '0008', '1110', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1110)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1110)'} - 0, '0008', '1032', '3', {}, {}, {} - 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. -% 0, % Code sequence macro "No baseline context." - }; - - -function details = build_PatientStudy - -details.Name = 'PatientStudy'; -details.SpecPart = 'PS 3.3 Sec. C.7.2.2'; -details.Attrs = { - 0, '0008', '1080', '3', {}, {}, {} - 0, '0010', '1010', '3', {}, {}, {} - 0, '0010', '1020', '3', {}, {}, {} - 0, '0010', '1030', '3', {}, {}, {} - 0, '0010', '2180', '3', {}, {}, {} - 0, '0010', '21B0', '3', {}, {}, {} - }; - - -function details = build_GeneralSeries - -details.Name = 'GeneralSeries'; -details.SpecPart = 'PS 3.3 Sec. C.7.3.1'; -details.Attrs = { - 0, '0008', '0060', '1', {}, modalityTerms, {} - 0, '0020', '000E', '1', {}, {}, {} - 0, '0020', '0011', '2', {}, {}, {} - 0, '0020', '0060', '2C', {}, {'L', 'R'}, {'and', ... - {'not', {'present', '(0020,0062)'}}, ... - {'present', '(0020,0060)'}} - 0, '0008', '0021', '3', {}, {}, {} - 0, '0008', '0031', '3', {}, {}, {} - 0, '0008', '1050', '3', {}, {}, {} - 0, '0018', '1030', '3', {}, {}, {} - 0, '0008', '103E', '3', {}, {}, {} - 0, '0008', '1070', '3', {}, {}, {} - 0, '0008', '1111', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1111)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1111)'} - 0, '0018', '0015', '3', {}, bodyPartTerms, {} - 0, '0018', '5100', '2C', {}, patientPositionTerms, {'or', ... - {'equal', '(0008,0016)', '1.2.840.10008.5.1.4.1.1.4'} ... - {'equal', '(0008,0016)', '1.2.840.10008.5.1.4.1.1.2'}} - 0, '0028', '0108', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - 0, '0028', '0109', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - 0, '0040', '0275', '3', {}, {}, {} - 1, '0040', '1001', '1C', {}, {}, {'present', '(0040,0275)'} - 1, '0040', '0009', '1C', {}, {}, {'present', '(0040,0275)'} - 1, '0040', '0007', '3', {}, {}, {} - 1, '0040', '0008', '3', {}, {}, {} -% 1, % Code sequence macro "No baseline context." - 0, '0040', '0253', '3', {}, {}, {} - 0, '0040', '0244', '3', {}, {}, {} - 0, '0040', '0245', '3', {}, {}, {} - 0, '0040', '0254', '3', {}, {}, {} - 0, '0040', '0260', '3', {}, {}, {} - 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. -% 0, % Code sequence macro "No baseline context." - }; - - -function details = build_FrameOfReference - -details.Name = 'FrameOfReference'; -details.SpecPart = 'PS 3.3 Sec. C.7.4.1'; -details.Attrs = { - 0, '0020', '0052', '1', {}, {}, {} - 0, '0020', '1040', '2', {}, {}, {} - }; - - -function details = build_GeneralEquipment - -details.Name = 'GeneralEquipment'; -details.SpecPart = 'PS 3.3 Sec. C.7.5.1'; -details.Attrs = { - 0, '0008', '0070', '2', {}, {}, {} - 0, '0008', '0080', '3', {}, {}, {} - 0, '0008', '0081', '3', {}, {}, {} - 0, '0008', '1010', '3', {}, {}, {} - 0, '0008', '1040', '3', {}, {}, {} - 0, '0008', '1090', '3', {}, {}, {} - 0, '0018', '1000', '3', {}, {}, {} - 0, '0018', '1020', '3', {}, {}, {} - 0, '0018', '1050', '3', {}, {}, {} - 0, '0018', '1200', '3', {}, {}, {} - 0, '0018', '1201', '3', {}, {}, {} - 0, '0028', '0120', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - }; - - -function details = build_GeneralImage - -details.Name = 'GeneralImage'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.1'; -details.Attrs = { - 0, '0020', '0013', '2', {}, {}, {} - 0, '0020', '0020', '2C', {}, {}, {'or', {'not', {'present', '(0020,0037)'}} ... - {'not', {'present', '(0020,0032)'}}} - 0, '0008', '0023', '2C', {}, {}, {'true'} - 0, '0008', '0033', '2C', {}, {}, {'true'} - 0, '0008', '0008', '3', {}, {}, {} - 0, '0020', '0012', '3', {}, {}, {} - 0, '0008', '0022', '3', {}, {}, {} - 0, '0008', '0032', '3', {}, {}, {} - 0, '0008', '002A', '3', {}, {}, {} - 0, '0008', '1140', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1140)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1140)'} - 1, '0008', '1160', '3', {}, {}, {} - 0, '0008', '2111', '3', {}, {}, {} - 0, '0008', '2112', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,2112)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,2112)'} - 1, '0008', '1160', '3', {}, {}, {} - 0, '0020', '1002', '3', {}, {}, {} - 0, '0020', '4000', '3', {}, {}, {} - 0, '0028', '0300', '3', {}, {'YES' 'NO'}, {} - 0, '0028', '0301', '3', {}, {'YES' 'NO'}, {} - 0, '0028', '2110', '3', {}, {0 1}, {} - 0, '0028', '2112', '3', {}, {}, {} - }; - - -function details = build_ImagePlane - -details.Name = 'ImagePlane'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.2'; -details.Attrs = { - 0, '0028', '0030', '1', {}, {}, {} - 0, '0020', '0037', '1', {}, {}, {} - 0, '0020', '0032', '1', {}, {}, {} - 0, '0018', '0050', '2', {}, {}, {} - 0, '0020', '1041', '3', {}, {}, {} - }; - - -function details = build_ImagePixel - -details.Name = 'ImagePixel'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.3'; -details.Attrs = { - 0, '0028', '0002', '1', {}, {}, {} - 0, '0028', '0004', '1', {}, {}, {} - 0, '0028', '0010', '1', {}, {}, {} - 0, '0028', '0011', '1', {}, {}, {} - 0, '0028', '0100', '1', {}, {}, {} - 0, '0028', '0101', '1', {}, {}, {} - 0, '0028', '0102', '1', {}, {}, {} - 0, '0028', '0103', '1', {}, {0 1}, {} - 0, '7FE0', '0010', '1', {}, {}, {} - 0, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} - 0, '0028', '0034', '1C', {}, {}, {'present', '(0028,0034)'} - 0, '0028', '0106', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - 0, '0028', '0107', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - 0, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1201', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1202', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1203', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '2000', '3', {}, {}, {} - 0, '0028', '0008', '1C', {}, {}, {'present', '(0028,0008)'} - 0, '0028', '0009', '1C', {}, {}, {'present', '(0028,0009)'} - 0, '0028', '7FE0', '1C', {}, {}, {'present', '(0028,7FE0)'} - 0, '0028', '0121', '1C', {}, {}, {'present', '(0028,0121)'} - }; - - -function details = build_ContrastBolus - -details.Name = 'ContrastBolus'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.4'; -details.Attrs = { - 0, '0018', '0010', '2', {}, {}, {} - 0, '0018', '0012', '3', {}, {}, {} - 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. -% 1, % Code sequence macro "Baseline context id is 12" - 0, '0018', '1040', '3', {}, {}, {} - 0, '0018', '0014', '3', {}, {}, {} -% 1, % Code sequence macro "Baseline context id is 11" - 1, '0018', '002A', '3', {}, {}, {} -% 2, % Code sequence macro "No baseline context." - 0, '0018', '1041', '3', {}, {}, {} - 0, '0018', '1042', '3', {}, {}, {} - 0, '0018', '1043', '3', {}, {}, {} - 0, '0018', '1044', '3', {}, {}, {} - 0, '0018', '1046', '3', {}, {}, {} - 0, '0018', '1047', '3', {}, {}, {} - 0, '0018', '1048', '3', {}, {'IODINE' - 'GADOLINIUM' - 'CARBON DIOXIDE' - 'BARIUM'}, {} - 0, '0018', '1049', '3', {}, {}, {} - }; - - -function details = build_MRImage - -details.Name = 'MRImage'; -details.SpecPart = 'PS 3.3 Sec. C.8.3.1'; -details.Attrs = { - 0, '0008', '0008', '1', {}, {}, {} - 0, '0028', '0002', '1', {}, {1}, {} - 0, '0028', '0004', '1', {}, {'MONOCHROME1' 'MONOCHROME2'}, {} - 0, '0028', '0100', '1', {}, {16}, {} - 0, '0018', '0020', '1', {}, {'SE' 'IR' 'GR' 'EP' 'RM'}, {} - 0, '0018', '0021', '1', {}, {'SK' 'MTC' 'SS' 'TRSS' 'SP' ... - 'MP' 'OSP' 'NONE'}, {} - 0, '0018', '0022', '1', {}, {'PER' 'RG' 'CG' 'PPG' 'FC' ... - 'PFF' 'PFP' 'SP' 'FS' 'CT'}, {} - 0, '0018', '0023', '1', {}, {'2D' '3D'}, {} - 0, '0018', '0080', '2C', {}, {}, {'not', ... - {'and', ... - {'equal', '(0018,0020)', 'EP'}, ... - {'equal', '(0018,0021)', 'SK'}}} - 0, '0018', '0081', '2', {}, {}, {} - 0, '0018', '0091', '2', {}, {}, {} - 0, '0018', '0082', '2C', {}, {}, {'or', ... - {'equal', '(0018,0020)', 'IR'}, ... - {'present', '(0018,0082)'}} - 0, '0018', '1060', '2C', {}, {}, {'or', ... - {'present', '(0018,1060)'}, ... - {'or', ... - {'equal', '(0018,0022)', 'RG'}, ... - {'equal', '(0018,0022)', 'CG'}, ... - {'equal', '(0018,0022)', 'CT'}, ... - {'equal', '(0018,0022)', 'PPG'}}} - 0, '0018', '0024', '3', {}, {}, {} - 0, '0018', '0025', '3', {}, {'Y' 'N'}, {} - 0, '0018', '0083', '3', {}, {}, {} - 0, '0018', '0084', '3', {}, {}, {} - 0, '0018', '0085', '3', {}, {}, {} - 0, '0018', '0086', '3', {}, {}, {} - 0, '0018', '0087', '3', {}, {}, {} - 0, '0018', '0088', '3', {}, {}, {} - 0, '0018', '0089', '3', {}, {}, {} - 0, '0018', '0093', '3', {}, {}, {} - 0, '0018', '0094', '3', {}, {}, {} - 0, '0018', '0095', '3', {}, {}, {} - 0, '0018', '1062', '3', {}, {}, {} - 0, '0018', '1080', '3', {}, {'Y' 'N'}, {} - 0, '0018', '1081', '3', {}, {}, {} - 0, '0018', '1082', '3', {}, {}, {} - 0, '0018', '1083', '3', {}, {}, {} - 0, '0018', '1084', '3', {}, {}, {} - 0, '0018', '1085', '3', {}, {}, {} - 0, '0018', '1086', '3', {}, {}, {} - 0, '0018', '1088', '3', {}, {}, {} - 0, '0018', '1090', '3', {}, {}, {} - 0, '0018', '1094', '3', {}, {}, {} - 0, '0018', '1100', '3', {}, {}, {} - 0, '0018', '1250', '3', {}, {}, {} - 0, '0018', '1251', '3', {}, {}, {} - 0, '0018', '1310', '3', {}, {}, {} - 0, '0018', '1312', '3', {}, {}, {} - 0, '0018', '1314', '3', {}, {}, {} - 0, '0018', '1316', '3', {}, {}, {} - 0, '0018', '1315', '3', {}, {'Y' 'N'}, {} - 0, '0018', '1318', '3', {}, {}, {} - 0, '0020', '0100', '3', {}, {}, {} - 0, '0020', '0105', '3', {}, {}, {} - 0, '0020', '0110', '3', {}, {}, {} - }; - - - -function details = build_OverlayPlane - -details.Name = 'OverlayPlane'; -details.SpecPart = 'PS 3.3 Sec. C.9.2'; -details.Attrs = { - 0, '60XX', '0010', '1', {}, {}, {} - 0, '60XX', '0011', '1', {}, {}, {} - 0, '60XX', '0040', '1', {}, {'G' 'R'}, {} - 0, '60XX', '0050', '1', {}, {}, {} - 0, '60XX', '0100', '1', {}, {1}, {} - 0, '60XX', '0102', '1', {}, {0}, {} - 0, '60XX', '3000', '1C', {}, {}, {'equal', '(60XX,0100)', 1} - 0, '60XX', '0022', '3', {}, {}, {} - 0, '60XX', '0045', '3', {}, {'USER' 'AUTOMATED'}, {} - 0, '60XX', '1500', '3', {}, {}, {} - 0, '60XX', '1301', '3', {}, {}, {} - 0, '60XX', '1302', '3', {}, {}, {} - 0, '60XX', '1303', '3', {}, {}, {} - }; - - -function details = build_VOILUT - -details.Name = 'VOILUT'; -details.SpecPart = 'PS 3.3 Sec. C.11.2'; -details.Attrs = { - 0, '0028', '3010', '3', {}, {}, {} - 1, '0028', '3002', '1C', {}, {}, {'present', '(0028,3010)'} - 1, '0028', '3003', '3', {}, {}, {} - 1, '0028', '3006', '1C', {}, {}, {'present', '(0028,3010)'} - 0, '0028', '1050', '3', {}, {}, {} - 0, '0028', '1051', '1C', {}, {}, {'present', '(0028,1050)'} - }; - - -function details = build_SOPCommon - -details.Name = 'SOPCommon'; -details.SpecPart = 'PS 3.3 Sec. C.12.1'; -details.Attrs = { - 0, '0008', '0016', '1', {}, {}, {} - 0, '0008', '0018', '1', {}, {}, {} - 0, '0008', '0005', '1C', {}, {}, {'present', '(0008,0005)'} - 0, '0008', '0012', '3', {}, {}, {} - 0, '0008', '0013', '3', {}, {}, {} - 0, '0008', '0014', '3', {}, {}, {} - 0, '0008', '0201', '3', {}, {}, {} - 0, '0020', '0013', '3', {}, {}, {} - 0, '0100', '0410', '3', {}, {'NS' 'OR' 'AO' 'AC'}, {} - 0, '0100', '0420', '3', {}, {}, {} - 0, '0100', '0424', '3', {}, {}, {} - 0, '0100', '0426', '3', {}, {}, {} - }; - - -function details = build_CTImage - -details.Name = 'CTImage'; -details.SpecPart = 'PS 3.3 Sec. C.8.2.1'; -details.Attrs = { - 0, '0008', '0008', '1', {}, {}, {} - 0, '0028', '0002', '1', {}, {1}, {} - 0, '0028', '0004', '1', {}, {'MONOCHROME1' 'MONOCHROME2'}, {} - 0, '0028', '0100', '1', {}, {16}, {} - 0, '0028', '0101', '1', {}, {16}, {} - 0, '0028', '0102', '1', {}, {15}, {} - 0, '0028', '1052', '1', {}, {}, {} - 0, '0028', '1053', '1', {}, {}, {} - 0, '0018', '0060', '2', {}, {}, {} - 0, '0020', '0012', '2', {}, {}, {} - 0, '0018', '0022', '3', {}, {}, {} - 0, '0018', '0090', '3', {}, {}, {} - 0, '0018', '1100', '3', {}, {}, {} - 0, '0018', '1110', '3', {}, {}, {} - 0, '0018', '1111', '3', {}, {}, {} - 0, '0018', '1120', '3', {}, {}, {} - 0, '0018', '1130', '3', {}, {}, {} - 0, '0018', '1140', '3', {}, {'CW', 'CC'}, {} - 0, '0018', '1150', '3', {}, {}, {} - 0, '0018', '1151', '3', {}, {}, {} - 0, '0018', '1152', '3', {}, {}, {} - 0, '0018', '1153', '3', {}, {}, {} - 0, '0018', '1160', '3', {}, {}, {} - 0, '0018', '1170', '3', {}, {}, {} - 0, '0018', '1190', '3', {}, {}, {} - 0, '0018', '1210', '3', {}, {}, {} - }; - - - -function details = build_SCImageEquipment - -details.Name = 'SCImageEquipment'; -details.SpecPart = 'PS 3.3 Sec. C.8.6.1'; -details.Attrs = { - 0, '0008', '0064', '1', {}, conversionTerms, {} - 0, '0008', '0060', '3', {}, modalityTerms, {} - 0, '0018', '1010', '3', {}, {}, {} - 0, '0018', '1016', '3', {}, {}, {} - 0, '0018', '1018', '3', {}, {}, {} - 0, '0018', '1019', '3', {}, {}, {} - 0, '0018', '1022', '3', {}, {}, {} - 0, '0018', '1023', '3', {}, {}, {} - }; - - - -function details = build_SCImage - -details.Name = 'SCImage'; -details.SpecPart = 'PS 3.3 Sec. C.8.6.2'; -details.Attrs = { - 0, '0018', '1012', '3', {}, {}, {} - 0, '0018', '1014', '3', {}, {}, {} - }; - - -function details = build_USFrameOfReference - -details.Name = 'USFrameOfReference'; -details.SpecPart = 'PS 3.3 Sec. C.8.5.4'; -details.Attrs = { - 0, '0018', '6018', '1', {}, {}, {} - 0, '0018', '601A', '1', {}, {}, {} - 0, '0018', '601C', '1', {}, {}, {} - 0, '0018', '601E', '1', {}, {}, {} - 0, '0018', '6024', '1', {}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... - 11, 12}, {} - 0, '0018', '6026', '1', {}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... - 11, 12}, {} - 0, '0018', '602C', '1', {}, {}, {} - 0, '0018', '602E', '1', {}, {}, {} - 0, '0018', '6020', '3', {}, {}, {} - 0, '0018', '6022', '3', {}, {}, {} - 0, '0018', '6028', '3', {}, {}, {} - 0, '0018', '602A', '3', {}, {}, {} - }; - - - -function details = build_PaletteColorLookupTable - -details.Name = 'PaletteColorLookupTable'; -details.SpecPart = 'PS 3.3 Sec. C.7.9'; -details.Attrs = { - 0, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - 0, '0028', '1199', '3', {}, {}, {} - 0, '0028', '1201', '1C', {}, {}, {'and', ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}}, ... - {'not', {'present', '(0028,1221)'}}} - 0, '0028', '1202', '1C', {}, {}, {'and', ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}}, ... - {'not', {'present', '(0028,1221)'}}} - 0, '0028', '1203', '1C', {}, {}, {'and', ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}}, ... - {'not', {'present', '(0028,1221)'}}} - 0, '0028', '1221', '1C', {}, {}, {'and', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... - {'present', '(0028,1221)'}} - 0, '0028', '1222', '1C', {}, {}, {'and', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... - {'present', '(0028,1222)'}} - 0, '0028', '1223', '1C', {}, {}, {'and', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... - {'present', '(0028,1223)'}} - }; - - - -function details = build_USRegionCalibration - -details.Name = 'USRegionCalibration'; -details.SpecPart = 'PS 3.3 Sec. C.8.5.5'; -details.Attrs = { - 0, '0018', '6011', '1', {}, {}, {} - 1, '0018', '6018', '1', {}, {}, {} - 1, '0018', '601A', '1', {}, {}, {} - 1, '0018', '601C', '1', {}, {}, {} - 1, '0018', '601E', '1', {}, {}, {} - 1, '0018', '6024', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, {} - 1, '0018', '6026', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, {} - 1, '0018', '602C', '1', {}, {}, {} - 1, '0018', '602E', '1', {}, {}, {} - 1, '0018', '6020', '3', {}, {}, {} - 1, '0018', '6022', '3', {}, {}, {} - 1, '0018', '6028', '3', {}, {}, {} - 1, '0018', '602A', '3', {}, {}, {} - 1, '0018', '6012', '1', {}, {0 1 2 3 4 5}, {} - 1, '0018', '6014', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ... - 15 16 17 18}, {} - 1, '0018', '6016', '1', {}, {}, {} - 1, '0018', '6044', '1C', {}, {0 1 2}, {'present', '(0018,6044)'} - 1, '0018', '6046', '1C', {}, {}, {'equal', '(0018,6044)', 0} - 1, '0018', '6048', '1C', {}, {}, {'equal', '(0018,6044)', 1} - 1, '0018', '604A', '1C', {}, {}, {'equal', '(0018,6044)', 1} - 1, '0018', '604C', '1C', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, ... - {'present', '(0018,6044)'} - 1, '0018', '604E', '1C', {}, {0 1 2 3 4 5 6 7 8 9}, ... - {'present', '(0018,6044)'} - 1, '0018', '6050', '1C', {}, {}, {'or', ... - {'equal', '(0018,6044)', 0}, ... - {'equal', '(0018,6044)', 1}} - 1, '0018', '6052', '1C', {}, {}, {'or', ... - {'equal', '(0018,6044)', 0}, ... - {'equal', '(0018,6044)', 1}} - 1, '0018', '6054', '1C', {}, {}, {'or', ... - {'equal', '(0018,6044)', 0}, ... - {'equal', '(0018,6044)', 1}} - 1, '0018', '6056', '1C', {}, {}, {'equal', '(0018,6044)', 2} - 1, '0018', '6058', '1C', {}, {}, {'equal', '(0018,6044)', 2} - 1, '0018', '605A', '1C', {}, {}, {'equal', '(0018,6044)', 2} - 1, '0018', '6030', '3', {}, {}, {} - 1, '0018', '6032', '3', {}, {}, {} - 1, '0018', '6034', '3', {}, {}, {} - 1, '0018', '6036', '3', {}, {}, {} - 1, '0018', '6038', '3', {}, {}, {} - 1, '0018', '603A', '3', {}, {}, {} - 1, '0018', '603C', '3', {}, {}, {} - 1, '0018', '603E', '3', {}, {}, {} - 1, '0018', '6040', '3', {}, {}, {} - 1, '0018', '6042', '3', {}, {}, {} - }; - - - -function details = build_USImage - -details.Name = 'USImage'; -details.SpecPart = 'PS 3.3 Sec. C.8.5.6'; -details.Attrs = { - 0, '0028', '0002', '1', {}, {1 3}, {} - 0, '0028', '0004', '1', {}, {'MONOCHROME2' 'PALETTE COLOR' 'RGB' ... - 'YBR_FULL' 'YBR_FULL_422' 'YBR_PARTIAL_422'}, {} - 0, '0028', '0100', '1', {}, {8 16}, {} - 0, '0028', '0101', '1', {}, {8 16}, {} - 0, '0028', '0102', '1', {}, {7 15}, {} - 0, '0028', '0006', '1C', {}, {0 1}, {'not', ... - {'equal', '(0028,0002)', 1}} - 0, '0028', '0103', '1', {}, {0}, {} - 0, '0028', '0009', '1C', {}, frameIncrementTerms, {'present', ... - '(0028,0008)'} - 0, '0008', '0008', '2', {}, {}, {} - 0, '0028', '2110', '1C', {}, {0 1}, {'present', '(0028,2110)'} - 0, '0008', '2124', '2C', {}, {}, {'present', '(0008,2124)'} - 0, '0008', '212A', '2C', {}, {}, {'present', '(0008,212A)'} - 0, '0028', '0014', '3', {}, {0 1}, {} - 0, '0008', '1130', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1130)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1130)'} - 0, '0008', '1145', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1145)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1145)'} - 0, '0008', '113A', '3', {}, {}, {} -% 1, % SOP Instance Reference Macro - 1, '0040', 'A170', '1', {}, {}, {} -% 2, % Code Sequence Macro, Defined Context ID is CID 7004 - 0, '0008', '2120', '3', {}, {}, {} - 0, '0040', '000A', '3', {}, {}, {} - 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. -% 1, % Code Sequence Macro, Baseline Context ID is 12002 - 0, '0008', '2122', '3', {}, {}, {} - 0, '0008', '2127', '3', {}, {}, {} - 0, '0008', '2128', '3', {}, {}, {} - 0, '0008', '2129', '3', {}, {}, {} - 0, '0008', '2130', '3', {}, {}, {} - 0, '0008', '2132', '3', {}, {}, {} - 0, '0008', '2218', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline Context ID is 1 - 1, '0008', '2220', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline Context ID is 2 - 0, '0008', '2228', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline Context ID is 1 - 1, '0008', '2230', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline Context ID is 2 - 0, '0008', '2240', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline Context ID is 4 - 1, '0008', '2242', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline Context ID is 5 - 0, '0008', '2244', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline Context ID is 6 - 1, '0008', '2246', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline Context ID is 7 - 0, '0008', '002A', '1C', {}, {}, {'or', ... - {'equal', '(0008,0060)', 'IVUS'}, ... - {'present', '(0008,002A)'}} - 0, '0018', '1060', '3', {}, {}, {} - 0, '0018', '1062', '3', {}, {}, {} - 0, '0018', '1080', '3', {}, {'Y', 'N'}, {} - 0, '0018', '1081', '3', {}, {}, {} - 0, '0018', '1082', '3', {}, {}, {} - 0, '0018', '1088', '3', {}, {}, {} - 0, '0018', '3100', '1C', {}, {'MOTOR_PULLBACK', - 'MANUAL_PULLBACK', - 'SELECTIVE', - 'GATED_PULLBACK'}, {'equal', ... - '(0008,0060)', 'IVUS'} - 0, '0018', '3101', '1C', {}, {}, {'equal', '(0018,3100)', 'MOTOR_PULLBACK'} - 0, '0018', '3102', '1C', {}, {}, {'equal', '(0018,3100)', 'GATED_PULLBACK'} - 0, '0018', '3103', '1C', {}, {}, {'or', ... - {'equal', '(0018,3100)', 'GATED_PULLBACK'}, ... - {'equal', '(0018,3100)', 'MOTOR_PULLBACK'}} - 0, '0018', '3104', '1C', {}, {}, {'or', ... - {'equal', '(0018,3100)', 'GATED_PULLBACK'}, ... - {'equal', '(0018,3100)', 'MOTOR_PULLBACK'}} - 0, '0018', '3105', '3', {}, {}, {} - 0, '0018', '5000', '3', {}, {}, {} - 0, '0018', '5010', '3', {}, {}, {} - 0, '0018', '6031', '3', {}, transducerTerms, {} - 0, '0018', '5012', '3', {}, {}, {} - 0, '0018', '5020', '3', {}, {}, {} - 0, '0018', '5022', '3', {}, {}, {} - 0, '0018', '5024', '3', {}, {}, {} - 0, '0018', '5026', '3', {}, {}, {} - 0, '0018', '5027', '3', {}, {}, {} - 0, '0018', '5028', '3', {}, {}, {} - 0, '0018', '5029', '3', {}, {}, {} - 0, '0018', '5050', '3', {}, {}, {} - 0, '0018', '5210', '3', {}, {}, {} - 0, '0018', '5212', '3', {}, {}, {} - 0, '60xx', '0045', '3', {}, {'ACTIVE 2D/BMODE IMAGE AREA'}, {} - }; - - - -function details = build_Cine - -details.Name = 'Cine'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.5'; -details.Attrs = { - 0, '0018', '1244', '3', {}, {0 1}, {} - 0, '0018', '1063', '1C', {}, {}, {'equal', '(0028,0009)', ... - uint16(sscanf('0018 1063', '%x')')} - 0, '0018', '1065', '1C', {}, {}, {'equal', '(0028,0009)', ... - uint16(sscanf('0018 1065', '%x')')} - 0, '0008', '2142', '3', {}, {}, {} - 0, '0008', '2143', '3', {}, {}, {} - 0, '0008', '2144', '3', {}, {}, {} - 0, '0018', '0040', '3', {}, {}, {} - 0, '0018', '1066', '3', {}, {}, {} - 0, '0018', '1067', '3', {}, {}, {} - 0, '0018', '0072', '3', {}, {}, {} - 0, '0018', '1242', '3', {}, {}, {} - }; - - -function details = build_MultiFrame - -details.Name = 'MultiFrame'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.6'; -details.Attrs = { - 0, '0028', '0008', '1', {}, {}, {} - 0, '0028', '0009', '1', {}, {}, {} - }; - - - -function details = build_ModalityLUT - -details.Name = 'ModalityLUT'; -details.SpecPart = 'PS 3.3 Sec. C.11.1'; -details.Attrs = { - 0, '0028', '3000', '1C', {}, {}, {'not', {'present', '(0028,1052)'}} - 1, '0028', '3002', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, ... - {}, {'present', '(0028,3000)'} - 1, '0028', '3003', '3', {}, {}, {} - 1, '0028', '3004', '1C', {}, {}, {'present', '(0028,3000)'} - 1, '0028', '3006', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, ... - {}, {'present', '(0028,3000)'} - 0, '0028', '1052', '1C', {}, {}, {'not', {'present', '(0028,3000)'}} - 0, '0028', '1053', '1C', {}, {}, {'present', '(0028,1052)'} - 0, '0028', '1054', '1C', {}, {'OD', 'US', 'US'}, ... - {'present', '(0028,1052)'} - }; - - - -function details = build_FramePointers - -details.Name = 'FramePointers'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.9'; -details.Attrs = { - 0, '0028', '6010', '3', {}, {}, {} - 0, '0028', '6020', '3', {}, {}, {} - 0, '0028', '6022', '3', {}, {}, {} - }; - - - -function details = build_Mask - -details.Name = 'Mask'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.10'; -details.Attrs = { - 0, '0028', '6100', '1', {}, {}, {} - 1, '0028', '6101', '1', {}, {'NONE', 'AVG_SUB', 'TID'}, {} - 1, '0028', '6102', '3', {}, {}, {} - 1, '0028', '6110', '1C', {}, {}, {'equal', '(0028,6101)', 'AVG_SUB'} - 1, '0028', '6112', '3', {}, {}, {} - 1, '0028', '6114', '3', {}, {}, {} - 1, '0028', '6120', '2C', {}, {}, {'equal', '(0028,6101)', 'TID'} - 1, '0028', '6190', '3', {}, {}, {} - 0, '0028', '1090', '2', {}, {'SUB', 'NAT'}, {} - }; - - - -function details = build_DisplayShutter - -details.Name = 'DisplayShutter'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.11'; -details.Attrs = { - 0, '0018', '1600', '1', {}, {'RECTANGULAR', 'CIRCULAR', 'POLYGONAL'}, {} - 0, '0018', '1602', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1604', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1606', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1608', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1610', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} - 0, '0018', '1612', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} - 0, '0018', '1620', '1C', {}, {}, {'equal', '(0018,1600)', 'POLYGONAL'} - 0, '0018', '1622', '3', {}, {}, {} - }; - - - -function details = build_Device - -details.Name = 'Device'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.12'; -details.Attrs = { - 0, '0050', '0010', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline context ID is 8 - 1, '0050', '0014', '3', {}, {}, {} - 1, '0050', '0016', '3', {}, {}, {} - 1, '0050', '0017', '2C', {}, {'FR', 'GA', 'IN', 'MM'}, ... - {'present', '(0050,0016)'} - 1, '0050', '0018', '3', {}, {}, {} - 1, '0050', '0019', '3', {}, {}, {} - 1, '0050', '0020', '3', {}, {}, {} - }; - - - -function details = build_Therapy - -details.Name = 'Therapy'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.13'; -details.Attrs = { - 0, '0018', '0036', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline context ID is 9 - 1, '0018', '0038', '2', {}, {'PRE', 'INTERMEDIATE', 'POST', 'NONE'}, {} - 1, '0018', '0029', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline context ID is 10 - 1, '0018', '0035', '3', {}, {}, {} - 1, '0018', '0027', '3', {}, {}, {} - 1, '0054', '0302', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline context ID is 11 - 1, '0018', '0039', '3', {}, {}, {} - }; - - - -function details = build_XRayImage - -details.Name = 'XRayImage'; -details.SpecPart = 'PS 3.3 Sec. C.8.7.1'; -details.Attrs = { - 0, '0028', '0009', '1C', {}, frameIncrementTerms, {'present', ... - '(0028,0008)'} - 0, '0028', '2110', '1C', {}, {0 1}, {'present', '(0028,2110)'} - 0, '0008', '0008', '1', {}, {}, {} - 0, '0028', '1040', '1', {}, {'LIN', 'LOG', 'DISP'}, {} - 0, '0028', '0002', '1', {}, {1}, {} - 0, '0028', '0004', '1', {}, {'MONOCHROME2'}, {} - 0, '0028', '0100', '1', {}, {8 16}, {} - 0, '0028', '0101', '1', {}, {8 10 12 16}, {} - 0, '0028', '0102', '1', {}, {7 9 11 15}, {} - 0, '0028', '0103', '1', {}, {0}, {} - 0, '0018', '0022', '3', {}, scanOptionsTerms, {} - 0, '0008', '2218', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline context ID is 1 - 1, '0008', '2220', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline context ID is 2 - 0, '0008', '2228', '3', {}, {}, {} -% 1, % Code Sequence Macro, Baseline context ID is 1 - 1, '0008', '2230', '3', {}, {}, {} -% 2, % Code Sequence Macro, Baseline context ID is 2 - 0, '0028', '6040', '3', {}, {}, {} - 0, '0008', '1140', '1C', {}, {}, {'present', '(0008,1140)'} - 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1140)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1140)'} - 0, '0008', '2111', '3', {}, {}, {} - 0, '0018', '1400', '3', {}, {}, {} - 0, '0050', '0004', '3', {}, {'YES', 'NO'}, {} - }; - - - -function details = build_XRayAcquisition - -details.Name = 'XRayAcquisition'; -details.SpecPart = 'PS 3.3 Sec. C.8.7.2'; -details.Attrs = { - 0, '0018', '0060', '2', {}, {}, {} - 0, '0018', '1155', '1', {}, {'SC', 'GR'}, {} - 0, '0018', '1151', '2C', {}, {}, {'not', {'present', '(0018,1152)'}} - 0, '0018', '1150', '2C', {}, {}, {'not', {'present', '(0018,1152)'}} - 0, '0018', '1152', '2C', {}, {}, {'not', {'and', ... - {'present', '(0018,1150)'}, ... - {'present', '(0018,1151)'}}} - 0, '0018', '1153', '3', {}, {}, {} - 0, '0018', '1166', '3', {}, {'IN', 'NONE'}, {} - 0, '0018', '1154', '3', {}, {}, {} - 0, '0018', '115A', '3', {}, {'CONTINUOUS', 'PULSED'}, {} - 0, '0018', '1161', '3', {}, {}, {} - 0, '0018', '1162', '3', {}, {}, {} - 0, '0018', '1147', '3', {}, {'ROUND', 'RECTANGLE'}, {} - 0, '0018', '1149', '3', {}, {}, {} - 0, '0018', '1164', '3', {}, {}, {} - 0, '0018', '1190', '3', {}, {}, {} - 0, '0018', '115E', '3', {}, {}, {} - }; - - - -function details = build_XRayCollimator - -details.Name = 'XRayCollimator'; -details.SpecPart = 'PS 3.3 Sec. C.8.7.3'; -details.Attrs = { - 0, '0018', '1700', '1', {}, {'RECTANGULAR', 'CIRCULAR', 'POLYGONAL'}, {} - 0, '0018', '1702', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1704', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1706', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1708', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} - 0, '0018', '1710', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} - 0, '0018', '1712', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} - 0, '0018', '1720', '1C', {}, {}, {'equal', '(0018,1600)', 'POLYGONAL'} - }; - - - -function details = build_XRayTable - -details.Name = 'XRayTable'; -details.SpecPart = 'PS 3.3 Sec. C.8.7.4'; -details.Attrs = { - 0, '0018', '1134', '2', {}, {'STATIC', 'DYNAMIC'}, {} - 0, '0018', '1135', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} - 0, '0018', '1137', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} - 0, '0018', '1136', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} - 0, '0018', '1138', '3', {}, {}, {} - }; - - - -function details = build_XAPositioner - -details.Name = 'XAPositioner'; -details.SpecPart = 'PS 3.3 Sec. C.8.7.5'; -details.Attrs = { - 0, '0018', '1111', '3', {}, {}, {} - 0, '0018', '1110', '3', {}, {}, {} - 0, '0018', '1114', '3', {}, {}, {} - 0, '0018', '1500', '2C', {}, {'STATIC', 'DYNAMIC'}, {'and', ... - {'present', '(0028,0008)'}, ... - {'not', {'equal', '(0028,0008)', 1}}} - 0, '0018', '1510', '2', {}, {}, {} - 0, '0018', '1511', '2', {}, {}, {} - 0, '0018', '1520', '2C', {}, {}, {'equal', '(0018,1500)', 'DYNAMIC'} - 0, '0018', '1521', '2C', {}, {}, {'equal', '(0018,1500)', 'DYNAMIC'} - 0, '0018', '1530', '3', {}, {}, {} - 0, '0018', '1531', '3', {}, {}, {} - }; - - - -function details = build_MultiFrameOverlay - -details.Name = 'MultiFrameOverlay'; -details.SpecPart = 'PS 3.3 Sec. C.9.3'; -details.Attrs = { - 0, '60xx', '0015', '1', {}, {}, {} - 0, '60xx', '0051', '3', {}, {}, {} - }; - - - -function details = build_Curve - -details.Name = 'Curve'; -details.SpecPart = 'PS 3.3 Sec. C.10.2'; -details.Attrs = { - 0, '50xx', '0005', '1', {}, {}, {} - 0, '50xx', '0010', '1', {}, {}, {} - 0, '50xx', '0020', '1', {}, curveTypeTerms, {} - 0, '50xx', '0103', '1', {}, {0 1 2 3 4}, {} - 0, '50xx', '3000', '1', curveVRlut, {}, {} - 0, '50xx', '0022', '3', {}, {}, {} - 0, '50xx', '0030', '3', {}, axisUnitsTerms, {} - 0, '50xx', '0040', '3', {}, {}, {} - 0, '50xx', '0104', '3', {}, {}, {} - 0, '50xx', '0105', '3', {}, {}, {} - 0, '50xx', '0106', '3', {}, {}, {} - 0, '50xx', '0110', '1C', {}, {}, {'present', '(50xx,0110)'} - 0, '50xx', '0112', '1C', curveVRlut, {}, {'present', '(50xx,0110)'} - 0, '50xx', '0114', '1C', curveVRlut, {}, {'present', '(50xx,0110)'} - 0, '50xx', '2500', '3', {}, {}, {} - 0, '50xx', '2600', '3', {}, {}, {} - 1, '0008', '1150', '1C', {}, {}, {'present', '(50xx,2600)'} - 1, '0008', '1155', '1C', {}, {}, {'present', '(50xx,2600)'} - 1, '50xx', '2610', '1C', {}, {}, {'present', '(50xx,2600)'} - }; - - - -function details = build_SCMultiFrameImage - -mono2gray = {'and', ... - {'equal', '(0028,0004)', 'MONOCHROME2'}, ... - {'not', {'equal', '(0028,0101)', 1}}}; - -details.Name = 'SC Multi-Frame Image'; -details.SpecPart = 'PS 3.3 Sec. C.8.6.3'; -details.Attrs = { - 0, '0028', '0301', '1', {}, {}, {} - 0, '2050', '0020', '1C', {}, {'IDENTITY'}, mono2gray - 0, '2010', '015E', '3', {}, {}, {} - 0, '2010', '0160', '3', {}, {}, {} - 0, '0028', '1052', '1C', {}, {}, mono2gray - 0, '0028', '1053', '1C', {}, {}, mono2gray - 0, '0028', '1054', '1C', {}, {}, mono2gray - 0, '0028', '0009', '1C', {}, {}, {'present', '(0028,0008)'} - 0, '0018', '2010', '1C', {}, {}, {'or', ... - {'equal', '(0008,0064)', 'DF'}, ... - {'present', '(0008,0064)'}} - 0, '0018', '2020', '3', {}, {}, {} - 0, '0018', '2030', '3', {}, {}, {} - }; - - - -function details = build_SCMultiFrameVector - -details.Name = 'SC Multi-Frame Vector'; -details.SpecPart = 'PS 3.3 Sec. C.8.6.4'; -details.Attrs = { - 0, '0018', '1065', '1C', {}, {}, {'present', '(0018,1065)'} - 0, '0018', '2001', '1C', {}, {}, {'present', '(0018,2001)'} - 0, '0018', '2002', '1C', {}, {}, {'present', '(0018,2002)'} - 0, '0018', '2003', '1C', {}, {}, {'present', '(0018,2003)'} - 0, '0018', '2004', '1C', {}, {}, {'present', '(0018,2004)'} - 0, '0018', '2005', '1C', {}, {}, {'present', '(0018,2005)'} - 0, '0018', '2006', '1C', {}, {}, {'present', '(0018,2006)'} - }; - - - -function details = build_ClinicalTrialStudy - -details.Name = 'Clinical Trial Study'; -details.SpecPart = 'PS 3.3 Sec. C.7.2.3'; -details.Attrs = { - 0, '0012', '0050', '2', {}, {}, {} - 0, '0012', '0051', '3', {}, {}, {} - 0, '0012', '0083', '3', {}, {}, {} - 1, '0012', '0084', '1C', {}, {}, {'or', ... - {'equal', '(0012,0085)', 'YES'}, ... - {'equal', '(0012,0085)', 'WITHDRAWN'}} - 1, '0012', '0020', '1C', {}, {}, {'equal', '(0012,0084)', 'NAMED_PROTOCOL'} - 1, '0012', '0085', '1', {}, {}, {} - }; - - - -function details = build_ClinicalTrialSubject - -details.Name = 'Clinical Trial Subject'; -details.SpecPart = 'PS 3.3 Sec. C.7.1.3'; -details.Attrs = { - 0, '0012', '0010', '1', {}, {}, {} - 0, '0012', '0020', '1', {}, {}, {} - 0, '0012', '0021', '2', {}, {}, {} - 0, '0012', '0030', '2', {}, {}, {} - 0, '0012', '0031', '2', {}, {}, {} - 0, '0012', '0040', '1C', {}, {}, {'or', ... - {'present', '(0012,0040)'}, ... - {'not', {'present', '(0012,0042)'}}} - 0, '0012', '0042', '1C', {}, {}, {'or', ... - {'present', '(0012,0042)'}, ... - {'not', {'present', '(0012,0040)'}}} - 0, '0012', '0081', '1C', {}, {}, {'present', '(0012,0082)'} - 0, '0012', '0082', '3', {}, {}, {} - }; - - - -function details = build_ClinicalTrialSeries - -details.Name = 'Clinical Trial Series'; -details.SpecPart = 'PS 3.3 Sec. C.7.3.2'; -details.Attrs = { - 0, '0012', '0060', '2', {}, {}, {} - 0, '0012', '0071', '3', {}, {}, {} - 0, '0012', '0072', '3', {}, {}, {} - }; - - - -function details = build_MRSeries - -details.Name = 'MR Series'; -details.SpecPart = 'PS 3.3 Sec. C.8.13.6'; -details.Attrs = { - 0, '0008', '0060', '1', {}, {}, {} - 0, '0008', '1111', '1C', {}, {}, {'present', '(0008,1111)'} - 1, '0008', '1150', '1', {}, {}, {} - 1, '0008', '1155', '1', {}, {}, {} - }; - - - -function details = build_Synchronization - -details.Name = 'Synchronization'; -details.SpecPart = 'PS 3.3 Sec. C.7.4.2'; -details.Attrs = { - 0, '0020', '0200', '1', {}, {}, {} - 0, '0018', '106A', '1', {}, {}, {} - 0, '0018', '1061', '3', {}, {}, {} - 0, '0018', '106C', '1C', {}, {}, {'present', '(0018,106C)'} - 0, '0018', '1800', '1', {}, {}, {} - 0, '0018', '1801', '3', {}, {}, {} - 0, '0018', '1802', '3', {}, {}, {} - 0, '0018', '1803', '3', {}, {}, {} - }; - - - -function details = build_EnhancedGeneralEquipment - -details.Name = 'Enhanced General Equipment'; -details.SpecPart = 'PS 3.3 Sec. C.7.5.2'; -details.Attrs = { - 0, '0008', '0070', '1', {}, {}, {} - 0, '0008', '1090', '1', {}, {}, {} - 0, '0018', '1000', '1', {}, {}, {} - 0, '0018', '1020', '1', {}, {}, {} - }; - - - -function details = build_EnhancedContrastBolus - -details.Name = 'Enhanced Contrast Bolus'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.4b'; -details.Attrs = [ - {0, '0018', '0012', '1', {}, {}, {}} - createCodeSequenceMacro(1) - {1, '0018', '9337', '1', {}, {}, {} - 1, '0018', '0014', '1', {}, {}, {}} - createCodeSequenceMacro(2) - {1, '0018', '9338', '1', {}, {}, {}} - createCodeSequenceMacro(2) - {1, '0018', '1041', '2', {}, {}, {} - 1, '0018', '1049', '2', {}, {}, {} - 1, '0018', '9425', '3', {}, {}, {} - 1, '0018', '9340', '3', {}, {}, {} - 2, '0018', '1041', '2', {}, {}, {} - 2, '0018', '1042', '3', {}, {}, {} - 2, '0018', '1043', '3', {}, {}, {} - 2, '0018', '1046', '3', {}, {}, {} - 2, '0018', '1047', '3', {}, {}, {}} - ]; - - - -function details = build_MultiFrameDimension - -details.Name = 'Multi-frame Dimension'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.17'; -details.Attrs = { - 0, '0020', '9221', '1', {}, {}, {} - 1, '0020', '9164', '1', {}, {}, {} - 0, '0020', '9311', '3', {}, {}, {} - 0, '0020', '9222', '1', {}, {}, {} - 1, '0020', '9165', '1', {}, {}, {} - 1, '0020', '9213', '1C', {}, {}, {'present', '(0020,9213)'} - 1, '0020', '9167', '1C', {}, {}, {'present', '(0020,9167)'} - 1, '0020', '9238', '1C', {}, {}, {'present', '(0020,9238)'} - 1, '0020', '9164', '1C', {}, {}, {'present', '(0020,9164)'} - 1, '0020', '9241', '3', {}, {}, {} - }; - - - -function details = build_CardiacSynchronization - -details.Name = 'Cardiac Synchronization'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.18.1'; -details.Attrs = { - 0, '0018', '9037', '1C', {}, {}, {'present', '(0018,9037)'} - 0, '0018', '9085', '1C', {}, {}, {'present', '(0018,9085)'} - 0, '0018', '9070', '1C', {}, {}, {'present', '(0018,9070)'} - 0, '0018', '9169', '1C', {}, {}, {'present', '(0018,9169)'} - 0, '0018', '1081', '2C', {}, {}, {'present', '(0018,1081)'} - 0, '0018', '1082', '2C', {}, {}, {'present', '(0018,1082)'} - 0, '0018', '1083', '2C', {}, {}, {'present', '(0018,1083)'} - 0, '0018', '1084', '2C', {}, {}, {'present', '(0018,1084)'} - 0, '0018', '1086', '3', {}, {}, {} - 0, '0018', '1064', '1C', {}, {}, {'present', '(0018,1064)'} - }; - - - -function details = build_RespiratorySynchronization - -details.Name = 'Respiratory Synchronization'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.18.2'; -details.Attrs = { - 0, '0018', '9170', '1C', {}, {}, {'present', '(0018,9170)'} - 0, '0018', '9171', '1C', {}, {}, {'present', '(0018,9171)'} - 0, '0020', '9256', '1C', {}, {}, {'present', '(0020,9256)'} - 0, '0020', '9250', '1C', {}, {}, {'present', '(0020,9250)'} - }; - - - -function details = build_BulkMotionSynchronization - -details.Name = 'Bulk Motion Synchronization'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.18.2'; -details.Attrs = { - 0, '0018', '9172', '1C', {}, {}, {'present', '(0018,9172)'} - 0, '0018', '9173', '1C', {}, {}, {'present', '(0018,9173)'} - }; - - - -function details = build_SupplementalPaletteColorLUT - -details.Name = 'Supplemental Palette Color Lookup Table'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.19'; -details.Attrs = { - 0, '0028', '1101', '1', {}, {}, {} - 0, '0028', '1102', '1', {}, {}, {} - 0, '0028', '1103', '1', {}, {}, {} - 0, '0028', '1201', '1', {}, {}, {} - 0, '0028', '1202', '1', {}, {}, {} - 0, '0028', '1203', '1', {}, {}, {} - }; - - - -function details = build_AcquisitionContext - -details.Name = 'Acquisition Context'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.14'; -details.Attrs = [... - {0, '0040', '0555', '2', {}, {}, {} - 1, '0040', 'A040', '3', {}, {}, {} - 1, '0040', 'A043', '1', {}, {}, {}} - createCodeSequenceMacro(2) - {1, '0040', 'A136', '1C', {}, {}, {'present', '(0040,A136)'} - 1, '0040', 'A30A', '1C', {}, {}, {'present', '(0040,A30A)'} - 1, '0040', '08EA', '1C', {}, {}, {'present', '(0040,A30A)'}} - createCodeSequenceMacro(2) - {1, '0040', 'A121', '1C', {}, {}, {'present', '(0040,A121)'} - 1, '0040', 'A122', '1C', {}, {}, {'present', '(0040,A122)'} - 1, '0040', 'A123', '1C', {}, {}, {'present', '(0040,A123)'} - 1, '0040', 'A160', '1C', {}, {}, {'present', '(0040,A160)'} - 1, '0040', 'A168', '1C', {}, {}, {'present', '(0040,A168)'}} - createCodeSequenceMacro(2) - {0, '0040', '0556', '3', {}, {}, {}} - ]; - - - -function details = build_MultiFrameFunctionalGroups - -details.Name = 'Multi-frame Functional Groups'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.16'; -details.Attrs = [... - {0, '5200', '9229', '2', {}, {}, {}} - createPixelMeasuresMacro(0) - createFrameContentMacro(0) - % Insert 0 or 1 functional group macros here. - {0, '5200', '9230', '1', {}, {}, {} - % Insert "number of frames" functional group macros here. - 0, '0020', '0013', '1', {}, {}, {} - 0, '0008', '0023', '1', {}, {}, {} - 0, '0008', '0033', '1', {}, {}, {} - 0, '0028', '0008', '1', {}, {}, {} - 0, '0020', '9228', '1C', {}, {}, {'present', '(0020,9161)'} - 0, '0028', '6010', '3', {}, {}, {} - 0, '0020', '9161', '1C', {}, {}, {'present', '(0020,9161)'} - 0, '0020', '0242', '1C', {}, {}, {'present', '(0020,9161)'} - 0, '0020', '9162', '1C', {}, {}, {'present', '(0020,9161)'} - 0, '0020', '9163', '3', {}, {}, {}} - ]; - - - -function details = build_Specimen - -details.Name = 'Specimen'; -details.SpecPart = 'PS 3.3 Sec. C.7.6.22'; -details.Attrs = [ - {0, '0040', '0512', '1', {}, {}, {} - 0, '0040', '0513', '2', {}, {}, {}} - createHL7v2HierarchicDesignator(1) - {0, '0040', '0515', '3', {}, {}, {} - 1, '0040', '0512', '1', {}, {}, {} - 1, '0040', '0513', '2', {}, {}, {}} - createHL7v2HierarchicDesignator(2) - {0, '0040', '0518', '2', {}, {}, {}} - createCodeSequenceMacro(1) - {0, '0040', '051A', '3', {}, {}, {} - 0, '0040', '0520', '3', {}, {}, {} - 1, '0050', '0012', '1', {}, {}, {}} - createCodeSequenceMacro(2) - {1, '0008', '0070', '3', {}, {}, {} - 1, '0008', '1090', '3', {}, {}, {} - 1, '0050', '001B', '3', {}, {}, {} - 1, '0050', '001C', '3', {}, {}, {} - 1, '0050', '0015', '3', {}, {}, {} - 1, '0050', '001D', '3', {}, {}, {} - 1, '0050', '0013', '3', {}, {}, {} - 1, '0050', '001A', '3', {}, {}, {} - 1, '0050', '001E', '3', {}, {}, {} - 0, '0040', '0560', '1', {}, {}, {} - 1, '0040', '0551', '1', {}, {}, {} - 1, '0040', '0562', '2', {}, {}, {}} - createHL7v2HierarchicDesignator(2) - {1, '0040', '0554', '1', {}, {}, {} - 1, '0040', '059A', '3', {}, {}, {}} - createCodeSequenceMacro(2) - {1, '0040', '0600', '3', {}, {}, {} - 1, '0040', '0602', '3', {}, {}, {} - 1, '0040', '0610', '2', {}, {}, {} - 2, '0040', '0612', '1', {}, {}, {}} - createContentItemMacro(3) - {1, '0008', '2228', '3', {}, {}, {}} % Primary Anatomic Structure Macro - createCodeSequenceMacro(2) % Primary Anatomic Structure Macro - {2, '0008', '2230', '3', {}, {}, {}} % Primary Anatomic Structure Macro - createCodeSequenceMacro(3) % Primary Anatomic Structure Macro - {1, '0040', '0620', '1C', {}, {}, {'present', '(0040,0620)'}} - createContentItemMacro(2) - ]; - - - -function details = build_MRPulseSequence - -details.Name = 'MR Pulse Sequence'; -details.SpecPart = 'PS 3.3 Sec. C.8.13.4'; -details.Attrs = { - 0, '0018', '9005', '1C', {}, {}, {'present', '(0018,9005)'} - 0, '0018', '0023', '1C', {}, {}, {'present', '(0018,9023)'} - 0, '0018', '9008', '1C', {}, {}, {'present', '(0018,9028)'} - 0, '0018', '9011', '1C', {}, {}, {'present', '(0018,9011)'} - 0, '0018', '9012', '1C', {}, {}, {'present', '(0018,9012)'} - 0, '0018', '9014', '1C', {}, {}, {'present', '(0018,9014)'} - 0, '0018', '9015', '1C', {}, {}, {'present', '(0018,9015)'} - 0, '0018', '9017', '1C', {}, {}, {'present', '(0018,9017)'} - 0, '0018', '9018', '1C', {}, {}, {'present', '(0018,9018)'} - 0, '0018', '9024', '1C', {}, {}, {'present', '(0018,9024)'} - 0, '0018', '9025', '1C', {}, {}, {'present', '(0018,9025)'} - 0, '0018', '9029', '1C', {}, {}, {'present', '(0018,9029)'} - 0, '0018', '9032', '1C', {}, {}, {'present', '(0018,9032)'} - 0, '0018', '9034', '1C', {}, {}, {'present', '(0018,9034)'} - 0, '0018', '9033', '1C', {}, {}, {'present', '(0018,9033)'} - 0, '0018', '9094', '1C', {}, {}, {'present', '(0018,9094)'} - 0, '0018', '9093', '1C', {}, {}, {'present', '(0018,9093)'} - }; - - - -function details = build_EnhancedMRImage - -details.Name = 'Enhanced MR Image'; -details.SpecPart = 'PS 3.3 Sec. C.8.13.1'; -details.Attrs = [ - createMRImageAndSpectroscopyInstance(0) - {0, '0008', '0008', '1', {}, {}, {}} - createMRImageDescription(0) - {0, '0028', '0002', '1', {}, {}, {} - 0, '0028', '0004', '1', {}, {}, {} - 0, '0028', '0100', '1', {}, {}, {} - 0, '0028', '0101', '1', {}, {}, {} - 0, '0028', '0102', '1', {}, {}, {} - 0, '0028', '0103', '1', {}, {}, {} - 0, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} - 0, '0018', '0088', '3', {}, {}, {} - 0, '0028', '0301', '1', {}, {'NO'}, {} - 0, '0028', '2110', '1', {}, {}, {} - 0, '0028', '2112', '1C', {}, {}, {'equal', '(0028,2110)', '01'} - 0, '0028', '2114', '1C', {}, {}, {'equal', '(0028,2110)', '01'} - 0, '2050', '0020', '1C', {}, {}, {'equal', '(0028,0004)', 'MONOCHROME2'} - 0, '0088', '0200', '3', {}, {}, {}} - createImagePixelMacro(1) - ]; - - - -function macro = createImagePixelMacro(level) - -macro = { - level, '0028', '0002', '1', {}, {}, {} - level, '0028', '0004', '1', {}, {}, {} - level, '0028', '0010', '1', {}, {}, {} - level, '0028', '0011', '1', {}, {}, {} - level, '0028', '0100', '1', {}, {}, {} - level, '0028', '0101', '1', {}, {}, {} - level, '0028', '0102', '1', {}, {}, {} - level, '0028', '0103', '1', {}, {0 1}, {} - level, '7FE0', '0010', '1', {}, {}, {} - level, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} - level, '0028', '0034', '1C', {}, {}, {'present', '(0028,0034)'} - level, '0028', '0106', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - level, '0028', '0107', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} - level, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... - {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '1201', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '1202', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '1203', '1C', {}, {}, {'or', ... - {'equal', '(0028,0004)', 'PALETTE COLOR'} ... - {'equal', '(0028,0004)', 'ARGB'}} - level, '0028', '2000', '3', {}, {}, {} - level, '0028', '0008', '1C', {}, {}, {'present', '(0028,0008)'} - level, '0028', '0009', '1C', {}, {}, {'present', '(0028,0009)'}}; - -function macro = createMRImageAndSpectroscopyInstance(level) - -% See PS 3.3 Table C.8-81. -macro = [... - {level, '0020', '0012', '3', {}, {}, {} - level, '0008', '002A', '1C', {}, {}, {'present', '(0008,002A)'} - level, '0018', '9073', '1C', {}, {}, {'present', '(0018,9073)'} - level, '0008', '9121', '3', {}, {}, {}} - createHierarchicalSOPInstanceRefMacro(level+1) - {level, '0008', '113A', '3', {}, {}, {}} - createHierarchicalSOPInstanceRefMacro(level+1) - {level, '0008', '9092', '1C', {}, {}, {'present', '(0008,1140)'}} - createHierarchicalSOPInstanceRefMacro(level+1) - {level, '0008', '9154', '1C', {}, {}, {'present', '(0008,2112)'}} - createHierarchicalSOPInstanceRefMacro(level+1) - {level, '0008', '9237', '1C', {}, {}, {'present', '(0008,9237)'}} - createHierarchicalSOPInstanceRefMacro(level+1) - {level, '0018', '9004', '1', {}, {}, {} - level, '0018', '9100', '1C', {}, {}, {'present', '(0018,9100)'} - level, '0018', '9064', '1C', {}, {}, {'present', '(0018,9064)'} - level, '0018', '0087', '1C', {}, {}, {'present', '(0018,0087)'} - level, '0018', '9174', '1', {}, {}, {} - level, '0018', '9175', '3', {}, {}, {} - level, '0020', '4000', '3', {}, {}, {}} - ]; - - - -function macro = createCodeSequenceMacro(level) - -contextGroupCondition = {'and', ... - {'present', '(0008,010B)'}, ... - {'equals', '(0008,010B)', 'Y'}}; - -CodeSequenceMacro = {... - '0008', '0100', '1', {}, {}, {} - '0008', '0102', '1', {}, {}, {} - '0008', '0103', '1C', {}, {}, {'present', '(0008,0103)'} - '0008', '0104', '1', {}, {}, {} - '0008', '010F', '3', {}, {}, {} - '0008', '0117', '3', {}, {}, {} - '0008', '0105', '1C', {}, {}, {'present', '(0008,010F)'} - '0008', '0106', '1C', {}, {}, {'present', '(0008,010F)'} - '0008', '010B', '3', {}, {}, {} - '0008', '0107', '1C', {}, {}, contextGroupCondition - '0008', '010D', '1C', {}, {}, contextGroupCondition - }; - -n = size(CodeSequenceMacro, 1); -macro = [repmat({level}, [n 1]), CodeSequenceMacro]; - - - -function macro = createHL7v2HierarchicDesignator(level) - -designatorMacro = {... - '0040', '0031', '1C', {}, {}, {'not', {'present', '(0040,0032)'}} - '0040', '0032', '1C', {}, {}, {'not', {'present', '(0040,0031)'}} - '0040', '0033', '1C', {}, {}, {'present', '(0040,0032)'} - }; - -n = size(designatorMacro, 1); -macro = [repmat({level}, [n 1]), designatorMacro]; - - - -function macro = createContentItemMacro(level) - -ContentItemMacro = [... - {level, '0040', 'A040', '1', {}, {}, {} - level, '0040', 'A043', '1', {}, {}, {}} - createCodeSequenceMacro(level+1) - {level, '0040', 'A120', '1C', {}, {}, {'equal', '(0040,A040)', 'DATETIME'} - level, '0040', 'A121', '1C', {}, {}, {'equal', '(0040,A040)', 'DATE'} - level, '0040', 'A122', '1C', {}, {}, {'equal', '(0040,A040)', 'TIME'} - level, '0040', 'A123', '1C', {}, {}, {'equal', '(0040,A040)', 'PNAME'} - level, '0040', 'A124', '1C', {}, {}, {'equal', '(0040,A040)', 'UIDREF'} - level, '0040', 'A160', '1C', {}, {}, {'equal', '(0040,A040)', 'TEXT'} - level, '0040', 'A168', '1C', {}, {}, {'equal', '(0040,A040)', 'CODE'}} - createCodeSequenceMacro(level+1) - {level, '0040', 'A30A', '1C', {}, {}, {'equal', '(0040,A040)', 'NUMERIC'} - level, '0040', '08EA', '1C', {}, {}, {'equal', '(0040,A040)', 'NUMERIC'}} - createCodeSequenceMacro(level+1) - {level, '0040', '1199', '1C', {}, {}, {'or', ... - {'equal', '(0040,A040)', 'COMPOSITE'}, ... - {'equal', '(0040,A040)', 'IMAGE'}} - level+1, '0008', '1150', '1', {}, {}, {} - level+1, '0008', '1155', '1', {}, {}, {} - level+1, '0008', '1160', '1C', {}, {}, {'present', '0008', '1160'} - level+1, '0062', '000B', '1C', {}, {}, {'present', '0062', '000B'}} - ]; - - - -function macro = createHierarchicalSOPInstanceRefMacro(level) - -% See PS 3.3 Table C.17-3 -macro = [... - {level, '0020', '000D', '1', {}, {}, {} - level, '0008', '1115', '1', {}, {}, {}} - createHierarchicalSeriesRefMacro(level+1) - ]; - - - -function macro = createHierarchicalSeriesRefMacro(level) - -% See PS 3.3 Table C.17-3a -macro = [... - {level, '0020', '000E', '1', {}, {}, {} - level, '0008', '0054', '3', {}, {}, {} - level, '0040', 'E011', '3', {}, {}, {} - level, '0088', '0130', '3', {}, {}, {} - level, '0088', '0140', '3', {}, {}, {} - level, '0008', '1199', '1', {}, {}, {}} - createSOPInstanceRefMacro(level+1) - {level+1, '0040', 'A170', '3', {}, {}, {}} - createCodeSequenceMacro(level+2) - {level+1, '0400', '0402', '3', {}, {}, {} - level+2, '0400', '0100', '1', {}, {}, {} - level+2, '0400', '0120', '1', {}, {}, {} - level+1, '0400', '0403', '3', {}, {}, {} - level+2, '0400', '0010', '1', {}, {}, {} - level+2, '0400', '0015', '1', {}, {}, {} - level+2, '0400', '0020', '1', {}, {}, {} - level+2, '0400', '0404', '1', {}, {}, {}} - ]; - - - -function macro = createSOPInstanceRefMacro(level) - -% See PS 3.3 Table 10-11 -macro = {... - level, '0008', '1150', '1', {}, {}, {} - level, '0008', '1155', '1', {}, {}, {} - }; - - - -function macro = createMRImageDescription(level) - -% See PS 3.3 Table C.8-82 -macro = {... - level, '0008', '9208', '1', {}, {}, {} - level, '0008', '9209', '1', {}, {}, {} - }; - - -function macro = createPixelMeasuresMacro(level) - -% See PS 3.3 Sec. C.7.6.16.2.1 -macro = {... - level, '0028', '9110', '1C', {}, {}, {'present', '(0028,9110)'} - level+1, '0028', '0030', '1C', {}, {}, {'present', '(0028,0030)'} - level+1, '0018', '0050', '1C', {}, {}, {'present', '(0018,0050)'} - }; - - -function macro = createFrameContentMacro(level) - -% See PS 3.3 Sec. C.7.6.16.2.2 -n = level+1; -macro = {... - level, '0028', '9111', '1C', {}, {}, {'present', '(0028,9111)'} - n, '0020', '9156', '3', {}, {}, {} - n, '0018', '9151', '1C', {}, {}, {'present', '(0018,9151)'} - n, '0018', '9074', '1C', {}, {}, {'present', '(0018,9074)'} - n, '0018', '9220', '1C', {}, {}, {'present', '(0018,9220)'} - n, '0018', '9236', '3', {}, {}, {} - n, '0018', '9214', '3', {}, {}, {} - n, '0020', '9157', '1C', {}, {}, {'present', '(0020,9157)'} - n, '0020', '9128', '1C', {}, {}, {'present', '(0020,9128)'} - n, '0020', '9056', '1C', {}, {}, {'present', '(0020,9056)'} - n, '0020', '9057', '1C', {}, {}, {'present', '(0020,9056)'} - n, '0020', '9158', '3', {}, {}, {} - n, '0020', '9453', '3', {}, {}, {} - }; - - -function macro = createPlanePositionPatientMacro(level) - -% See PS 3.3 Sec. C.7.6.16.2.3 -macro = {... - level, '0020', '9113', '1C', {}, {}, {'present', '(0020,9113)'} - level+1, '0020', '0032', '1C', {}, {}, {'present', '(0020,0032)'} - }; - - - -function terms = modalityTerms -%MODALITYDEFINEDTERMS Modality defined terms -% -% See PS 3.3 Sec. C.7.3.1.1.1 - -terms = {'CR', 'MR', 'US', 'BI', 'DD', 'ES', 'MA', 'PT', 'ST', 'XA', ... - 'RTIMAGE', 'RTSTRUCT', 'RTRECORD', 'DX', 'IO', 'GM', 'XC', 'AU', ... - 'EPS', 'SR', 'CT', 'NM', 'OT', 'CD', 'DG', 'LS', 'MS', 'RG', 'TG', ... - 'RF', 'RTDOSE', 'RTPLAN', 'HC', 'MG', 'PX', 'SM', 'PR', 'ECG', ... - 'HD'}; - - - -function terms = bodyPartTerms -%BODYPARTTERMS Body part defined terms -% -% See PS 3.3 Sec. C.7.3.1 - -terms = {'SKULL', 'CSPINE', 'TSPINE', 'LSPINE', 'SSPINE', 'COCCYX', 'CHEST', ... - 'CLAVICLE', 'BREAST', 'ABDOMEN', 'PELVIS', 'HIP', 'SHOULDER', ... - 'ELBOX', 'KNEE', 'ANKLE', 'HAND', 'FOOT', 'EXTREMITY', 'HEAD', ... - 'HEART', 'NECK', 'LEG', 'ARM', 'JAW'}; - - - -function terms = patientPositionTerms -%PATIENTPOSITIONTERMS Patient position defined terms -% -% See PS 3.3 Sec. C.7.3.1.1.2 - -terms = {'HFP', 'HFS', 'HFDR', 'HFDL', 'FFDR', 'FFDL', 'FFP', 'FFS'}; - - - -function terms = conversionTerms -%CONVERSIONTERMS Secondary Capture conversion type defined terms -% -% See PS 3.3 Sec. C.8.6.1 - -terms = {'DV', 'DI', 'DF', 'WSD', 'SD', 'SI', 'DRW', 'SYN'}; - - - -function terms = transducerTerms -%TRANSDUCERTERMS Transducer type defined terms -% -% See PS 3.3 Sec. C.8.5.6 - -terms = {'SECTOR_PHASED', 'SECTOR_MECH', 'SECTOR_ANNULAR', 'LINEAR', ... - 'CURVED LINEAR', 'SINGLE CRYSTAL', 'SPLIT XTAL CWD', 'IV_PHASED', ... - 'IV_ROT XTAL', 'IV_ROT MIRROR', 'ENDOCAV_PA', 'ENDOCAV_MECH', ... - 'ENDOCAV_CLA', 'ENDOCAV_AA', 'ENDOCAV_LINEAR', 'VECTOR_PHASED'}; - - - -function tmers = frameIncrementTerms -%FRAMEINCREMENTTERMS Frame increment pointer defined values -% -% See PS 3.3 Sec. C.8.5.6.1.4 - -terms = {uint16(sscanf('0018 1063', '%x')'), ... - uint16(sscanf('0018 1065', '%x')')}; - - - -function terms = scanOptionsTerms -%SCANOPTIONSTERMS Scan Options defined terms -% -% See PS 3.3 Sec. C.8.7.1.1.4 - -terms = {'EKG', 'PHY', 'TOMO', 'CHASE', 'STEP', 'ROTA'}; - - - -function terms = curveTypeTerms -%CURVETYPETERMS Type of Data for curves defined terms -% -% See PS 3.3 Sec. C.10.2.1.1 - -terms = {'TAC', 'PROF', 'HIST', 'ROI', 'TABL', 'FILT', 'POLY', 'ECG', ... - 'PRESSURE', 'FLOW', 'PHYSIO', 'RESP'}; - - - -function terms = axisUnitsTerms -%AXISUNITSTERMS Axis Units defined terms -% -% See PS 3.3 Sec. C.10.2.1.3 - -terms = {'SEC', 'CNTS', 'MM', 'PIXL', 'NONE', 'BPM', 'CM', 'CMS', 'CM2', ... - 'CM2S', 'CM3', 'CM3S', 'CMS2', 'DB', 'DBS', 'DEG', 'GM', 'GMM2', ... - 'HZ', 'IN', 'KG', 'LMIN', 'LMINM2', 'M2', 'MS2', 'MLM2', 'MILS', ... - 'MILV', 'MMHG', 'PCNT', 'LB'}; - - - -function lut = curveVRlut -%CURVEVRLUT VR lookup table for curve-related data - -lut = {'(50xx,0103)', {0, 'US'}, {1, 'SS'}, {2, 'FL'}, {3, 'FD'}, {4, 'SL'}}; +function module_details = dicom_modules(module_name) +%DICOM_MODULES Repository of DICOM module attributes and details. +% DETAILS = DICOM_MODULES(NAME) returns a structure array of details +% about the module NAME. DETAILS is a structure with fields +% +% - Name The module's name. +% - SpecPart Where this module is defined in the DICOM spec. +% - Attrs The attributes that define a module. A cell array with +% the following meanings attributed to the columns: +% +% (1) Depth in the module. Nonzero indicates a sequence. +% (2) Group +% (3) Element +% (4) Attribute type (see PS 3.3 Sec. 5.4) +% (5) VR mapping +% (6) Enumerated values. If this is present, an attribute's value +% must be one or more of the items in the cell array (or empty if +% type 2 or 2C). +% (7) LISP-like condition if type 1C or 2C. +% +% See also DICOM_IODS, dicom-dict.txt. + +% Copyright 1993-2010 The MathWorks, Inc. +% + +switch (module_name) +case 'FileMetadata' + module_details = build_FileMetadata; +case 'Patient' + module_details = build_Patient; +case 'GeneralStudy' + module_details = build_GeneralStudy; +case 'PatientStudy' + module_details = build_PatientStudy; +case 'GeneralSeries' + module_details = build_GeneralSeries; +case 'FrameOfReference' + module_details = build_FrameOfReference; +case 'GeneralEquipment' + module_details = build_GeneralEquipment; +case 'GeneralImage' + module_details = build_GeneralImage; +case 'ImagePlane' + module_details = build_ImagePlane; +case 'ImagePixel' + module_details = build_ImagePixel; +case 'ContrastBolus' + module_details = build_ContrastBolus; +case 'MRImage' + module_details = build_MRImage; +case 'CTImage' + module_details = build_CTImage; +case 'OverlayPlane' + module_details = build_OverlayPlane; +case 'VOILUT' + module_details = build_VOILUT; +case 'SOPCommon' + module_details = build_SOPCommon; +case 'SCImageEquipment' + module_details = build_SCImageEquipment; +case 'SCImage' + module_details = build_SCImage; +case 'USFrameOfReference' + module_details = build_USFrameOfReference; +case 'PaletteColorLookupTable' + module_details = build_PaletteColorLookupTable; +case 'USRegionCalibration' + module_details = build_USRegionCalibration; +case 'USImage' + module_details = build_USImage; +case 'Cine' + module_details = build_Cine; +case 'MultiFrame' + module_details = build_MultiFrame; +case 'ModalityLUT' + module_details = build_ModalityLUT; +case 'FramePointers' + module_details = build_FramePointers; +case 'Mask' + module_details = build_Mask; +case 'DisplayShutter' + module_details = build_DisplayShutter; +case 'Device' + module_details = build_Device; +case 'Therapy' + module_details = build_Therapy; +case 'XRayImage' + module_details = build_XRayImage; +case 'XRayAcquisition' + module_details = build_XRayAcquisition; +case 'XRayCollimator' + module_details = build_XRayCollimator; +case 'XRayTable' + module_details = build_XRayTable; +case 'XAPositioner' + module_details = build_XAPositioner; +case 'MultiFrameOverlay' + module_details = build_MultiFrameOverlay; +case 'Curve' + module_details = build_Curve; +case 'SCMultiFrameImage' + module_details = build_SCMultiFrameImage; +case 'SCMultiFrameVector' + module_details = build_SCMultiFrameVector; +case 'MRSeries' + module_details = build_MRSeries; +case 'Synchronization' + module_details = build_Synchronization; +case 'EnhancedGeneralEquipment' + module_details = build_EnhancedGeneralEquipment; +case 'ClinicalTrialSeries' + module_details = build_ClinicalTrialSeries; +case 'ClinicalTrialStudy' + module_details = build_ClinicalTrialStudy; +case 'ClinicalTrialSubject' + module_details = build_ClinicalTrialSubject; +case 'EnhancedContrastBolus' + module_details = build_EnhancedContrastBolus; +case 'MultiFrameDimension' + module_details = build_MultiFrameDimension; +case 'CardiacSynchronization' + module_details = build_CardiacSynchronization; +case 'RespiratorySynchronization' + module_details = build_RespiratorySynchronization; +case 'BulkMotionSynchronization' + module_details = build_BulkMotionSynchronization; +case 'SupplementalPaletteColorLUT' + module_details = build_SupplementalPaletteColorLUT; +case 'AcquisitionContext' + module_details = build_AcquisitionContext; +case 'MultiFrameFunctionalGroups' + module_details = build_MultiFrameFunctionalGroups; +case 'Specimen' + module_details = build_Specimen; +case 'MRPulseSequence' + module_details = build_MRPulseSequence; +case 'EnhancedMRImage' + module_details = build_EnhancedMRImage; +case '' + module_details = build_; +otherwise + module_details = []; +end + + + +function details = build_FileMetadata + +details.Name = 'FileMetadata'; +details.SpecPart = 'PS 3.10 Sec. 7.1'; +details.Attrs = { + 0, '0002', '0001', '1', {}, {uint8([0 1])}, {} + 0, '0002', '0002', '1', {}, {}, {} + 0, '0002', '0003', '1', {}, {}, {} + 0, '0002', '0010', '1', {}, {}, {} + 0, '0002', '0012', '1', {}, {}, {} + 0, '0002', '0013', '3', {}, {}, {} + 0, '0002', '0016', '3', {}, {}, {} + 0, '0002', '0100', '3', {}, {}, {} + 0, '0002', '0102', '1C', {}, {}, {'present', '(0002,0100)'} + }; + + +function details = build_Patient + +details.Name = 'Patient'; +details.SpecPart = 'PS 3.3 Sec. C.7.1.1'; +details.Attrs = { + 0, '0010', '0010', '2', {}, {}, {} + 0, '0010', '0020', '2', {}, {}, {} + 0, '0010', '0030', '2', {}, {}, {} + 0, '0010', '0040', '2', {}, {'M' 'F' '0'}, {} + 0, '0008', '1120', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1120)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1120)'} + 0, '0010', '0032', '3', {}, {}, {} + 0, '0010', '1000', '3', {}, {}, {} + 0, '0010', '1001', '3', {}, {}, {} + 0, '0010', '2160', '3', {}, {}, {} + 0, '0010', '4000', '3', {}, {}, {} + }; + + +function details = build_GeneralStudy + +details.Name = 'GeneralStudy'; +details.SpecPart = 'PS 3.3 Sec. C.7.2.1'; +details.Attrs = { + 0, '0020', '000D', '1', {}, {}, {} + 0, '0008', '0020', '2', {}, {}, {} + 0, '0008', '0030', '2', {}, {}, {} + 0, '0008', '0090', '2', {}, {}, {} + 0, '0020', '0010', '2', {}, {}, {} + 0, '0008', '0050', '2', {}, {}, {} + 0, '0008', '1030', '3', {}, {}, {} + 0, '0008', '1048', '3', {}, {}, {} + 0, '0008', '1060', '3', {}, {}, {} + 0, '0008', '1110', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1110)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1110)'} + 0, '0008', '1032', '3', {}, {}, {} + 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. +% 0, % Code sequence macro "No baseline context." + }; + + +function details = build_PatientStudy + +details.Name = 'PatientStudy'; +details.SpecPart = 'PS 3.3 Sec. C.7.2.2'; +details.Attrs = { + 0, '0008', '1080', '3', {}, {}, {} + 0, '0010', '1010', '3', {}, {}, {} + 0, '0010', '1020', '3', {}, {}, {} + 0, '0010', '1030', '3', {}, {}, {} + 0, '0010', '2180', '3', {}, {}, {} + 0, '0010', '21B0', '3', {}, {}, {} + }; + + +function details = build_GeneralSeries + +details.Name = 'GeneralSeries'; +details.SpecPart = 'PS 3.3 Sec. C.7.3.1'; +details.Attrs = { + 0, '0008', '0060', '1', {}, modalityTerms, {} + 0, '0020', '000E', '1', {}, {}, {} + 0, '0020', '0011', '2', {}, {}, {} + 0, '0020', '0060', '2C', {}, {'L', 'R'}, {'and', ... + {'not', {'present', '(0020,0062)'}}, ... + {'present', '(0020,0060)'}} + 0, '0008', '0021', '3', {}, {}, {} + 0, '0008', '0031', '3', {}, {}, {} + 0, '0008', '1050', '3', {}, {}, {} + 0, '0018', '1030', '3', {}, {}, {} + 0, '0008', '103E', '3', {}, {}, {} + 0, '0008', '1070', '3', {}, {}, {} + 0, '0008', '1111', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1111)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1111)'} + 0, '0018', '0015', '3', {}, bodyPartTerms, {} + 0, '0018', '5100', '2C', {}, patientPositionTerms, {'or', ... + {'equal', '(0008,0016)', '1.2.840.10008.5.1.4.1.1.4'} ... + {'equal', '(0008,0016)', '1.2.840.10008.5.1.4.1.1.2'}} + 0, '0028', '0108', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + 0, '0028', '0109', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + 0, '0040', '0275', '3', {}, {}, {} + 1, '0040', '1001', '1C', {}, {}, {'present', '(0040,0275)'} + 1, '0040', '0009', '1C', {}, {}, {'present', '(0040,0275)'} + 1, '0040', '0007', '3', {}, {}, {} + 1, '0040', '0008', '3', {}, {}, {} +% 1, % Code sequence macro "No baseline context." + 0, '0040', '0253', '3', {}, {}, {} + 0, '0040', '0244', '3', {}, {}, {} + 0, '0040', '0245', '3', {}, {}, {} + 0, '0040', '0254', '3', {}, {}, {} + 0, '0040', '0260', '3', {}, {}, {} + 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. +% 0, % Code sequence macro "No baseline context." + }; + + +function details = build_FrameOfReference + +details.Name = 'FrameOfReference'; +details.SpecPart = 'PS 3.3 Sec. C.7.4.1'; +details.Attrs = { + 0, '0020', '0052', '1', {}, {}, {} + 0, '0020', '1040', '2', {}, {}, {} + }; + + +function details = build_GeneralEquipment + +details.Name = 'GeneralEquipment'; +details.SpecPart = 'PS 3.3 Sec. C.7.5.1'; +details.Attrs = { + 0, '0008', '0070', '2', {}, {}, {} + 0, '0008', '0080', '3', {}, {}, {} + 0, '0008', '0081', '3', {}, {}, {} + 0, '0008', '1010', '3', {}, {}, {} + 0, '0008', '1040', '3', {}, {}, {} + 0, '0008', '1090', '3', {}, {}, {} + 0, '0018', '1000', '3', {}, {}, {} + 0, '0018', '1020', '3', {}, {}, {} + 0, '0018', '1050', '3', {}, {}, {} + 0, '0018', '1200', '3', {}, {}, {} + 0, '0018', '1201', '3', {}, {}, {} + 0, '0028', '0120', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + }; + + +function details = build_GeneralImage + +details.Name = 'GeneralImage'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.1'; +details.Attrs = { + 0, '0020', '0013', '2', {}, {}, {} + 0, '0020', '0020', '2C', {}, {}, {'or', {'not', {'present', '(0020,0037)'}} ... + {'not', {'present', '(0020,0032)'}}} + 0, '0008', '0023', '2C', {}, {}, {'true'} + 0, '0008', '0033', '2C', {}, {}, {'true'} + 0, '0008', '0008', '3', {}, {}, {} + 0, '0020', '0012', '3', {}, {}, {} + 0, '0008', '0022', '3', {}, {}, {} + 0, '0008', '0032', '3', {}, {}, {} + 0, '0008', '002A', '3', {}, {}, {} + 0, '0008', '1140', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1140)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1140)'} + 1, '0008', '1160', '3', {}, {}, {} + 0, '0008', '2111', '3', {}, {}, {} + 0, '0008', '2112', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,2112)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,2112)'} + 1, '0008', '1160', '3', {}, {}, {} + 0, '0020', '1002', '3', {}, {}, {} + 0, '0020', '4000', '3', {}, {}, {} + 0, '0028', '0300', '3', {}, {'YES' 'NO'}, {} + 0, '0028', '0301', '3', {}, {'YES' 'NO'}, {} + 0, '0028', '2110', '3', {}, {0 1}, {} + 0, '0028', '2112', '3', {}, {}, {} + }; + + +function details = build_ImagePlane + +details.Name = 'ImagePlane'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.2'; +details.Attrs = { + 0, '0028', '0030', '1', {}, {}, {} + 0, '0020', '0037', '1', {}, {}, {} + 0, '0020', '0032', '1', {}, {}, {} + 0, '0018', '0050', '2', {}, {}, {} + 0, '0020', '1041', '3', {}, {}, {} + }; + + +function details = build_ImagePixel + +details.Name = 'ImagePixel'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.3'; +details.Attrs = { + 0, '0028', '0002', '1', {}, {}, {} + 0, '0028', '0004', '1', {}, {}, {} + 0, '0028', '0010', '1', {}, {}, {} + 0, '0028', '0011', '1', {}, {}, {} + 0, '0028', '0100', '1', {}, {}, {} + 0, '0028', '0101', '1', {}, {}, {} + 0, '0028', '0102', '1', {}, {}, {} + 0, '0028', '0103', '1', {}, {0 1}, {} + 0, '7FE0', '0010', '1', {}, {}, {} + 0, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} + 0, '0028', '0034', '1C', {}, {}, {'present', '(0028,0034)'} + 0, '0028', '0106', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + 0, '0028', '0107', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + 0, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1201', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1202', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1203', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '2000', '3', {}, {}, {} + 0, '0028', '0008', '1C', {}, {}, {'present', '(0028,0008)'} + 0, '0028', '0009', '1C', {}, {}, {'present', '(0028,0009)'} + 0, '0028', '7FE0', '1C', {}, {}, {'present', '(0028,7FE0)'} + 0, '0028', '0121', '1C', {}, {}, {'present', '(0028,0121)'} + }; + + +function details = build_ContrastBolus + +details.Name = 'ContrastBolus'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.4'; +details.Attrs = { + 0, '0018', '0010', '2', {}, {}, {} + 0, '0018', '0012', '3', {}, {}, {} + 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. +% 1, % Code sequence macro "Baseline context id is 12" + 0, '0018', '1040', '3', {}, {}, {} + 0, '0018', '0014', '3', {}, {}, {} +% 1, % Code sequence macro "Baseline context id is 11" + 1, '0018', '002A', '3', {}, {}, {} +% 2, % Code sequence macro "No baseline context." + 0, '0018', '1041', '3', {}, {}, {} + 0, '0018', '1042', '3', {}, {}, {} + 0, '0018', '1043', '3', {}, {}, {} + 0, '0018', '1044', '3', {}, {}, {} + 0, '0018', '1046', '3', {}, {}, {} + 0, '0018', '1047', '3', {}, {}, {} + 0, '0018', '1048', '3', {}, {'IODINE' + 'GADOLINIUM' + 'CARBON DIOXIDE' + 'BARIUM'}, {} + 0, '0018', '1049', '3', {}, {}, {} + }; + + +function details = build_MRImage + +details.Name = 'MRImage'; +details.SpecPart = 'PS 3.3 Sec. C.8.3.1'; +details.Attrs = { + 0, '0008', '0008', '1', {}, {}, {} + 0, '0028', '0002', '1', {}, {1}, {} + 0, '0028', '0004', '1', {}, {'MONOCHROME1' 'MONOCHROME2'}, {} + 0, '0028', '0100', '1', {}, {16}, {} + 0, '0018', '0020', '1', {}, {'SE' 'IR' 'GR' 'EP' 'RM'}, {} + 0, '0018', '0021', '1', {}, {'SK' 'MTC' 'SS' 'TRSS' 'SP' ... + 'MP' 'OSP' 'NONE'}, {} + 0, '0018', '0022', '1', {}, {'PER' 'RG' 'CG' 'PPG' 'FC' ... + 'PFF' 'PFP' 'SP' 'FS' 'CT'}, {} + 0, '0018', '0023', '1', {}, {'2D' '3D'}, {} + 0, '0018', '0080', '2C', {}, {}, {'not', ... + {'and', ... + {'equal', '(0018,0020)', 'EP'}, ... + {'equal', '(0018,0021)', 'SK'}}} + 0, '0018', '0081', '2', {}, {}, {} + 0, '0018', '0091', '2', {}, {}, {} + 0, '0018', '0082', '2C', {}, {}, {'or', ... + {'equal', '(0018,0020)', 'IR'}, ... + {'present', '(0018,0082)'}} + 0, '0018', '1060', '2C', {}, {}, {'or', ... + {'present', '(0018,1060)'}, ... + {'or', ... + {'equal', '(0018,0022)', 'RG'}, ... + {'equal', '(0018,0022)', 'CG'}, ... + {'equal', '(0018,0022)', 'CT'}, ... + {'equal', '(0018,0022)', 'PPG'}}} + 0, '0018', '0024', '3', {}, {}, {} + 0, '0018', '0025', '3', {}, {'Y' 'N'}, {} + 0, '0018', '0083', '3', {}, {}, {} + 0, '0018', '0084', '3', {}, {}, {} + 0, '0018', '0085', '3', {}, {}, {} + 0, '0018', '0086', '3', {}, {}, {} + 0, '0018', '0087', '3', {}, {}, {} + 0, '0018', '0088', '3', {}, {}, {} + 0, '0018', '0089', '3', {}, {}, {} + 0, '0018', '0093', '3', {}, {}, {} + 0, '0018', '0094', '3', {}, {}, {} + 0, '0018', '0095', '3', {}, {}, {} + 0, '0018', '1062', '3', {}, {}, {} + 0, '0018', '1080', '3', {}, {'Y' 'N'}, {} + 0, '0018', '1081', '3', {}, {}, {} + 0, '0018', '1082', '3', {}, {}, {} + 0, '0018', '1083', '3', {}, {}, {} + 0, '0018', '1084', '3', {}, {}, {} + 0, '0018', '1085', '3', {}, {}, {} + 0, '0018', '1086', '3', {}, {}, {} + 0, '0018', '1088', '3', {}, {}, {} + 0, '0018', '1090', '3', {}, {}, {} + 0, '0018', '1094', '3', {}, {}, {} + 0, '0018', '1100', '3', {}, {}, {} + 0, '0018', '1250', '3', {}, {}, {} + 0, '0018', '1251', '3', {}, {}, {} + 0, '0018', '1310', '3', {}, {}, {} + 0, '0018', '1312', '3', {}, {}, {} + 0, '0018', '1314', '3', {}, {}, {} + 0, '0018', '1316', '3', {}, {}, {} + 0, '0018', '1315', '3', {}, {'Y' 'N'}, {} + 0, '0018', '1318', '3', {}, {}, {} + 0, '0020', '0100', '3', {}, {}, {} + 0, '0020', '0105', '3', {}, {}, {} + 0, '0020', '0110', '3', {}, {}, {} + }; + + + +function details = build_OverlayPlane + +details.Name = 'OverlayPlane'; +details.SpecPart = 'PS 3.3 Sec. C.9.2'; +details.Attrs = { + 0, '60XX', '0010', '1', {}, {}, {} + 0, '60XX', '0011', '1', {}, {}, {} + 0, '60XX', '0040', '1', {}, {'G' 'R'}, {} + 0, '60XX', '0050', '1', {}, {}, {} + 0, '60XX', '0100', '1', {}, {1}, {} + 0, '60XX', '0102', '1', {}, {0}, {} + 0, '60XX', '3000', '1C', {}, {}, {'equal', '(60XX,0100)', 1} + 0, '60XX', '0022', '3', {}, {}, {} + 0, '60XX', '0045', '3', {}, {'USER' 'AUTOMATED'}, {} + 0, '60XX', '1500', '3', {}, {}, {} + 0, '60XX', '1301', '3', {}, {}, {} + 0, '60XX', '1302', '3', {}, {}, {} + 0, '60XX', '1303', '3', {}, {}, {} + }; + + +function details = build_VOILUT + +details.Name = 'VOILUT'; +details.SpecPart = 'PS 3.3 Sec. C.11.2'; +details.Attrs = { + 0, '0028', '3010', '3', {}, {}, {} + 1, '0028', '3002', '1C', {}, {}, {'present', '(0028,3010)'} + 1, '0028', '3003', '3', {}, {}, {} + 1, '0028', '3006', '1C', {}, {}, {'present', '(0028,3010)'} + 0, '0028', '1050', '3', {}, {}, {} + 0, '0028', '1051', '1C', {}, {}, {'present', '(0028,1050)'} + }; + + +function details = build_SOPCommon + +details.Name = 'SOPCommon'; +details.SpecPart = 'PS 3.3 Sec. C.12.1'; +details.Attrs = { + 0, '0008', '0016', '1', {}, {}, {} + 0, '0008', '0018', '1', {}, {}, {} + 0, '0008', '0005', '1C', {}, {}, {'present', '(0008,0005)'} + 0, '0008', '0012', '3', {}, {}, {} + 0, '0008', '0013', '3', {}, {}, {} + 0, '0008', '0014', '3', {}, {}, {} + 0, '0008', '0201', '3', {}, {}, {} + 0, '0020', '0013', '3', {}, {}, {} + 0, '0100', '0410', '3', {}, {'NS' 'OR' 'AO' 'AC'}, {} + 0, '0100', '0420', '3', {}, {}, {} + 0, '0100', '0424', '3', {}, {}, {} + 0, '0100', '0426', '3', {}, {}, {} + }; + + +function details = build_CTImage + +details.Name = 'CTImage'; +details.SpecPart = 'PS 3.3 Sec. C.8.2.1'; +details.Attrs = { + 0, '0008', '0008', '1', {}, {}, {} + 0, '0028', '0002', '1', {}, {1}, {} + 0, '0028', '0004', '1', {}, {'MONOCHROME1' 'MONOCHROME2'}, {} + 0, '0028', '0100', '1', {}, {16}, {} + 0, '0028', '0101', '1', {}, {16}, {} + 0, '0028', '0102', '1', {}, {15}, {} + 0, '0028', '1052', '1', {}, {}, {} + 0, '0028', '1053', '1', {}, {}, {} + 0, '0018', '0060', '2', {}, {}, {} + 0, '0020', '0012', '2', {}, {}, {} + 0, '0018', '0022', '3', {}, {}, {} + 0, '0018', '0090', '3', {}, {}, {} + 0, '0018', '1100', '3', {}, {}, {} + 0, '0018', '1110', '3', {}, {}, {} + 0, '0018', '1111', '3', {}, {}, {} + 0, '0018', '1120', '3', {}, {}, {} + 0, '0018', '1130', '3', {}, {}, {} + 0, '0018', '1140', '3', {}, {'CW', 'CC'}, {} + 0, '0018', '1150', '3', {}, {}, {} + 0, '0018', '1151', '3', {}, {}, {} + 0, '0018', '1152', '3', {}, {}, {} + 0, '0018', '1153', '3', {}, {}, {} + 0, '0018', '1160', '3', {}, {}, {} + 0, '0018', '1170', '3', {}, {}, {} + 0, '0018', '1190', '3', {}, {}, {} + 0, '0018', '1210', '3', {}, {}, {} + }; + + + +function details = build_SCImageEquipment + +details.Name = 'SCImageEquipment'; +details.SpecPart = 'PS 3.3 Sec. C.8.6.1'; +details.Attrs = { + 0, '0008', '0064', '1', {}, conversionTerms, {} + 0, '0008', '0060', '3', {}, modalityTerms, {} + 0, '0018', '1010', '3', {}, {}, {} + 0, '0018', '1016', '3', {}, {}, {} + 0, '0018', '1018', '3', {}, {}, {} + 0, '0018', '1019', '3', {}, {}, {} + 0, '0018', '1022', '3', {}, {}, {} + 0, '0018', '1023', '3', {}, {}, {} + }; + + + +function details = build_SCImage + +details.Name = 'SCImage'; +details.SpecPart = 'PS 3.3 Sec. C.8.6.2'; +details.Attrs = { + 0, '0018', '1012', '3', {}, {}, {} + 0, '0018', '1014', '3', {}, {}, {} + }; + + +function details = build_USFrameOfReference + +details.Name = 'USFrameOfReference'; +details.SpecPart = 'PS 3.3 Sec. C.8.5.4'; +details.Attrs = { + 0, '0018', '6018', '1', {}, {}, {} + 0, '0018', '601A', '1', {}, {}, {} + 0, '0018', '601C', '1', {}, {}, {} + 0, '0018', '601E', '1', {}, {}, {} + 0, '0018', '6024', '1', {}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... + 11, 12}, {} + 0, '0018', '6026', '1', {}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ... + 11, 12}, {} + 0, '0018', '602C', '1', {}, {}, {} + 0, '0018', '602E', '1', {}, {}, {} + 0, '0018', '6020', '3', {}, {}, {} + 0, '0018', '6022', '3', {}, {}, {} + 0, '0018', '6028', '3', {}, {}, {} + 0, '0018', '602A', '3', {}, {}, {} + }; + + + +function details = build_PaletteColorLookupTable + +details.Name = 'PaletteColorLookupTable'; +details.SpecPart = 'PS 3.3 Sec. C.7.9'; +details.Attrs = { + 0, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + 0, '0028', '1199', '3', {}, {}, {} + 0, '0028', '1201', '1C', {}, {}, {'and', ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}}, ... + {'not', {'present', '(0028,1221)'}}} + 0, '0028', '1202', '1C', {}, {}, {'and', ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}}, ... + {'not', {'present', '(0028,1221)'}}} + 0, '0028', '1203', '1C', {}, {}, {'and', ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}}, ... + {'not', {'present', '(0028,1221)'}}} + 0, '0028', '1221', '1C', {}, {}, {'and', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... + {'present', '(0028,1221)'}} + 0, '0028', '1222', '1C', {}, {}, {'and', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... + {'present', '(0028,1222)'}} + 0, '0028', '1223', '1C', {}, {}, {'and', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'}, ... + {'present', '(0028,1223)'}} + }; + + + +function details = build_USRegionCalibration + +details.Name = 'USRegionCalibration'; +details.SpecPart = 'PS 3.3 Sec. C.8.5.5'; +details.Attrs = { + 0, '0018', '6011', '1', {}, {}, {} + 1, '0018', '6018', '1', {}, {}, {} + 1, '0018', '601A', '1', {}, {}, {} + 1, '0018', '601C', '1', {}, {}, {} + 1, '0018', '601E', '1', {}, {}, {} + 1, '0018', '6024', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, {} + 1, '0018', '6026', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, {} + 1, '0018', '602C', '1', {}, {}, {} + 1, '0018', '602E', '1', {}, {}, {} + 1, '0018', '6020', '3', {}, {}, {} + 1, '0018', '6022', '3', {}, {}, {} + 1, '0018', '6028', '3', {}, {}, {} + 1, '0018', '602A', '3', {}, {}, {} + 1, '0018', '6012', '1', {}, {0 1 2 3 4 5}, {} + 1, '0018', '6014', '1', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ... + 15 16 17 18}, {} + 1, '0018', '6016', '1', {}, {}, {} + 1, '0018', '6044', '1C', {}, {0 1 2}, {'present', '(0018,6044)'} + 1, '0018', '6046', '1C', {}, {}, {'equal', '(0018,6044)', 0} + 1, '0018', '6048', '1C', {}, {}, {'equal', '(0018,6044)', 1} + 1, '0018', '604A', '1C', {}, {}, {'equal', '(0018,6044)', 1} + 1, '0018', '604C', '1C', {}, {0 1 2 3 4 5 6 7 8 9 10 11 12}, ... + {'present', '(0018,6044)'} + 1, '0018', '604E', '1C', {}, {0 1 2 3 4 5 6 7 8 9}, ... + {'present', '(0018,6044)'} + 1, '0018', '6050', '1C', {}, {}, {'or', ... + {'equal', '(0018,6044)', 0}, ... + {'equal', '(0018,6044)', 1}} + 1, '0018', '6052', '1C', {}, {}, {'or', ... + {'equal', '(0018,6044)', 0}, ... + {'equal', '(0018,6044)', 1}} + 1, '0018', '6054', '1C', {}, {}, {'or', ... + {'equal', '(0018,6044)', 0}, ... + {'equal', '(0018,6044)', 1}} + 1, '0018', '6056', '1C', {}, {}, {'equal', '(0018,6044)', 2} + 1, '0018', '6058', '1C', {}, {}, {'equal', '(0018,6044)', 2} + 1, '0018', '605A', '1C', {}, {}, {'equal', '(0018,6044)', 2} + 1, '0018', '6030', '3', {}, {}, {} + 1, '0018', '6032', '3', {}, {}, {} + 1, '0018', '6034', '3', {}, {}, {} + 1, '0018', '6036', '3', {}, {}, {} + 1, '0018', '6038', '3', {}, {}, {} + 1, '0018', '603A', '3', {}, {}, {} + 1, '0018', '603C', '3', {}, {}, {} + 1, '0018', '603E', '3', {}, {}, {} + 1, '0018', '6040', '3', {}, {}, {} + 1, '0018', '6042', '3', {}, {}, {} + }; + + + +function details = build_USImage + +details.Name = 'USImage'; +details.SpecPart = 'PS 3.3 Sec. C.8.5.6'; +details.Attrs = { + 0, '0028', '0002', '1', {}, {1 3}, {} + 0, '0028', '0004', '1', {}, {'MONOCHROME2' 'PALETTE COLOR' 'RGB' ... + 'YBR_FULL' 'YBR_FULL_422' 'YBR_PARTIAL_422'}, {} + 0, '0028', '0100', '1', {}, {8 16}, {} + 0, '0028', '0101', '1', {}, {8 16}, {} + 0, '0028', '0102', '1', {}, {7 15}, {} + 0, '0028', '0006', '1C', {}, {0 1}, {'not', ... + {'equal', '(0028,0002)', 1}} + 0, '0028', '0103', '1', {}, {0}, {} + 0, '0028', '0009', '1C', {}, frameIncrementTerms, {'present', ... + '(0028,0008)'} + 0, '0008', '0008', '2', {}, {}, {} + 0, '0028', '2110', '1C', {}, {0 1}, {'present', '(0028,2110)'} + 0, '0008', '2124', '2C', {}, {}, {'present', '(0008,2124)'} + 0, '0008', '212A', '2C', {}, {}, {'present', '(0008,212A)'} + 0, '0028', '0014', '3', {}, {0 1}, {} + 0, '0008', '1130', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1130)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1130)'} + 0, '0008', '1145', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1145)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1145)'} + 0, '0008', '113A', '3', {}, {}, {} +% 1, % SOP Instance Reference Macro + 1, '0040', 'A170', '1', {}, {}, {} +% 2, % Code Sequence Macro, Defined Context ID is CID 7004 + 0, '0008', '2120', '3', {}, {}, {} + 0, '0040', '000A', '3', {}, {}, {} + 1, 'code', 'sequ', 'X', {}, {}, {} % This is a placeholder. +% 1, % Code Sequence Macro, Baseline Context ID is 12002 + 0, '0008', '2122', '3', {}, {}, {} + 0, '0008', '2127', '3', {}, {}, {} + 0, '0008', '2128', '3', {}, {}, {} + 0, '0008', '2129', '3', {}, {}, {} + 0, '0008', '2130', '3', {}, {}, {} + 0, '0008', '2132', '3', {}, {}, {} + 0, '0008', '2218', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline Context ID is 1 + 1, '0008', '2220', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline Context ID is 2 + 0, '0008', '2228', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline Context ID is 1 + 1, '0008', '2230', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline Context ID is 2 + 0, '0008', '2240', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline Context ID is 4 + 1, '0008', '2242', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline Context ID is 5 + 0, '0008', '2244', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline Context ID is 6 + 1, '0008', '2246', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline Context ID is 7 + 0, '0008', '002A', '1C', {}, {}, {'or', ... + {'equal', '(0008,0060)', 'IVUS'}, ... + {'present', '(0008,002A)'}} + 0, '0018', '1060', '3', {}, {}, {} + 0, '0018', '1062', '3', {}, {}, {} + 0, '0018', '1080', '3', {}, {'Y', 'N'}, {} + 0, '0018', '1081', '3', {}, {}, {} + 0, '0018', '1082', '3', {}, {}, {} + 0, '0018', '1088', '3', {}, {}, {} + 0, '0018', '3100', '1C', {}, {'MOTOR_PULLBACK', + 'MANUAL_PULLBACK', + 'SELECTIVE', + 'GATED_PULLBACK'}, {'equal', ... + '(0008,0060)', 'IVUS'} + 0, '0018', '3101', '1C', {}, {}, {'equal', '(0018,3100)', 'MOTOR_PULLBACK'} + 0, '0018', '3102', '1C', {}, {}, {'equal', '(0018,3100)', 'GATED_PULLBACK'} + 0, '0018', '3103', '1C', {}, {}, {'or', ... + {'equal', '(0018,3100)', 'GATED_PULLBACK'}, ... + {'equal', '(0018,3100)', 'MOTOR_PULLBACK'}} + 0, '0018', '3104', '1C', {}, {}, {'or', ... + {'equal', '(0018,3100)', 'GATED_PULLBACK'}, ... + {'equal', '(0018,3100)', 'MOTOR_PULLBACK'}} + 0, '0018', '3105', '3', {}, {}, {} + 0, '0018', '5000', '3', {}, {}, {} + 0, '0018', '5010', '3', {}, {}, {} + 0, '0018', '6031', '3', {}, transducerTerms, {} + 0, '0018', '5012', '3', {}, {}, {} + 0, '0018', '5020', '3', {}, {}, {} + 0, '0018', '5022', '3', {}, {}, {} + 0, '0018', '5024', '3', {}, {}, {} + 0, '0018', '5026', '3', {}, {}, {} + 0, '0018', '5027', '3', {}, {}, {} + 0, '0018', '5028', '3', {}, {}, {} + 0, '0018', '5029', '3', {}, {}, {} + 0, '0018', '5050', '3', {}, {}, {} + 0, '0018', '5210', '3', {}, {}, {} + 0, '0018', '5212', '3', {}, {}, {} + 0, '60xx', '0045', '3', {}, {'ACTIVE 2D/BMODE IMAGE AREA'}, {} + }; + + + +function details = build_Cine + +details.Name = 'Cine'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.5'; +details.Attrs = { + 0, '0018', '1244', '3', {}, {0 1}, {} + 0, '0018', '1063', '1C', {}, {}, {'equal', '(0028,0009)', ... + uint16(sscanf('0018 1063', '%x')')} + 0, '0018', '1065', '1C', {}, {}, {'equal', '(0028,0009)', ... + uint16(sscanf('0018 1065', '%x')')} + 0, '0008', '2142', '3', {}, {}, {} + 0, '0008', '2143', '3', {}, {}, {} + 0, '0008', '2144', '3', {}, {}, {} + 0, '0018', '0040', '3', {}, {}, {} + 0, '0018', '1066', '3', {}, {}, {} + 0, '0018', '1067', '3', {}, {}, {} + 0, '0018', '0072', '3', {}, {}, {} + 0, '0018', '1242', '3', {}, {}, {} + }; + + +function details = build_MultiFrame + +details.Name = 'MultiFrame'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.6'; +details.Attrs = { + 0, '0028', '0008', '1', {}, {}, {} + 0, '0028', '0009', '1', {}, {}, {} + }; + + + +function details = build_ModalityLUT + +details.Name = 'ModalityLUT'; +details.SpecPart = 'PS 3.3 Sec. C.11.1'; +details.Attrs = { + 0, '0028', '3000', '1C', {}, {}, {'not', {'present', '(0028,1052)'}} + 1, '0028', '3002', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, ... + {}, {'present', '(0028,3000)'} + 1, '0028', '3003', '3', {}, {}, {} + 1, '0028', '3004', '1C', {}, {}, {'present', '(0028,3000)'} + 1, '0028', '3006', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, ... + {}, {'present', '(0028,3000)'} + 0, '0028', '1052', '1C', {}, {}, {'not', {'present', '(0028,3000)'}} + 0, '0028', '1053', '1C', {}, {}, {'present', '(0028,1052)'} + 0, '0028', '1054', '1C', {}, {'OD', 'US', 'US'}, ... + {'present', '(0028,1052)'} + }; + + + +function details = build_FramePointers + +details.Name = 'FramePointers'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.9'; +details.Attrs = { + 0, '0028', '6010', '3', {}, {}, {} + 0, '0028', '6020', '3', {}, {}, {} + 0, '0028', '6022', '3', {}, {}, {} + }; + + + +function details = build_Mask + +details.Name = 'Mask'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.10'; +details.Attrs = { + 0, '0028', '6100', '1', {}, {}, {} + 1, '0028', '6101', '1', {}, {'NONE', 'AVG_SUB', 'TID'}, {} + 1, '0028', '6102', '3', {}, {}, {} + 1, '0028', '6110', '1C', {}, {}, {'equal', '(0028,6101)', 'AVG_SUB'} + 1, '0028', '6112', '3', {}, {}, {} + 1, '0028', '6114', '3', {}, {}, {} + 1, '0028', '6120', '2C', {}, {}, {'equal', '(0028,6101)', 'TID'} + 1, '0028', '6190', '3', {}, {}, {} + 0, '0028', '1090', '2', {}, {'SUB', 'NAT'}, {} + }; + + + +function details = build_DisplayShutter + +details.Name = 'DisplayShutter'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.11'; +details.Attrs = { + 0, '0018', '1600', '1', {}, {'RECTANGULAR', 'CIRCULAR', 'POLYGONAL'}, {} + 0, '0018', '1602', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1604', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1606', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1608', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1610', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} + 0, '0018', '1612', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} + 0, '0018', '1620', '1C', {}, {}, {'equal', '(0018,1600)', 'POLYGONAL'} + 0, '0018', '1622', '3', {}, {}, {} + }; + + + +function details = build_Device + +details.Name = 'Device'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.12'; +details.Attrs = { + 0, '0050', '0010', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline context ID is 8 + 1, '0050', '0014', '3', {}, {}, {} + 1, '0050', '0016', '3', {}, {}, {} + 1, '0050', '0017', '2C', {}, {'FR', 'GA', 'IN', 'MM'}, ... + {'present', '(0050,0016)'} + 1, '0050', '0018', '3', {}, {}, {} + 1, '0050', '0019', '3', {}, {}, {} + 1, '0050', '0020', '3', {}, {}, {} + }; + + + +function details = build_Therapy + +details.Name = 'Therapy'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.13'; +details.Attrs = { + 0, '0018', '0036', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline context ID is 9 + 1, '0018', '0038', '2', {}, {'PRE', 'INTERMEDIATE', 'POST', 'NONE'}, {} + 1, '0018', '0029', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline context ID is 10 + 1, '0018', '0035', '3', {}, {}, {} + 1, '0018', '0027', '3', {}, {}, {} + 1, '0054', '0302', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline context ID is 11 + 1, '0018', '0039', '3', {}, {}, {} + }; + + + +function details = build_XRayImage + +details.Name = 'XRayImage'; +details.SpecPart = 'PS 3.3 Sec. C.8.7.1'; +details.Attrs = { + 0, '0028', '0009', '1C', {}, frameIncrementTerms, {'present', ... + '(0028,0008)'} + 0, '0028', '2110', '1C', {}, {0 1}, {'present', '(0028,2110)'} + 0, '0008', '0008', '1', {}, {}, {} + 0, '0028', '1040', '1', {}, {'LIN', 'LOG', 'DISP'}, {} + 0, '0028', '0002', '1', {}, {1}, {} + 0, '0028', '0004', '1', {}, {'MONOCHROME2'}, {} + 0, '0028', '0100', '1', {}, {8 16}, {} + 0, '0028', '0101', '1', {}, {8 10 12 16}, {} + 0, '0028', '0102', '1', {}, {7 9 11 15}, {} + 0, '0028', '0103', '1', {}, {0}, {} + 0, '0018', '0022', '3', {}, scanOptionsTerms, {} + 0, '0008', '2218', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline context ID is 1 + 1, '0008', '2220', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline context ID is 2 + 0, '0008', '2228', '3', {}, {}, {} +% 1, % Code Sequence Macro, Baseline context ID is 1 + 1, '0008', '2230', '3', {}, {}, {} +% 2, % Code Sequence Macro, Baseline context ID is 2 + 0, '0028', '6040', '3', {}, {}, {} + 0, '0008', '1140', '1C', {}, {}, {'present', '(0008,1140)'} + 1, '0008', '1150', '1C', {}, {}, {'present', '(0008,1140)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(0008,1140)'} + 0, '0008', '2111', '3', {}, {}, {} + 0, '0018', '1400', '3', {}, {}, {} + 0, '0050', '0004', '3', {}, {'YES', 'NO'}, {} + }; + + + +function details = build_XRayAcquisition + +details.Name = 'XRayAcquisition'; +details.SpecPart = 'PS 3.3 Sec. C.8.7.2'; +details.Attrs = { + 0, '0018', '0060', '2', {}, {}, {} + 0, '0018', '1155', '1', {}, {'SC', 'GR'}, {} + 0, '0018', '1151', '2C', {}, {}, {'not', {'present', '(0018,1152)'}} + 0, '0018', '1150', '2C', {}, {}, {'not', {'present', '(0018,1152)'}} + 0, '0018', '1152', '2C', {}, {}, {'not', {'and', ... + {'present', '(0018,1150)'}, ... + {'present', '(0018,1151)'}}} + 0, '0018', '1153', '3', {}, {}, {} + 0, '0018', '1166', '3', {}, {'IN', 'NONE'}, {} + 0, '0018', '1154', '3', {}, {}, {} + 0, '0018', '115A', '3', {}, {'CONTINUOUS', 'PULSED'}, {} + 0, '0018', '1161', '3', {}, {}, {} + 0, '0018', '1162', '3', {}, {}, {} + 0, '0018', '1147', '3', {}, {'ROUND', 'RECTANGLE'}, {} + 0, '0018', '1149', '3', {}, {}, {} + 0, '0018', '1164', '3', {}, {}, {} + 0, '0018', '1190', '3', {}, {}, {} + 0, '0018', '115E', '3', {}, {}, {} + }; + + + +function details = build_XRayCollimator + +details.Name = 'XRayCollimator'; +details.SpecPart = 'PS 3.3 Sec. C.8.7.3'; +details.Attrs = { + 0, '0018', '1700', '1', {}, {'RECTANGULAR', 'CIRCULAR', 'POLYGONAL'}, {} + 0, '0018', '1702', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1704', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1706', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1708', '1C', {}, {}, {'equal', '(0018,1600)', 'RECTANGULAR'} + 0, '0018', '1710', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} + 0, '0018', '1712', '1C', {}, {}, {'equal', '(0018,1600)', 'CIRCULAR'} + 0, '0018', '1720', '1C', {}, {}, {'equal', '(0018,1600)', 'POLYGONAL'} + }; + + + +function details = build_XRayTable + +details.Name = 'XRayTable'; +details.SpecPart = 'PS 3.3 Sec. C.8.7.4'; +details.Attrs = { + 0, '0018', '1134', '2', {}, {'STATIC', 'DYNAMIC'}, {} + 0, '0018', '1135', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} + 0, '0018', '1137', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} + 0, '0018', '1136', '2C', {}, {}, {'equal', '(0018,1134)', 'DYNAMIC'} + 0, '0018', '1138', '3', {}, {}, {} + }; + + + +function details = build_XAPositioner + +details.Name = 'XAPositioner'; +details.SpecPart = 'PS 3.3 Sec. C.8.7.5'; +details.Attrs = { + 0, '0018', '1111', '3', {}, {}, {} + 0, '0018', '1110', '3', {}, {}, {} + 0, '0018', '1114', '3', {}, {}, {} + 0, '0018', '1500', '2C', {}, {'STATIC', 'DYNAMIC'}, {'and', ... + {'present', '(0028,0008)'}, ... + {'not', {'equal', '(0028,0008)', 1}}} + 0, '0018', '1510', '2', {}, {}, {} + 0, '0018', '1511', '2', {}, {}, {} + 0, '0018', '1520', '2C', {}, {}, {'equal', '(0018,1500)', 'DYNAMIC'} + 0, '0018', '1521', '2C', {}, {}, {'equal', '(0018,1500)', 'DYNAMIC'} + 0, '0018', '1530', '3', {}, {}, {} + 0, '0018', '1531', '3', {}, {}, {} + }; + + + +function details = build_MultiFrameOverlay + +details.Name = 'MultiFrameOverlay'; +details.SpecPart = 'PS 3.3 Sec. C.9.3'; +details.Attrs = { + 0, '60xx', '0015', '1', {}, {}, {} + 0, '60xx', '0051', '3', {}, {}, {} + }; + + + +function details = build_Curve + +details.Name = 'Curve'; +details.SpecPart = 'PS 3.3 Sec. C.10.2'; +details.Attrs = { + 0, '50xx', '0005', '1', {}, {}, {} + 0, '50xx', '0010', '1', {}, {}, {} + 0, '50xx', '0020', '1', {}, curveTypeTerms, {} + 0, '50xx', '0103', '1', {}, {0 1 2 3 4}, {} + 0, '50xx', '3000', '1', curveVRlut, {}, {} + 0, '50xx', '0022', '3', {}, {}, {} + 0, '50xx', '0030', '3', {}, axisUnitsTerms, {} + 0, '50xx', '0040', '3', {}, {}, {} + 0, '50xx', '0104', '3', {}, {}, {} + 0, '50xx', '0105', '3', {}, {}, {} + 0, '50xx', '0106', '3', {}, {}, {} + 0, '50xx', '0110', '1C', {}, {}, {'present', '(50xx,0110)'} + 0, '50xx', '0112', '1C', curveVRlut, {}, {'present', '(50xx,0110)'} + 0, '50xx', '0114', '1C', curveVRlut, {}, {'present', '(50xx,0110)'} + 0, '50xx', '2500', '3', {}, {}, {} + 0, '50xx', '2600', '3', {}, {}, {} + 1, '0008', '1150', '1C', {}, {}, {'present', '(50xx,2600)'} + 1, '0008', '1155', '1C', {}, {}, {'present', '(50xx,2600)'} + 1, '50xx', '2610', '1C', {}, {}, {'present', '(50xx,2600)'} + }; + + + +function details = build_SCMultiFrameImage + +mono2gray = {'and', ... + {'equal', '(0028,0004)', 'MONOCHROME2'}, ... + {'not', {'equal', '(0028,0101)', 1}}}; + +details.Name = 'SC Multi-Frame Image'; +details.SpecPart = 'PS 3.3 Sec. C.8.6.3'; +details.Attrs = { + 0, '0028', '0301', '1', {}, {}, {} + 0, '2050', '0020', '1C', {}, {'IDENTITY'}, mono2gray + 0, '2010', '015E', '3', {}, {}, {} + 0, '2010', '0160', '3', {}, {}, {} + 0, '0028', '1052', '1C', {}, {}, mono2gray + 0, '0028', '1053', '1C', {}, {}, mono2gray + 0, '0028', '1054', '1C', {}, {}, mono2gray + 0, '0028', '0009', '1C', {}, {}, {'present', '(0028,0008)'} + 0, '0018', '2010', '1C', {}, {}, {'or', ... + {'equal', '(0008,0064)', 'DF'}, ... + {'present', '(0008,0064)'}} + 0, '0018', '2020', '3', {}, {}, {} + 0, '0018', '2030', '3', {}, {}, {} + }; + + + +function details = build_SCMultiFrameVector + +details.Name = 'SC Multi-Frame Vector'; +details.SpecPart = 'PS 3.3 Sec. C.8.6.4'; +details.Attrs = { + 0, '0018', '1065', '1C', {}, {}, {'present', '(0018,1065)'} + 0, '0018', '2001', '1C', {}, {}, {'present', '(0018,2001)'} + 0, '0018', '2002', '1C', {}, {}, {'present', '(0018,2002)'} + 0, '0018', '2003', '1C', {}, {}, {'present', '(0018,2003)'} + 0, '0018', '2004', '1C', {}, {}, {'present', '(0018,2004)'} + 0, '0018', '2005', '1C', {}, {}, {'present', '(0018,2005)'} + 0, '0018', '2006', '1C', {}, {}, {'present', '(0018,2006)'} + }; + + + +function details = build_ClinicalTrialStudy + +details.Name = 'Clinical Trial Study'; +details.SpecPart = 'PS 3.3 Sec. C.7.2.3'; +details.Attrs = { + 0, '0012', '0050', '2', {}, {}, {} + 0, '0012', '0051', '3', {}, {}, {} + 0, '0012', '0083', '3', {}, {}, {} + 1, '0012', '0084', '1C', {}, {}, {'or', ... + {'equal', '(0012,0085)', 'YES'}, ... + {'equal', '(0012,0085)', 'WITHDRAWN'}} + 1, '0012', '0020', '1C', {}, {}, {'equal', '(0012,0084)', 'NAMED_PROTOCOL'} + 1, '0012', '0085', '1', {}, {}, {} + }; + + + +function details = build_ClinicalTrialSubject + +details.Name = 'Clinical Trial Subject'; +details.SpecPart = 'PS 3.3 Sec. C.7.1.3'; +details.Attrs = { + 0, '0012', '0010', '1', {}, {}, {} + 0, '0012', '0020', '1', {}, {}, {} + 0, '0012', '0021', '2', {}, {}, {} + 0, '0012', '0030', '2', {}, {}, {} + 0, '0012', '0031', '2', {}, {}, {} + 0, '0012', '0040', '1C', {}, {}, {'or', ... + {'present', '(0012,0040)'}, ... + {'not', {'present', '(0012,0042)'}}} + 0, '0012', '0042', '1C', {}, {}, {'or', ... + {'present', '(0012,0042)'}, ... + {'not', {'present', '(0012,0040)'}}} + 0, '0012', '0081', '1C', {}, {}, {'present', '(0012,0082)'} + 0, '0012', '0082', '3', {}, {}, {} + }; + + + +function details = build_ClinicalTrialSeries + +details.Name = 'Clinical Trial Series'; +details.SpecPart = 'PS 3.3 Sec. C.7.3.2'; +details.Attrs = { + 0, '0012', '0060', '2', {}, {}, {} + 0, '0012', '0071', '3', {}, {}, {} + 0, '0012', '0072', '3', {}, {}, {} + }; + + + +function details = build_MRSeries + +details.Name = 'MR Series'; +details.SpecPart = 'PS 3.3 Sec. C.8.13.6'; +details.Attrs = { + 0, '0008', '0060', '1', {}, {}, {} + 0, '0008', '1111', '1C', {}, {}, {'present', '(0008,1111)'} + 1, '0008', '1150', '1', {}, {}, {} + 1, '0008', '1155', '1', {}, {}, {} + }; + + + +function details = build_Synchronization + +details.Name = 'Synchronization'; +details.SpecPart = 'PS 3.3 Sec. C.7.4.2'; +details.Attrs = { + 0, '0020', '0200', '1', {}, {}, {} + 0, '0018', '106A', '1', {}, {}, {} + 0, '0018', '1061', '3', {}, {}, {} + 0, '0018', '106C', '1C', {}, {}, {'present', '(0018,106C)'} + 0, '0018', '1800', '1', {}, {}, {} + 0, '0018', '1801', '3', {}, {}, {} + 0, '0018', '1802', '3', {}, {}, {} + 0, '0018', '1803', '3', {}, {}, {} + }; + + + +function details = build_EnhancedGeneralEquipment + +details.Name = 'Enhanced General Equipment'; +details.SpecPart = 'PS 3.3 Sec. C.7.5.2'; +details.Attrs = { + 0, '0008', '0070', '1', {}, {}, {} + 0, '0008', '1090', '1', {}, {}, {} + 0, '0018', '1000', '1', {}, {}, {} + 0, '0018', '1020', '1', {}, {}, {} + }; + + + +function details = build_EnhancedContrastBolus + +details.Name = 'Enhanced Contrast Bolus'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.4b'; +details.Attrs = [ + {0, '0018', '0012', '1', {}, {}, {}} + createCodeSequenceMacro(1) + {1, '0018', '9337', '1', {}, {}, {} + 1, '0018', '0014', '1', {}, {}, {}} + createCodeSequenceMacro(2) + {1, '0018', '9338', '1', {}, {}, {}} + createCodeSequenceMacro(2) + {1, '0018', '1041', '2', {}, {}, {} + 1, '0018', '1049', '2', {}, {}, {} + 1, '0018', '9425', '3', {}, {}, {} + 1, '0018', '9340', '3', {}, {}, {} + 2, '0018', '1041', '2', {}, {}, {} + 2, '0018', '1042', '3', {}, {}, {} + 2, '0018', '1043', '3', {}, {}, {} + 2, '0018', '1046', '3', {}, {}, {} + 2, '0018', '1047', '3', {}, {}, {}} + ]; + + + +function details = build_MultiFrameDimension + +details.Name = 'Multi-frame Dimension'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.17'; +details.Attrs = { + 0, '0020', '9221', '1', {}, {}, {} + 1, '0020', '9164', '1', {}, {}, {} + 0, '0020', '9311', '3', {}, {}, {} + 0, '0020', '9222', '1', {}, {}, {} + 1, '0020', '9165', '1', {}, {}, {} + 1, '0020', '9213', '1C', {}, {}, {'present', '(0020,9213)'} + 1, '0020', '9167', '1C', {}, {}, {'present', '(0020,9167)'} + 1, '0020', '9238', '1C', {}, {}, {'present', '(0020,9238)'} + 1, '0020', '9164', '1C', {}, {}, {'present', '(0020,9164)'} + 1, '0020', '9241', '3', {}, {}, {} + }; + + + +function details = build_CardiacSynchronization + +details.Name = 'Cardiac Synchronization'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.18.1'; +details.Attrs = { + 0, '0018', '9037', '1C', {}, {}, {'present', '(0018,9037)'} + 0, '0018', '9085', '1C', {}, {}, {'present', '(0018,9085)'} + 0, '0018', '9070', '1C', {}, {}, {'present', '(0018,9070)'} + 0, '0018', '9169', '1C', {}, {}, {'present', '(0018,9169)'} + 0, '0018', '1081', '2C', {}, {}, {'present', '(0018,1081)'} + 0, '0018', '1082', '2C', {}, {}, {'present', '(0018,1082)'} + 0, '0018', '1083', '2C', {}, {}, {'present', '(0018,1083)'} + 0, '0018', '1084', '2C', {}, {}, {'present', '(0018,1084)'} + 0, '0018', '1086', '3', {}, {}, {} + 0, '0018', '1064', '1C', {}, {}, {'present', '(0018,1064)'} + }; + + + +function details = build_RespiratorySynchronization + +details.Name = 'Respiratory Synchronization'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.18.2'; +details.Attrs = { + 0, '0018', '9170', '1C', {}, {}, {'present', '(0018,9170)'} + 0, '0018', '9171', '1C', {}, {}, {'present', '(0018,9171)'} + 0, '0020', '9256', '1C', {}, {}, {'present', '(0020,9256)'} + 0, '0020', '9250', '1C', {}, {}, {'present', '(0020,9250)'} + }; + + + +function details = build_BulkMotionSynchronization + +details.Name = 'Bulk Motion Synchronization'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.18.2'; +details.Attrs = { + 0, '0018', '9172', '1C', {}, {}, {'present', '(0018,9172)'} + 0, '0018', '9173', '1C', {}, {}, {'present', '(0018,9173)'} + }; + + + +function details = build_SupplementalPaletteColorLUT + +details.Name = 'Supplemental Palette Color Lookup Table'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.19'; +details.Attrs = { + 0, '0028', '1101', '1', {}, {}, {} + 0, '0028', '1102', '1', {}, {}, {} + 0, '0028', '1103', '1', {}, {}, {} + 0, '0028', '1201', '1', {}, {}, {} + 0, '0028', '1202', '1', {}, {}, {} + 0, '0028', '1203', '1', {}, {}, {} + }; + + + +function details = build_AcquisitionContext + +details.Name = 'Acquisition Context'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.14'; +details.Attrs = [... + {0, '0040', '0555', '2', {}, {}, {} + 1, '0040', 'A040', '3', {}, {}, {} + 1, '0040', 'A043', '1', {}, {}, {}} + createCodeSequenceMacro(2) + {1, '0040', 'A136', '1C', {}, {}, {'present', '(0040,A136)'} + 1, '0040', 'A30A', '1C', {}, {}, {'present', '(0040,A30A)'} + 1, '0040', '08EA', '1C', {}, {}, {'present', '(0040,A30A)'}} + createCodeSequenceMacro(2) + {1, '0040', 'A121', '1C', {}, {}, {'present', '(0040,A121)'} + 1, '0040', 'A122', '1C', {}, {}, {'present', '(0040,A122)'} + 1, '0040', 'A123', '1C', {}, {}, {'present', '(0040,A123)'} + 1, '0040', 'A160', '1C', {}, {}, {'present', '(0040,A160)'} + 1, '0040', 'A168', '1C', {}, {}, {'present', '(0040,A168)'}} + createCodeSequenceMacro(2) + {0, '0040', '0556', '3', {}, {}, {}} + ]; + + + +function details = build_MultiFrameFunctionalGroups + +details.Name = 'Multi-frame Functional Groups'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.16'; +details.Attrs = [... + {0, '5200', '9229', '2', {}, {}, {}} + createPixelMeasuresMacro(0) + createFrameContentMacro(0) + % Insert 0 or 1 functional group macros here. + {0, '5200', '9230', '1', {}, {}, {} + % Insert "number of frames" functional group macros here. + 0, '0020', '0013', '1', {}, {}, {} + 0, '0008', '0023', '1', {}, {}, {} + 0, '0008', '0033', '1', {}, {}, {} + 0, '0028', '0008', '1', {}, {}, {} + 0, '0020', '9228', '1C', {}, {}, {'present', '(0020,9161)'} + 0, '0028', '6010', '3', {}, {}, {} + 0, '0020', '9161', '1C', {}, {}, {'present', '(0020,9161)'} + 0, '0020', '0242', '1C', {}, {}, {'present', '(0020,9161)'} + 0, '0020', '9162', '1C', {}, {}, {'present', '(0020,9161)'} + 0, '0020', '9163', '3', {}, {}, {}} + ]; + + + +function details = build_Specimen + +details.Name = 'Specimen'; +details.SpecPart = 'PS 3.3 Sec. C.7.6.22'; +details.Attrs = [ + {0, '0040', '0512', '1', {}, {}, {} + 0, '0040', '0513', '2', {}, {}, {}} + createHL7v2HierarchicDesignator(1) + {0, '0040', '0515', '3', {}, {}, {} + 1, '0040', '0512', '1', {}, {}, {} + 1, '0040', '0513', '2', {}, {}, {}} + createHL7v2HierarchicDesignator(2) + {0, '0040', '0518', '2', {}, {}, {}} + createCodeSequenceMacro(1) + {0, '0040', '051A', '3', {}, {}, {} + 0, '0040', '0520', '3', {}, {}, {} + 1, '0050', '0012', '1', {}, {}, {}} + createCodeSequenceMacro(2) + {1, '0008', '0070', '3', {}, {}, {} + 1, '0008', '1090', '3', {}, {}, {} + 1, '0050', '001B', '3', {}, {}, {} + 1, '0050', '001C', '3', {}, {}, {} + 1, '0050', '0015', '3', {}, {}, {} + 1, '0050', '001D', '3', {}, {}, {} + 1, '0050', '0013', '3', {}, {}, {} + 1, '0050', '001A', '3', {}, {}, {} + 1, '0050', '001E', '3', {}, {}, {} + 0, '0040', '0560', '1', {}, {}, {} + 1, '0040', '0551', '1', {}, {}, {} + 1, '0040', '0562', '2', {}, {}, {}} + createHL7v2HierarchicDesignator(2) + {1, '0040', '0554', '1', {}, {}, {} + 1, '0040', '059A', '3', {}, {}, {}} + createCodeSequenceMacro(2) + {1, '0040', '0600', '3', {}, {}, {} + 1, '0040', '0602', '3', {}, {}, {} + 1, '0040', '0610', '2', {}, {}, {} + 2, '0040', '0612', '1', {}, {}, {}} + createContentItemMacro(3) + {1, '0008', '2228', '3', {}, {}, {}} % Primary Anatomic Structure Macro + createCodeSequenceMacro(2) % Primary Anatomic Structure Macro + {2, '0008', '2230', '3', {}, {}, {}} % Primary Anatomic Structure Macro + createCodeSequenceMacro(3) % Primary Anatomic Structure Macro + {1, '0040', '0620', '1C', {}, {}, {'present', '(0040,0620)'}} + createContentItemMacro(2) + ]; + + + +function details = build_MRPulseSequence + +details.Name = 'MR Pulse Sequence'; +details.SpecPart = 'PS 3.3 Sec. C.8.13.4'; +details.Attrs = { + 0, '0018', '9005', '1C', {}, {}, {'present', '(0018,9005)'} + 0, '0018', '0023', '1C', {}, {}, {'present', '(0018,9023)'} + 0, '0018', '9008', '1C', {}, {}, {'present', '(0018,9028)'} + 0, '0018', '9011', '1C', {}, {}, {'present', '(0018,9011)'} + 0, '0018', '9012', '1C', {}, {}, {'present', '(0018,9012)'} + 0, '0018', '9014', '1C', {}, {}, {'present', '(0018,9014)'} + 0, '0018', '9015', '1C', {}, {}, {'present', '(0018,9015)'} + 0, '0018', '9017', '1C', {}, {}, {'present', '(0018,9017)'} + 0, '0018', '9018', '1C', {}, {}, {'present', '(0018,9018)'} + 0, '0018', '9024', '1C', {}, {}, {'present', '(0018,9024)'} + 0, '0018', '9025', '1C', {}, {}, {'present', '(0018,9025)'} + 0, '0018', '9029', '1C', {}, {}, {'present', '(0018,9029)'} + 0, '0018', '9032', '1C', {}, {}, {'present', '(0018,9032)'} + 0, '0018', '9034', '1C', {}, {}, {'present', '(0018,9034)'} + 0, '0018', '9033', '1C', {}, {}, {'present', '(0018,9033)'} + 0, '0018', '9094', '1C', {}, {}, {'present', '(0018,9094)'} + 0, '0018', '9093', '1C', {}, {}, {'present', '(0018,9093)'} + }; + + + +function details = build_EnhancedMRImage + +details.Name = 'Enhanced MR Image'; +details.SpecPart = 'PS 3.3 Sec. C.8.13.1'; +details.Attrs = [ + createMRImageAndSpectroscopyInstance(0) + {0, '0008', '0008', '1', {}, {}, {}} + createMRImageDescription(0) + {0, '0028', '0002', '1', {}, {}, {} + 0, '0028', '0004', '1', {}, {}, {} + 0, '0028', '0100', '1', {}, {}, {} + 0, '0028', '0101', '1', {}, {}, {} + 0, '0028', '0102', '1', {}, {}, {} + 0, '0028', '0103', '1', {}, {}, {} + 0, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} + 0, '0018', '0088', '3', {}, {}, {} + 0, '0028', '0301', '1', {}, {'NO'}, {} + 0, '0028', '2110', '1', {}, {}, {} + 0, '0028', '2112', '1C', {}, {}, {'equal', '(0028,2110)', '01'} + 0, '0028', '2114', '1C', {}, {}, {'equal', '(0028,2110)', '01'} + 0, '2050', '0020', '1C', {}, {}, {'equal', '(0028,0004)', 'MONOCHROME2'} + 0, '0088', '0200', '3', {}, {}, {}} + createImagePixelMacro(1) + ]; + + + +function macro = createImagePixelMacro(level) + +macro = { + level, '0028', '0002', '1', {}, {}, {} + level, '0028', '0004', '1', {}, {}, {} + level, '0028', '0010', '1', {}, {}, {} + level, '0028', '0011', '1', {}, {}, {} + level, '0028', '0100', '1', {}, {}, {} + level, '0028', '0101', '1', {}, {}, {} + level, '0028', '0102', '1', {}, {}, {} + level, '0028', '0103', '1', {}, {0 1}, {} + level, '7FE0', '0010', '1', {}, {}, {} + level, '0028', '0006', '1C', {}, {}, {'not', {'equal', '(0028,0002)', 1}} + level, '0028', '0034', '1C', {}, {}, {'present', '(0028,0034)'} + level, '0028', '0106', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + level, '0028', '0107', '3', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, {} + level, '0028', '1101', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '1102', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '1103', '1C', {'(0028,0103)', {0, 'US'}, {1, 'SS'}}, {}, ... + {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '1201', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '1202', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '1203', '1C', {}, {}, {'or', ... + {'equal', '(0028,0004)', 'PALETTE COLOR'} ... + {'equal', '(0028,0004)', 'ARGB'}} + level, '0028', '2000', '3', {}, {}, {} + level, '0028', '0008', '1C', {}, {}, {'present', '(0028,0008)'} + level, '0028', '0009', '1C', {}, {}, {'present', '(0028,0009)'}}; + +function macro = createMRImageAndSpectroscopyInstance(level) + +% See PS 3.3 Table C.8-81. +macro = [... + {level, '0020', '0012', '3', {}, {}, {} + level, '0008', '002A', '1C', {}, {}, {'present', '(0008,002A)'} + level, '0018', '9073', '1C', {}, {}, {'present', '(0018,9073)'} + level, '0008', '9121', '3', {}, {}, {}} + createHierarchicalSOPInstanceRefMacro(level+1) + {level, '0008', '113A', '3', {}, {}, {}} + createHierarchicalSOPInstanceRefMacro(level+1) + {level, '0008', '9092', '1C', {}, {}, {'present', '(0008,1140)'}} + createHierarchicalSOPInstanceRefMacro(level+1) + {level, '0008', '9154', '1C', {}, {}, {'present', '(0008,2112)'}} + createHierarchicalSOPInstanceRefMacro(level+1) + {level, '0008', '9237', '1C', {}, {}, {'present', '(0008,9237)'}} + createHierarchicalSOPInstanceRefMacro(level+1) + {level, '0018', '9004', '1', {}, {}, {} + level, '0018', '9100', '1C', {}, {}, {'present', '(0018,9100)'} + level, '0018', '9064', '1C', {}, {}, {'present', '(0018,9064)'} + level, '0018', '0087', '1C', {}, {}, {'present', '(0018,0087)'} + level, '0018', '9174', '1', {}, {}, {} + level, '0018', '9175', '3', {}, {}, {} + level, '0020', '4000', '3', {}, {}, {}} + ]; + + + +function macro = createCodeSequenceMacro(level) + +contextGroupCondition = {'and', ... + {'present', '(0008,010B)'}, ... + {'equals', '(0008,010B)', 'Y'}}; + +CodeSequenceMacro = {... + '0008', '0100', '1', {}, {}, {} + '0008', '0102', '1', {}, {}, {} + '0008', '0103', '1C', {}, {}, {'present', '(0008,0103)'} + '0008', '0104', '1', {}, {}, {} + '0008', '010F', '3', {}, {}, {} + '0008', '0117', '3', {}, {}, {} + '0008', '0105', '1C', {}, {}, {'present', '(0008,010F)'} + '0008', '0106', '1C', {}, {}, {'present', '(0008,010F)'} + '0008', '010B', '3', {}, {}, {} + '0008', '0107', '1C', {}, {}, contextGroupCondition + '0008', '010D', '1C', {}, {}, contextGroupCondition + }; + +n = size(CodeSequenceMacro, 1); +macro = [repmat({level}, [n 1]), CodeSequenceMacro]; + + + +function macro = createHL7v2HierarchicDesignator(level) + +designatorMacro = {... + '0040', '0031', '1C', {}, {}, {'not', {'present', '(0040,0032)'}} + '0040', '0032', '1C', {}, {}, {'not', {'present', '(0040,0031)'}} + '0040', '0033', '1C', {}, {}, {'present', '(0040,0032)'} + }; + +n = size(designatorMacro, 1); +macro = [repmat({level}, [n 1]), designatorMacro]; + + + +function macro = createContentItemMacro(level) + +ContentItemMacro = [... + {level, '0040', 'A040', '1', {}, {}, {} + level, '0040', 'A043', '1', {}, {}, {}} + createCodeSequenceMacro(level+1) + {level, '0040', 'A120', '1C', {}, {}, {'equal', '(0040,A040)', 'DATETIME'} + level, '0040', 'A121', '1C', {}, {}, {'equal', '(0040,A040)', 'DATE'} + level, '0040', 'A122', '1C', {}, {}, {'equal', '(0040,A040)', 'TIME'} + level, '0040', 'A123', '1C', {}, {}, {'equal', '(0040,A040)', 'PNAME'} + level, '0040', 'A124', '1C', {}, {}, {'equal', '(0040,A040)', 'UIDREF'} + level, '0040', 'A160', '1C', {}, {}, {'equal', '(0040,A040)', 'TEXT'} + level, '0040', 'A168', '1C', {}, {}, {'equal', '(0040,A040)', 'CODE'}} + createCodeSequenceMacro(level+1) + {level, '0040', 'A30A', '1C', {}, {}, {'equal', '(0040,A040)', 'NUMERIC'} + level, '0040', '08EA', '1C', {}, {}, {'equal', '(0040,A040)', 'NUMERIC'}} + createCodeSequenceMacro(level+1) + {level, '0040', '1199', '1C', {}, {}, {'or', ... + {'equal', '(0040,A040)', 'COMPOSITE'}, ... + {'equal', '(0040,A040)', 'IMAGE'}} + level+1, '0008', '1150', '1', {}, {}, {} + level+1, '0008', '1155', '1', {}, {}, {} + level+1, '0008', '1160', '1C', {}, {}, {'present', '0008', '1160'} + level+1, '0062', '000B', '1C', {}, {}, {'present', '0062', '000B'}} + ]; + + + +function macro = createHierarchicalSOPInstanceRefMacro(level) + +% See PS 3.3 Table C.17-3 +macro = [... + {level, '0020', '000D', '1', {}, {}, {} + level, '0008', '1115', '1', {}, {}, {}} + createHierarchicalSeriesRefMacro(level+1) + ]; + + + +function macro = createHierarchicalSeriesRefMacro(level) + +% See PS 3.3 Table C.17-3a +macro = [... + {level, '0020', '000E', '1', {}, {}, {} + level, '0008', '0054', '3', {}, {}, {} + level, '0040', 'E011', '3', {}, {}, {} + level, '0088', '0130', '3', {}, {}, {} + level, '0088', '0140', '3', {}, {}, {} + level, '0008', '1199', '1', {}, {}, {}} + createSOPInstanceRefMacro(level+1) + {level+1, '0040', 'A170', '3', {}, {}, {}} + createCodeSequenceMacro(level+2) + {level+1, '0400', '0402', '3', {}, {}, {} + level+2, '0400', '0100', '1', {}, {}, {} + level+2, '0400', '0120', '1', {}, {}, {} + level+1, '0400', '0403', '3', {}, {}, {} + level+2, '0400', '0010', '1', {}, {}, {} + level+2, '0400', '0015', '1', {}, {}, {} + level+2, '0400', '0020', '1', {}, {}, {} + level+2, '0400', '0404', '1', {}, {}, {}} + ]; + + + +function macro = createSOPInstanceRefMacro(level) + +% See PS 3.3 Table 10-11 +macro = {... + level, '0008', '1150', '1', {}, {}, {} + level, '0008', '1155', '1', {}, {}, {} + }; + + + +function macro = createMRImageDescription(level) + +% See PS 3.3 Table C.8-82 +macro = {... + level, '0008', '9208', '1', {}, {}, {} + level, '0008', '9209', '1', {}, {}, {} + }; + + +function macro = createPixelMeasuresMacro(level) + +% See PS 3.3 Sec. C.7.6.16.2.1 +macro = {... + level, '0028', '9110', '1C', {}, {}, {'present', '(0028,9110)'} + level+1, '0028', '0030', '1C', {}, {}, {'present', '(0028,0030)'} + level+1, '0018', '0050', '1C', {}, {}, {'present', '(0018,0050)'} + }; + + +function macro = createFrameContentMacro(level) + +% See PS 3.3 Sec. C.7.6.16.2.2 +n = level+1; +macro = {... + level, '0028', '9111', '1C', {}, {}, {'present', '(0028,9111)'} + n, '0020', '9156', '3', {}, {}, {} + n, '0018', '9151', '1C', {}, {}, {'present', '(0018,9151)'} + n, '0018', '9074', '1C', {}, {}, {'present', '(0018,9074)'} + n, '0018', '9220', '1C', {}, {}, {'present', '(0018,9220)'} + n, '0018', '9236', '3', {}, {}, {} + n, '0018', '9214', '3', {}, {}, {} + n, '0020', '9157', '1C', {}, {}, {'present', '(0020,9157)'} + n, '0020', '9128', '1C', {}, {}, {'present', '(0020,9128)'} + n, '0020', '9056', '1C', {}, {}, {'present', '(0020,9056)'} + n, '0020', '9057', '1C', {}, {}, {'present', '(0020,9056)'} + n, '0020', '9158', '3', {}, {}, {} + n, '0020', '9453', '3', {}, {}, {} + }; + + +function macro = createPlanePositionPatientMacro(level) + +% See PS 3.3 Sec. C.7.6.16.2.3 +macro = {... + level, '0020', '9113', '1C', {}, {}, {'present', '(0020,9113)'} + level+1, '0020', '0032', '1C', {}, {}, {'present', '(0020,0032)'} + }; + + + +function terms = modalityTerms +%MODALITYDEFINEDTERMS Modality defined terms +% +% See PS 3.3 Sec. C.7.3.1.1.1 + +terms = {'CR', 'MR', 'US', 'BI', 'DD', 'ES', 'MA', 'PT', 'ST', 'XA', ... + 'RTIMAGE', 'RTSTRUCT', 'RTRECORD', 'DX', 'IO', 'GM', 'XC', 'AU', ... + 'EPS', 'SR', 'CT', 'NM', 'OT', 'CD', 'DG', 'LS', 'MS', 'RG', 'TG', ... + 'RF', 'RTDOSE', 'RTPLAN', 'HC', 'MG', 'PX', 'SM', 'PR', 'ECG', ... + 'HD'}; + + + +function terms = bodyPartTerms +%BODYPARTTERMS Body part defined terms +% +% See PS 3.3 Sec. C.7.3.1 + +terms = {'SKULL', 'CSPINE', 'TSPINE', 'LSPINE', 'SSPINE', 'COCCYX', 'CHEST', ... + 'CLAVICLE', 'BREAST', 'ABDOMEN', 'PELVIS', 'HIP', 'SHOULDER', ... + 'ELBOX', 'KNEE', 'ANKLE', 'HAND', 'FOOT', 'EXTREMITY', 'HEAD', ... + 'HEART', 'NECK', 'LEG', 'ARM', 'JAW'}; + + + +function terms = patientPositionTerms +%PATIENTPOSITIONTERMS Patient position defined terms +% +% See PS 3.3 Sec. C.7.3.1.1.2 + +terms = {'HFP', 'HFS', 'HFDR', 'HFDL', 'FFDR', 'FFDL', 'FFP', 'FFS'}; + + + +function terms = conversionTerms +%CONVERSIONTERMS Secondary Capture conversion type defined terms +% +% See PS 3.3 Sec. C.8.6.1 + +terms = {'DV', 'DI', 'DF', 'WSD', 'SD', 'SI', 'DRW', 'SYN'}; + + + +function terms = transducerTerms +%TRANSDUCERTERMS Transducer type defined terms +% +% See PS 3.3 Sec. C.8.5.6 + +terms = {'SECTOR_PHASED', 'SECTOR_MECH', 'SECTOR_ANNULAR', 'LINEAR', ... + 'CURVED LINEAR', 'SINGLE CRYSTAL', 'SPLIT XTAL CWD', 'IV_PHASED', ... + 'IV_ROT XTAL', 'IV_ROT MIRROR', 'ENDOCAV_PA', 'ENDOCAV_MECH', ... + 'ENDOCAV_CLA', 'ENDOCAV_AA', 'ENDOCAV_LINEAR', 'VECTOR_PHASED'}; + + + +function tmers = frameIncrementTerms +%FRAMEINCREMENTTERMS Frame increment pointer defined values +% +% See PS 3.3 Sec. C.8.5.6.1.4 + +terms = {uint16(sscanf('0018 1063', '%x')'), ... + uint16(sscanf('0018 1065', '%x')')}; + + + +function terms = scanOptionsTerms +%SCANOPTIONSTERMS Scan Options defined terms +% +% See PS 3.3 Sec. C.8.7.1.1.4 + +terms = {'EKG', 'PHY', 'TOMO', 'CHASE', 'STEP', 'ROTA'}; + + + +function terms = curveTypeTerms +%CURVETYPETERMS Type of Data for curves defined terms +% +% See PS 3.3 Sec. C.10.2.1.1 + +terms = {'TAC', 'PROF', 'HIST', 'ROI', 'TABL', 'FILT', 'POLY', 'ECG', ... + 'PRESSURE', 'FLOW', 'PHYSIO', 'RESP'}; + + + +function terms = axisUnitsTerms +%AXISUNITSTERMS Axis Units defined terms +% +% See PS 3.3 Sec. C.10.2.1.3 + +terms = {'SEC', 'CNTS', 'MM', 'PIXL', 'NONE', 'BPM', 'CM', 'CMS', 'CM2', ... + 'CM2S', 'CM3', 'CM3S', 'CMS2', 'DB', 'DBS', 'DEG', 'GM', 'GMM2', ... + 'HZ', 'IN', 'KG', 'LMIN', 'LMINM2', 'M2', 'MS2', 'MLM2', 'MILS', ... + 'MILV', 'MMHG', 'PCNT', 'LB'}; + + + +function lut = curveVRlut +%CURVEVRLUT VR lookup table for curve-related data + +lut = {'(50xx,0103)', {0, 'US'}, {1, 'SS'}, {2, 'FL'}, {3, 'FD'}, {4, 'SL'}}; diff --git a/CT/private/dicom_name_lookup.m b/CT/private/dicom_name_lookup.m index 6d7c359..8ecc6ef 100644 --- a/CT/private/dicom_name_lookup.m +++ b/CT/private/dicom_name_lookup.m @@ -1,24 +1,24 @@ -function name = dicom_name_lookup(groupStr, elementStr, dictionary) -%DICOM_NAME_LOOKUP Get an attribute's from the DICOM data dictionary. -% NAME = DICOM_NAME_LOOKUP(GROUP, ELEMENT, DICTIONARY) returns the NAME -% of the DICOM attribute with the GROUP and ELEMENT specified as strings. -% -% The purpose of this function is to avoid hardcoding mutable attribute -% names into access and modification functions. -% -% Example: -% dicom_name_lookup('7fe0','0010', dictionary) returns 'PixelData' -% (provided that its name is PixelData in the data dictionary). -% -% See also DICOM_DICT_LOOKUP, DICOM_GET_DICTIONARY. - -% Copyright 1993-2010 The MathWorks, Inc. -% - -attr = dicom_dict_lookup(groupStr, elementStr, dictionary); - -if (isempty(attr)) - name = ''; -else - name = attr.Name; -end +function name = dicom_name_lookup(groupStr, elementStr, dictionary) +%DICOM_NAME_LOOKUP Get an attribute's from the DICOM data dictionary. +% NAME = DICOM_NAME_LOOKUP(GROUP, ELEMENT, DICTIONARY) returns the NAME +% of the DICOM attribute with the GROUP and ELEMENT specified as strings. +% +% The purpose of this function is to avoid hardcoding mutable attribute +% names into access and modification functions. +% +% Example: +% dicom_name_lookup('7fe0','0010', dictionary) returns 'PixelData' +% (provided that its name is PixelData in the data dictionary). +% +% See also DICOM_DICT_LOOKUP, DICOM_GET_DICTIONARY. + +% Copyright 1993-2010 The MathWorks, Inc. +% + +attr = dicom_dict_lookup(groupStr, elementStr, dictionary); + +if (isempty(attr)) + name = ''; +else + name = attr.Name; +end diff --git a/CT/private/dicom_open_msg.m b/CT/private/dicom_open_msg.m index b115eaa..5b96156 100644 --- a/CT/private/dicom_open_msg.m +++ b/CT/private/dicom_open_msg.m @@ -1,45 +1,45 @@ -function file = dicom_open_msg(file, mode) -%DICOM_OPEN_MSG Open the next DICOM message in the pool for processing. -% FILE = DICOM_OPEN_MSG(FILE, 'r') opens the next DICOM message in the -% pool for reading. -% -% FILE = DICOM_OPEN_MSG(FILE, 'w') opens the next DICOM message in the -% pool for writing. -% -% Note: The pool of messages is generated by DICOM_GET_MSG. - -% Copyright 1993-2010 The MathWorks, Inc. - -% Open the message. -switch (lower(mode)) -case 'r' - - file.FID = fopen(file.Filename, 'r', 'ieee-le'); - - if (file.FID < 0) - - error(message('images:dicom_open_msg:fileOpenRead', file.Filename{ file.Current_Message })); - - end - -case 'w' - - file.FID = fopen(file.Filename, 'w', 'ieee-le'); - - if (file.FID < 0) - - error(message('images:dicom_open_msg:fileOpenWrite', file.Filename)); - - end - -otherwise - - error(message('images:dicom_open_msg:openMode')) - -end - -% Set local files to default transfer syntax (for fragments). -% DICOM_SET_MMETA_ENCODING will change this if appropriate. -file.Current_Endian = 'ieee-le'; -file.Pixel_Endian = 'ieee-le'; -file.Current_VR = 'Implicit'; +function file = dicom_open_msg(file, mode) +%DICOM_OPEN_MSG Open the next DICOM message in the pool for processing. +% FILE = DICOM_OPEN_MSG(FILE, 'r') opens the next DICOM message in the +% pool for reading. +% +% FILE = DICOM_OPEN_MSG(FILE, 'w') opens the next DICOM message in the +% pool for writing. +% +% Note: The pool of messages is generated by DICOM_GET_MSG. + +% Copyright 1993-2010 The MathWorks, Inc. + +% Open the message. +switch (lower(mode)) +case 'r' + + file.FID = fopen(file.Filename, 'r', 'ieee-le'); + + if (file.FID < 0) + + error(message('images:dicom_open_msg:fileOpenRead', file.Filename{ file.Current_Message })); + + end + +case 'w' + + file.FID = fopen(file.Filename, 'w', 'ieee-le'); + + if (file.FID < 0) + + error(message('images:dicom_open_msg:fileOpenWrite', file.Filename)); + + end + +otherwise + + error(message('images:dicom_open_msg:openMode')) + +end + +% Set local files to default transfer syntax (for fragments). +% DICOM_SET_MMETA_ENCODING will change this if appropriate. +file.Current_Endian = 'ieee-le'; +file.Pixel_Endian = 'ieee-le'; +file.Current_VR = 'Implicit'; diff --git a/CT/private/dicom_prep_FileMetadata.m b/CT/private/dicom_prep_FileMetadata.m index f7c7006..6f15ce7 100644 --- a/CT/private/dicom_prep_FileMetadata.m +++ b/CT/private/dicom_prep_FileMetadata.m @@ -1,62 +1,62 @@ -function metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary) -%DICOM_PREP_FILEMETADATA Fill necessary file metadata information. -% -% See PS 3.10 Sec. 7.1 - -% Copyright 1993-2010 The MathWorks, Inc. - -metadata.(dicom_name_lookup('0002', '0001', dictionary)) = uint8([0 1]); -metadata.(dicom_name_lookup('0002', '0002', dictionary)) = IOD_UID; -metadata.(dicom_name_lookup('0002', '0003', dictionary)) = ... - metadata.(dicom_name_lookup('0008', '0018', dictionary)); -metadata.(dicom_name_lookup('0002', '0010', dictionary)) = txfr; - -[uid, name] = get_implementation_details; -metadata.(dicom_name_lookup('0002', '0012', dictionary)) = uid; -metadata.(dicom_name_lookup('0002', '0013', dictionary)) = name; - - - -function [implementation_UID, implementation_name] = get_implementation_details -%GET_IMPLEMENTATION_DETAILS Create implementation class UID and name. - -% The Implementation Class UID (0002,0012) is a UID that we create which -% identifies the modality (i.e., the Image Processing Toolbox) writing -% the file. The Implementation Version Name (0002,0013 is a description -% of the modality (16 characters max). These values must be updated -% together. - -% Class UIDs generated by the Image Processing Toolbox have the following -% structure: -% -% IPT_UID_ROOT.3.x.y -% -% Where 3 is the (new) constant value for class UIDs, x is the constant class, -% and y is the individual instance of the class. - -ipt_root = dicom_generate_uid('ipt_root'); -ipt_uid_definitions = '3'; -imp_class_constant = '100'; - -% This is quite slow and does not compile. Hard-code the value until its -% performance improves. - -% ipt_ver = ver('images'); -% -% if (isempty(ipt_ver)) -% error('images:dicom_prep_FileMetadata:unknownIPT', 'Unrecognized Image Processing Toolbox version.') -% end -% -% idx = find(ipt_ver(1).Version > '9'); -% if (~isempty(idx)) -% ipt_ver(1).Version(idx(1):end) = ''; -% end -ipt_ver(1).Version = '7.1'; - -implementation_UID = [ipt_root, '.', ... - ipt_uid_definitions, '.', ... - imp_class_constant, '.', ... - ipt_ver(1).Version]; - -implementation_name = ['MATLAB IPT ' ipt_ver(1).Version]; - +function metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary) +%DICOM_PREP_FILEMETADATA Fill necessary file metadata information. +% +% See PS 3.10 Sec. 7.1 + +% Copyright 1993-2010 The MathWorks, Inc. + +metadata.(dicom_name_lookup('0002', '0001', dictionary)) = uint8([0 1]); +metadata.(dicom_name_lookup('0002', '0002', dictionary)) = IOD_UID; +metadata.(dicom_name_lookup('0002', '0003', dictionary)) = ... + metadata.(dicom_name_lookup('0008', '0018', dictionary)); +metadata.(dicom_name_lookup('0002', '0010', dictionary)) = txfr; + +[uid, name] = get_implementation_details; +metadata.(dicom_name_lookup('0002', '0012', dictionary)) = uid; +metadata.(dicom_name_lookup('0002', '0013', dictionary)) = name; + + + +function [implementation_UID, implementation_name] = get_implementation_details +%GET_IMPLEMENTATION_DETAILS Create implementation class UID and name. + +% The Implementation Class UID (0002,0012) is a UID that we create which +% identifies the modality (i.e., the Image Processing Toolbox) writing +% the file. The Implementation Version Name (0002,0013 is a description +% of the modality (16 characters max). These values must be updated +% together. + +% Class UIDs generated by the Image Processing Toolbox have the following +% structure: +% +% IPT_UID_ROOT.3.x.y +% +% Where 3 is the (new) constant value for class UIDs, x is the constant class, +% and y is the individual instance of the class. + +ipt_root = dicom_generate_uid('ipt_root'); +ipt_uid_definitions = '3'; +imp_class_constant = '100'; + +% This is quite slow and does not compile. Hard-code the value until its +% performance improves. + +% ipt_ver = ver('images'); +% +% if (isempty(ipt_ver)) +% error('images:dicom_prep_FileMetadata:unknownIPT', 'Unrecognized Image Processing Toolbox version.') +% end +% +% idx = find(ipt_ver(1).Version > '9'); +% if (~isempty(idx)) +% ipt_ver(1).Version(idx(1):end) = ''; +% end +ipt_ver(1).Version = '7.1'; + +implementation_UID = [ipt_root, '.', ... + ipt_uid_definitions, '.', ... + imp_class_constant, '.', ... + ipt_ver(1).Version]; + +implementation_name = ['MATLAB IPT ' ipt_ver(1).Version]; + diff --git a/CT/private/dicom_prep_FrameOfReference.m b/CT/private/dicom_prep_FrameOfReference.m index 216c5a6..66c4c97 100644 --- a/CT/private/dicom_prep_FrameOfReference.m +++ b/CT/private/dicom_prep_FrameOfReference.m @@ -1,12 +1,12 @@ -function metadata = dicom_prep_FrameOfReference(metadata, dictionary) -%DICOM_PREP_FRAMEOFREFERENCE Fill necessary values for Frame of Reference. -% -% See PS 3.3 Sec C.7.4.1 - -% Copyright 1993-2010 The MathWorks, Inc. -% - -name = dicom_name_lookup('0020', '0052', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = dicomuid; -end +function metadata = dicom_prep_FrameOfReference(metadata, dictionary) +%DICOM_PREP_FRAMEOFREFERENCE Fill necessary values for Frame of Reference. +% +% See PS 3.3 Sec C.7.4.1 + +% Copyright 1993-2010 The MathWorks, Inc. +% + +name = dicom_name_lookup('0020', '0052', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = dicomuid; +end diff --git a/CT/private/dicom_prep_GeneralImage.m b/CT/private/dicom_prep_GeneralImage.m index 54f5e5f..714c09d 100644 --- a/CT/private/dicom_prep_GeneralImage.m +++ b/CT/private/dicom_prep_GeneralImage.m @@ -1,41 +1,41 @@ -function metadata = dicom_prep_GeneralImage(metadata, dictionary) -%DICOM_PREP_GENERALIMAGE Set necessary values for General Image module -% -% See PS 3.3 Sec C.7.6.1 - -% Copyright 1993-2010 The MathWorks, Inc. -% - -image_time = now; - -ImageDate_name = dicom_name_lookup('0008', '0023', dictionary); -ImageTime_name = dicom_name_lookup('0008', '0033', dictionary); - -if (~isfield(metadata, ImageDate_name)) - metadata.(ImageDate_name) = convert_date(image_time, 'DA'); -end - -if (~isfield(metadata, ImageTime_name)) - metadata.(ImageTime_name) = convert_date(image_time, 'TM'); -end - - - -function dicomDate = convert_date(ML_date, formatString) -%CONVERT_DATE Convert a MATLAB datenum to a DICOM date/time string -% -% See PS 3.5 Sec. 6.2 for DICOM date/time formats. - -vec = datevec(ML_date); - -switch (formatString) -case 'DA' - % YYYYMMDD - dicomDate = sprintf('%04d%02d%02d', vec(1:3)); -case 'DT' - % YYYYMMDDHHMMSS.FFFFFF - dicomDate = sprintf('%04d%02d%02d%02d%02d%09.6f', vec); -case 'TM' - %HHMMSS.FFFFFF - dicomDate = sprintf('%02d%02d%09.6f', vec(4:6)); -end +function metadata = dicom_prep_GeneralImage(metadata, dictionary) +%DICOM_PREP_GENERALIMAGE Set necessary values for General Image module +% +% See PS 3.3 Sec C.7.6.1 + +% Copyright 1993-2010 The MathWorks, Inc. +% + +image_time = now; + +ImageDate_name = dicom_name_lookup('0008', '0023', dictionary); +ImageTime_name = dicom_name_lookup('0008', '0033', dictionary); + +if (~isfield(metadata, ImageDate_name)) + metadata.(ImageDate_name) = convert_date(image_time, 'DA'); +end + +if (~isfield(metadata, ImageTime_name)) + metadata.(ImageTime_name) = convert_date(image_time, 'TM'); +end + + + +function dicomDate = convert_date(ML_date, formatString) +%CONVERT_DATE Convert a MATLAB datenum to a DICOM date/time string +% +% See PS 3.5 Sec. 6.2 for DICOM date/time formats. + +vec = datevec(ML_date); + +switch (formatString) +case 'DA' + % YYYYMMDD + dicomDate = sprintf('%04d%02d%02d', vec(1:3)); +case 'DT' + % YYYYMMDDHHMMSS.FFFFFF + dicomDate = sprintf('%04d%02d%02d%02d%02d%09.6f', vec); +case 'TM' + %HHMMSS.FFFFFF + dicomDate = sprintf('%02d%02d%09.6f', vec(4:6)); +end diff --git a/CT/private/dicom_prep_GeneralSeries.m b/CT/private/dicom_prep_GeneralSeries.m index caeda87..df26a3c 100644 --- a/CT/private/dicom_prep_GeneralSeries.m +++ b/CT/private/dicom_prep_GeneralSeries.m @@ -1,16 +1,16 @@ -function metadata = dicom_prep_GeneralSeries(metadata, dictionary) -%DICOM_PREP_DICOM_PREP_GENERALSERIES Fill values for General Study module. -% -% See PS 3.3 Sec. C.7.2.1 - -% Copyright 1993-2010 The MathWorks, Inc. - -name = dicom_name_lookup('0008', '0060', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = 'OT'; -end - -name = dicom_name_lookup('0020', '000E', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = dicomuid; -end +function metadata = dicom_prep_GeneralSeries(metadata, dictionary) +%DICOM_PREP_DICOM_PREP_GENERALSERIES Fill values for General Study module. +% +% See PS 3.3 Sec. C.7.2.1 + +% Copyright 1993-2010 The MathWorks, Inc. + +name = dicom_name_lookup('0008', '0060', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = 'OT'; +end + +name = dicom_name_lookup('0020', '000E', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = dicomuid; +end diff --git a/CT/private/dicom_prep_GeneralStudy.m b/CT/private/dicom_prep_GeneralStudy.m index c295aae..87d9823 100644 --- a/CT/private/dicom_prep_GeneralStudy.m +++ b/CT/private/dicom_prep_GeneralStudy.m @@ -1,11 +1,11 @@ -function metadata = dicom_prep_GeneralStudy(metadata, dictionary) -%DICOM_PREP_DICOM_PREP_GENERALSTUDY Fill values for General Study module. -% -% See PS 3.3 Sec. C.7.2.1 - -% Copyright 1993-2010 The MathWorks, Inc. - -name = dicom_name_lookup('0020', '000D', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = dicomuid; -end +function metadata = dicom_prep_GeneralStudy(metadata, dictionary) +%DICOM_PREP_DICOM_PREP_GENERALSTUDY Fill values for General Study module. +% +% See PS 3.3 Sec. C.7.2.1 + +% Copyright 1993-2010 The MathWorks, Inc. + +name = dicom_name_lookup('0020', '000D', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = dicomuid; +end diff --git a/CT/private/dicom_prep_ImagePixel.m b/CT/private/dicom_prep_ImagePixel.m index b247542..0bef522 100644 --- a/CT/private/dicom_prep_ImagePixel.m +++ b/CT/private/dicom_prep_ImagePixel.m @@ -1,404 +1,404 @@ -function metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useExistingBitDepths, dictionary) -%DICOM_PREP_IMAGEPIXEL Set necessary values for Image Pixel module. -% -% See PS 3.3 Sec C.7.6.3 - -% Copyright 1993-2014 The MathWorks, Inc. - -metadata(1).(dicom_name_lookup('0028', '0002', dictionary)) = size(X, 3); -metadata.(dicom_name_lookup('0028', '0004', dictionary)) = getPhotometricInterp(metadata, X, map, txfr, dictionary); -metadata.(dicom_name_lookup('0028', '0010', dictionary)) = size(X, 1); -metadata.(dicom_name_lookup('0028', '0011', dictionary)) = size(X, 2); - -[ba, bs, hb, pr] = getPixelStorage(X, txfr, useExistingBitDepths, metadata, dictionary); -metadata.(dicom_name_lookup('0028', '0100', dictionary)) = ba; -metadata.(dicom_name_lookup('0028', '0101', dictionary)) = bs; -metadata.(dicom_name_lookup('0028', '0102', dictionary)) = hb; -metadata.(dicom_name_lookup('0028', '0103', dictionary)) = pr; - -metadata.(dicom_name_lookup('7FE0', '0010', dictionary)) = encodePixelData(metadata, X, map, txfr, dictionary); - -% Values that depend on the number of samples present. -if (metadata.(dicom_name_lookup('0028', '0002', dictionary)) > 1) - - metadata.(dicom_name_lookup('0028', '0006', dictionary)) = 0; % Interleaved pixels. - -else - - % Only include min/max pixel value for single-sample (grayscale) images. - switch class(X) - case {'single', 'double', 'logical'} - % The min/max values will have changed during prep. - data = metadata.(dicom_name_lookup('7FE0', '0010', dictionary)); - metadata.(dicom_name_lookup('0028', '0106', dictionary)) = min(data(:)); - metadata.(dicom_name_lookup('0028', '0107', dictionary)) = max(data(:)); - otherwise - metadata.(dicom_name_lookup('0028', '0106', dictionary)) = min(X(:)); - metadata.(dicom_name_lookup('0028', '0107', dictionary)) = max(X(:)); - end - -end - -if (~isempty(map)) - [rdes, gdes, bdes, rdata, gdata, bdata] = processColormap(X, map); - metadata.(dicom_name_lookup('0028', '1101', dictionary)) = rdes; - metadata.(dicom_name_lookup('0028', '1102', dictionary)) = gdes; - metadata.(dicom_name_lookup('0028', '1103', dictionary)) = bdes; - metadata.(dicom_name_lookup('0028', '1201', dictionary)) = rdata; - metadata.(dicom_name_lookup('0028', '1202', dictionary)) = gdata; - metadata.(dicom_name_lookup('0028', '1203', dictionary)) = bdata; -end - -% Technically (0028,0008) isn't part of the Image Pixel module, but it's -% fundamental to several overlapping, optional modules that we don't -% have "prep" functions for yet; and we need to put the right value when -% copying metadata. -if ((isfield(metadata, dicom_name_lookup('0028', '0008', dictionary))) || ... - (size(X,4) > 1)) - - % Add the attribute if it isn't present and there's more than one frame. - metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X, 4); - -end - - -function pInt = getPhotometricInterp(metadata, X, map, txfr, dictionary) -%GETPHOTOMETRICINTERP Get the string code for the image's photometric interp - -if (isempty(map)) - - if (size(X, 3) == 1) - - % 0 is black. Grayscale images should always have a value of - % 'MONOCHROME1' or 'MONOCHROME2', regardless of compression. The - % default should be consistent with IPT's interpretation that the - % "minimum is black," which is 'MONOCHROME2'. - pIntFieldname = dicom_name_lookup('0028', '0004', dictionary); - if isfield(metadata, pIntFieldname) - switch (metadata.(pIntFieldname)) - case {'MONOCHROME1', 'MONOCHROME2'} - pInt = metadata.(pIntFieldname); - otherwise - pInt = 'MONOCHROME2'; - end - else - pInt = 'MONOCHROME2'; - end - - elseif (size(X, 3) == 3) - - switch (txfr) - case {'1.2.840.10008.1.2.4.50' - '1.2.840.10008.1.2.4.51' - '1.2.840.10008.1.2.4.52' - '1.2.840.10008.1.2.4.53' - '1.2.840.10008.1.2.4.54' - '1.2.840.10008.1.2.4.55' - '1.2.840.10008.1.2.4.56' - '1.2.840.10008.1.2.4.57' - '1.2.840.10008.1.2.4.58' - '1.2.840.10008.1.2.4.59' - '1.2.840.10008.1.2.4.60' - '1.2.840.10008.1.2.4.61' - '1.2.840.10008.1.2.4.62' - '1.2.840.10008.1.2.4.63' - '1.2.840.10008.1.2.4.64' - '1.2.840.10008.1.2.4.65' - '1.2.840.10008.1.2.4.66' - '1.2.840.10008.1.2.4.70' - '1.2.840.10008.1.2.4.80' - '1.2.840.10008.1.2.4.81'} - - pInt = 'YBR_FULL_422'; - - case {'1.2.840.10008.1.2.4.90' - '1.2.840.10008.1.2.4.92'} - - % See PS 3.5 Sec. 8.2.4 - pInt = 'YBR_RCT'; - - case {'1.2.840.10008.1.2.4.91' - '1.2.840.10008.1.2.4.93'} - - % See PS 3.5 Sec. 8.2.4 - pInt = 'YBR_ICT'; - - otherwise - - pInt = 'RGB'; - - end - - else - - error(message('images:dicom_prep_ImagePixel:photoInterp')) - - end - -else - - if (size(X, 3) == 1) - pInt = 'PALETTE COLOR'; - elseif (size(X, 3) == 4) - pInt = 'RGBA'; - else - error(message('images:dicom_prep_ImagePixel:photoInterp')) - end - -end - - - -function [ba, bs, hb, pr] = getPixelStorage(X, txfr, useExistingBitDepths, metadata, dictionary) -%GETPIXELSTORAGE Get details about the pixel cells. - -switch (class(X)) -case {'uint8', 'logical'} - ba = 8; - bs = 8; - pr = 0; - -case {'int8'} - ba = 8; - bs = 8; - pr = 1; - -case {'uint16'} - ba = 16; - bs = 16; - pr = 0; - -case {'int16'} - ba = 16; - bs = 16; - pr = 1; - -case {'uint32'} - ba = 32; - bs = 32; - pr = 0; - -case {'int32'} - ba = 32; - bs = 32; - pr = 1; - -case {'double'} - - switch (txfr) - case '1.2.840.10008.1.2.4.50' - ba = 8; - bs = 8; - pr = 0; - - otherwise - % Customers report that UINT8 data isn't large enough. - ba = 16; - bs = 16; - pr = 0; - - end - -otherwise - - error(message('images:dicom_prep_ImagePixel:bitDepth')) - -end - -hb = ba - 1; - -% Override values if the user requested it and the metadata is there. -computedBitsRequired = ba; - -if (useExistingBitDepths) - % BitsAllocated - if isfield(metadata, dicom_name_lookup('0028', '0100', dictionary)) - ba = metadata.(dicom_name_lookup('0028', '0100', dictionary)); - end - - % BitsStored - if isfield(metadata, dicom_name_lookup('0028', '0101', dictionary)) - bs = metadata.(dicom_name_lookup('0028', '0101', dictionary)); - end - - % HighBit - if isfield(metadata, dicom_name_lookup('0028', '0102', dictionary)) - hb = metadata.(dicom_name_lookup('0028', '0102', dictionary)); - end -end - -% Ensure that the metadata values are reasonable. (Technically, there's -% nothing wrong with having a BA or BS value of less than 8 if the data is -% in an 8-bit integer type.) -if (bs > ba) - - error(message('images:dicom_prep_ImagePixel:bitsStoredTooBig', ... - dicom_name_lookup('0028', '0101', dictionary), ... - dicom_name_lookup('0028', '0100', dictionary))) - -elseif (hb >= ba) - - error(message('images:dicom_prep_ImagePixel:highBitTooBig', ... - dicom_name_lookup('0028', '0102', dictionary), ... - dicom_name_lookup('0028', '0100', dictionary))) - -elseif ((computedBitsRequired == 32) && (bs <= 16)) - - error(message('images:dicom_prep_ImagePixel:bitsStoredTooSmall', ... - dicom_name_lookup('0028', '0101', dictionary))) - -elseif ((computedBitsRequired == 16) && (bs <= 8)) - - error(message('images:dicom_prep_ImagePixel:bitsStoredTooSmall', ... - dicom_name_lookup('0028', '0101', dictionary))) - -end - - - -function [rdes, gdes, bdes, rdata, gdata, bdata] = processColormap(~, map) -%PROCESSCOLORMAP Turn a MATLAB colormap into a DICOM colormap. - -% Always use 16-bits. - -% First descriptor: number of rows in the table. -map_rows = size(map, 1); - -if (map_rows == (2^16)) - map_rows = 0; -end - -% Second descriptor: index to start row mapping. Always 0 for MATLAB's use -% of colormaps. - -% Third descriptor: bit-depth. -map_bits = 16; - -rdes = [map_rows 0 map_bits]; -gdes = rdes; -bdes = rdes; - -% PS 3.3 Sec. C.7.6.3.1.6 says data must span the full range. -rdata = uint16(map(:, 1) .* (2 ^ map_bits - 1)); -gdata = uint16(map(:, 2) .* (2 ^ map_bits - 1)); -bdata = uint16(map(:, 3) .* (2 ^ map_bits - 1)); - - - -function pixelData = encodePixelData(metadata, X, map, txfr, dictionary) -%ENCODEPIXELCELLS Turn a MATLAB image into DICOM-encoded pixel data. - -% -% Rescale logical and double data. -% -switch (txfr) -case {'1.2.840.10008.1.2.4.50' - '1.2.840.10008.1.2.4.51' - '1.2.840.10008.1.2.4.52' - '1.2.840.10008.1.2.4.53' - '1.2.840.10008.1.2.4.54' - '1.2.840.10008.1.2.4.55' - '1.2.840.10008.1.2.4.56' - '1.2.840.10008.1.2.4.57' - '1.2.840.10008.1.2.4.58' - '1.2.840.10008.1.2.4.59' - '1.2.840.10008.1.2.4.60' - '1.2.840.10008.1.2.4.61' - '1.2.840.10008.1.2.4.62' - '1.2.840.10008.1.2.4.63' - '1.2.840.10008.1.2.4.64' - '1.2.840.10008.1.2.4.65' - '1.2.840.10008.1.2.4.66' - '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'} - - % Let IMWRITE handle all of the transformations. - pixelCells = X; - -otherwise - - % Handle the special syntaxes where endianness changes for pixels. - X = changePixelEndian(X, txfr, metadata, dictionary); - - pixelCells = dicom_encode_pixel_cells(X, map, ... - metadata.(dicom_name_lookup('0028', '0100', dictionary)), ... - metadata.(dicom_name_lookup('0028', '0101', dictionary)), ... - metadata.(dicom_name_lookup('0028', '0102', dictionary))); - -end - -% -% Encode pixels. -% -uid_details = dicom_uid_decode(txfr); -if (uid_details.Compressed) - - % Compress the pixel cells and add delimiters. - pixelData = dicom_compress_pixel_cells(pixelCells, txfr, ... - metadata.(dicom_name_lookup('0028','0100', dictionary)), ... - size(X)); - pixelData = dicom_encode_attrs(pixelData, txfr, dicom_uid_decode(txfr)); - pixelData = pixelData{1}; % Encoding produces a cell array. - -else - - % Create the (possibly packed) pixel cells. - pixelData = pixelCells; - -end - - - -function out = changePixelEndian(in, txfr, metadata, dictionary) -%CHANGEPIXELENDIAN Swap pixel bytes for special transfer syntaxes - -uid_details = dicom_uid_decode(txfr); - -% After this function, the pixel data should be in the right order so that -% dicom_encode_attr can translate it directly to the output endianness. -% Special syntaxes where (a) the pixel endianness doesn't match the -% endianness of the rest of the metadata or (b) 32-bit data is being -% written on a big-endian machine to a little-endian transfer syntax -% require "pre-work" on the pixels to allow correct encoding later. -in_class = class(in); -if (~isequal(uid_details.Endian, uid_details.PixelEndian)) - - % Pixel data is stored OB (unswapped) iff bits/sample <= 8, otherwise - % it's stored OW (swapped). When swapping OW data, words are swapped - % not the entire data. So a 32-bit sample with bytes (ABCD) becomes - % (BADC). - - if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) <= 16) % BitsAllocated - out = dicom_typecast(in(:), 'uint8', true); - out = dicom_typecast(out, in_class); - else - out = dicom_typecast(in(:), 'uint16', false); - out = dicom_typecast(out, 'uint8', true); - out = dicom_typecast(out, in_class); - end - - out = reshape(out, size(in)); - -else - - [~, ~, endian] = computer; - if (isequal(endian, 'B') && ... - isequal(uid_details.PixelEndian, 'ieee-le') && ... - metadata.(dicom_name_lookup('0028', '0100', dictionary)) > 16) - - % The pixels need to look like the final little-endian - % representation, which will then be undone and redone. - out = dicom_typecast(in(:), 'uint16', true); - out = dicom_typecast(out, 'uint8', true); - out = dicom_typecast(out, in_class); - - out = reshape(out, size(in)); - - else - out = in; - end - -end +function metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useExistingBitDepths, dictionary) +%DICOM_PREP_IMAGEPIXEL Set necessary values for Image Pixel module. +% +% See PS 3.3 Sec C.7.6.3 + +% Copyright 1993-2014 The MathWorks, Inc. + +metadata(1).(dicom_name_lookup('0028', '0002', dictionary)) = size(X, 3); +metadata.(dicom_name_lookup('0028', '0004', dictionary)) = getPhotometricInterp(metadata, X, map, txfr, dictionary); +metadata.(dicom_name_lookup('0028', '0010', dictionary)) = size(X, 1); +metadata.(dicom_name_lookup('0028', '0011', dictionary)) = size(X, 2); + +[ba, bs, hb, pr] = getPixelStorage(X, txfr, useExistingBitDepths, metadata, dictionary); +metadata.(dicom_name_lookup('0028', '0100', dictionary)) = ba; +metadata.(dicom_name_lookup('0028', '0101', dictionary)) = bs; +metadata.(dicom_name_lookup('0028', '0102', dictionary)) = hb; +metadata.(dicom_name_lookup('0028', '0103', dictionary)) = pr; + +metadata.(dicom_name_lookup('7FE0', '0010', dictionary)) = encodePixelData(metadata, X, map, txfr, dictionary); + +% Values that depend on the number of samples present. +if (metadata.(dicom_name_lookup('0028', '0002', dictionary)) > 1) + + metadata.(dicom_name_lookup('0028', '0006', dictionary)) = 0; % Interleaved pixels. + +else + + % Only include min/max pixel value for single-sample (grayscale) images. + switch class(X) + case {'single', 'double', 'logical'} + % The min/max values will have changed during prep. + data = metadata.(dicom_name_lookup('7FE0', '0010', dictionary)); + metadata.(dicom_name_lookup('0028', '0106', dictionary)) = min(data(:)); + metadata.(dicom_name_lookup('0028', '0107', dictionary)) = max(data(:)); + otherwise + metadata.(dicom_name_lookup('0028', '0106', dictionary)) = min(X(:)); + metadata.(dicom_name_lookup('0028', '0107', dictionary)) = max(X(:)); + end + +end + +if (~isempty(map)) + [rdes, gdes, bdes, rdata, gdata, bdata] = processColormap(X, map); + metadata.(dicom_name_lookup('0028', '1101', dictionary)) = rdes; + metadata.(dicom_name_lookup('0028', '1102', dictionary)) = gdes; + metadata.(dicom_name_lookup('0028', '1103', dictionary)) = bdes; + metadata.(dicom_name_lookup('0028', '1201', dictionary)) = rdata; + metadata.(dicom_name_lookup('0028', '1202', dictionary)) = gdata; + metadata.(dicom_name_lookup('0028', '1203', dictionary)) = bdata; +end + +% Technically (0028,0008) isn't part of the Image Pixel module, but it's +% fundamental to several overlapping, optional modules that we don't +% have "prep" functions for yet; and we need to put the right value when +% copying metadata. +if ((isfield(metadata, dicom_name_lookup('0028', '0008', dictionary))) || ... + (size(X,4) > 1)) + + % Add the attribute if it isn't present and there's more than one frame. + metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X, 4); + +end + + +function pInt = getPhotometricInterp(metadata, X, map, txfr, dictionary) +%GETPHOTOMETRICINTERP Get the string code for the image's photometric interp + +if (isempty(map)) + + if (size(X, 3) == 1) + + % 0 is black. Grayscale images should always have a value of + % 'MONOCHROME1' or 'MONOCHROME2', regardless of compression. The + % default should be consistent with IPT's interpretation that the + % "minimum is black," which is 'MONOCHROME2'. + pIntFieldname = dicom_name_lookup('0028', '0004', dictionary); + if isfield(metadata, pIntFieldname) + switch (metadata.(pIntFieldname)) + case {'MONOCHROME1', 'MONOCHROME2'} + pInt = metadata.(pIntFieldname); + otherwise + pInt = 'MONOCHROME2'; + end + else + pInt = 'MONOCHROME2'; + end + + elseif (size(X, 3) == 3) + + switch (txfr) + case {'1.2.840.10008.1.2.4.50' + '1.2.840.10008.1.2.4.51' + '1.2.840.10008.1.2.4.52' + '1.2.840.10008.1.2.4.53' + '1.2.840.10008.1.2.4.54' + '1.2.840.10008.1.2.4.55' + '1.2.840.10008.1.2.4.56' + '1.2.840.10008.1.2.4.57' + '1.2.840.10008.1.2.4.58' + '1.2.840.10008.1.2.4.59' + '1.2.840.10008.1.2.4.60' + '1.2.840.10008.1.2.4.61' + '1.2.840.10008.1.2.4.62' + '1.2.840.10008.1.2.4.63' + '1.2.840.10008.1.2.4.64' + '1.2.840.10008.1.2.4.65' + '1.2.840.10008.1.2.4.66' + '1.2.840.10008.1.2.4.70' + '1.2.840.10008.1.2.4.80' + '1.2.840.10008.1.2.4.81'} + + pInt = 'YBR_FULL_422'; + + case {'1.2.840.10008.1.2.4.90' + '1.2.840.10008.1.2.4.92'} + + % See PS 3.5 Sec. 8.2.4 + pInt = 'YBR_RCT'; + + case {'1.2.840.10008.1.2.4.91' + '1.2.840.10008.1.2.4.93'} + + % See PS 3.5 Sec. 8.2.4 + pInt = 'YBR_ICT'; + + otherwise + + pInt = 'RGB'; + + end + + else + + error(message('images:dicom_prep_ImagePixel:photoInterp')) + + end + +else + + if (size(X, 3) == 1) + pInt = 'PALETTE COLOR'; + elseif (size(X, 3) == 4) + pInt = 'RGBA'; + else + error(message('images:dicom_prep_ImagePixel:photoInterp')) + end + +end + + + +function [ba, bs, hb, pr] = getPixelStorage(X, txfr, useExistingBitDepths, metadata, dictionary) +%GETPIXELSTORAGE Get details about the pixel cells. + +switch (class(X)) +case {'uint8', 'logical'} + ba = 8; + bs = 8; + pr = 0; + +case {'int8'} + ba = 8; + bs = 8; + pr = 1; + +case {'uint16'} + ba = 16; + bs = 16; + pr = 0; + +case {'int16'} + ba = 16; + bs = 16; + pr = 1; + +case {'uint32'} + ba = 32; + bs = 32; + pr = 0; + +case {'int32'} + ba = 32; + bs = 32; + pr = 1; + +case {'double'} + + switch (txfr) + case '1.2.840.10008.1.2.4.50' + ba = 8; + bs = 8; + pr = 0; + + otherwise + % Customers report that UINT8 data isn't large enough. + ba = 16; + bs = 16; + pr = 0; + + end + +otherwise + + error(message('images:dicom_prep_ImagePixel:bitDepth')) + +end + +hb = ba - 1; + +% Override values if the user requested it and the metadata is there. +computedBitsRequired = ba; + +if (useExistingBitDepths) + % BitsAllocated + if isfield(metadata, dicom_name_lookup('0028', '0100', dictionary)) + ba = metadata.(dicom_name_lookup('0028', '0100', dictionary)); + end + + % BitsStored + if isfield(metadata, dicom_name_lookup('0028', '0101', dictionary)) + bs = metadata.(dicom_name_lookup('0028', '0101', dictionary)); + end + + % HighBit + if isfield(metadata, dicom_name_lookup('0028', '0102', dictionary)) + hb = metadata.(dicom_name_lookup('0028', '0102', dictionary)); + end +end + +% Ensure that the metadata values are reasonable. (Technically, there's +% nothing wrong with having a BA or BS value of less than 8 if the data is +% in an 8-bit integer type.) +if (bs > ba) + + error(message('images:dicom_prep_ImagePixel:bitsStoredTooBig', ... + dicom_name_lookup('0028', '0101', dictionary), ... + dicom_name_lookup('0028', '0100', dictionary))) + +elseif (hb >= ba) + + error(message('images:dicom_prep_ImagePixel:highBitTooBig', ... + dicom_name_lookup('0028', '0102', dictionary), ... + dicom_name_lookup('0028', '0100', dictionary))) + +elseif ((computedBitsRequired == 32) && (bs <= 16)) + + error(message('images:dicom_prep_ImagePixel:bitsStoredTooSmall', ... + dicom_name_lookup('0028', '0101', dictionary))) + +elseif ((computedBitsRequired == 16) && (bs <= 8)) + + error(message('images:dicom_prep_ImagePixel:bitsStoredTooSmall', ... + dicom_name_lookup('0028', '0101', dictionary))) + +end + + + +function [rdes, gdes, bdes, rdata, gdata, bdata] = processColormap(~, map) +%PROCESSCOLORMAP Turn a MATLAB colormap into a DICOM colormap. + +% Always use 16-bits. + +% First descriptor: number of rows in the table. +map_rows = size(map, 1); + +if (map_rows == (2^16)) + map_rows = 0; +end + +% Second descriptor: index to start row mapping. Always 0 for MATLAB's use +% of colormaps. + +% Third descriptor: bit-depth. +map_bits = 16; + +rdes = [map_rows 0 map_bits]; +gdes = rdes; +bdes = rdes; + +% PS 3.3 Sec. C.7.6.3.1.6 says data must span the full range. +rdata = uint16(map(:, 1) .* (2 ^ map_bits - 1)); +gdata = uint16(map(:, 2) .* (2 ^ map_bits - 1)); +bdata = uint16(map(:, 3) .* (2 ^ map_bits - 1)); + + + +function pixelData = encodePixelData(metadata, X, map, txfr, dictionary) +%ENCODEPIXELCELLS Turn a MATLAB image into DICOM-encoded pixel data. + +% +% Rescale logical and double data. +% +switch (txfr) +case {'1.2.840.10008.1.2.4.50' + '1.2.840.10008.1.2.4.51' + '1.2.840.10008.1.2.4.52' + '1.2.840.10008.1.2.4.53' + '1.2.840.10008.1.2.4.54' + '1.2.840.10008.1.2.4.55' + '1.2.840.10008.1.2.4.56' + '1.2.840.10008.1.2.4.57' + '1.2.840.10008.1.2.4.58' + '1.2.840.10008.1.2.4.59' + '1.2.840.10008.1.2.4.60' + '1.2.840.10008.1.2.4.61' + '1.2.840.10008.1.2.4.62' + '1.2.840.10008.1.2.4.63' + '1.2.840.10008.1.2.4.64' + '1.2.840.10008.1.2.4.65' + '1.2.840.10008.1.2.4.66' + '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'} + + % Let IMWRITE handle all of the transformations. + pixelCells = X; + +otherwise + + % Handle the special syntaxes where endianness changes for pixels. + X = changePixelEndian(X, txfr, metadata, dictionary); + + pixelCells = dicom_encode_pixel_cells(X, map, ... + metadata.(dicom_name_lookup('0028', '0100', dictionary)), ... + metadata.(dicom_name_lookup('0028', '0101', dictionary)), ... + metadata.(dicom_name_lookup('0028', '0102', dictionary))); + +end + +% +% Encode pixels. +% +uid_details = dicom_uid_decode(txfr); +if (uid_details.Compressed) + + % Compress the pixel cells and add delimiters. + pixelData = dicom_compress_pixel_cells(pixelCells, txfr, ... + metadata.(dicom_name_lookup('0028','0100', dictionary)), ... + size(X)); + pixelData = dicom_encode_attrs(pixelData, txfr, dicom_uid_decode(txfr)); + pixelData = pixelData{1}; % Encoding produces a cell array. + +else + + % Create the (possibly packed) pixel cells. + pixelData = pixelCells; + +end + + + +function out = changePixelEndian(in, txfr, metadata, dictionary) +%CHANGEPIXELENDIAN Swap pixel bytes for special transfer syntaxes + +uid_details = dicom_uid_decode(txfr); + +% After this function, the pixel data should be in the right order so that +% dicom_encode_attr can translate it directly to the output endianness. +% Special syntaxes where (a) the pixel endianness doesn't match the +% endianness of the rest of the metadata or (b) 32-bit data is being +% written on a big-endian machine to a little-endian transfer syntax +% require "pre-work" on the pixels to allow correct encoding later. +in_class = class(in); +if (~isequal(uid_details.Endian, uid_details.PixelEndian)) + + % Pixel data is stored OB (unswapped) iff bits/sample <= 8, otherwise + % it's stored OW (swapped). When swapping OW data, words are swapped + % not the entire data. So a 32-bit sample with bytes (ABCD) becomes + % (BADC). + + if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) <= 16) % BitsAllocated + out = dicom_typecast(in(:), 'uint8', true); + out = dicom_typecast(out, in_class); + else + out = dicom_typecast(in(:), 'uint16', false); + out = dicom_typecast(out, 'uint8', true); + out = dicom_typecast(out, in_class); + end + + out = reshape(out, size(in)); + +else + + [~, ~, endian] = computer; + if (isequal(endian, 'B') && ... + isequal(uid_details.PixelEndian, 'ieee-le') && ... + metadata.(dicom_name_lookup('0028', '0100', dictionary)) > 16) + + % The pixels need to look like the final little-endian + % representation, which will then be undone and redone. + out = dicom_typecast(in(:), 'uint16', true); + out = dicom_typecast(out, 'uint8', true); + out = dicom_typecast(out, in_class); + + out = reshape(out, size(in)); + + else + out = in; + end + +end diff --git a/CT/private/dicom_prep_MRMultiFrame.m b/CT/private/dicom_prep_MRMultiFrame.m index eab1429..48e7624 100644 --- a/CT/private/dicom_prep_MRMultiFrame.m +++ b/CT/private/dicom_prep_MRMultiFrame.m @@ -1,104 +1,104 @@ -function metadata = dicom_prep_MRMultiFrame(metadata, X, txfr, dictionary) -%DICOM_PREP_MRMULTIFRAME Set multi-frame frame pointer, etc. -% -% See PS 3.3 Sec C.8.13.6 - -% Copyright 2010 The MathWorks, Inc. - -% Make sure that the Shared Functional Groups Sequnece and -% Per-frame Functional Groups Sequence attributes are provided. -% We can't make these values out of whole cloth. -if (~isfield(metadata, dicom_name_lookup('5200','9229', dictionary))) - - error(message('images:dicom_prep_MRMultiFrame:missingSharedSequence', dicom_name_lookup( '5200', '9229', dictionary ))) - -elseif (~isfield(metadata, dicom_name_lookup('5200','9230', dictionary))) - - error(message('images:dicom_prep_MRMultiFrame:missingPerFrameSequence', dicom_name_lookup( '5200', '9230', dictionary ))) - -elseif (~isfield(metadata, dicom_name_lookup('0008','0008', dictionary))) - - error(message('images:dicom_prep_MRMultiFrame:missingImageType', dicom_name_lookup( '0008', '0008', dictionary ))) - -end - - -numFrames = size(X,4); -% MultiFrame - C.7.6.6 -if (numFrames > 1) - metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X,4); -end - - -% Make sure that (0028,0009) has a value. -framePointerName = dicom_name_lookup('0028', '0009', dictionary); -if (~isfield(metadata, framePointerName)) - - % To Do. Fill this. - -end - - -% Fill what values we can invent if they're missing. -if (~isfield(metadata, dicom_name_lookup('0020','0013', dictionary))) % Instance number - - metadata.(dicom_name_lookup('0020','0013', dictionary)) = '1'; - -end - -when = now; - -if (~isfield(metadata, dicom_name_lookup('0008','0023', dictionary))) % Content date - - metadata.(dicom_name_lookup('0008','0023', dictionary)) = datestr(when, 'yyyymmdd'); - -end - -if (~isfield(metadata, dicom_name_lookup('0008','0033', dictionary))) % Content time - - metadata.(dicom_name_lookup('0008','0033', dictionary)) = datestr(when, 'HHMMSS'); - -end - -details = dicom_uid_decode(txfr); -if (hasLossyCompression(details, metadata, dictionary)) - - metadata.(dicom_name_lookup('0028','2110', dictionary)) = '01'; - metadata.(dicom_name_lookup('0028','2114', dictionary)) = details.CompressionType; - -else - - metadata.(dicom_name_lookup('0028','2110', dictionary)) = '00'; - -end - - -if (isequal(metadata.(dicom_name_lookup('0028','0004', dictionary)), ... - 'MONOCHROME2')) - - metadata.(dicom_name_lookup('2050', '0020', dictionary)) = 'IDENTITY'; - -end - - - -function tf = hasLossyCompression(details, metadata, dictionary) - -lossyAttrName = dicom_name_lookup('0028','2110', dictionary); - -if (details.LossyCompression) - - % This compression will be lossy. - tf = true; - -elseif (isfield(metadata, lossyAttrName)) - - % Lossy-ness can't be shaken. It has to be passed along. - tf = isequal(metadata.(lossyAttrName), '01'); - -else - - tf = false; - -end - +function metadata = dicom_prep_MRMultiFrame(metadata, X, txfr, dictionary) +%DICOM_PREP_MRMULTIFRAME Set multi-frame frame pointer, etc. +% +% See PS 3.3 Sec C.8.13.6 + +% Copyright 2010 The MathWorks, Inc. + +% Make sure that the Shared Functional Groups Sequnece and +% Per-frame Functional Groups Sequence attributes are provided. +% We can't make these values out of whole cloth. +if (~isfield(metadata, dicom_name_lookup('5200','9229', dictionary))) + + error(message('images:dicom_prep_MRMultiFrame:missingSharedSequence', dicom_name_lookup( '5200', '9229', dictionary ))) + +elseif (~isfield(metadata, dicom_name_lookup('5200','9230', dictionary))) + + error(message('images:dicom_prep_MRMultiFrame:missingPerFrameSequence', dicom_name_lookup( '5200', '9230', dictionary ))) + +elseif (~isfield(metadata, dicom_name_lookup('0008','0008', dictionary))) + + error(message('images:dicom_prep_MRMultiFrame:missingImageType', dicom_name_lookup( '0008', '0008', dictionary ))) + +end + + +numFrames = size(X,4); +% MultiFrame - C.7.6.6 +if (numFrames > 1) + metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X,4); +end + + +% Make sure that (0028,0009) has a value. +framePointerName = dicom_name_lookup('0028', '0009', dictionary); +if (~isfield(metadata, framePointerName)) + + % To Do. Fill this. + +end + + +% Fill what values we can invent if they're missing. +if (~isfield(metadata, dicom_name_lookup('0020','0013', dictionary))) % Instance number + + metadata.(dicom_name_lookup('0020','0013', dictionary)) = '1'; + +end + +when = now; + +if (~isfield(metadata, dicom_name_lookup('0008','0023', dictionary))) % Content date + + metadata.(dicom_name_lookup('0008','0023', dictionary)) = datestr(when, 'yyyymmdd'); + +end + +if (~isfield(metadata, dicom_name_lookup('0008','0033', dictionary))) % Content time + + metadata.(dicom_name_lookup('0008','0033', dictionary)) = datestr(when, 'HHMMSS'); + +end + +details = dicom_uid_decode(txfr); +if (hasLossyCompression(details, metadata, dictionary)) + + metadata.(dicom_name_lookup('0028','2110', dictionary)) = '01'; + metadata.(dicom_name_lookup('0028','2114', dictionary)) = details.CompressionType; + +else + + metadata.(dicom_name_lookup('0028','2110', dictionary)) = '00'; + +end + + +if (isequal(metadata.(dicom_name_lookup('0028','0004', dictionary)), ... + 'MONOCHROME2')) + + metadata.(dicom_name_lookup('2050', '0020', dictionary)) = 'IDENTITY'; + +end + + + +function tf = hasLossyCompression(details, metadata, dictionary) + +lossyAttrName = dicom_name_lookup('0028','2110', dictionary); + +if (details.LossyCompression) + + % This compression will be lossy. + tf = true; + +elseif (isfield(metadata, lossyAttrName)) + + % Lossy-ness can't be shaken. It has to be passed along. + tf = isequal(metadata.(lossyAttrName), '01'); + +else + + tf = false; + +end + diff --git a/CT/private/dicom_prep_SCImageEquipment.m b/CT/private/dicom_prep_SCImageEquipment.m index 98d349e..28ac440 100644 --- a/CT/private/dicom_prep_SCImageEquipment.m +++ b/CT/private/dicom_prep_SCImageEquipment.m @@ -1,22 +1,22 @@ -function metadata = dicom_prep_SCImageEquipment(metadata, dictionary) -%DICOM_PREP_SCIMAGEEQUIPMENT Set necessary values for Image Pixel module -% -% See PS 3.3 Sec C.8.1 - -% Copyright 1993-2010 The MathWorks, Inc. - -name = dicom_name_lookup('0008', '0064', dictionary); -if (~isfield(metadata, name)) - metadata(1).(name) = 'WSD'; -end - -name = dicom_name_lookup('0018', '1016', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = 'MathWorks'; -end - -name = dicom_name_lookup('0018', '1018', dictionary); -if (~isfield(metadata, name)) - metadata.(name) = 'MATLAB'; -end - +function metadata = dicom_prep_SCImageEquipment(metadata, dictionary) +%DICOM_PREP_SCIMAGEEQUIPMENT Set necessary values for Image Pixel module +% +% See PS 3.3 Sec C.8.1 + +% Copyright 1993-2010 The MathWorks, Inc. + +name = dicom_name_lookup('0008', '0064', dictionary); +if (~isfield(metadata, name)) + metadata(1).(name) = 'WSD'; +end + +name = dicom_name_lookup('0018', '1016', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = 'MathWorks'; +end + +name = dicom_name_lookup('0018', '1018', dictionary); +if (~isfield(metadata, name)) + metadata.(name) = 'MATLAB'; +end + diff --git a/CT/private/dicom_prep_SCMultiFrame.m b/CT/private/dicom_prep_SCMultiFrame.m index cecdfe7..936f366 100644 --- a/CT/private/dicom_prep_SCMultiFrame.m +++ b/CT/private/dicom_prep_SCMultiFrame.m @@ -1,30 +1,30 @@ -function metadata = dicom_prep_SCMultiFrame(metadata, X, dictionary) -%DICOM_PREP_SCMULTIFRAME Set multi-frame frame pointer, etc. -% -% See PS 3.3 Sec C.8.6.4 - -% Copyright 2010 The MathWorks, Inc. - -numFrames = size(X,4); -% MultiFrame - C.7.6.6 -if (numFrames > 1) - metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X,4); -end - - -% Make sure that (0028,0009) has a value. -framePointerName = dicom_name_lookup('0028', '0009', dictionary); -if (~isfield(metadata, framePointerName)) - - % The most generic thing we can do is create values for - % (0018,2002) "Frame Label Vector." - value = sprintf('Frame %d\\', 1:numFrames); - value(end) = ''; % Remove trailing slash. - - metadata.(dicom_name_lookup('0018', '2002', dictionary)) = value; - - % Make (0028,0009) point at (0018,2002). - metadata.(framePointerName) = uint16(sscanf('0018,2002', '%x,%x')); - -end - +function metadata = dicom_prep_SCMultiFrame(metadata, X, dictionary) +%DICOM_PREP_SCMULTIFRAME Set multi-frame frame pointer, etc. +% +% See PS 3.3 Sec C.8.6.4 + +% Copyright 2010 The MathWorks, Inc. + +numFrames = size(X,4); +% MultiFrame - C.7.6.6 +if (numFrames > 1) + metadata.(dicom_name_lookup('0028', '0008', dictionary)) = size(X,4); +end + + +% Make sure that (0028,0009) has a value. +framePointerName = dicom_name_lookup('0028', '0009', dictionary); +if (~isfield(metadata, framePointerName)) + + % The most generic thing we can do is create values for + % (0018,2002) "Frame Label Vector." + value = sprintf('Frame %d\\', 1:numFrames); + value(end) = ''; % Remove trailing slash. + + metadata.(dicom_name_lookup('0018', '2002', dictionary)) = value; + + % Make (0028,0009) point at (0018,2002). + metadata.(framePointerName) = uint16(sscanf('0018,2002', '%x,%x')); + +end + diff --git a/CT/private/dicom_prep_SOPCommon.m b/CT/private/dicom_prep_SOPCommon.m index 58044d9..0c9931f 100644 --- a/CT/private/dicom_prep_SOPCommon.m +++ b/CT/private/dicom_prep_SOPCommon.m @@ -1,10 +1,10 @@ -function metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary) -%DICOM_PREP_DICOM_PREP_SOPCOMMON Fill necessary values for Frame of Reference. -% -% See PS 3.3 Sec. C.12.1 - -% Copyright 1993-2010 The MathWorks, Inc. -% - -metadata.(dicom_name_lookup('0008', '0016', dictionary)) = IOD_UID; -metadata.(dicom_name_lookup('0008', '0018', dictionary)) = dicomuid; +function metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary) +%DICOM_PREP_DICOM_PREP_SOPCOMMON Fill necessary values for Frame of Reference. +% +% See PS 3.3 Sec. C.12.1 + +% Copyright 1993-2010 The MathWorks, Inc. +% + +metadata.(dicom_name_lookup('0008', '0016', dictionary)) = IOD_UID; +metadata.(dicom_name_lookup('0008', '0018', dictionary)) = dicomuid; diff --git a/CT/private/dicom_prep_metadata.m b/CT/private/dicom_prep_metadata.m index 7581eaa..4aa64f1 100644 --- a/CT/private/dicom_prep_metadata.m +++ b/CT/private/dicom_prep_metadata.m @@ -1,162 +1,162 @@ -function metadata = dicom_prep_metadata(IOD_UID, metadata, X, map, txfr, useMetadataBitDepths, dictionary) -%DICOM_PREP_METADATA Set the necessary metadata values for this IOD. -% METADATA = DICOM_PREP_METADATA(UID, METADATA, X, MAP, TXFR) sets all -% of the type 1 and type 2 metadata derivable from the image (e.g. bit -% depths) or that must be unique (e.g. UIDs). This function also -% builds the image pixel data. - -% Copyright 1993-2014 The MathWorks, Inc. - -switch (IOD_UID) -case '1.2.840.10008.5.1.4.1.1.2' - metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'CT'; - - metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); - metadata = dicom_prep_FrameOfReference(metadata, dictionary); - metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); - metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); - metadata = dicom_prep_GeneralStudy(metadata, dictionary); - metadata = dicom_prep_GeneralSeries(metadata, dictionary); - metadata = dicom_prep_GeneralImage(metadata, dictionary); - -case '1.2.840.10008.5.1.4.1.1.4' - metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'MR'; - - metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); - metadata = dicom_prep_FrameOfReference(metadata, dictionary); - metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); - metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); - metadata = dicom_prep_GeneralStudy(metadata, dictionary); - metadata = dicom_prep_GeneralSeries(metadata, dictionary); - metadata = dicom_prep_GeneralImage(metadata, dictionary); - -case '1.2.840.10008.5.1.4.1.1.4.1' - metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'MR'; - - metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); - metadata = dicom_prep_FrameOfReference(metadata, dictionary); - metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); - metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); - metadata = dicom_prep_GeneralStudy(metadata, dictionary); - metadata = dicom_prep_GeneralSeries(metadata, dictionary); - metadata = dicom_prep_GeneralImage(metadata, dictionary); - metadata = dicom_prep_MRMultiFrame(metadata, X, txfr, dictionary); - -case '1.2.840.10008.5.1.4.1.1.7' - name = dicom_name_lookup('0008', '0060', dictionary); - if (~isfield(metadata, name)) - metadata(1).(name) = 'OT'; - end - - metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); - metadata = dicom_prep_FrameOfReference(metadata, dictionary); - metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); - metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); - metadata = dicom_prep_GeneralStudy(metadata, dictionary); - metadata = dicom_prep_GeneralSeries(metadata, dictionary); - metadata = dicom_prep_GeneralImage(metadata, dictionary); - metadata = dicom_prep_SCImageEquipment(metadata, dictionary); - -case {'1.2.840.10008.5.1.4.1.1.7.1' - '1.2.840.10008.5.1.4.1.1.7.2' - '1.2.840.10008.5.1.4.1.1.7.3' - '1.2.840.10008.5.1.4.1.1.7.4'} - name = dicom_name_lookup('0008', '0060', dictionary); - if (~isfield(metadata, name)) - metadata(1).(name) = 'OT'; - end - - metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); - metadata = dicom_prep_FrameOfReference(metadata, dictionary); - metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); - metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); - metadata = dicom_prep_GeneralStudy(metadata, dictionary); - metadata = dicom_prep_GeneralSeries(metadata, dictionary); - metadata = dicom_prep_GeneralImage(metadata, dictionary); - metadata = dicom_prep_SCImageEquipment(metadata, dictionary); - - % This needs to come last. - metadata = dicom_prep_SCMultiFrame(metadata, X, dictionary); - - checkSC(IOD_UID, metadata, X, dictionary); - -otherwise - - % Unsupported SOP Class in verification mode. Display a message. - if (usejava('Desktop') && desktop('-inuse')) - docRef = 'help dicomwrite'; - else - docRef = 'help dicomwrite'; - end - - error(message('images:dicom_prep_metadata:unsupportedClass', ... - IOD_UID, docRef)) - -end - - - -function checkSC(IOD_UID, metadata, X, dictionary) -%checkSC Make sure that the metadata matches what it should. - -photometricInterp = metadata.(dicom_name_lookup('0028','0004', dictionary)); - -switch (IOD_UID) -case '1.2.840.10008.5.1.4.1.1.7.1' - - if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 1) - error(message('images:dicom_prep_metadata:bitDepthForLogicalSC', IOD_UID, 1)) - - elseif (size(X,3) ~= 1) - error(message('images:dicom_prep_metadata:numSamplesForLogicalSC', IOD_UID)) - - elseif (~isequal(photometricInterp, 'MONOCHROME2')) - error(message('images:dicom_prep_metadata:photoInterpForLogicalSC', IOD_UID)) - - end - -case '1.2.840.10008.5.1.4.1.1.7.2' - - if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 8) - error(message('images:dicom_prep_metadata:bitDepthFor8bitSC', IOD_UID, 8)) - - elseif (size(X,3) ~= 1) - error(message('images:dicom_prep_metadata:numSamplesFor8bitSC', IOD_UID)) - - elseif (~isequal(photometricInterp, 'MONOCHROME2')) - error(message('images:dicom_prep_metadata:photoInterpFor8bitSC', IOD_UID)) - - end - -case '1.2.840.10008.5.1.4.1.1.7.3' - - if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 16) - error(message('images:dicom_prep_metadata:bitDepthFor16bitSC', IOD_UID, 16)) - - elseif (size(X,3) ~= 1) - error(message('images:dicom_prep_metadata:numSamplesFor16bitSC', IOD_UID)) - - elseif (~isequal(photometricInterp, 'MONOCHROME2')) - error(message('images:dicom_prep_metadata:photoInterpFor16bitSC', IOD_UID)) - - end - -case '1.2.840.10008.5.1.4.1.1.7.4' - - if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 8) - - error(message('images:dicom_prep_metadata:bitDepthForColorSC', IOD_UID, 8)) - - elseif (size(X,3) ~= 3) - - error(message('images:dicom_prep_metadata:numSamplesForColorSC', IOD_UID)) - - elseif (~isequal(photometricInterp, 'RGB') && ... - ~isequal(photometricInterp, 'YBR_FULL_422')) - - error(message('images:dicom_prep_metadata:photoInterpForColorSC', IOD_UID)) - - end - -end - +function metadata = dicom_prep_metadata(IOD_UID, metadata, X, map, txfr, useMetadataBitDepths, dictionary) +%DICOM_PREP_METADATA Set the necessary metadata values for this IOD. +% METADATA = DICOM_PREP_METADATA(UID, METADATA, X, MAP, TXFR) sets all +% of the type 1 and type 2 metadata derivable from the image (e.g. bit +% depths) or that must be unique (e.g. UIDs). This function also +% builds the image pixel data. + +% Copyright 1993-2014 The MathWorks, Inc. + +switch (IOD_UID) +case '1.2.840.10008.5.1.4.1.1.2' + metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'CT'; + + metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); + metadata = dicom_prep_FrameOfReference(metadata, dictionary); + metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); + metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); + metadata = dicom_prep_GeneralStudy(metadata, dictionary); + metadata = dicom_prep_GeneralSeries(metadata, dictionary); + metadata = dicom_prep_GeneralImage(metadata, dictionary); + +case '1.2.840.10008.5.1.4.1.1.4' + metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'MR'; + + metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); + metadata = dicom_prep_FrameOfReference(metadata, dictionary); + metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); + metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); + metadata = dicom_prep_GeneralStudy(metadata, dictionary); + metadata = dicom_prep_GeneralSeries(metadata, dictionary); + metadata = dicom_prep_GeneralImage(metadata, dictionary); + +case '1.2.840.10008.5.1.4.1.1.4.1' + metadata(1).(dicom_name_lookup('0008', '0060', dictionary)) = 'MR'; + + metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); + metadata = dicom_prep_FrameOfReference(metadata, dictionary); + metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); + metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); + metadata = dicom_prep_GeneralStudy(metadata, dictionary); + metadata = dicom_prep_GeneralSeries(metadata, dictionary); + metadata = dicom_prep_GeneralImage(metadata, dictionary); + metadata = dicom_prep_MRMultiFrame(metadata, X, txfr, dictionary); + +case '1.2.840.10008.5.1.4.1.1.7' + name = dicom_name_lookup('0008', '0060', dictionary); + if (~isfield(metadata, name)) + metadata(1).(name) = 'OT'; + end + + metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); + metadata = dicom_prep_FrameOfReference(metadata, dictionary); + metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); + metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); + metadata = dicom_prep_GeneralStudy(metadata, dictionary); + metadata = dicom_prep_GeneralSeries(metadata, dictionary); + metadata = dicom_prep_GeneralImage(metadata, dictionary); + metadata = dicom_prep_SCImageEquipment(metadata, dictionary); + +case {'1.2.840.10008.5.1.4.1.1.7.1' + '1.2.840.10008.5.1.4.1.1.7.2' + '1.2.840.10008.5.1.4.1.1.7.3' + '1.2.840.10008.5.1.4.1.1.7.4'} + name = dicom_name_lookup('0008', '0060', dictionary); + if (~isfield(metadata, name)) + metadata(1).(name) = 'OT'; + end + + metadata = dicom_prep_ImagePixel(metadata, X, map, txfr, useMetadataBitDepths, dictionary); + metadata = dicom_prep_FrameOfReference(metadata, dictionary); + metadata = dicom_prep_SOPCommon(metadata, IOD_UID, dictionary); + metadata = dicom_prep_FileMetadata(metadata, IOD_UID, txfr, dictionary); + metadata = dicom_prep_GeneralStudy(metadata, dictionary); + metadata = dicom_prep_GeneralSeries(metadata, dictionary); + metadata = dicom_prep_GeneralImage(metadata, dictionary); + metadata = dicom_prep_SCImageEquipment(metadata, dictionary); + + % This needs to come last. + metadata = dicom_prep_SCMultiFrame(metadata, X, dictionary); + + checkSC(IOD_UID, metadata, X, dictionary); + +otherwise + + % Unsupported SOP Class in verification mode. Display a message. + if (usejava('Desktop') && desktop('-inuse')) + docRef = 'help dicomwrite'; + else + docRef = 'help dicomwrite'; + end + + error(message('images:dicom_prep_metadata:unsupportedClass', ... + IOD_UID, docRef)) + +end + + + +function checkSC(IOD_UID, metadata, X, dictionary) +%checkSC Make sure that the metadata matches what it should. + +photometricInterp = metadata.(dicom_name_lookup('0028','0004', dictionary)); + +switch (IOD_UID) +case '1.2.840.10008.5.1.4.1.1.7.1' + + if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 1) + error(message('images:dicom_prep_metadata:bitDepthForLogicalSC', IOD_UID, 1)) + + elseif (size(X,3) ~= 1) + error(message('images:dicom_prep_metadata:numSamplesForLogicalSC', IOD_UID)) + + elseif (~isequal(photometricInterp, 'MONOCHROME2')) + error(message('images:dicom_prep_metadata:photoInterpForLogicalSC', IOD_UID)) + + end + +case '1.2.840.10008.5.1.4.1.1.7.2' + + if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 8) + error(message('images:dicom_prep_metadata:bitDepthFor8bitSC', IOD_UID, 8)) + + elseif (size(X,3) ~= 1) + error(message('images:dicom_prep_metadata:numSamplesFor8bitSC', IOD_UID)) + + elseif (~isequal(photometricInterp, 'MONOCHROME2')) + error(message('images:dicom_prep_metadata:photoInterpFor8bitSC', IOD_UID)) + + end + +case '1.2.840.10008.5.1.4.1.1.7.3' + + if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 16) + error(message('images:dicom_prep_metadata:bitDepthFor16bitSC', IOD_UID, 16)) + + elseif (size(X,3) ~= 1) + error(message('images:dicom_prep_metadata:numSamplesFor16bitSC', IOD_UID)) + + elseif (~isequal(photometricInterp, 'MONOCHROME2')) + error(message('images:dicom_prep_metadata:photoInterpFor16bitSC', IOD_UID)) + + end + +case '1.2.840.10008.5.1.4.1.1.7.4' + + if (metadata.(dicom_name_lookup('0028', '0100', dictionary)) ~= 8) + + error(message('images:dicom_prep_metadata:bitDepthForColorSC', IOD_UID, 8)) + + elseif (size(X,3) ~= 3) + + error(message('images:dicom_prep_metadata:numSamplesForColorSC', IOD_UID)) + + elseif (~isequal(photometricInterp, 'RGB') && ... + ~isequal(photometricInterp, 'YBR_FULL_422')) + + error(message('images:dicom_prep_metadata:photoInterpForColorSC', IOD_UID)) + + end + +end + diff --git a/CT/private/dicom_set_imfinfo_values.m b/CT/private/dicom_set_imfinfo_values.m index f68c110..4da0ee6 100644 --- a/CT/private/dicom_set_imfinfo_values.m +++ b/CT/private/dicom_set_imfinfo_values.m @@ -1,57 +1,57 @@ -function out = dicom_set_imfinfo_values(in, file) -%DICOM_SET_IMFINFO_VALUES Get IMFINFO-specific values from DICOM data. - -% Copyright 1993-2010 The MathWorks, Inc. - -out = in; - -if (nargin == 2) - - % Allow Info struct to act as input to DICOMREAD. - out.FileStruct = file; - out.FileStruct.FID = -1; - out.StartOfPixelData = ftell(file.FID); - -end - -% Fill other IMFINFO-specific values - -if (isfield(in, 'Columns')) - out.Width = in.Columns; -end - -if (isfield(in, 'Rows')) - out.Height = in.Rows; -end - -if (isfield(in, 'BitsStored')) - out.BitDepth = in.BitsStored; -end - -if (isfield(in, 'PhotometricInterpretation')) - - % See PS 3.3-1999 Sec. C.7.6.3.1.2. - - switch (in.PhotometricInterpretation) - case {'MONOCHROME1', 'MONOCHROME2'} - out.ColorType = 'grayscale'; - - case {'PALETTE COLOR'} - out.ColorType = 'indexed'; - - case {'RGB', 'HSV', 'ARGB', 'CMYK', 'YBR_FULL', 'YBR_FULL_422', ... - 'YBR_PARTIAL_422'} - out.ColorType = 'truecolor'; - - end - -end - -if (isfield(in, 'RecognitionCode')) - - % This retired code (0008,0010) should only be present in ACR-NEMA. - - out.Format = 'ACR/NEMA'; - out.FormatVersion = in.RecognitionCode; - -end +function out = dicom_set_imfinfo_values(in, file) +%DICOM_SET_IMFINFO_VALUES Get IMFINFO-specific values from DICOM data. + +% Copyright 1993-2010 The MathWorks, Inc. + +out = in; + +if (nargin == 2) + + % Allow Info struct to act as input to DICOMREAD. + out.FileStruct = file; + out.FileStruct.FID = -1; + out.StartOfPixelData = ftell(file.FID); + +end + +% Fill other IMFINFO-specific values + +if (isfield(in, 'Columns')) + out.Width = in.Columns; +end + +if (isfield(in, 'Rows')) + out.Height = in.Rows; +end + +if (isfield(in, 'BitsStored')) + out.BitDepth = in.BitsStored; +end + +if (isfield(in, 'PhotometricInterpretation')) + + % See PS 3.3-1999 Sec. C.7.6.3.1.2. + + switch (in.PhotometricInterpretation) + case {'MONOCHROME1', 'MONOCHROME2'} + out.ColorType = 'grayscale'; + + case {'PALETTE COLOR'} + out.ColorType = 'indexed'; + + case {'RGB', 'HSV', 'ARGB', 'CMYK', 'YBR_FULL', 'YBR_FULL_422', ... + 'YBR_PARTIAL_422'} + out.ColorType = 'truecolor'; + + end + +end + +if (isfield(in, 'RecognitionCode')) + + % This retired code (0008,0010) should only be present in ACR-NEMA. + + out.Format = 'ACR/NEMA'; + out.FormatVersion = in.RecognitionCode; + +end diff --git a/CT/private/dicom_strmatch.m b/CT/private/dicom_strmatch.m index cbc14c2..dfc1fc9 100644 --- a/CT/private/dicom_strmatch.m +++ b/CT/private/dicom_strmatch.m @@ -1,8 +1,8 @@ -function idx = dicom_strmatch(str, cellOfStrings) -%DICOM_STRMATCH Find substrings at beginning of larger string. -% IDX = DICOM_STRMATCH(STR, CELLOFSTRINGS) looks and acts like -% STRMATCH but isn't STRMATCH. - -% Copyright 2011 The MathWorks, Inc. - -idx = find(strncmpi(str, cellOfStrings, numel(str))); +function idx = dicom_strmatch(str, cellOfStrings) +%DICOM_STRMATCH Find substrings at beginning of larger string. +% IDX = DICOM_STRMATCH(STR, CELLOFSTRINGS) looks and acts like +% STRMATCH but isn't STRMATCH. + +% Copyright 2011 The MathWorks, Inc. + +idx = find(strncmpi(str, cellOfStrings, numel(str))); diff --git a/CT/private/dicom_supported_txfr_syntax.m b/CT/private/dicom_supported_txfr_syntax.m index ffb7064..87210c2 100644 --- a/CT/private/dicom_supported_txfr_syntax.m +++ b/CT/private/dicom_supported_txfr_syntax.m @@ -1,97 +1,97 @@ -function [supported, encapsulated, name] = dicom_supported_txfr_syntax(txfr_uid) -%DICOM_SUPPORTED_TXFR_SYNTAX Determine if a transfer syntax is supported. -% [TF, ENCAP, NAME] = DICOM_SUPPORTED_TXFR_SYNTAX(UID) uses the unique -% identifier UID of transfer syntax NAME to see if it is supported. If -% UID is supported TF has a value of 1; TF will be 0 otherwise. ENCAP -% is 1 if the syntax is used for uncapsulated data, which is usually -% compressed. -% -% If UID belongs to an unknown transfer syntax, TF is 0 and ENCAP is 1, -% as all native transfer syntaxes are specified. -% -% See also DICOM_UID_DECODE. - -% Copyright 1993-2005 The MathWorks, Inc. - - -% Get details about the syntax. -uid = dicom_uid_decode(txfr_uid); - -switch (txfr_uid) -case {'1.2.840.10008.1.2'} - - % Default syntax. All applications must support it. - supported = 1; - encapsulated = 0; - name = uid.Name; - - -case {'1.2.840.10008.1.2.1' - '1.2.840.10008.1.2.2' - '1.2.840.113619.5.2'} - - % Other uncompressed formats. - supported = 1; - encapsulated = 0; - name = uid.Name; - -case {'1.2.840.10008.1.2.5'} - - % RLE. - supported = 1; - encapsulated = 1; - name = uid.Name; - -case {'1.2.840.10008.1.2.4.57' - '1.2.840.10008.1.2.4.65' - '1.2.840.10008.1.2.4.70'} - - % JPEG lossless syntaxes. - supported = 1; - encapsulated = 1; - name = uid.Name; - -case {'1.2.840.10008.1.2.4.50' - '1.2.840.10008.1.2.4.51' - '1.2.840.10008.1.2.4.53' - '1.2.840.10008.1.2.4.55' - '1.2.840.10008.1.2.4.59' - '1.2.840.10008.1.2.4.61' - '1.2.840.10008.1.2.4.63'} - - % JPEG lossy syntaxes. - supported = 1; - encapsulated = 1; - name = uid.Name; - -case {'1.2.840.10008.1.2.4.52' - '1.2.840.10008.1.2.4.54' - '1.2.840.10008.1.2.4.56' - '1.2.840.10008.1.2.4.58' - '1.2.840.10008.1.2.4.60' - '1.2.840.10008.1.2.4.62' - '1.2.840.10008.1.2.4.64' - '1.2.840.10008.1.2.4.66'} - - % JPEG lossy syntaxes with arithmetic coding. - supported = 0; - encapsulated = 1; - name = uid.Name; - -otherwise - - % Some other transfer syntax. Unsupported. - supported = 0; - encapsulated = 1; - - if (~isempty(uid)) - - name = uid.Name; - - else - - name = txfr_uid; - - end - -end +function [supported, encapsulated, name] = dicom_supported_txfr_syntax(txfr_uid) +%DICOM_SUPPORTED_TXFR_SYNTAX Determine if a transfer syntax is supported. +% [TF, ENCAP, NAME] = DICOM_SUPPORTED_TXFR_SYNTAX(UID) uses the unique +% identifier UID of transfer syntax NAME to see if it is supported. If +% UID is supported TF has a value of 1; TF will be 0 otherwise. ENCAP +% is 1 if the syntax is used for uncapsulated data, which is usually +% compressed. +% +% If UID belongs to an unknown transfer syntax, TF is 0 and ENCAP is 1, +% as all native transfer syntaxes are specified. +% +% See also DICOM_UID_DECODE. + +% Copyright 1993-2005 The MathWorks, Inc. + + +% Get details about the syntax. +uid = dicom_uid_decode(txfr_uid); + +switch (txfr_uid) +case {'1.2.840.10008.1.2'} + + % Default syntax. All applications must support it. + supported = 1; + encapsulated = 0; + name = uid.Name; + + +case {'1.2.840.10008.1.2.1' + '1.2.840.10008.1.2.2' + '1.2.840.113619.5.2'} + + % Other uncompressed formats. + supported = 1; + encapsulated = 0; + name = uid.Name; + +case {'1.2.840.10008.1.2.5'} + + % RLE. + supported = 1; + encapsulated = 1; + name = uid.Name; + +case {'1.2.840.10008.1.2.4.57' + '1.2.840.10008.1.2.4.65' + '1.2.840.10008.1.2.4.70'} + + % JPEG lossless syntaxes. + supported = 1; + encapsulated = 1; + name = uid.Name; + +case {'1.2.840.10008.1.2.4.50' + '1.2.840.10008.1.2.4.51' + '1.2.840.10008.1.2.4.53' + '1.2.840.10008.1.2.4.55' + '1.2.840.10008.1.2.4.59' + '1.2.840.10008.1.2.4.61' + '1.2.840.10008.1.2.4.63'} + + % JPEG lossy syntaxes. + supported = 1; + encapsulated = 1; + name = uid.Name; + +case {'1.2.840.10008.1.2.4.52' + '1.2.840.10008.1.2.4.54' + '1.2.840.10008.1.2.4.56' + '1.2.840.10008.1.2.4.58' + '1.2.840.10008.1.2.4.60' + '1.2.840.10008.1.2.4.62' + '1.2.840.10008.1.2.4.64' + '1.2.840.10008.1.2.4.66'} + + % JPEG lossy syntaxes with arithmetic coding. + supported = 0; + encapsulated = 1; + name = uid.Name; + +otherwise + + % Some other transfer syntax. Unsupported. + supported = 0; + encapsulated = 1; + + if (~isempty(uid)) + + name = uid.Name; + + else + + name = txfr_uid; + + end + +end diff --git a/CT/private/dicom_tag_lookup.m b/CT/private/dicom_tag_lookup.m index 4b02713..051d5b7 100644 --- a/CT/private/dicom_tag_lookup.m +++ b/CT/private/dicom_tag_lookup.m @@ -1,117 +1,117 @@ -function tag = dicom_tag_lookup(attr_name, dictionary) -%DICOM_TAG_LOOKUP Look up the data dictionary tag from a attribute name. - -% Copyright 1993-2011 The MathWorks, Inc. - -persistent all_names all_tags prev_dictionary tag_cache; -mlock - -if ((isempty(all_names)) || (~isequal(prev_dictionary, dictionary))) - - [all_names, all_tags] = get_dictionary_info(dictionary); - prev_dictionary = dictionary; - tag_cache = struct([]); - -end - -% As an optimization, attributes are cached the first time they're found. -% Query the cache. -if (isfield(tag_cache, attr_name)) - tag = tag_cache.(attr_name); - return -end - -% Look for the name among the attributes. -idx = find(strcmp(attr_name, all_names)); - -if (isempty(idx)) - - if (~isempty(strfind(lower(attr_name), 'private_'))) - - % It's private. Parse out the group and element values. - tag = parse_private_name(attr_name); - - else - - % It's not a DICOM attribute. - tag = []; - - end - -else - - if (numel(idx) > 1) - - warning(message('images:dicom_tag_lookup:multipleAttrib', attr_name)); - - idx = idx(1); - - end - - % Look for the index in the sparse array. - % (row, column) values are (group + 1, element + 1) - [group, element] = find(all_tags == idx); - tag = [group element] - 1; - -end - -tag_cache(1).(attr_name) = tag; - - - -function [all_names, all_tags] = get_dictionary_info(dictionary) -%GET_DICTIONARY_INFO Get necessary details from the data dictionary - -if (findstr('.mat', dictionary)) - - dict = load(dictionary); - - all_names = {dict.values(:).Name}; - all_tags = dict.tags; - -else - - [all_tags, values] = dicom_load_dictionary(dictionary); - - all_names = {values(:).Name}; - -end - - - -function tag = parse_private_name(attr_name) -%PARSE_PRIVATE_NAME Get the group and element from a private attribute. - -attr_name = lower(attr_name); -idx = find(attr_name == '_'); - -if (~isempty(strfind(lower(attr_name), 'creator'))) - - % (gggg,0010-00ff) are Private Creator Data attributes: - % "Private_gggg_eexx_Creator" --> (gggg,00ee). - - if (isempty(idx)) - tag = []; - return; - end - - group = sscanf(attr_name((idx(1) + 1):(idx(1) + 4)), '%x'); - element = sscanf(attr_name((idx(2) + 1):(idx(2) + 4)), '%x'); - - tag = [group element]; - -elseif (~isempty(strfind(lower(attr_name), 'grouplength'))) - - % Skip Private Group Length attributes. - group = sscanf(attr_name(9:12), '%x'); - tag = [group 0]; - -else - - % Normal private data attributes: "Private_gggg_eeee". - group = sscanf(attr_name((idx(1) + 1):(idx(1) + 4)), '%x'); - element = sscanf(attr_name((idx(2) + 1):(idx(2) + 4)), '%x'); - - tag = [group element]; - -end +function tag = dicom_tag_lookup(attr_name, dictionary) +%DICOM_TAG_LOOKUP Look up the data dictionary tag from a attribute name. + +% Copyright 1993-2011 The MathWorks, Inc. + +persistent all_names all_tags prev_dictionary tag_cache; +mlock + +if ((isempty(all_names)) || (~isequal(prev_dictionary, dictionary))) + + [all_names, all_tags] = get_dictionary_info(dictionary); + prev_dictionary = dictionary; + tag_cache = struct([]); + +end + +% As an optimization, attributes are cached the first time they're found. +% Query the cache. +if (isfield(tag_cache, attr_name)) + tag = tag_cache.(attr_name); + return +end + +% Look for the name among the attributes. +idx = find(strcmp(attr_name, all_names)); + +if (isempty(idx)) + + if (~isempty(strfind(lower(attr_name), 'private_'))) + + % It's private. Parse out the group and element values. + tag = parse_private_name(attr_name); + + else + + % It's not a DICOM attribute. + tag = []; + + end + +else + + if (numel(idx) > 1) + + warning(message('images:dicom_tag_lookup:multipleAttrib', attr_name)); + + idx = idx(1); + + end + + % Look for the index in the sparse array. + % (row, column) values are (group + 1, element + 1) + [group, element] = find(all_tags == idx); + tag = [group element] - 1; + +end + +tag_cache(1).(attr_name) = tag; + + + +function [all_names, all_tags] = get_dictionary_info(dictionary) +%GET_DICTIONARY_INFO Get necessary details from the data dictionary + +if (findstr('.mat', dictionary)) + + dict = load(dictionary); + + all_names = {dict.values(:).Name}; + all_tags = dict.tags; + +else + + [all_tags, values] = dicom_load_dictionary(dictionary); + + all_names = {values(:).Name}; + +end + + + +function tag = parse_private_name(attr_name) +%PARSE_PRIVATE_NAME Get the group and element from a private attribute. + +attr_name = lower(attr_name); +idx = find(attr_name == '_'); + +if (~isempty(strfind(lower(attr_name), 'creator'))) + + % (gggg,0010-00ff) are Private Creator Data attributes: + % "Private_gggg_eexx_Creator" --> (gggg,00ee). + + if (isempty(idx)) + tag = []; + return; + end + + group = sscanf(attr_name((idx(1) + 1):(idx(1) + 4)), '%x'); + element = sscanf(attr_name((idx(2) + 1):(idx(2) + 4)), '%x'); + + tag = [group element]; + +elseif (~isempty(strfind(lower(attr_name), 'grouplength'))) + + % Skip Private Group Length attributes. + group = sscanf(attr_name(9:12), '%x'); + tag = [group 0]; + +else + + % Normal private data attributes: "Private_gggg_eeee". + group = sscanf(attr_name((idx(1) + 1):(idx(1) + 4)), '%x'); + element = sscanf(attr_name((idx(2) + 1):(idx(2) + 4)), '%x'); + + tag = [group element]; + +end diff --git a/CT/private/dicom_typecast.m b/CT/private/dicom_typecast.m index 200a37c..d29922a 100644 --- a/CT/private/dicom_typecast.m +++ b/CT/private/dicom_typecast.m @@ -1,47 +1,47 @@ -function out = dicom_typecast(in, datatype, varargin) -%DICOM_TYPECAST Convert datatypes, swapping as requested. -% Y = DICOM_TYPECAST(X, DATATYPE) calls to MATLAB's TYPECAST function -% and does not swap data endianness. -% -% Y = DICOM_TYPECAST(X, DATATYPE, SWAP) calls MATLAB's TYPECAST -% function and changes the endianness if SWAP is true and leaves the -% bytes alone otherwise. -% -% Note: DATATYPE must be one of 'UINT8', 'INT8', 'UINT16', 'INT16', -% 'UINT32', 'INT32', 'SINGLE', or 'DOUBLE'. -% - -% Copyright 1993-2005 The MathWorks, Inc. - -% Determine whether to swap. -if (nargin == 2) - - swap = false; - -else - - swap = varargin{1}; - -end - -% Change the type and swap as required. -if (swap) - - % Byte-sized inputs need to be swapped after casting. Others - % should be swapped first. - switch (class(in)) - case {'uint8', 'int8'} - - out = swapbytes(typecast(in, datatype)); - - otherwise - - out = typecast(swapbytes(in), datatype); - - end - -else - - out = typecast(in, datatype); - -end +function out = dicom_typecast(in, datatype, varargin) +%DICOM_TYPECAST Convert datatypes, swapping as requested. +% Y = DICOM_TYPECAST(X, DATATYPE) calls to MATLAB's TYPECAST function +% and does not swap data endianness. +% +% Y = DICOM_TYPECAST(X, DATATYPE, SWAP) calls MATLAB's TYPECAST +% function and changes the endianness if SWAP is true and leaves the +% bytes alone otherwise. +% +% Note: DATATYPE must be one of 'UINT8', 'INT8', 'UINT16', 'INT16', +% 'UINT32', 'INT32', 'SINGLE', or 'DOUBLE'. +% + +% Copyright 1993-2005 The MathWorks, Inc. + +% Determine whether to swap. +if (nargin == 2) + + swap = false; + +else + + swap = varargin{1}; + +end + +% Change the type and swap as required. +if (swap) + + % Byte-sized inputs need to be swapped after casting. Others + % should be swapped first. + switch (class(in)) + case {'uint8', 'int8'} + + out = swapbytes(typecast(in, datatype)); + + otherwise + + out = typecast(swapbytes(in), datatype); + + end + +else + + out = typecast(in, datatype); + +end diff --git a/CT/private/dicom_uid_decode.m b/CT/private/dicom_uid_decode.m index c3b5dfd..015ee54 100644 --- a/CT/private/dicom_uid_decode.m +++ b/CT/private/dicom_uid_decode.m @@ -1,742 +1,742 @@ -function UID_details = dicom_uid_decode(UID_string) -%DICOM_UID_DECODE Get information about a Unique Identifier (UID). -% DETAILS = DICOM_UID_DECODE(UID) get DETAILS about the DICOM unique -% identifier contained in the string UID. -% -% DETAILS contains the Name of the UID and its type (SOP class, -% transfer syntax, etc.). If UID corresponds to a transfer syntax, -% DETAILS also contains the endianness and VR necessary for reading the -% image pixels and whether these pixels are compressed. -% -% Note: Only UID's listed in PS 3.6-1999 are included here. -% Along with a few additions from PS 3.6-2009. - -% Copyright 1993-2013 The MathWorks, Inc. - -if ((~ischar(UID_string)) || (numel(UID_string) ~= length(UID_string))) - - UID_details = struct([]); - return - -end - -UID_details.Value = UID_string; -UID_details.Name = ''; -UID_details.Type = ''; -UID_details.Compressed = []; -UID_details.Endian = ''; -UID_details.PixelEndian = ''; -UID_details.VR = ''; -UID_details.LossyCompression = false; -UID_details.CompressionType = ''; - -switch (UID_string) - % SOP classes - -case '1.2.840.10008.1.1' - UID_details.Name = 'Verification SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.1.3.10' - UID_details.Name = 'Media Storage Directory Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.1.9' - UID_details.Name = 'Basic Study Content Notification SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.1.20.1' - UID_details.Name = 'Storage Commitment Push Model SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.1.20.2' - UID_details.Name = 'Storage Commitment Pull Model SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.1.1' - UID_details.Name = 'Detached Patient Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.2.1' - UID_details.Name = 'Detached Visit Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.3.1' - UID_details.Name = 'Detached Study Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.3.2' - UID_details.Name = 'Study Component Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.3.3' - UID_details.Name = 'Modality Performed Procedure Step SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.3.4' - UID_details.Name = 'Modality Performed Procedure Step Retrieve SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.3.5' - UID_details.Name = 'Modality Performed Procedure Step Notification SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.5.1' - UID_details.Name = 'Detached Results Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.3.1.2.6.1' - UID_details.Name = 'Detached Interpretation Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.1' - UID_details.Name = 'Basic Film Session SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.2' - UID_details.Name = 'Basic Film Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.4' - UID_details.Name = 'Basic Grayscale Image Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.4.1' - UID_details.Name = 'Basic Color Image Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.4.2' - UID_details.Name = 'Referenced Image Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.14' - UID_details.Name = 'Print Job SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.15' - UID_details.Name = 'Basic Annotation Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.16' - UID_details.Name = 'Printer SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.22' - UID_details.Name = 'VOI LUT Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.23' - UID_details.Name = 'Presentation LUT SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.24' - UID_details.Name = 'Image Overlay Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.24.1' - UID_details.Name = 'Basic Print Image Overlay Box SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.26' - UID_details.Name = 'Print Queue Management SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.27' - UID_details.Name = 'Stored Print Storage SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.29' - UID_details.Name = 'Hardcopy Grayscale Image Storage SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.30' - UID_details.Name = 'Hardcopy Color Image Storage SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.1.31' - UID_details.Name = 'Pull Print Request SOP Class'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1' - UID_details.Name = 'Computed Radiography Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.1' - UID_details.Name = 'Digital X-Ray Image Storage - For Presentation'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.1.1' - UID_details.Name = 'Digital X-Ray Image Storage - For Processing'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.2' - UID_details.Name = 'Digital Mammography X-Ray Image Storage - For Presentation'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.2.1' - UID_details.Name = 'Digital Mammography X-Ray Image Storage - For Processing'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.3' - UID_details.Name = 'Digital Intra-oral X-Ray Image Storage - For Presentation'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.1.3.1' - UID_details.Name = 'Digital Intra-oral X-Ray Image Storage - For Processing'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.2' - UID_details.Name = 'CT Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.3' - UID_details.Name = 'Ultrasound Multi-frame Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.3.1' - UID_details.Name = 'Ultrasound Multi-frame Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.4' - UID_details.Name = 'MR Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.4.1' - UID_details.Name = 'Enhanced MR Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.4.2' - UID_details.Name = 'MR Spectroscopy Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.4.3' - UID_details.Name = 'Enhanced MR Color Image'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.5' - UID_details.Name = 'Nuclear Medicine Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.6' - UID_details.Name = 'Ultrasound Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.6.1' - UID_details.Name = 'Ultrasound Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.7' - UID_details.Name = 'Secondary Capture Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.7.1' - UID_details.Name = 'Multi-frame Single Bit Secondary Capture Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.7.2' - UID_details.Name = 'Multi-frame Grayscale Byte Secondary Capture Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.7.3' - UID_details.Name = 'Multi-frame Grayscale Word Secondary Capture Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.7.4' - UID_details.Name = 'Multi-frame True Color Secondary Capture Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.8' - UID_details.Name = 'Standalone Overlay Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.9' - UID_details.Name = 'Standalone Curve Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.10' - UID_details.Name = 'Standalone Modality LUT Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.11' - UID_details.Name = 'Standalone VOI LUT Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.12.1' - UID_details.Name = 'X-Ray Angiographic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.12.2' - UID_details.Name = 'X-Ray Radiofluoroscopic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.12.3' - UID_details.Name = 'X-Ray Angiographic Bi-Plane Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.20' - UID_details.Name = 'Nuclear Medicine Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.1' - UID_details.Name = 'VL Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.2' - UID_details.Name = 'VL Multi-frame Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.1.1' - UID_details.Name = 'VL Endoscopic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.1.2' - UID_details.Name = 'VL Microscopic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.1.3' - UID_details.Name = 'VL Slide-Coordinates Microscopic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.77.1.4' - UID_details.Name = 'VL Photographic Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.128' - UID_details.Name = 'Positron Emission Tomography Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.129' - UID_details.Name = 'Standalone PET Curve Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.1' - UID_details.Name = 'RT Image Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.2' - UID_details.Name = 'RT Dose Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.3' - UID_details.Name = 'RT Structure Set Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.4' - UID_details.Name = 'RT Beams Treatment Record Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.5' - UID_details.Name = 'RT Plan Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.6' - UID_details.Name = 'RT Brachy Treatment Record Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.1.481.7' - UID_details.Name = 'RT Treatment Summary Record Storage'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.1.1' - UID_details.Name = 'Patient Root Query/Retrieve Information Model - FIND'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.1.2' - UID_details.Name = 'Patient Root Query/Retrieve Information Model - MOVE'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.1.3' - UID_details.Name = 'Patient Root Query/Retrieve Information Model - GET'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.2.1' - UID_details.Name = 'Study Root Query/Retrieve Information Model - FIND'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.2.2' - UID_details.Name = 'Study Root Query/Retrieve Information Model - MOVE'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.2.3' - UID_details.Name = 'Study Root Query/Retrieve Information Model - GET'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.3.1' - UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - FIND'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.3.2' - UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - MOVE'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.1.2.3.3' - UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - GET'; - UID_details.Type = 'SOP Class'; - -case '1.2.840.10008.5.1.4.31' - UID_details.Name = 'Modality Worklist Information Model - FIND'; - UID_details.Type = 'SOP Class'; - - % Well-known SOP Instances -case '1.2.840.10008.1.20.1.1' - UID_details.Name = 'Storage Commitment Push Model SOP Instance'; - UID_details.Type = 'Well-known SOP Instance'; - -case '1.2.840.10008.1.20.2.1' - UID_details.Name = 'Storage Commitment Pull Model SOP Instance'; - UID_details.Type = 'Well-known SOP Instance'; - - - - % Application Context Names -case '1.2.840.10008.3.1.1.1' - UID_details.Name = 'DICOM Application Context Name'; - UID_details.Type = 'Application Context Name'; - - % Meta SOP Classes -case '1.2.840.10008.3.1.2.1.4' - UID_details.Name = 'Detached Patient Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.3.1.2.5.4' - UID_details.Name = 'Detached Results Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.3.1.2.5.5' - UID_details.Name = 'Detached Study Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.5.1.1.9' - UID_details.Name = 'Basic Grayscale Print Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.5.1.1.9.1' - UID_details.Name = 'Referenced Grayscale Print Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.5.1.1.18' - UID_details.Name = 'Basic Color Print Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.5.1.1.18.1' - UID_details.Name = 'Referenced Color Print Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - -case '1.2.840.10008.5.1.1.32' - UID_details.Name = 'Pull Stored Print Management Meta SOP Class'; - UID_details.Type = 'Meta SOP Class'; - - % Well-known Printer SOP Instance -case '1.2.840.10008.5.1.1.16.376' - UID_details.Name = 'Printer Configuration Retrieval SOP Class'; - UID_details.Type = 'Well-known Printer SOP Instance'; - -case '1.2.840.10008.5.1.1.17' - UID_details.Name = 'Printer SOP Instance'; - UID_details.Type = 'Well-known Printer SOP Instance'; - -case '1.2.840.10008.5.1.1.17.376' - UID_details.Name = 'Printer Configuration Retrieval SOP Instance'; - UID_details.Type = 'Well-known Printer SOP Instance'; - - - % Well-known Print Queue SOP Instance -case '1.2.840.10008.5.1.1.25' - UID_details.Name = 'Print Queue SOP Instance'; - UID_details.Type = 'Well-known Print Queue SOP Instance'; - - - % Transfer Syntaxes -case '1.2.840.10008.1.2' - UID_details.Name = 'Implicit VR Little Endian'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 0; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Implicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.1' - UID_details.Name = 'Explicit VR Little Endian'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 0; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.2' - UID_details.Name = 'Explicit VR Big Endian'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 0; - UID_details.Endian = 'ieee-be'; - UID_details.PixelEndian = 'ieee-be'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.50' - UID_details.Name = 'JPEG Baseline (Process 1)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.51' - UID_details.Name = 'JPEG Extended (Process 2 & 4)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.52' - UID_details.Name = 'JPEG Extended (Process 3 & 5)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.53' - UID_details.Name = 'JPEG Spectral Selection, Non-Hierarchical (Process 6 & 8)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.54' - UID_details.Name = 'JPEG Spectral Selection, Non-Hierarchical (Process 7 & 9)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.55' - UID_details.Name = 'JPEG Full Progression, Non-Hierarchical (Process 10 & 12)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.56' - UID_details.Name = 'JPEG Full Progression, Non-Hierarchical (Process 11 & 13)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.57' - UID_details.Name = 'JPEG Lossless, Non-Hierarchical (Process 14)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.58' - UID_details.Name = 'JPEG Lossless, Non-Hierarchical (Process 15)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.59' - UID_details.Name = 'JPEG Extended, Hierarchical (Process 16 & 18)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.60' - UID_details.Name = 'JPEG Extended, Hierarchical (Process 17 & 19)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.61' - UID_details.Name = 'JPEG Spectral Selection, Hierarchical (Process 20 & 22)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.62' - UID_details.Name = 'JPEG Spectral Selection, Hierarchical (Process 21 & 23)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.63' - UID_details.Name = 'JPEG Full Progression, Hierarchical (Process 24 & 26)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.64' - UID_details.Name = 'JPEG Full Progression, Hierarchical (Process 25 & 27)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_10918_1'; - -case '1.2.840.10008.1.2.4.65' - UID_details.Name = 'JPEG Lossless, Hierarchical (Process 28)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.66' - UID_details.Name = 'JPEG Lossless, Hierarchical (Process 29)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.70' - UID_details.Name = 'JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1])'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.80' - UID_details.Name = 'JPEG-LS Lossless'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.10008.1.2.4.81' - UID_details.Name = 'JPEG-LS Lossy (Near-Lossless)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_14495_1'; - -case '1.2.840.10008.1.2.5' - UID_details.Name = 'RLE Lossless'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - -case '1.2.840.113619.5.2' - UID_details.Name = 'GE Implicit VR Little Endian Except Big Endian Pixels'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 0; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-be'; - UID_details.VR = 'Implicit'; - UID_details.LossyCompression = false; - - case '1.2.840.10008.1.2.4.90' - UID_details.Name = 'JPEG 2000 Image Compression (Lossless Only)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - - case '1.2.840.10008.1.2.4.91' - UID_details.Name = 'JPEG 2000 Image Compression'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_15444_1'; - - case '1.2.840.10008.1.2.4.92' - UID_details.Name = 'JPEG 2000 Part 2 Multi-component Image Compression (Lossless Only)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - - case '1.2.840.10008.1.2.4.93' - UID_details.Name = 'JPEG 2000 Part 2 Multi-component Image Compression'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = true; - UID_details.CompressionType = 'ISO_15444_1'; - - case '1.3.46.670589.33.1.4.1' - UID_details.Name = 'Philips Explicit VR Little Endian (RLE)'; - UID_details.Type = 'Transfer Syntax'; - UID_details.Compressed = 1; - UID_details.Endian = 'ieee-le'; - UID_details.PixelEndian = 'ieee-le'; - UID_details.VR = 'Explicit'; - UID_details.LossyCompression = false; - - -otherwise - - UID_details.Value = ''; - -end - +function UID_details = dicom_uid_decode(UID_string) +%DICOM_UID_DECODE Get information about a Unique Identifier (UID). +% DETAILS = DICOM_UID_DECODE(UID) get DETAILS about the DICOM unique +% identifier contained in the string UID. +% +% DETAILS contains the Name of the UID and its type (SOP class, +% transfer syntax, etc.). If UID corresponds to a transfer syntax, +% DETAILS also contains the endianness and VR necessary for reading the +% image pixels and whether these pixels are compressed. +% +% Note: Only UID's listed in PS 3.6-1999 are included here. +% Along with a few additions from PS 3.6-2009. + +% Copyright 1993-2013 The MathWorks, Inc. + +if ((~ischar(UID_string)) || (numel(UID_string) ~= length(UID_string))) + + UID_details = struct([]); + return + +end + +UID_details.Value = UID_string; +UID_details.Name = ''; +UID_details.Type = ''; +UID_details.Compressed = []; +UID_details.Endian = ''; +UID_details.PixelEndian = ''; +UID_details.VR = ''; +UID_details.LossyCompression = false; +UID_details.CompressionType = ''; + +switch (UID_string) + % SOP classes + +case '1.2.840.10008.1.1' + UID_details.Name = 'Verification SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.1.3.10' + UID_details.Name = 'Media Storage Directory Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.1.9' + UID_details.Name = 'Basic Study Content Notification SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.1.20.1' + UID_details.Name = 'Storage Commitment Push Model SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.1.20.2' + UID_details.Name = 'Storage Commitment Pull Model SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.1.1' + UID_details.Name = 'Detached Patient Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.2.1' + UID_details.Name = 'Detached Visit Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.3.1' + UID_details.Name = 'Detached Study Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.3.2' + UID_details.Name = 'Study Component Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.3.3' + UID_details.Name = 'Modality Performed Procedure Step SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.3.4' + UID_details.Name = 'Modality Performed Procedure Step Retrieve SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.3.5' + UID_details.Name = 'Modality Performed Procedure Step Notification SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.5.1' + UID_details.Name = 'Detached Results Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.3.1.2.6.1' + UID_details.Name = 'Detached Interpretation Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.1' + UID_details.Name = 'Basic Film Session SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.2' + UID_details.Name = 'Basic Film Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.4' + UID_details.Name = 'Basic Grayscale Image Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.4.1' + UID_details.Name = 'Basic Color Image Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.4.2' + UID_details.Name = 'Referenced Image Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.14' + UID_details.Name = 'Print Job SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.15' + UID_details.Name = 'Basic Annotation Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.16' + UID_details.Name = 'Printer SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.22' + UID_details.Name = 'VOI LUT Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.23' + UID_details.Name = 'Presentation LUT SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.24' + UID_details.Name = 'Image Overlay Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.24.1' + UID_details.Name = 'Basic Print Image Overlay Box SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.26' + UID_details.Name = 'Print Queue Management SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.27' + UID_details.Name = 'Stored Print Storage SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.29' + UID_details.Name = 'Hardcopy Grayscale Image Storage SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.30' + UID_details.Name = 'Hardcopy Color Image Storage SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.1.31' + UID_details.Name = 'Pull Print Request SOP Class'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1' + UID_details.Name = 'Computed Radiography Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.1' + UID_details.Name = 'Digital X-Ray Image Storage - For Presentation'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.1.1' + UID_details.Name = 'Digital X-Ray Image Storage - For Processing'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.2' + UID_details.Name = 'Digital Mammography X-Ray Image Storage - For Presentation'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.2.1' + UID_details.Name = 'Digital Mammography X-Ray Image Storage - For Processing'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.3' + UID_details.Name = 'Digital Intra-oral X-Ray Image Storage - For Presentation'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.1.3.1' + UID_details.Name = 'Digital Intra-oral X-Ray Image Storage - For Processing'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.2' + UID_details.Name = 'CT Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.3' + UID_details.Name = 'Ultrasound Multi-frame Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.3.1' + UID_details.Name = 'Ultrasound Multi-frame Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.4' + UID_details.Name = 'MR Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.4.1' + UID_details.Name = 'Enhanced MR Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.4.2' + UID_details.Name = 'MR Spectroscopy Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.4.3' + UID_details.Name = 'Enhanced MR Color Image'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.5' + UID_details.Name = 'Nuclear Medicine Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.6' + UID_details.Name = 'Ultrasound Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.6.1' + UID_details.Name = 'Ultrasound Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.7' + UID_details.Name = 'Secondary Capture Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.7.1' + UID_details.Name = 'Multi-frame Single Bit Secondary Capture Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.7.2' + UID_details.Name = 'Multi-frame Grayscale Byte Secondary Capture Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.7.3' + UID_details.Name = 'Multi-frame Grayscale Word Secondary Capture Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.7.4' + UID_details.Name = 'Multi-frame True Color Secondary Capture Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.8' + UID_details.Name = 'Standalone Overlay Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.9' + UID_details.Name = 'Standalone Curve Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.10' + UID_details.Name = 'Standalone Modality LUT Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.11' + UID_details.Name = 'Standalone VOI LUT Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.12.1' + UID_details.Name = 'X-Ray Angiographic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.12.2' + UID_details.Name = 'X-Ray Radiofluoroscopic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.12.3' + UID_details.Name = 'X-Ray Angiographic Bi-Plane Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.20' + UID_details.Name = 'Nuclear Medicine Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.1' + UID_details.Name = 'VL Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.2' + UID_details.Name = 'VL Multi-frame Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.1.1' + UID_details.Name = 'VL Endoscopic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.1.2' + UID_details.Name = 'VL Microscopic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.1.3' + UID_details.Name = 'VL Slide-Coordinates Microscopic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.77.1.4' + UID_details.Name = 'VL Photographic Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.128' + UID_details.Name = 'Positron Emission Tomography Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.129' + UID_details.Name = 'Standalone PET Curve Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.1' + UID_details.Name = 'RT Image Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.2' + UID_details.Name = 'RT Dose Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.3' + UID_details.Name = 'RT Structure Set Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.4' + UID_details.Name = 'RT Beams Treatment Record Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.5' + UID_details.Name = 'RT Plan Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.6' + UID_details.Name = 'RT Brachy Treatment Record Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.1.481.7' + UID_details.Name = 'RT Treatment Summary Record Storage'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.1.1' + UID_details.Name = 'Patient Root Query/Retrieve Information Model - FIND'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.1.2' + UID_details.Name = 'Patient Root Query/Retrieve Information Model - MOVE'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.1.3' + UID_details.Name = 'Patient Root Query/Retrieve Information Model - GET'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.2.1' + UID_details.Name = 'Study Root Query/Retrieve Information Model - FIND'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.2.2' + UID_details.Name = 'Study Root Query/Retrieve Information Model - MOVE'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.2.3' + UID_details.Name = 'Study Root Query/Retrieve Information Model - GET'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.3.1' + UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - FIND'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.3.2' + UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - MOVE'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.1.2.3.3' + UID_details.Name = 'Patient/Study Only Query/Retrieve Information Model - GET'; + UID_details.Type = 'SOP Class'; + +case '1.2.840.10008.5.1.4.31' + UID_details.Name = 'Modality Worklist Information Model - FIND'; + UID_details.Type = 'SOP Class'; + + % Well-known SOP Instances +case '1.2.840.10008.1.20.1.1' + UID_details.Name = 'Storage Commitment Push Model SOP Instance'; + UID_details.Type = 'Well-known SOP Instance'; + +case '1.2.840.10008.1.20.2.1' + UID_details.Name = 'Storage Commitment Pull Model SOP Instance'; + UID_details.Type = 'Well-known SOP Instance'; + + + + % Application Context Names +case '1.2.840.10008.3.1.1.1' + UID_details.Name = 'DICOM Application Context Name'; + UID_details.Type = 'Application Context Name'; + + % Meta SOP Classes +case '1.2.840.10008.3.1.2.1.4' + UID_details.Name = 'Detached Patient Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.3.1.2.5.4' + UID_details.Name = 'Detached Results Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.3.1.2.5.5' + UID_details.Name = 'Detached Study Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.5.1.1.9' + UID_details.Name = 'Basic Grayscale Print Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.5.1.1.9.1' + UID_details.Name = 'Referenced Grayscale Print Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.5.1.1.18' + UID_details.Name = 'Basic Color Print Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.5.1.1.18.1' + UID_details.Name = 'Referenced Color Print Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + +case '1.2.840.10008.5.1.1.32' + UID_details.Name = 'Pull Stored Print Management Meta SOP Class'; + UID_details.Type = 'Meta SOP Class'; + + % Well-known Printer SOP Instance +case '1.2.840.10008.5.1.1.16.376' + UID_details.Name = 'Printer Configuration Retrieval SOP Class'; + UID_details.Type = 'Well-known Printer SOP Instance'; + +case '1.2.840.10008.5.1.1.17' + UID_details.Name = 'Printer SOP Instance'; + UID_details.Type = 'Well-known Printer SOP Instance'; + +case '1.2.840.10008.5.1.1.17.376' + UID_details.Name = 'Printer Configuration Retrieval SOP Instance'; + UID_details.Type = 'Well-known Printer SOP Instance'; + + + % Well-known Print Queue SOP Instance +case '1.2.840.10008.5.1.1.25' + UID_details.Name = 'Print Queue SOP Instance'; + UID_details.Type = 'Well-known Print Queue SOP Instance'; + + + % Transfer Syntaxes +case '1.2.840.10008.1.2' + UID_details.Name = 'Implicit VR Little Endian'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 0; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Implicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.1' + UID_details.Name = 'Explicit VR Little Endian'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 0; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.2' + UID_details.Name = 'Explicit VR Big Endian'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 0; + UID_details.Endian = 'ieee-be'; + UID_details.PixelEndian = 'ieee-be'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.50' + UID_details.Name = 'JPEG Baseline (Process 1)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.51' + UID_details.Name = 'JPEG Extended (Process 2 & 4)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.52' + UID_details.Name = 'JPEG Extended (Process 3 & 5)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.53' + UID_details.Name = 'JPEG Spectral Selection, Non-Hierarchical (Process 6 & 8)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.54' + UID_details.Name = 'JPEG Spectral Selection, Non-Hierarchical (Process 7 & 9)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.55' + UID_details.Name = 'JPEG Full Progression, Non-Hierarchical (Process 10 & 12)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.56' + UID_details.Name = 'JPEG Full Progression, Non-Hierarchical (Process 11 & 13)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.57' + UID_details.Name = 'JPEG Lossless, Non-Hierarchical (Process 14)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.58' + UID_details.Name = 'JPEG Lossless, Non-Hierarchical (Process 15)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.59' + UID_details.Name = 'JPEG Extended, Hierarchical (Process 16 & 18)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.60' + UID_details.Name = 'JPEG Extended, Hierarchical (Process 17 & 19)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.61' + UID_details.Name = 'JPEG Spectral Selection, Hierarchical (Process 20 & 22)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.62' + UID_details.Name = 'JPEG Spectral Selection, Hierarchical (Process 21 & 23)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.63' + UID_details.Name = 'JPEG Full Progression, Hierarchical (Process 24 & 26)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.64' + UID_details.Name = 'JPEG Full Progression, Hierarchical (Process 25 & 27)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_10918_1'; + +case '1.2.840.10008.1.2.4.65' + UID_details.Name = 'JPEG Lossless, Hierarchical (Process 28)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.66' + UID_details.Name = 'JPEG Lossless, Hierarchical (Process 29)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.70' + UID_details.Name = 'JPEG Lossless, Non-Hierarchical, First-Order Prediction (Process 14 [Selection Value 1])'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.80' + UID_details.Name = 'JPEG-LS Lossless'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.10008.1.2.4.81' + UID_details.Name = 'JPEG-LS Lossy (Near-Lossless)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_14495_1'; + +case '1.2.840.10008.1.2.5' + UID_details.Name = 'RLE Lossless'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + +case '1.2.840.113619.5.2' + UID_details.Name = 'GE Implicit VR Little Endian Except Big Endian Pixels'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 0; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-be'; + UID_details.VR = 'Implicit'; + UID_details.LossyCompression = false; + + case '1.2.840.10008.1.2.4.90' + UID_details.Name = 'JPEG 2000 Image Compression (Lossless Only)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + + case '1.2.840.10008.1.2.4.91' + UID_details.Name = 'JPEG 2000 Image Compression'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_15444_1'; + + case '1.2.840.10008.1.2.4.92' + UID_details.Name = 'JPEG 2000 Part 2 Multi-component Image Compression (Lossless Only)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + + case '1.2.840.10008.1.2.4.93' + UID_details.Name = 'JPEG 2000 Part 2 Multi-component Image Compression'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = true; + UID_details.CompressionType = 'ISO_15444_1'; + + case '1.3.46.670589.33.1.4.1' + UID_details.Name = 'Philips Explicit VR Little Endian (RLE)'; + UID_details.Type = 'Transfer Syntax'; + UID_details.Compressed = 1; + UID_details.Endian = 'ieee-le'; + UID_details.PixelEndian = 'ieee-le'; + UID_details.VR = 'Explicit'; + UID_details.LossyCompression = false; + + +otherwise + + UID_details.Value = ''; + +end + diff --git a/CT/private/dicom_undefined_length.m b/CT/private/dicom_undefined_length.m index 71f45b3..cf040ba 100644 --- a/CT/private/dicom_undefined_length.m +++ b/CT/private/dicom_undefined_length.m @@ -1,12 +1,12 @@ -function value = dicom_undefined_length -%DICOM_UNDEFINED_LENGTH Return the UINT32 value 0xFFFFFFFF. -% -% VALUE = DICOM_UNDEFINED_LENGTH() returns the constant value -% given by the command uint32(inf). In DICOM, this value -% represents an undefined length. -% -% This function exists to prevent integer overflow warnings. - -% Copyright 1993-2005 The MathWorks, Inc. - -value = uint32(4294967295); % 4294967295 == 0xFFFFFFFF == uint32(inf) +function value = dicom_undefined_length +%DICOM_UNDEFINED_LENGTH Return the UINT32 value 0xFFFFFFFF. +% +% VALUE = DICOM_UNDEFINED_LENGTH() returns the constant value +% given by the command uint32(inf). In DICOM, this value +% represents an undefined length. +% +% This function exists to prevent integer overflow warnings. + +% Copyright 1993-2005 The MathWorks, Inc. + +value = uint32(4294967295); % 4294967295 == 0xFFFFFFFF == uint32(inf) diff --git a/CT/private/dicom_write_stream.m b/CT/private/dicom_write_stream.m index dcf2e35..9a8588f 100644 --- a/CT/private/dicom_write_stream.m +++ b/CT/private/dicom_write_stream.m @@ -1,47 +1,47 @@ -function [file, msg] = dicom_write_stream(file, data_streams) -%DICOM_WRITE_STREAM Write an encoded stream to a file. -% [FILE, MSG] = DICOM_WRITE_STREAM(FILE, STREAMS) writes the UINT8 data -% streams stored in the cell array STREAMS to the message specified in -% FILE. An updated FILE structure is returned along with any -% diagnostic information in the MSG character array. -% -% See also DICOM_OPEN_MSG, DICOM_GET_MSG. - -% Copyright 1993-2010 The MathWorks, Inc. - - -msg = ''; - -if (file.FID < 0) - - msg = message('images:dicomwrite:invalidFID',... - file.Filename); - return - -end - -% Write the DICOM preamble. -count1 = fwrite(file.FID, repmat(uint8(0), [128 1]), 'uint8'); -count2 = fwrite(file.FID, uint8([68 73 67 77]), 'uint8'); % DICM - -if ((count1 + count2) ~= 132) - - msg = message('images:dicomwrite:preambleTruncated',... - file.Filename); - return - -end - -% Write the data to the file. -for p = 1:length(data_streams) - count = fwrite(file.FID, data_streams{p}, 'uint8'); - - if (count ~= numel(data_streams{p})) - msg = message('images:dicomwrite:dataTruncated',... - file.Filename); - - return - - end - -end +function [file, msg] = dicom_write_stream(file, data_streams) +%DICOM_WRITE_STREAM Write an encoded stream to a file. +% [FILE, MSG] = DICOM_WRITE_STREAM(FILE, STREAMS) writes the UINT8 data +% streams stored in the cell array STREAMS to the message specified in +% FILE. An updated FILE structure is returned along with any +% diagnostic information in the MSG character array. +% +% See also DICOM_OPEN_MSG, DICOM_GET_MSG. + +% Copyright 1993-2010 The MathWorks, Inc. + + +msg = ''; + +if (file.FID < 0) + + msg = message('images:dicomwrite:invalidFID',... + file.Filename); + return + +end + +% Write the DICOM preamble. +count1 = fwrite(file.FID, repmat(uint8(0), [128 1]), 'uint8'); +count2 = fwrite(file.FID, uint8([68 73 67 77]), 'uint8'); % DICM + +if ((count1 + count2) ~= 132) + + msg = message('images:dicomwrite:preambleTruncated',... + file.Filename); + return + +end + +% Write the data to the file. +for p = 1:length(data_streams) + count = fwrite(file.FID, data_streams{p}, 'uint8'); + + if (count ~= numel(data_streams{p})) + msg = message('images:dicomwrite:dataTruncated',... + file.Filename); + + return + + end + +end diff --git a/CT/private/dicomlookup_actions.m b/CT/private/dicomlookup_actions.m index c2f1064..29d931e 100644 --- a/CT/private/dicomlookup_actions.m +++ b/CT/private/dicomlookup_actions.m @@ -1,77 +1,77 @@ -function [value1, value2] = dicomlookup_actions(varargin) -%DICOMLOOKUP_ACTIONS Call dictionary lookup functions without checking. -% -% This function requires correct inputs. Use the public function -% DICOMLOOKUP for error checking. - -% Copyright 2006-2010 The MathWorks, Inc. - -if (nargin == 2) - - % Look up the tag for a given attribute name. - tag = dicom_tag_lookup(varargin{1}, varargin{2}); - - if (~isempty(tag)) - value1 = tag(1); - value2 = tag(2); - else - value1 = []; - value2 = []; - end - -else - - % Look up the name when given a group and an attribute. - group = varargin{1}; - element = varargin{2}; - dictionary = varargin{3}; - - % Look up the attribute. - attr = dicomlookup_helper(group, element, dictionary); - - if (~isempty(attr)) - - % The attribute was in the dictionary; use its name. - name = attr.Name; - - else - - % Construct Private names if the group is odd. - if (rem(group, 2) == 1) - - if (element == 0) - % (gggg,0000) is Private Group Length. - name = sprintf('Private_%04x_GroupLength', group); - - elseif (element < 16) - % (gggg,0001-000f) are not allowed. - name = ''; - - elseif ((element >= 16) && (element <= 255)) - % (gggg,0010-00ff) are Private Creator Data Elements. - - % Private attributes are assigned in blocks of 256. The - % Private Creator Data Elements (gggg,0010-00ff) reserve a - % block. For example, (gggg,0030) reserves elements - % (gggg,3000-30ff). - name = sprintf('Private_%04x_%02xxx_Creator', group, element); - - else - % The rest are normal private metadata. - name = sprintf('Private_%04x_%04x', group, element); - - end - - else - - % The public attribute was not found. - name = ''; - - end - end - - % Assign the output. - value1 = name; - value2 = []; - -end +function [value1, value2] = dicomlookup_actions(varargin) +%DICOMLOOKUP_ACTIONS Call dictionary lookup functions without checking. +% +% This function requires correct inputs. Use the public function +% DICOMLOOKUP for error checking. + +% Copyright 2006-2010 The MathWorks, Inc. + +if (nargin == 2) + + % Look up the tag for a given attribute name. + tag = dicom_tag_lookup(varargin{1}, varargin{2}); + + if (~isempty(tag)) + value1 = tag(1); + value2 = tag(2); + else + value1 = []; + value2 = []; + end + +else + + % Look up the name when given a group and an attribute. + group = varargin{1}; + element = varargin{2}; + dictionary = varargin{3}; + + % Look up the attribute. + attr = dicomlookup_helper(group, element, dictionary); + + if (~isempty(attr)) + + % The attribute was in the dictionary; use its name. + name = attr.Name; + + else + + % Construct Private names if the group is odd. + if (rem(group, 2) == 1) + + if (element == 0) + % (gggg,0000) is Private Group Length. + name = sprintf('Private_%04x_GroupLength', group); + + elseif (element < 16) + % (gggg,0001-000f) are not allowed. + name = ''; + + elseif ((element >= 16) && (element <= 255)) + % (gggg,0010-00ff) are Private Creator Data Elements. + + % Private attributes are assigned in blocks of 256. The + % Private Creator Data Elements (gggg,0010-00ff) reserve a + % block. For example, (gggg,0030) reserves elements + % (gggg,3000-30ff). + name = sprintf('Private_%04x_%02xxx_Creator', group, element); + + else + % The rest are normal private metadata. + name = sprintf('Private_%04x_%04x', group, element); + + end + + else + + % The public attribute was not found. + name = ''; + + end + end + + % Assign the output. + value1 = name; + value2 = []; + +end diff --git a/CT/private/h5attput.m b/CT/private/h5attput.m index c17eb06..2313a66 100644 --- a/CT/private/h5attput.m +++ b/CT/private/h5attput.m @@ -1,187 +1,187 @@ -function h5attput ( h5file, varargin ) -% H5ATTPUT Add HDF5 attribute to existing file. -% -% H5ATTPUT(HDFFILE,VARNAME,ATTNAME,ATTVALUE) creates/overwrites the -% attribute named ATTNAME with the value ATTVALUE. The parent object -% VARNAME can be either an HDF5 variable or group. VARNAME must be a -% complete pathname. -% -% H5ATTPUT(HDFFILE,ATTNAME,ATTVALUE) does the same thing, but the -% complete path to the parent object is embedded within ATTNAME. -% -% Simple strings will be created in a scalar dataspace. - -% Copyright 2008-2011 The MathWorks, Inc. - - -narginchk(3,4) -nargoutchk(0,0) - -[varname, attname, attvalue] = parse_and_validate_inputs ( h5file, varargin{:} ); - -flags = 'H5F_ACC_RDWR'; -plist_id = 'H5P_DEFAULT'; - -file_id = H5F.open ( h5file, flags, plist_id ); - -[parent_id, parent_obj_is_group] = set_parent_id ( file_id, varname ); -dataspace_id = set_dataspace_id ( attvalue ); -datatype_id = set_datatype_id ( attvalue ); -att_id = set_attribute_id ( parent_id, attname, datatype_id, dataspace_id ); - - -H5A.write(att_id,datatype_id,attvalue); - -H5T.close(datatype_id); -H5S.close(dataspace_id); -H5A.close(att_id); - -if parent_obj_is_group - H5G.close(parent_id); -else - H5D.close(parent_id); -end - -H5F.close(file_id); - - -%=============================================================================== -% SET_ATTRIBUTE_ID -% -% Setup the attribute ID. We need to check as to whether or not the attribute -% already exists. -function att_id = set_attribute_id ( parent_id, attname, datatype_id, dataspace_id ) - -try - att_id = H5A.open_name ( parent_id, attname ); -catch - att_id = H5A.create ( parent_id, attname, datatype_id, dataspace_id,'H5P_DEFAULT' ); -end - - -%=============================================================================== -% SET_DATASPACE_ID -% -% Setup the dataspace ID. This just depends on how many elements the -% attribute actually has. -function dataspace_id = set_dataspace_id ( attvalue ) - -if ischar(attvalue) - dataspace_id = H5S.create('H5S_SCALAR'); -else - dims = size(attvalue); - - if (dims(1) ~= numel(attvalue)) - rank = ndims(attvalue); - dims = fliplr(size(attvalue)); - else - rank = 1; - dims = dims(1); - end - - dataspace_id = H5S.create_simple ( rank, dims, dims ); -end - - -%=============================================================================== -% SET_PARENT_ID -% -% If the given variable is "/", then we know we are creating a group attribute. -% Otherwise try to open the variable as a dataset. If that fails, then it -% must be a subgroup. -function [parent_id, parent_obj_is_group] = set_parent_id ( file_id, varname ) -if strcmp(varname,'/') - parent_obj_is_group = true; - parent_id = H5G.open ( file_id, varname ); -else - try - parent_id=H5D.open(file_id,varname); - parent_obj_is_group = false; - catch - parent_id = H5G.open ( file_id, varname ); - parent_obj_is_group = true; - end -end - -%=============================================================================== -% SET_DATATYPE_ID -% -% We need to choose an appropriate HDF5 datatype based upon the attribute -% data. -function datatype_id = set_datatype_id ( attvalue ) -switch class(attvalue) - case 'double' - datatype_id = H5T.copy('H5T_NATIVE_DOUBLE'); - case 'single' - datatype_id = H5T.copy('H5T_NATIVE_FLOAT'); - case 'int64' - datatype_id = H5T.copy('H5T_NATIVE_LLONG'); - case 'uint64' - datatype_id = H5T.copy('H5T_NATIVE_ULLONG'); - case 'int32' - datatype_id = H5T.copy('H5T_NATIVE_INT'); - case 'uint32' - datatype_id = H5T.copy('H5T_NATIVE_UINT'); - case 'int16' - datatype_id = H5T.copy('H5T_NATIVE_SHORT'); - case 'uint16' - datatype_id = H5T.copy('H5T_NATIVE_USHORT'); - case 'int8' - datatype_id = H5T.copy('H5T_NATIVE_SCHAR'); - case 'uint8' - datatype_id = H5T.copy('H5T_NATIVE_UCHAR'); - case 'char' - datatype_id = H5T.copy('H5T_C_S1'); - H5T.set_size(datatype_id,length(attvalue)); - H5T.set_strpad(datatype_id,'H5T_STR_NULLTERM'); - otherwise - error(message('images:H5ATTPUT:unsupportedDatatype', class( attvalue ))); -end -return -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% PARSE_AND_VALIDATE_INPUTS -% -% The VARNAME and ATTNAME values may need to be parsed from one of the inputs, -% and all but the ATTVALUE input must have their datatypes checked. -function [varname,attname,attvalue] = parse_and_validate_inputs(hfile,varargin) - -if ~ischar(hfile) - error(message('images:H5ATTPUT:badFilenameDatatype')); -end - -if nargin == 3 - varname = varargin{1}; - attvalue = varargin{2}; - - slashes = strfind(varname, '/'); - if isempty(slashes) - error (message('images:H5ATTGET:badAttributePath', varname)); - elseif slashes == 1 - - % - % case of "/attname" is different than "/path/to/attname" - attname = varname(2:end); - varname = varname(1); - - else - attname = varname(slashes(end)+1:end); - varname = varname(1:slashes(end)-1); - end - -else - varname = varargin{1}; - attname = varargin{2}; - attvalue = varargin{3}; -end - -if ~ischar(varname) - error(message('images:H5ATTPUT:badVarnameDatatype')); -end - -if ~ischar(attname) - error(message('images:H5ATTPUT:badAttnameDatatype')); -end - - - -return +function h5attput ( h5file, varargin ) +% H5ATTPUT Add HDF5 attribute to existing file. +% +% H5ATTPUT(HDFFILE,VARNAME,ATTNAME,ATTVALUE) creates/overwrites the +% attribute named ATTNAME with the value ATTVALUE. The parent object +% VARNAME can be either an HDF5 variable or group. VARNAME must be a +% complete pathname. +% +% H5ATTPUT(HDFFILE,ATTNAME,ATTVALUE) does the same thing, but the +% complete path to the parent object is embedded within ATTNAME. +% +% Simple strings will be created in a scalar dataspace. + +% Copyright 2008-2011 The MathWorks, Inc. + + +narginchk(3,4) +nargoutchk(0,0) + +[varname, attname, attvalue] = parse_and_validate_inputs ( h5file, varargin{:} ); + +flags = 'H5F_ACC_RDWR'; +plist_id = 'H5P_DEFAULT'; + +file_id = H5F.open ( h5file, flags, plist_id ); + +[parent_id, parent_obj_is_group] = set_parent_id ( file_id, varname ); +dataspace_id = set_dataspace_id ( attvalue ); +datatype_id = set_datatype_id ( attvalue ); +att_id = set_attribute_id ( parent_id, attname, datatype_id, dataspace_id ); + + +H5A.write(att_id,datatype_id,attvalue); + +H5T.close(datatype_id); +H5S.close(dataspace_id); +H5A.close(att_id); + +if parent_obj_is_group + H5G.close(parent_id); +else + H5D.close(parent_id); +end + +H5F.close(file_id); + + +%=============================================================================== +% SET_ATTRIBUTE_ID +% +% Setup the attribute ID. We need to check as to whether or not the attribute +% already exists. +function att_id = set_attribute_id ( parent_id, attname, datatype_id, dataspace_id ) + +try + att_id = H5A.open_name ( parent_id, attname ); +catch + att_id = H5A.create ( parent_id, attname, datatype_id, dataspace_id,'H5P_DEFAULT' ); +end + + +%=============================================================================== +% SET_DATASPACE_ID +% +% Setup the dataspace ID. This just depends on how many elements the +% attribute actually has. +function dataspace_id = set_dataspace_id ( attvalue ) + +if ischar(attvalue) + dataspace_id = H5S.create('H5S_SCALAR'); +else + dims = size(attvalue); + + if (dims(1) ~= numel(attvalue)) + rank = ndims(attvalue); + dims = fliplr(size(attvalue)); + else + rank = 1; + dims = dims(1); + end + + dataspace_id = H5S.create_simple ( rank, dims, dims ); +end + + +%=============================================================================== +% SET_PARENT_ID +% +% If the given variable is "/", then we know we are creating a group attribute. +% Otherwise try to open the variable as a dataset. If that fails, then it +% must be a subgroup. +function [parent_id, parent_obj_is_group] = set_parent_id ( file_id, varname ) +if strcmp(varname,'/') + parent_obj_is_group = true; + parent_id = H5G.open ( file_id, varname ); +else + try + parent_id=H5D.open(file_id,varname); + parent_obj_is_group = false; + catch + parent_id = H5G.open ( file_id, varname ); + parent_obj_is_group = true; + end +end + +%=============================================================================== +% SET_DATATYPE_ID +% +% We need to choose an appropriate HDF5 datatype based upon the attribute +% data. +function datatype_id = set_datatype_id ( attvalue ) +switch class(attvalue) + case 'double' + datatype_id = H5T.copy('H5T_NATIVE_DOUBLE'); + case 'single' + datatype_id = H5T.copy('H5T_NATIVE_FLOAT'); + case 'int64' + datatype_id = H5T.copy('H5T_NATIVE_LLONG'); + case 'uint64' + datatype_id = H5T.copy('H5T_NATIVE_ULLONG'); + case 'int32' + datatype_id = H5T.copy('H5T_NATIVE_INT'); + case 'uint32' + datatype_id = H5T.copy('H5T_NATIVE_UINT'); + case 'int16' + datatype_id = H5T.copy('H5T_NATIVE_SHORT'); + case 'uint16' + datatype_id = H5T.copy('H5T_NATIVE_USHORT'); + case 'int8' + datatype_id = H5T.copy('H5T_NATIVE_SCHAR'); + case 'uint8' + datatype_id = H5T.copy('H5T_NATIVE_UCHAR'); + case 'char' + datatype_id = H5T.copy('H5T_C_S1'); + H5T.set_size(datatype_id,length(attvalue)); + H5T.set_strpad(datatype_id,'H5T_STR_NULLTERM'); + otherwise + error(message('images:H5ATTPUT:unsupportedDatatype', class( attvalue ))); +end +return +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% PARSE_AND_VALIDATE_INPUTS +% +% The VARNAME and ATTNAME values may need to be parsed from one of the inputs, +% and all but the ATTVALUE input must have their datatypes checked. +function [varname,attname,attvalue] = parse_and_validate_inputs(hfile,varargin) + +if ~ischar(hfile) + error(message('images:H5ATTPUT:badFilenameDatatype')); +end + +if nargin == 3 + varname = varargin{1}; + attvalue = varargin{2}; + + slashes = strfind(varname, '/'); + if isempty(slashes) + error (message('images:H5ATTGET:badAttributePath', varname)); + elseif slashes == 1 + + % + % case of "/attname" is different than "/path/to/attname" + attname = varname(2:end); + varname = varname(1); + + else + attname = varname(slashes(end)+1:end); + varname = varname(1:slashes(end)-1); + end + +else + varname = varargin{1}; + attname = varargin{2}; + attvalue = varargin{3}; +end + +if ~ischar(varname) + error(message('images:H5ATTPUT:badVarnameDatatype')); +end + +if ~ischar(attname) + error(message('images:H5ATTPUT:badAttnameDatatype')); +end + + + +return diff --git a/CT/private/isanalyze75.m b/CT/private/isanalyze75.m index 50eda9d..bf199bc 100644 --- a/CT/private/isanalyze75.m +++ b/CT/private/isanalyze75.m @@ -1,78 +1,78 @@ -function TF = isanalyze75(filename) -%ISANALYZE75 Return true for a header file of a Mayo Analyze 7.5 data set. -% -% TF = ISANALYZE75(FILENAME) returns TRUE if FILENAME is a header file of -% a Mayo Analyze 7.5 data set. -% -% FILENAME is considered to be a valid header file of a Mayo Analyze 7.5 -% data set if the header size is 348 bytes. -% -% Example -% ------- -% TF = isanalyze75('brainMRI.hdr'); -% -% See also ANALYZE75INFO, ANALYZE75READ. - -% Copyright 2005-2011 The MathWorks, Inc. - - -% Check the number of input arguments. -narginchk(1,1); - -% Check if filename is a string. -if ~isa(filename,'char') - error(message('images:isanalyze75:invalidInputArgument')) -end - -% Open the HDR file -fid = analyze75open(filename, 'hdr', 'r'); - -% Check headerSize -TF = validateHeaderSize(fid, filename); - -% Close the file -fclose(fid); - - - -%%% -%%% Function validateHeaderSize -%%% -function TF = validateHeaderSize(fid, filename) - -% Analyze 7.5 format standard header size -analyzeHeader = int32(348); -% Interfile header - swapbytes(typecast(uint8('!INT'), 'int32')) -interfileHeader = int32(558452308); -% Possible extended header size -extendedRange = int32([348 2000]); - -% Read headerSize. -headerSize = fread(fid, 1, 'int32=>int32'); -swappedHeaderSize = swapbytes(headerSize); - -% Compare with Standard Analyze 7.5 headerSize. -if ((headerSize == analyzeHeader)||(swappedHeaderSize == analyzeHeader)) - TF = true; -% Compare with Interfile header -elseif ((headerSize == interfileHeader) || ... - (swappedHeaderSize == interfileHeader)) - TF = false; -% Check for extended headerSize -elseif ((headerSize > extendedRange(1)) ... - && (headerSize < extendedRange(2))) ... - || ((swappedHeaderSize > extendedRange(1)) ... - && (swappedHeaderSize < extendedRange(2))) - % Return true but warn that this may not be a valid Analyze 7.5 file - TF = true; - warning(message('images:isanalyze75:incorrectHeaderSize', filename')); -% Invalid headerSize -else - TF = false; -end - - - - - - +function TF = isanalyze75(filename) +%ISANALYZE75 Return true for a header file of a Mayo Analyze 7.5 data set. +% +% TF = ISANALYZE75(FILENAME) returns TRUE if FILENAME is a header file of +% a Mayo Analyze 7.5 data set. +% +% FILENAME is considered to be a valid header file of a Mayo Analyze 7.5 +% data set if the header size is 348 bytes. +% +% Example +% ------- +% TF = isanalyze75('brainMRI.hdr'); +% +% See also ANALYZE75INFO, ANALYZE75READ. + +% Copyright 2005-2011 The MathWorks, Inc. + + +% Check the number of input arguments. +narginchk(1,1); + +% Check if filename is a string. +if ~isa(filename,'char') + error(message('images:isanalyze75:invalidInputArgument')) +end + +% Open the HDR file +fid = analyze75open(filename, 'hdr', 'r'); + +% Check headerSize +TF = validateHeaderSize(fid, filename); + +% Close the file +fclose(fid); + + + +%%% +%%% Function validateHeaderSize +%%% +function TF = validateHeaderSize(fid, filename) + +% Analyze 7.5 format standard header size +analyzeHeader = int32(348); +% Interfile header - swapbytes(typecast(uint8('!INT'), 'int32')) +interfileHeader = int32(558452308); +% Possible extended header size +extendedRange = int32([348 2000]); + +% Read headerSize. +headerSize = fread(fid, 1, 'int32=>int32'); +swappedHeaderSize = swapbytes(headerSize); + +% Compare with Standard Analyze 7.5 headerSize. +if ((headerSize == analyzeHeader)||(swappedHeaderSize == analyzeHeader)) + TF = true; +% Compare with Interfile header +elseif ((headerSize == interfileHeader) || ... + (swappedHeaderSize == interfileHeader)) + TF = false; +% Check for extended headerSize +elseif ((headerSize > extendedRange(1)) ... + && (headerSize < extendedRange(2))) ... + || ((swappedHeaderSize > extendedRange(1)) ... + && (swappedHeaderSize < extendedRange(2))) + % Return true but warn that this may not be a valid Analyze 7.5 file + TF = true; + warning(message('images:isanalyze75:incorrectHeaderSize', filename')); +% Invalid headerSize +else + TF = false; +end + + + + + + diff --git a/CT/private/nitfReadJPEG.m b/CT/private/nitfReadJPEG.m index 0893ea2..dce20f0 100644 --- a/CT/private/nitfReadJPEG.m +++ b/CT/private/nitfReadJPEG.m @@ -1,197 +1,197 @@ -function X = nitfReadJPEG(details, region) -%nitfReadJPEG Extract JPEG-compressed image from NITF file. -% X = nitfReadJPEG(DETAILS, REGION) returns the image X from the NITF -% file described in the DETAILS structure. The REGION argument is -% currently not used. -% -% See MIL-STD-188-198 for details of this scheme. - -% Copyright 2010-2013 The MathWorks, Inc. - -% Currently there's no pixel subsetting for NITF I/O. -if (~isempty(region)) - error(message('images:nitfread:jpegSubsetting')) -end - -% Get the JPEG data from the file. -compData = getJpegStream(details); - -% Get the number of blocks, dimensions and markers. -nColsBlocks = double(swapbytes(typecast(compData(15:16), 'uint16'))); -nRowsBlocks = double(swapbytes(typecast(compData(17:18), 'uint16'))); - -h = details.tileHeight; -w = details.tileWidth; - -[startIdx, stopIdx, maskedBlocks] = getBlockLocations(compData, details, nColsBlocks, nRowsBlocks); - -% Create the full-sized image. -X = createOutputArray(details); - -% Read all of the blocks. -name = [tempname '.jpg']; -cleaner = onCleanup(@() delete(name)); - -idx = 1; -r = 1; -for j = 1:nRowsBlocks - c = 1; - for i = 1:nColsBlocks - - % If the block is masked, update the position and go to the next. - if (maskedBlocks(idx)) - c = c + w; - idx = idx + 1; - continue; - end - - start = startIdx(idx); - stop = stopIdx(idx); - frameData = compData(start:stop); - - % Read the portion of the image and store it. - data = getImageDataFromFrame(name, frameData); - X(r:(r+h-1), c:(c+w-1), :) = data; - - % Update the position. - c = c + w; - idx = idx + 1; - end - - r = r + h; -end - - - -function compData = getJpegStream(details) - -% Open the NITF file. -fid = fopen(details.filename, 'rb'); -if (fid == 0) - error(message('images:nitfread:jpegFileOpen', details.filename)) -else - c = onCleanup(@() fclose(fid)); -end - -% Move to the location in the file where the JPEG stream begins. -details.offsetStart = convertOffsetType(details.offsetStart); - -status = fseek(fid, details.offsetStart, 'bof'); -if (status ~= 0) - error(message('images:nitfread:jpegFseek')); -end - -% Read the JPEG data, which comprises the rest of the NITF file. -compData = fread(fid, inf, 'uint8=>uint8'); - -% Trim leading 0xFF pad values before SOI. -while ((numel(compData > 2)) && (compData(2) == 255)) - compData(1) = []; -end - -% Validate the JPEG stream. It should start with an SOI marker (0xFF 0xD8) -% and then an APP6 marker (0xFF 0xE6). -if (~isequal(compData(1:2), [255; 216])) - error(message('images:nitfread:badEncapsulation')) -end - -if (~isequal(compData(3:4), [255; 230])) - error(message('images:nitfread:missingAppMarker')) -end - - - -function [startIdx, stopIdx, skipIdx] = getBlockLocations(compData, details, nColsBlocks, nRowsBlocks) - -% Find the Image markers -SOIidx = findpattern(compData', sscanf('ff d8', '%x')'); -EOIidx = findpattern(compData', sscanf('ff d9', '%x')'); -SOFidx = sort([findpattern(compData', sscanf('ff c0', '%x')'), ... - findpattern(compData', sscanf('ff c1', '%x')'), ... - findpattern(compData', sscanf('ff c2', '%x')'), ... - findpattern(compData', sscanf('ff c3', '%x')')]); - -if ((numel(SOIidx) ~= numel(EOIidx)) || ... - (numel(SOIidx) ~= numel(SOFidx))) - error(message('images:nitfread:jpegSoiEoiCount')) -end - -% Does the NITF file use block masking, where some tiles are "virtual"? -if (isfield(details, 'blockOffsets')) - blockMasking = true; - skipIdx = (details.blockOffsets == intmax('uint32')); -else - blockMasking = false; - skipIdx = false(nRowsBlocks * nColsBlocks, 1); -end - -if ((numel(SOIidx) ~= (nColsBlocks * nRowsBlocks)) && ~blockMasking) - warning(message('images:nitfread:wrongSOFIndexCount')); -end - -% Compute where each tile's JPEG data starts and ends. -if (~blockMasking) - % If there's no masking, the data starts and stops with each SOI/EOI pair. - startIdx = SOIidx; - stopIdx = EOIidx + 1; -else - % If there is masking, the data start positions are given by the - % blockOffset values. NaN indicates a masked out frame. - startIdx = details.blockOffsets + 1; - startIdx(skipIdx) = NaN; - - % Each tile ends directly before the next one starts. (Masked frames - % are tricky, so just use the end of the stream.) Shift the start - % values one over and subtract one to determine where they end. The - % last stop value is the last compressed value location. - stopIdx = [(startIdx(2:end) - 1) numel(compData)]; - stopIdx(isnan(stopIdx)) = numel(compData); - stopIdx(skipIdx) = NaN; -end - - - -function X = createOutputArray(details) - -imgSize = [details.imageHeight, details.imageWidth, details.samplesPerPixel]; -if (details.bitsPerSample <= 8) - X = zeros(imgSize, 'uint8'); -elseif (details.bitsPerSample <= 16) - X = zeros(imgSize, 'uint16'); -else - error(message('images:nitfread:jpegBitDepth', details.bitsPerSample)); -end - - - -function img = getImageDataFromFrame(name, frameData) - -fid = fopen(name, 'wb'); -if (fid == 0) - error(message('images:nitfread:tmpJpegFileOpen', name)) -else - c = onCleanup(@() fclose(fid)); -end - -fwrite(fid, frameData, 'uint8'); -delete(c); - -img = imread(name, 'jpeg'); - - - -function idx = findpattern(array, pattern) -%FINDPATTERN Find a pattern in an array. - -% Despite its name, "strfind" is very good at finding numeric patterns in -% numeric vectors. -idx = strfind(array, pattern); - - -function out = convertOffsetType(in) - -if (in > intmax('uint32')) - error(message('images:nitfread:offsetTooBigForJPEG')) -end - -out = double(in); +function X = nitfReadJPEG(details, region) +%nitfReadJPEG Extract JPEG-compressed image from NITF file. +% X = nitfReadJPEG(DETAILS, REGION) returns the image X from the NITF +% file described in the DETAILS structure. The REGION argument is +% currently not used. +% +% See MIL-STD-188-198 for details of this scheme. + +% Copyright 2010-2013 The MathWorks, Inc. + +% Currently there's no pixel subsetting for NITF I/O. +if (~isempty(region)) + error(message('images:nitfread:jpegSubsetting')) +end + +% Get the JPEG data from the file. +compData = getJpegStream(details); + +% Get the number of blocks, dimensions and markers. +nColsBlocks = double(swapbytes(typecast(compData(15:16), 'uint16'))); +nRowsBlocks = double(swapbytes(typecast(compData(17:18), 'uint16'))); + +h = details.tileHeight; +w = details.tileWidth; + +[startIdx, stopIdx, maskedBlocks] = getBlockLocations(compData, details, nColsBlocks, nRowsBlocks); + +% Create the full-sized image. +X = createOutputArray(details); + +% Read all of the blocks. +name = [tempname '.jpg']; +cleaner = onCleanup(@() delete(name)); + +idx = 1; +r = 1; +for j = 1:nRowsBlocks + c = 1; + for i = 1:nColsBlocks + + % If the block is masked, update the position and go to the next. + if (maskedBlocks(idx)) + c = c + w; + idx = idx + 1; + continue; + end + + start = startIdx(idx); + stop = stopIdx(idx); + frameData = compData(start:stop); + + % Read the portion of the image and store it. + data = getImageDataFromFrame(name, frameData); + X(r:(r+h-1), c:(c+w-1), :) = data; + + % Update the position. + c = c + w; + idx = idx + 1; + end + + r = r + h; +end + + + +function compData = getJpegStream(details) + +% Open the NITF file. +fid = fopen(details.filename, 'rb'); +if (fid == 0) + error(message('images:nitfread:jpegFileOpen', details.filename)) +else + c = onCleanup(@() fclose(fid)); +end + +% Move to the location in the file where the JPEG stream begins. +details.offsetStart = convertOffsetType(details.offsetStart); + +status = fseek(fid, details.offsetStart, 'bof'); +if (status ~= 0) + error(message('images:nitfread:jpegFseek')); +end + +% Read the JPEG data, which comprises the rest of the NITF file. +compData = fread(fid, inf, 'uint8=>uint8'); + +% Trim leading 0xFF pad values before SOI. +while ((numel(compData > 2)) && (compData(2) == 255)) + compData(1) = []; +end + +% Validate the JPEG stream. It should start with an SOI marker (0xFF 0xD8) +% and then an APP6 marker (0xFF 0xE6). +if (~isequal(compData(1:2), [255; 216])) + error(message('images:nitfread:badEncapsulation')) +end + +if (~isequal(compData(3:4), [255; 230])) + error(message('images:nitfread:missingAppMarker')) +end + + + +function [startIdx, stopIdx, skipIdx] = getBlockLocations(compData, details, nColsBlocks, nRowsBlocks) + +% Find the Image markers +SOIidx = findpattern(compData', sscanf('ff d8', '%x')'); +EOIidx = findpattern(compData', sscanf('ff d9', '%x')'); +SOFidx = sort([findpattern(compData', sscanf('ff c0', '%x')'), ... + findpattern(compData', sscanf('ff c1', '%x')'), ... + findpattern(compData', sscanf('ff c2', '%x')'), ... + findpattern(compData', sscanf('ff c3', '%x')')]); + +if ((numel(SOIidx) ~= numel(EOIidx)) || ... + (numel(SOIidx) ~= numel(SOFidx))) + error(message('images:nitfread:jpegSoiEoiCount')) +end + +% Does the NITF file use block masking, where some tiles are "virtual"? +if (isfield(details, 'blockOffsets')) + blockMasking = true; + skipIdx = (details.blockOffsets == intmax('uint32')); +else + blockMasking = false; + skipIdx = false(nRowsBlocks * nColsBlocks, 1); +end + +if ((numel(SOIidx) ~= (nColsBlocks * nRowsBlocks)) && ~blockMasking) + warning(message('images:nitfread:wrongSOFIndexCount')); +end + +% Compute where each tile's JPEG data starts and ends. +if (~blockMasking) + % If there's no masking, the data starts and stops with each SOI/EOI pair. + startIdx = SOIidx; + stopIdx = EOIidx + 1; +else + % If there is masking, the data start positions are given by the + % blockOffset values. NaN indicates a masked out frame. + startIdx = details.blockOffsets + 1; + startIdx(skipIdx) = NaN; + + % Each tile ends directly before the next one starts. (Masked frames + % are tricky, so just use the end of the stream.) Shift the start + % values one over and subtract one to determine where they end. The + % last stop value is the last compressed value location. + stopIdx = [(startIdx(2:end) - 1) numel(compData)]; + stopIdx(isnan(stopIdx)) = numel(compData); + stopIdx(skipIdx) = NaN; +end + + + +function X = createOutputArray(details) + +imgSize = [details.imageHeight, details.imageWidth, details.samplesPerPixel]; +if (details.bitsPerSample <= 8) + X = zeros(imgSize, 'uint8'); +elseif (details.bitsPerSample <= 16) + X = zeros(imgSize, 'uint16'); +else + error(message('images:nitfread:jpegBitDepth', details.bitsPerSample)); +end + + + +function img = getImageDataFromFrame(name, frameData) + +fid = fopen(name, 'wb'); +if (fid == 0) + error(message('images:nitfread:tmpJpegFileOpen', name)) +else + c = onCleanup(@() fclose(fid)); +end + +fwrite(fid, frameData, 'uint8'); +delete(c); + +img = imread(name, 'jpeg'); + + + +function idx = findpattern(array, pattern) +%FINDPATTERN Find a pattern in an array. + +% Despite its name, "strfind" is very good at finding numeric patterns in +% numeric vectors. +idx = strfind(array, pattern); + + +function out = convertOffsetType(in) + +if (in > intmax('uint32')) + error(message('images:nitfread:offsetTooBigForJPEG')) +end + +out = double(in); diff --git a/CT/private/nitfReadMeta.m b/CT/private/nitfReadMeta.m index e926cb6..ee2f32b 100644 --- a/CT/private/nitfReadMeta.m +++ b/CT/private/nitfReadMeta.m @@ -1,38 +1,38 @@ -function meta = nitfReadMeta(meta, fieldsTable, fid) -%nitfReadMeta Append attributes to metadata structure. -% OUTMETA = nitfReadMeta(INMETA, FIELDSTABLE, FID) reads attributes -% from the file with handle FID and append it to INMETA the metadata -% structure. The FIELDSTABLE cell array contains details about the -% attributes, such as short names, long names, and data length. - -% Copyright 2008 The MathWorks, Inc. - -% For the sake of performance, pre-allocate storage for the new -% attributes and then insert attributes into the pre-sized struct array. -% Use "offset" to facilitate adding the new attributes. -metaOffset = numel(meta); -numToAppend = size(fieldsTable, 1); - -meta(metaOffset + numToAppend).value = ''; - -% As an optimization, convert the lengths part of the cell array to a -% numeric array. -dataLengths = [fieldsTable{:,3}]; - -% Read all of the data at once. Extract individual values in the loop. -data = fread(fid, sum(dataLengths), 'uint8=>char')'; - -% Insert the new attributes. -dataOffset = 0; -for p = 1:numToAppend - - start = dataOffset + 1; - stop = dataOffset + dataLengths(p); - - meta(metaOffset + p).name = fieldsTable{p,1}; - meta(metaOffset + p).vname = fieldsTable{p,2}; - meta(metaOffset + p).value = data(start:stop); - - dataOffset = stop; - -end +function meta = nitfReadMeta(meta, fieldsTable, fid) +%nitfReadMeta Append attributes to metadata structure. +% OUTMETA = nitfReadMeta(INMETA, FIELDSTABLE, FID) reads attributes +% from the file with handle FID and append it to INMETA the metadata +% structure. The FIELDSTABLE cell array contains details about the +% attributes, such as short names, long names, and data length. + +% Copyright 2008 The MathWorks, Inc. + +% For the sake of performance, pre-allocate storage for the new +% attributes and then insert attributes into the pre-sized struct array. +% Use "offset" to facilitate adding the new attributes. +metaOffset = numel(meta); +numToAppend = size(fieldsTable, 1); + +meta(metaOffset + numToAppend).value = ''; + +% As an optimization, convert the lengths part of the cell array to a +% numeric array. +dataLengths = [fieldsTable{:,3}]; + +% Read all of the data at once. Extract individual values in the loop. +data = fread(fid, sum(dataLengths), 'uint8=>char')'; + +% Insert the new attributes. +dataOffset = 0; +for p = 1:numToAppend + + start = dataOffset + 1; + stop = dataOffset + dataLengths(p); + + meta(metaOffset + p).name = fieldsTable{p,1}; + meta(metaOffset + p).vname = fieldsTable{p,2}; + meta(metaOffset + p).value = data(start:stop); + + dataOffset = stop; + +end diff --git a/CT/private/nitfReadMetaMulti.m b/CT/private/nitfReadMetaMulti.m index 7d402cf..8cf864f 100644 --- a/CT/private/nitfReadMetaMulti.m +++ b/CT/private/nitfReadMetaMulti.m @@ -1,40 +1,40 @@ -function meta = nitfReadMetaMulti(meta, fieldsTable, fid, index) -%nitfReadMetaMulti Append attributes to metadata structure. -% OUTMETA = nitfReadMetaMulti(INMETA, FIELDSTABLE, FID, INDEX) reads -% attributes from the file with handle FID and append it to INMETA the -% metadata structure. The FIELDSTABLE cell array contains details -% about the attributes, such as short names, long names, and data -% length. Unlike nitfReadMeta() the .name and .vname fields are -% templates with sprintf() patterns. - -% Copyright 2008 The MathWorks, Inc. - -% For the sake of performance, pre-allocate storage for the new -% attributes and then insert attributes into the pre-sized struct array. -% Use "offset" to facilitate adding the new attributes. -metaOffset = numel(meta); -numToAppend = size(fieldsTable, 1); - -meta(metaOffset + numToAppend).value = ''; - -% As an optimization, convert the lengths part of the cell array to a -% numeric array. -dataLengths = [fieldsTable{:,3}]; - -% Read all of the data at once. Extract individual values in the loop. -data = fread(fid, sum(dataLengths), 'uint8=>char')'; - -% Insert the new attributes. -dataOffset = 0; -for p = 1:numToAppend - - start = dataOffset + 1; - stop = dataOffset + dataLengths(p); - - meta(metaOffset + p).name = sprintf(fieldsTable{p,1}, index); - meta(metaOffset + p).vname = sprintf(fieldsTable{p,2}, index); - meta(metaOffset + p).value = data(start:stop); - - dataOffset = stop; - -end +function meta = nitfReadMetaMulti(meta, fieldsTable, fid, index) +%nitfReadMetaMulti Append attributes to metadata structure. +% OUTMETA = nitfReadMetaMulti(INMETA, FIELDSTABLE, FID, INDEX) reads +% attributes from the file with handle FID and append it to INMETA the +% metadata structure. The FIELDSTABLE cell array contains details +% about the attributes, such as short names, long names, and data +% length. Unlike nitfReadMeta() the .name and .vname fields are +% templates with sprintf() patterns. + +% Copyright 2008 The MathWorks, Inc. + +% For the sake of performance, pre-allocate storage for the new +% attributes and then insert attributes into the pre-sized struct array. +% Use "offset" to facilitate adding the new attributes. +metaOffset = numel(meta); +numToAppend = size(fieldsTable, 1); + +meta(metaOffset + numToAppend).value = ''; + +% As an optimization, convert the lengths part of the cell array to a +% numeric array. +dataLengths = [fieldsTable{:,3}]; + +% Read all of the data at once. Extract individual values in the loop. +data = fread(fid, sum(dataLengths), 'uint8=>char')'; + +% Insert the new attributes. +dataOffset = 0; +for p = 1:numToAppend + + start = dataOffset + 1; + stop = dataOffset + dataLengths(p); + + meta(metaOffset + p).name = sprintf(fieldsTable{p,1}, index); + meta(metaOffset + p).vname = sprintf(fieldsTable{p,2}, index); + meta(metaOffset + p).value = data(start:stop); + + dataOffset = stop; + +end diff --git a/CT/private/nitfReadMetaNumeric.m b/CT/private/nitfReadMetaNumeric.m index 883a988..3f95745 100644 --- a/CT/private/nitfReadMetaNumeric.m +++ b/CT/private/nitfReadMetaNumeric.m @@ -1,34 +1,34 @@ -function meta = nitfReadMetaNumeric(meta, fieldsTable, fid) -%nitfReadMetaNumeric Append attributes to metadata structure. -% OUTMETA = nitfReadMetaNumeric(INMETA, FIELDSTABLE, FID) reads -% numeric attributes from the file with handle FID and append it to -% INMETA the metadata structure. The FIELDSTABLE cell array contains -% details about the attributes: -% -% * Column 1: short names -% * Column 2: long names -% * Column 3: number of elements (not number of bytes) -% * Column 4: datatype - -% Copyright 2009 The MathWorks, Inc. - -% For the sake of performance, pre-allocate storage for the new -% attributes and then insert attributes into the pre-sized struct array. -% Use "offset" to facilitate adding the new attributes. -metaOffset = numel(meta); -numToAppend = size(fieldsTable, 1); - -meta(metaOffset + numToAppend).value = ''; - -% As an optimization, convert the lengths part of the cell array to a -% numeric array. -dataLengths = [fieldsTable{:,3}]; - -% Read and insert the new attributes. -for p = 1:numToAppend - - meta(metaOffset + p).name = fieldsTable{p,1}; - meta(metaOffset + p).vname = fieldsTable{p,2}; - meta(metaOffset + p).value = fread(fid, dataLengths(p), fieldsTable{p,4}, 'ieee-be'); - -end +function meta = nitfReadMetaNumeric(meta, fieldsTable, fid) +%nitfReadMetaNumeric Append attributes to metadata structure. +% OUTMETA = nitfReadMetaNumeric(INMETA, FIELDSTABLE, FID) reads +% numeric attributes from the file with handle FID and append it to +% INMETA the metadata structure. The FIELDSTABLE cell array contains +% details about the attributes: +% +% * Column 1: short names +% * Column 2: long names +% * Column 3: number of elements (not number of bytes) +% * Column 4: datatype + +% Copyright 2009 The MathWorks, Inc. + +% For the sake of performance, pre-allocate storage for the new +% attributes and then insert attributes into the pre-sized struct array. +% Use "offset" to facilitate adding the new attributes. +metaOffset = numel(meta); +numToAppend = size(fieldsTable, 1); + +meta(metaOffset + numToAppend).value = ''; + +% As an optimization, convert the lengths part of the cell array to a +% numeric array. +dataLengths = [fieldsTable{:,3}]; + +% Read and insert the new attributes. +for p = 1:numToAppend + + meta(metaOffset + p).name = fieldsTable{p,1}; + meta(metaOffset + p).vname = fieldsTable{p,2}; + meta(metaOffset + p).value = fread(fid, dataLengths(p), fieldsTable{p,4}, 'ieee-be'); + +end diff --git a/CT/private/nitfparse20.m b/CT/private/nitfparse20.m index 32052b5..7fd8ad5 100644 --- a/CT/private/nitfparse20.m +++ b/CT/private/nitfparse20.m @@ -1,502 +1,502 @@ -function nitf_meta = nitfparse20(fid) -%NITFPARSE20 Parse the fields in an NITF2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -nitf_meta = struct([]); -fields = {'FHDR', 'FileTypeAndVersion', 9 - 'CLEVEL', 'ComplianceLevel', 2 - 'STYPE', 'SystemType', 4 - 'OSTAID', 'OriginatingStationID', 10 - 'FDT', 'FileDateAndTime', 14 - 'FTITLE', 'FileTitle', 80 - 'FSCLAS', 'FileSecurityClassification', 1 - 'FSCODE', 'FileCodewords', 40 - 'FSCTLH', 'FileControlAndHandling', 40 - 'FSREL', 'FileReleasingInstructions', 40 - 'FSCAUT', 'FileClassificationAuthority', 20 - 'FSCTLN', 'FileSecurityControlNumber', 20 - 'FSDWNG', 'FileSecurityDowngrade', 6}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -%FSDWNG is NITF_META(13) and the last item extracted in the loop above. Depending -%on its value there will be an FSDEVT or File Downgrading Event field. -%If FSDWNG is "999998", add the FSDEVT field to the meta data struct -%and insert values. -fsdwng = nitf_meta(end).value; -nitf_meta = checkFDSWNG(fid, nitf_meta, fsdwng); - -%Pull in the next several required fields -fields = {'FSCOP', 'MessageCopyNumber', 5 - 'FSCPYS', 'MessageNumberOfCopies', 5 - 'ENCRYP', 'Encryption', 1 - 'ONAME', 'OriginatorsName', 27 - 'OPHONE', 'OriginatorsPhoneNumber', 18 - 'FL', 'FileLength', 12 - 'HL', 'NITFFileHeaderLength', 6 - 'NUMI', 'NumberOfImages', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numi = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid); - -%Next field gives the number of symbol (graphic) segments. -fields = {'NUMS', 'NumberOfSymbols', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -nums = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums, nitf_meta, fid); - -%Next group is Labels -fields = {'NUML', 'NumberOfLabels', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numl = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, laLengths] = processtoplabelsubheadermeta(numl, nitf_meta, fid); - -%Next group is text files -fields = {'NUMT', 'NumberOfTextFiles', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numt = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid); - -%Next group is Data Extension Segments -fields = {'NUMDES', 'NumberOfDataExtensionSegments', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numdes = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid); - -%Next group is Reserved Extension Segments -fields = {'NUMRES', 'NumberOfReservedDataExtensionSegments', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numres = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid); - -%Next group is User Define Header Segments which contain tagged record extensions. -%Get the Header Data Length -fields = {'UDHDL', 'UserDefinedHeaderDataLength', 5}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -UDHDL = sscanf(nitf_meta(end).value, '%f'); -nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid); - -%Next group is Extended Header Segments which contain tagged record extensions. -%Get the Header Data Length -fields = {'XHDL', 'ExtendedHeaderDataLength', 5}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -XHDL = sscanf(nitf_meta(end).value, '%f'); -if XHDL > 0 - - % Handle any Extended Header Data - % Note: for our initial release we do not provide explicit support - % for tagged record extensions. All of the contents of the Extended - % Header Data (XHD) field are extracted into a single element. - fields = {'XHDL', 'ExtendedHeaderData', XHDL}; - nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -end - -%Call the image segment metadata parser for each image subheader - -%First, we want the nitf_meta struct to have a ImageSubHeader field. This -%will have the total offset of the subheader(s), and the value will be -%a struct which contains a list of image subheaders. Each subheader in -%the list is displayed table form when the user clicks on the struct -%value. - -%Build the structure of ImageSubHeaders -if numi > 0 - nitf_meta = processImageSubheaders(nitf_meta,numi,fid, imLengths); -end - -%Build the structure of SymbolSubHeaders -if nums > 0 - nitf_meta = processGraphicSubheaders(nitf_meta,nums ,fid, grLengths); -end - -%Build the structure of LabelSubHeaders -if numl > 0 - nitf_meta = processLabelSubheaders(nitf_meta,numl ,fid, laLengths); -end - -%Build the structure of TextSubHeaders -if numt > 0 - nitf_meta = processTextSubheaders(nitf_meta,numt ,fid, teLengths); -end - -%Build the structure of DataExtensionSubHeaders -if numdes > 0 - nitf_meta = processDESubheaders(nitf_meta,numdes ,fid, deLengths); -end - -%Build the structure of ReservedExtensionSubHeaders -if numres > 0 - nitf_meta = processRESubheaders(nitf_meta,numres ,fid, reLengths, reHeaderLengths); -end - - - -function nitf_meta = checkFDSWNG(fid, nitf_meta, fsdwng) -%TODO Factor this and other functions like it out! -fsdwng = sscanf(fsdwng, '%f'); -if (isequal(fsdwng, 999998)) - fields = {'FSDEVT', 'FileDowngradingEvent', 40}; - nitf_meta = nitfReadMeta(nitf_meta, fields, fid); -end - - - -function [nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid) -%Add the nth image subheader information to the nitf_meta struct -%This struct contains the image subheader length and image length -%for all the images in the file -imLengths(1).value = ''; - -if (numi > 0) - - % Preallocate the lengths structure. - imLengths(numi).value = ''; - - nitf_meta(end + 1).name = 'ImageAndSubHeaderLengths'; - nitf_meta(end).vname = 'ImageAndImageSubheaderLengths'; - - fields = {'LISH%03d', 'LengthOfNthImageSubheader', 6 - 'LI%010d', 'LengthOfNthImage', 10}; - - for currentImage = 1:numi - - % Setup. - nitf_metaISL(currentImage).name = sprintf('Image%03d', currentImage); - nitf_metaISL(currentImage).vname = [nitf_metaISL(currentImage).name 'ImageAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentImage); - nitf_metaISL(currentImage).value = tempStruct; - - % Update lengths with the value read. - imLengths(currentImage).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaISL; - -end - - - -function [nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums,nitf_meta, fid) - -grLengths(1).value = ''; - -if (nums > 0) - - % Preallocate the lengths structure. - grLengths(nums).value = ''; - - nitf_meta(end + 1).name = 'SymbolAndSubHeaderLengths'; - nitf_meta(end).vname = 'SymbolAndSymbolSubheaderLengths'; - - fields = {'LSSH%03d', 'LengthOfNthGraphicSubheader', 4 - 'LS%06d', 'LengthOfNthGraphic', 6}; - - for currentGraphic = 1:nums - - % Setup. - nitf_metaGSL(currentGraphic).name = sprintf('Graphic%03d', currentGraphic); - nitf_metaGSL(currentGraphic).vname = [nitf_metaGSL(currentGraphic).name 'GraphicAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentGraphic); - nitf_metaGSL(currentGraphic).value = tempStruct; - - % Update lengths with the value read. - grLengths(currentGraphic).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaGSL; - -end - - - -function [nitf_meta, laLengths] = processtoplabelsubheadermeta(numl, nitf_meta, fid) - -laLengths(1).value = ''; - -if (numl > 0) - - % Preallocate the lengths structure. - laLengths(numl).value = ''; - - nitf_meta(end + 1).name = 'LabelAndSubHeaderLengths'; - nitf_meta(end).vname = 'LabelAndLabelSubheaderLengths'; - - fields = {'LLSH%03d', 'LengthOfNthLabelSubheader', 4 - 'LL%06d', 'LengthOfNthLabel', 3}; - - for currentLabel = 1:numl - - % Setup. - nitf_metaLSL(currentLabel).name = sprintf('Label%03d', currentLabel); - nitf_metaLSL(currentLabel).vname = [nitf_metaLSL(currentLabel).name 'LabelAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentLabel); - nitf_metaLSL(currentLabel).value = tempStruct; - - % Update lengths with the value read. - laLengths(currentLabel).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaLSL; - -end - - - -function [nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid) - -teLengths(1).value = ''; - -if (numt > 0) - - % Preallocate the lengths structure. - teLengths(numt).value = ''; - - nitf_meta(end + 1).name = 'TextAndSubHeaderLengths'; - nitf_meta(end).vname = 'TextAndTextSubheaderLengths'; - - fields = {'LTSH%03d', 'LengthOfNthTextSubheader', 4 - 'LT%03d', 'LengthOfNthText', 5}; - - for currentText = 1:numt - - % Setup. - nitf_metaLTL(currentText).name = sprintf('Text%03d', currentText); - nitf_metaLTL(currentText).vname = [nitf_metaLTL(currentText).name 'TextAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentText); - nitf_metaLTL(currentText).value = tempStruct; - - % Update lengths with the value read. - teLengths(currentText).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaLTL; - -end - - - -function [nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid) - -deLengths(1).value = ''; - -if (numdes > 0) - - % Preallocate the lengths structure. - deLengths(numdes).value = ''; - - nitf_meta(end + 1).name = 'DataExtensionsAndSubHeaderLengths'; - nitf_meta(end).vname = 'DataExtensionAndDataExtensionSubheaderLengths'; - - fields = {'LDSH%03d', 'LengthOfNthDataExtensionSubheader', 4 - 'LD%03d', 'LengthOfNthDataExtension', 9}; - - for currentDES = 1:numdes - - % Setup. - nitf_metaDES(currentDES).name = sprintf('DataExtension%03d', currentDES); - nitf_metaDES(currentDES).vname = [nitf_metaDES(currentDES).name 'DataExtensionAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentDES); - nitf_metaDES(currentDES).value = tempStruct; - - % Update lengths with the value read. - deLengths(currentDES).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaDES; - -end - - - -function [nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid) - -reLengths(1).value = ''; -reHeaderLengths(1).value = ''; - -if (numres > 0) - - % Preallocate the lengths structure. - reLengths(numres).value = ''; - reHeaderLengths(numres).value = ''; - - nitf_meta(end + 1).name = 'ReservedExtensionsAndSubHeaderLengths'; - nitf_meta(end).vname = 'ReservedExtensionAndReservedExtensionSubheaderLengths'; - - fields = {'LRSH%03d', 'LengthOfNthReservedExtensionSegmentSubheader', 4 - 'LR%03d', 'LengthOfNthReservedExtensionSegmentData', 7}; - - for currentRES = 1:numres - - % Setup. - nitf_metaRES(currentRES).name = sprintf('ReservedExtension%03d', currentRES); - nitf_metaRES(currentRES).vname = [nitf_metaRES(currentRES).name 'ReservedExtensionAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentRES); - nitf_metaRES(currentRES).value = tempStruct; - - % Update lengths with the value read. - reHeaderLengths(currentRES).value = tempStruct(1).value; - reLengths(currentRES).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaRES; - -end - - - -function nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid) - -if (UDHDL > 0) - - fields = {'UDHOFL', 'UserDefinedHeaderOverflow', 3 - 'UDHDL', 'UserDefinedHeaderData', UDHDL - 3}; - nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -end - - - -function nitf_meta = processImageSubheaders(nitf_meta, numi, fid, imLengths) -%Add the nth image subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'ImageSubHeaders'; -nitf_meta(end).vname = 'ImageSubheaderMetadata'; - -%Preallocate memory for ISnitf_meta -ISnitf_meta(numi).name = ''; - -for imageNumber = 1:numi - ISnitf_meta(imageNumber).name = sprintf('IS%03d', imageNumber); - ISnitf_meta(imageNumber).vname = sprintf('ImageSubheader%03d', imageNumber); - ISnitf_meta(imageNumber).value = parseimagesubheader20( fid, imLengths(imageNumber).value); -end - -nitf_meta(end).value = ISnitf_meta; - - - -function nitf_meta = processGraphicSubheaders(nitf_meta, nums, fid, grLengths) -%Add the nth symbol subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'SymbolSubHeaders'; -nitf_meta(end).vname = 'SymbolSubheaderMetadata'; - -%Preallocate memory for ISnitf_meta -IGnitf_meta(nums).name = ''; - -for graphicNumber = 1:nums - IGnitf_meta(graphicNumber).name = sprintf('IG%03d', graphicNumber); - IGnitf_meta(graphicNumber).vname = sprintf('SymbolSubheader%03d', graphicNumber); - IGnitf_meta(graphicNumber).value = parsegraphicsubheader20( fid, grLengths(graphicNumber).value); -end - -nitf_meta(end).value = IGnitf_meta; - - - -function nitf_meta = processLabelSubheaders(nitf_meta, numl, fid, laLengths) -%Add the nth text subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'LabelSubHeaders'; -nitf_meta(end).vname = 'LabelSubheaderMetadata'; - -%Preallocate memory for ILnitf_meta -ILnitf_meta(numl).value = ''; - -for labelNumber = 1:numl - ILnitf_meta(labelNumber).name = sprintf('IL%03d', labelNumber); - ILnitf_meta(labelNumber).vname = sprintf('LabelSubheader%03d', labelNumber); - ILnitf_meta(labelNumber).value = parselabelsubheader20( fid, laLengths(labelNumber).value); -end - -nitf_meta(end).value = ILnitf_meta; - - - -function nitf_meta = processTextSubheaders(nitf_meta, numt, fid, teLengths) -%Add the nth text subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'TextSubHeaders'; -nitf_meta(end).vname = 'TextSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta -ITnitf_meta(numt).value = ''; - -for textNumber = 1:numt - ITnitf_meta(textNumber).name = sprintf('IT%03d', textNumber); - ITnitf_meta(textNumber).vname = sprintf('TextSubheader%03d', textNumber); - ITnitf_meta(textNumber).value = parsetextsubheader20( fid, teLengths(textNumber).value); -end - -nitf_meta(end).value = ITnitf_meta; - - - -function nitf_meta = processDESubheaders(nitf_meta, numdes, fid, deLengths) -%Add the nth data extension subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'DataExtensionSubHeaders'; -nitf_meta(end).vname = 'DataExtensionSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta -DEnitf_meta(numdes).value = ''; - -for deNumber = 1:numdes - DEnitf_meta(deNumber).name = sprintf('DE%03d', deNumber); - DEnitf_meta(deNumber).vname = sprintf('DataExtensionSubheader%03d', deNumber); - DEnitf_meta(deNumber).value = parseDEsubheader20( fid, deLengths(deNumber).value); -end - -nitf_meta(end).value = DEnitf_meta; - - - -function nitf_meta = processRESubheaders(nitf_meta, numres, fid, reLengths, reHeaderLengths) -%Add the nth reserved extension header to the nitf_meta struct - -nitf_meta(end + 1).name = 'ReservedExtensionSubHeaders'; -nitf_meta(end).vname = 'ReservedExtensionSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta..How to do this? -REnitf_meta(numres).value = ''; - -for reNumber = 1:numres - REnitf_meta(reNumber).name = sprintf('RE%03d', reNumber); - REnitf_meta(reNumber).vname = sprintf('ReserveExtensionSubheader%03d', reNumber); - REnitf_meta(reNumber).value = parseREsubheader20( fid, reLengths(reNumber).value, reHeaderLengths(reNumber).value); -end - -nitf_meta(end).value = REnitf_meta; +function nitf_meta = nitfparse20(fid) +%NITFPARSE20 Parse the fields in an NITF2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +nitf_meta = struct([]); +fields = {'FHDR', 'FileTypeAndVersion', 9 + 'CLEVEL', 'ComplianceLevel', 2 + 'STYPE', 'SystemType', 4 + 'OSTAID', 'OriginatingStationID', 10 + 'FDT', 'FileDateAndTime', 14 + 'FTITLE', 'FileTitle', 80 + 'FSCLAS', 'FileSecurityClassification', 1 + 'FSCODE', 'FileCodewords', 40 + 'FSCTLH', 'FileControlAndHandling', 40 + 'FSREL', 'FileReleasingInstructions', 40 + 'FSCAUT', 'FileClassificationAuthority', 20 + 'FSCTLN', 'FileSecurityControlNumber', 20 + 'FSDWNG', 'FileSecurityDowngrade', 6}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +%FSDWNG is NITF_META(13) and the last item extracted in the loop above. Depending +%on its value there will be an FSDEVT or File Downgrading Event field. +%If FSDWNG is "999998", add the FSDEVT field to the meta data struct +%and insert values. +fsdwng = nitf_meta(end).value; +nitf_meta = checkFDSWNG(fid, nitf_meta, fsdwng); + +%Pull in the next several required fields +fields = {'FSCOP', 'MessageCopyNumber', 5 + 'FSCPYS', 'MessageNumberOfCopies', 5 + 'ENCRYP', 'Encryption', 1 + 'ONAME', 'OriginatorsName', 27 + 'OPHONE', 'OriginatorsPhoneNumber', 18 + 'FL', 'FileLength', 12 + 'HL', 'NITFFileHeaderLength', 6 + 'NUMI', 'NumberOfImages', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numi = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid); + +%Next field gives the number of symbol (graphic) segments. +fields = {'NUMS', 'NumberOfSymbols', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +nums = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums, nitf_meta, fid); + +%Next group is Labels +fields = {'NUML', 'NumberOfLabels', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numl = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, laLengths] = processtoplabelsubheadermeta(numl, nitf_meta, fid); + +%Next group is text files +fields = {'NUMT', 'NumberOfTextFiles', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numt = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid); + +%Next group is Data Extension Segments +fields = {'NUMDES', 'NumberOfDataExtensionSegments', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numdes = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid); + +%Next group is Reserved Extension Segments +fields = {'NUMRES', 'NumberOfReservedDataExtensionSegments', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numres = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid); + +%Next group is User Define Header Segments which contain tagged record extensions. +%Get the Header Data Length +fields = {'UDHDL', 'UserDefinedHeaderDataLength', 5}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +UDHDL = sscanf(nitf_meta(end).value, '%f'); +nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid); + +%Next group is Extended Header Segments which contain tagged record extensions. +%Get the Header Data Length +fields = {'XHDL', 'ExtendedHeaderDataLength', 5}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +XHDL = sscanf(nitf_meta(end).value, '%f'); +if XHDL > 0 + + % Handle any Extended Header Data + % Note: for our initial release we do not provide explicit support + % for tagged record extensions. All of the contents of the Extended + % Header Data (XHD) field are extracted into a single element. + fields = {'XHDL', 'ExtendedHeaderData', XHDL}; + nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +end + +%Call the image segment metadata parser for each image subheader + +%First, we want the nitf_meta struct to have a ImageSubHeader field. This +%will have the total offset of the subheader(s), and the value will be +%a struct which contains a list of image subheaders. Each subheader in +%the list is displayed table form when the user clicks on the struct +%value. + +%Build the structure of ImageSubHeaders +if numi > 0 + nitf_meta = processImageSubheaders(nitf_meta,numi,fid, imLengths); +end + +%Build the structure of SymbolSubHeaders +if nums > 0 + nitf_meta = processGraphicSubheaders(nitf_meta,nums ,fid, grLengths); +end + +%Build the structure of LabelSubHeaders +if numl > 0 + nitf_meta = processLabelSubheaders(nitf_meta,numl ,fid, laLengths); +end + +%Build the structure of TextSubHeaders +if numt > 0 + nitf_meta = processTextSubheaders(nitf_meta,numt ,fid, teLengths); +end + +%Build the structure of DataExtensionSubHeaders +if numdes > 0 + nitf_meta = processDESubheaders(nitf_meta,numdes ,fid, deLengths); +end + +%Build the structure of ReservedExtensionSubHeaders +if numres > 0 + nitf_meta = processRESubheaders(nitf_meta,numres ,fid, reLengths, reHeaderLengths); +end + + + +function nitf_meta = checkFDSWNG(fid, nitf_meta, fsdwng) +%TODO Factor this and other functions like it out! +fsdwng = sscanf(fsdwng, '%f'); +if (isequal(fsdwng, 999998)) + fields = {'FSDEVT', 'FileDowngradingEvent', 40}; + nitf_meta = nitfReadMeta(nitf_meta, fields, fid); +end + + + +function [nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid) +%Add the nth image subheader information to the nitf_meta struct +%This struct contains the image subheader length and image length +%for all the images in the file +imLengths(1).value = ''; + +if (numi > 0) + + % Preallocate the lengths structure. + imLengths(numi).value = ''; + + nitf_meta(end + 1).name = 'ImageAndSubHeaderLengths'; + nitf_meta(end).vname = 'ImageAndImageSubheaderLengths'; + + fields = {'LISH%03d', 'LengthOfNthImageSubheader', 6 + 'LI%010d', 'LengthOfNthImage', 10}; + + for currentImage = 1:numi + + % Setup. + nitf_metaISL(currentImage).name = sprintf('Image%03d', currentImage); + nitf_metaISL(currentImage).vname = [nitf_metaISL(currentImage).name 'ImageAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentImage); + nitf_metaISL(currentImage).value = tempStruct; + + % Update lengths with the value read. + imLengths(currentImage).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaISL; + +end + + + +function [nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums,nitf_meta, fid) + +grLengths(1).value = ''; + +if (nums > 0) + + % Preallocate the lengths structure. + grLengths(nums).value = ''; + + nitf_meta(end + 1).name = 'SymbolAndSubHeaderLengths'; + nitf_meta(end).vname = 'SymbolAndSymbolSubheaderLengths'; + + fields = {'LSSH%03d', 'LengthOfNthGraphicSubheader', 4 + 'LS%06d', 'LengthOfNthGraphic', 6}; + + for currentGraphic = 1:nums + + % Setup. + nitf_metaGSL(currentGraphic).name = sprintf('Graphic%03d', currentGraphic); + nitf_metaGSL(currentGraphic).vname = [nitf_metaGSL(currentGraphic).name 'GraphicAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentGraphic); + nitf_metaGSL(currentGraphic).value = tempStruct; + + % Update lengths with the value read. + grLengths(currentGraphic).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaGSL; + +end + + + +function [nitf_meta, laLengths] = processtoplabelsubheadermeta(numl, nitf_meta, fid) + +laLengths(1).value = ''; + +if (numl > 0) + + % Preallocate the lengths structure. + laLengths(numl).value = ''; + + nitf_meta(end + 1).name = 'LabelAndSubHeaderLengths'; + nitf_meta(end).vname = 'LabelAndLabelSubheaderLengths'; + + fields = {'LLSH%03d', 'LengthOfNthLabelSubheader', 4 + 'LL%06d', 'LengthOfNthLabel', 3}; + + for currentLabel = 1:numl + + % Setup. + nitf_metaLSL(currentLabel).name = sprintf('Label%03d', currentLabel); + nitf_metaLSL(currentLabel).vname = [nitf_metaLSL(currentLabel).name 'LabelAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentLabel); + nitf_metaLSL(currentLabel).value = tempStruct; + + % Update lengths with the value read. + laLengths(currentLabel).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaLSL; + +end + + + +function [nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid) + +teLengths(1).value = ''; + +if (numt > 0) + + % Preallocate the lengths structure. + teLengths(numt).value = ''; + + nitf_meta(end + 1).name = 'TextAndSubHeaderLengths'; + nitf_meta(end).vname = 'TextAndTextSubheaderLengths'; + + fields = {'LTSH%03d', 'LengthOfNthTextSubheader', 4 + 'LT%03d', 'LengthOfNthText', 5}; + + for currentText = 1:numt + + % Setup. + nitf_metaLTL(currentText).name = sprintf('Text%03d', currentText); + nitf_metaLTL(currentText).vname = [nitf_metaLTL(currentText).name 'TextAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentText); + nitf_metaLTL(currentText).value = tempStruct; + + % Update lengths with the value read. + teLengths(currentText).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaLTL; + +end + + + +function [nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid) + +deLengths(1).value = ''; + +if (numdes > 0) + + % Preallocate the lengths structure. + deLengths(numdes).value = ''; + + nitf_meta(end + 1).name = 'DataExtensionsAndSubHeaderLengths'; + nitf_meta(end).vname = 'DataExtensionAndDataExtensionSubheaderLengths'; + + fields = {'LDSH%03d', 'LengthOfNthDataExtensionSubheader', 4 + 'LD%03d', 'LengthOfNthDataExtension', 9}; + + for currentDES = 1:numdes + + % Setup. + nitf_metaDES(currentDES).name = sprintf('DataExtension%03d', currentDES); + nitf_metaDES(currentDES).vname = [nitf_metaDES(currentDES).name 'DataExtensionAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentDES); + nitf_metaDES(currentDES).value = tempStruct; + + % Update lengths with the value read. + deLengths(currentDES).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaDES; + +end + + + +function [nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid) + +reLengths(1).value = ''; +reHeaderLengths(1).value = ''; + +if (numres > 0) + + % Preallocate the lengths structure. + reLengths(numres).value = ''; + reHeaderLengths(numres).value = ''; + + nitf_meta(end + 1).name = 'ReservedExtensionsAndSubHeaderLengths'; + nitf_meta(end).vname = 'ReservedExtensionAndReservedExtensionSubheaderLengths'; + + fields = {'LRSH%03d', 'LengthOfNthReservedExtensionSegmentSubheader', 4 + 'LR%03d', 'LengthOfNthReservedExtensionSegmentData', 7}; + + for currentRES = 1:numres + + % Setup. + nitf_metaRES(currentRES).name = sprintf('ReservedExtension%03d', currentRES); + nitf_metaRES(currentRES).vname = [nitf_metaRES(currentRES).name 'ReservedExtensionAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentRES); + nitf_metaRES(currentRES).value = tempStruct; + + % Update lengths with the value read. + reHeaderLengths(currentRES).value = tempStruct(1).value; + reLengths(currentRES).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaRES; + +end + + + +function nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid) + +if (UDHDL > 0) + + fields = {'UDHOFL', 'UserDefinedHeaderOverflow', 3 + 'UDHDL', 'UserDefinedHeaderData', UDHDL - 3}; + nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +end + + + +function nitf_meta = processImageSubheaders(nitf_meta, numi, fid, imLengths) +%Add the nth image subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'ImageSubHeaders'; +nitf_meta(end).vname = 'ImageSubheaderMetadata'; + +%Preallocate memory for ISnitf_meta +ISnitf_meta(numi).name = ''; + +for imageNumber = 1:numi + ISnitf_meta(imageNumber).name = sprintf('IS%03d', imageNumber); + ISnitf_meta(imageNumber).vname = sprintf('ImageSubheader%03d', imageNumber); + ISnitf_meta(imageNumber).value = parseimagesubheader20( fid, imLengths(imageNumber).value); +end + +nitf_meta(end).value = ISnitf_meta; + + + +function nitf_meta = processGraphicSubheaders(nitf_meta, nums, fid, grLengths) +%Add the nth symbol subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'SymbolSubHeaders'; +nitf_meta(end).vname = 'SymbolSubheaderMetadata'; + +%Preallocate memory for ISnitf_meta +IGnitf_meta(nums).name = ''; + +for graphicNumber = 1:nums + IGnitf_meta(graphicNumber).name = sprintf('IG%03d', graphicNumber); + IGnitf_meta(graphicNumber).vname = sprintf('SymbolSubheader%03d', graphicNumber); + IGnitf_meta(graphicNumber).value = parsegraphicsubheader20( fid, grLengths(graphicNumber).value); +end + +nitf_meta(end).value = IGnitf_meta; + + + +function nitf_meta = processLabelSubheaders(nitf_meta, numl, fid, laLengths) +%Add the nth text subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'LabelSubHeaders'; +nitf_meta(end).vname = 'LabelSubheaderMetadata'; + +%Preallocate memory for ILnitf_meta +ILnitf_meta(numl).value = ''; + +for labelNumber = 1:numl + ILnitf_meta(labelNumber).name = sprintf('IL%03d', labelNumber); + ILnitf_meta(labelNumber).vname = sprintf('LabelSubheader%03d', labelNumber); + ILnitf_meta(labelNumber).value = parselabelsubheader20( fid, laLengths(labelNumber).value); +end + +nitf_meta(end).value = ILnitf_meta; + + + +function nitf_meta = processTextSubheaders(nitf_meta, numt, fid, teLengths) +%Add the nth text subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'TextSubHeaders'; +nitf_meta(end).vname = 'TextSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta +ITnitf_meta(numt).value = ''; + +for textNumber = 1:numt + ITnitf_meta(textNumber).name = sprintf('IT%03d', textNumber); + ITnitf_meta(textNumber).vname = sprintf('TextSubheader%03d', textNumber); + ITnitf_meta(textNumber).value = parsetextsubheader20( fid, teLengths(textNumber).value); +end + +nitf_meta(end).value = ITnitf_meta; + + + +function nitf_meta = processDESubheaders(nitf_meta, numdes, fid, deLengths) +%Add the nth data extension subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'DataExtensionSubHeaders'; +nitf_meta(end).vname = 'DataExtensionSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta +DEnitf_meta(numdes).value = ''; + +for deNumber = 1:numdes + DEnitf_meta(deNumber).name = sprintf('DE%03d', deNumber); + DEnitf_meta(deNumber).vname = sprintf('DataExtensionSubheader%03d', deNumber); + DEnitf_meta(deNumber).value = parseDEsubheader20( fid, deLengths(deNumber).value); +end + +nitf_meta(end).value = DEnitf_meta; + + + +function nitf_meta = processRESubheaders(nitf_meta, numres, fid, reLengths, reHeaderLengths) +%Add the nth reserved extension header to the nitf_meta struct + +nitf_meta(end + 1).name = 'ReservedExtensionSubHeaders'; +nitf_meta(end).vname = 'ReservedExtensionSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta..How to do this? +REnitf_meta(numres).value = ''; + +for reNumber = 1:numres + REnitf_meta(reNumber).name = sprintf('RE%03d', reNumber); + REnitf_meta(reNumber).vname = sprintf('ReserveExtensionSubheader%03d', reNumber); + REnitf_meta(reNumber).value = parseREsubheader20( fid, reLengths(reNumber).value, reHeaderLengths(reNumber).value); +end + +nitf_meta(end).value = REnitf_meta; diff --git a/CT/private/nitfparse21.m b/CT/private/nitfparse21.m index 7c65758..1e85a87 100644 --- a/CT/private/nitfparse21.m +++ b/CT/private/nitfparse21.m @@ -1,435 +1,435 @@ -function nitf_meta = nitfparse21(fid) -%NITFPARSE21 Parse the fields in an NITF2.1 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -nitf_meta = struct([]); -fields = {'FHDR', 'FileProfileNameAndVersion', 9 - 'CLEVEL', 'ComplexityLevel', 2 - 'STYPE', 'StandardType', 4 - 'OSTAID', 'OriginatingStationID', 10 - 'FDT', 'FileDateAndTime', 14 - 'FTITLE', 'FileTitle', 80 - 'FSCLAS', 'FileSecurityClassification', 1 - 'FSCLSY', 'FileSecurityClassificationSystem', 2 - 'FSCODE', 'FileCodewords', 11 - 'FSCTLH', 'FileControlAndHandling', 2 - 'FSREL', 'FileReleasingInstructions', 20 - 'FSDCTP', 'FileDeclassificationType', 2 - 'FSDCDT', 'FileDeclassificationDate', 8 - 'FSDCXM', 'FileDeclassificationExemption', 4 - 'FSDG', 'FileDowngrade', 1 - 'FSDGT', 'FileDowngradeDate', 8 - 'FSCLTX', 'FileClassificationText', 43 - 'FSCATP', 'FileClassificationAuthorityType', 1 - 'FSCAUT', 'FileClassificationAuthority', 40 - 'FSCRSN', 'FileClassificationReason', 1 - 'FSSRDT', 'FileSecuritySourceDate', 8 - 'FSCTLN', 'FileSecurityControlNumber', 15 - 'FSCOP', 'FileCopyNumber', 5 - 'FSCPYS', 'FileNumberOfCopies', 5 - 'ENCRYP', 'Encryption', 1 - 'FBKGC', 'FileBackgroundColor', 3 - 'ONAME', 'OriginatorName', 24 - 'OPHONE', 'OriginatorPhoneNumber', 18 - 'FL', 'FileLength', 12 - 'HL', 'NITFFileHeaderLength', 6 - 'NUMI', 'NumberOfImages', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numi = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid); - -%Next field gives the number of graphics segments. -fields = {'NUMS', 'NumberOfGraphics', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -nums = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums, nitf_meta, fid); - -%Next field is reserved for future use but it is a required field -fields = {'NUMX', 'ReservedForFutureUse', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -%Next group is text files -fields = {'NUMT', 'NumberOfTextFiles', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numt = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid); - -%Next group is Data Extension Segments -fields = {'NUMDES', 'NumberOfDataExtensionSegments', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numdes = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid); - -%Next group is Reserved Extension Segments -fields = {'NUMRES', 'NumberOfReservedDataExtensionSegments', 3}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -numres = sscanf(nitf_meta(end).value, '%f'); -[nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid); - -%Next group is User Define Header Segments which contain tagged record extensions. -%Get the Header Data Length -fields = {'UDHDL', 'UserDefinedHeaderDataLength', 5}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -UDHDL = sscanf(nitf_meta(end).value, '%f'); -nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid); - -%Next group is Extended Header Segments which contain tagged record extensions. -%Get the Header Data Length -fields = {'XHDL', 'ExtendedHeaderDataLength', 5}; -nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -XHDL = sscanf(nitf_meta(end).value, '%f'); -nitf_meta = processtopxsubheadermeta(XHDL, nitf_meta, fid); - -%Call the image segment metadata parser for each image subheader - -% We want the image subheader(s) to be drilllable from the structure -% editor. Image subheader data will not be visible from command line -% feedback without explicitly accessing the structure. - -%First, we want the nitf_meta struct to have a ImageSubHeader field. This -%will have the total offset of the subheader(s), and the value will be -%a struct which contains a list of image subheaders. Each subheader in -%the list is displayed table form when the user clicks on the struct -%value. - -%Build the structure of ImageSubHeaders -if numi > 0 - nitf_meta = processImageSubheaders(nitf_meta,numi,fid, imLengths); -end - -%Build the structure of SymbolSubHeaders -if nums > 0 - nitf_meta = processGraphicSubheaders(nitf_meta,nums ,fid, grLengths); -end - -%Build the structure of TextSubHeaders -if numt > 0 - nitf_meta = processTextSubheaders(nitf_meta,numt ,fid, teLengths); -end - -%Build the structure of DataExtensionSubHeaders -if numdes > 0 - nitf_meta = processDESubheaders(nitf_meta,numdes ,fid, deLengths); -end - -%Build the structure of ReservedExtensionSubHeaders -if numres > 0 - nitf_meta = processRESubheaders(nitf_meta,numres ,fid, reLengths, reHeaderLengths); -end - - - -function [nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid) -%Add the nth image subheader information to the nitf_meta struct -%This struct contains the image subheader length and image length -%for all the images in the file -imLengths(1).value = ''; - -if (numi > 0) - - % Preallocate the lengths structure. - imLengths(numi).value = ''; - - nitf_meta(end + 1).name = 'ImageAndSubHeaderLengths'; - nitf_meta(end).vname = 'ImageAndImageSubheaderLengths'; - - fields = {'LISH%03d', 'LengthOfNthImageSubheader', 6 - 'LI%010d', 'LengthOfNthImage', 10}; - - for currentImage = 1:numi - - % Setup. - nitf_metaISL(currentImage).name = sprintf('Image%03d', currentImage); - nitf_metaISL(currentImage).vname = [nitf_metaISL(currentImage).name 'ImageAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentImage); - nitf_metaISL(currentImage).value = tempStruct; - - % Update lengths with the value read. - imLengths(currentImage).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaISL; - -end - - - -function [nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums,nitf_meta, fid) - -grLengths(1).value = ''; - -if (nums > 0) - - % Preallocate the lengths structure. - grLengths(nums).value = ''; - - nitf_meta(end + 1).name = 'GraphicAndSubHeaderLengths'; - nitf_meta(end).vname = 'GraphicAndGraphicSubheaderLengths'; - - fields = {'LSSH%03d', 'LengthOfNthGraphicSubheader', 4 - 'LS%06d', 'LengthOfNthGraphic', 6}; - - for currentGraphic = 1:nums - - % Setup. - nitf_metaGSL(currentGraphic).name = sprintf('Graphic%03d', currentGraphic); - nitf_metaGSL(currentGraphic).vname = [nitf_metaGSL(currentGraphic).name 'GraphicAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentGraphic); - nitf_metaGSL(currentGraphic).value = tempStruct; - - % Update lengths with the value read. - grLengths(currentGraphic).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaGSL; - -end - - - -function [nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid) - -teLengths(1).value = ''; - -if (numt > 0) - - % Preallocate the lengths structure. - teLengths(numt).value = ''; - - nitf_meta(end + 1).name = 'TextAndSubHeaderLengths'; - nitf_meta(end).vname = 'TextAndTextSubheaderLengths'; - - fields = {'LTSH%03d', 'LengthOfNthTextSubheader', 4 - 'LT%03d', 'LengthOfNthText', 5}; - - for currentText = 1:numt - - % Setup. - nitf_metaLTL(currentText).name = sprintf('Text%03d', currentText); - nitf_metaLTL(currentText).vname = [nitf_metaLTL(currentText).name 'TextAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentText); - nitf_metaLTL(currentText).value = tempStruct; - - % Update lengths with the value read. - teLengths(currentText).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaLTL; - -end - - - -function [nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid) - -deLengths(1).value = ''; - -if (numdes > 0) - - % Preallocate the lengths structure. - deLengths(numdes).value = ''; - - nitf_meta(end + 1).name = 'DataExtensionsAndSubHeaderLengths'; - nitf_meta(end).vname = 'DataExtensionAndDataExtensionSubheaderLengths'; - - fields = {'LDSH%03d', 'LengthOfNthDataExtensionSubheader', 4 - 'LD%03d', 'LengthOfNthDataExtension', 9}; - - for currentDES = 1:numdes - - % Setup. - nitf_metaDES(currentDES).name = sprintf('DataExtension%03d', currentDES); - nitf_metaDES(currentDES).vname = [nitf_metaDES(currentDES).name 'DataExtensionAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentDES); - nitf_metaDES(currentDES).value = tempStruct; - - % Update lengths with the value read. - deLengths(currentDES).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaDES; - -end - - - -function [nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid) - -reLengths(1).value = ''; -reHeaderLengths(1).value = ''; - -if (numres > 0) - - % Preallocate the lengths structure. - reLengths(numres).value = ''; - reHeaderLengths(numres).value = ''; - - nitf_meta(end + 1).name = 'ReservedExtensionsAndSubHeaderLengths'; - nitf_meta(end).vname = 'ReservedExtensionAndReservedExtensionSubheaderLengths'; - - fields = {'LRSH%03d', 'LengthOfNthReservedExtensionSegmentSubheader', 4 - 'LR%03d', 'LengthOfNthReservedExtensionSegmentData', 7}; - - for currentRES = 1:numres - - % Setup. - nitf_metaRES(currentRES).name = sprintf('ReservedExtension%03d', currentRES); - nitf_metaRES(currentRES).vname = [nitf_metaRES(currentRES).name 'ReservedExtensionAndSubheaderLengths']; - - % Parse the data. - tempStruct = struct([]); - tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentRES); - nitf_metaRES(currentRES).value = tempStruct; - - % Update lengths with the value read. - reHeaderLengths(currentRES).value = tempStruct(1).value; - reLengths(currentRES).value = tempStruct(2).value; - - end - - nitf_meta(end).value = nitf_metaRES; - -end - - - -function nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid) - -if (UDHDL > 0) - - fields = {'UDHOFL', 'UserDefinedHeaderOverflow', 3 - 'UDHDL', 'UserDefinedHeaderData', UDHDL - 3}; - nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -end - - - -function nitf_meta = processtopxsubheadermeta(XHDL, nitf_meta, fid) - -if (XHDL > 0) - - fields = {'XHDLOFL', 'UserHeaderDataOverflow', 3 - 'XHDL', 'ExtendedHeaderData', XHDL - 3}; - nitf_meta = nitfReadMeta(nitf_meta, fields, fid); - -end - - - -function nitf_meta = processImageSubheaders(nitf_meta, numi, fid, imLengths) -%Add the nth image subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'ImageSubHeaders'; -nitf_meta(end).vname = 'ImageSubheaderMetadata'; - -%Preallocate memory for ISnitf_meta -ISnitf_meta(numi).name = ''; - -for imageNumber = 1:numi - ISnitf_meta(imageNumber).name = sprintf('IS%03d', imageNumber); - ISnitf_meta(imageNumber).vname = sprintf('ImageSubheader%03d', imageNumber); - ISnitf_meta(imageNumber).value = parseimagesubheader( fid, imLengths(imageNumber).value); -end - -nitf_meta(end).value = ISnitf_meta; - - - -function nitf_meta = processGraphicSubheaders(nitf_meta, nums, fid, grLengths) -%Add the nth graphic subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'GraphicSubHeaders'; -nitf_meta(end).vname = 'GraphicSubheaderMetadata'; - -%Preallocate memory for ISnitf_meta -IGnitf_meta(nums).name = ''; - -for graphicNumber = 1:nums - IGnitf_meta(graphicNumber).name = sprintf('IG%03d', graphicNumber); - IGnitf_meta(graphicNumber).vname = sprintf('GraphicSubheader%03d', graphicNumber); - IGnitf_meta(graphicNumber).value = parsegraphicsubheader( fid, grLengths(graphicNumber).value); -end - -nitf_meta(end).value = IGnitf_meta; - - - -function nitf_meta = processTextSubheaders(nitf_meta, numt, fid, teLengths) -%Add the nth text subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'TextSubHeaders'; -nitf_meta(end).vname = 'TextSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta -ITnitf_meta(numt).value = ''; - -for textNumber = 1:numt - ITnitf_meta(textNumber).name = sprintf('IT%03d', textNumber); - ITnitf_meta(textNumber).vname = sprintf('TextSubheader%03d', textNumber); - ITnitf_meta(textNumber).value = parsetextsubheader( fid, teLengths(textNumber).value); -end - -nitf_meta(end).value = ITnitf_meta; - - - -function nitf_meta = processDESubheaders(nitf_meta, numdes, fid, deLengths) -%Add the nth data extension subheader to the nitf_meta struct - -nitf_meta(end + 1).name = 'DataExtensionSubHeaders'; -nitf_meta(end).vname = 'DataExtensionSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta -DEnitf_meta(numdes).value = ''; - -for deNumber = 1:numdes - DEnitf_meta(deNumber).name = sprintf('DE%03d', deNumber); - DEnitf_meta(deNumber).vname = sprintf('DataExtensionSubheader%03d', deNumber); - DEnitf_meta(deNumber).value = parseDEsubheader( fid, deLengths(deNumber).value); -end - -nitf_meta(end).value = DEnitf_meta; - - - -function nitf_meta = processRESubheaders(nitf_meta, numres, fid, reLengths, reHeaderLengths) -%Add the nth reserved extension header to the nitf_meta struct - -nitf_meta(end + 1).name = 'ReservedExtensionSubHeaders'; -nitf_meta(end).vname = 'ReservedExtensionSubheaderMetadata'; - -%Preallocate memory for ITnitf_meta..How to do this? -REnitf_meta(numres).value = ''; - -for reNumber = 1:numres - REnitf_meta(reNumber).name = sprintf('RE%03d', reNumber); - REnitf_meta(reNumber).vname = sprintf('ReserveExtensionSubheader%03d', reNumber); - REnitf_meta(reNumber).value = parseREsubheader20( fid, reLengths(reNumber).value, reHeaderLengths(reNumber).value); -end - -nitf_meta(end).value = REnitf_meta; +function nitf_meta = nitfparse21(fid) +%NITFPARSE21 Parse the fields in an NITF2.1 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +nitf_meta = struct([]); +fields = {'FHDR', 'FileProfileNameAndVersion', 9 + 'CLEVEL', 'ComplexityLevel', 2 + 'STYPE', 'StandardType', 4 + 'OSTAID', 'OriginatingStationID', 10 + 'FDT', 'FileDateAndTime', 14 + 'FTITLE', 'FileTitle', 80 + 'FSCLAS', 'FileSecurityClassification', 1 + 'FSCLSY', 'FileSecurityClassificationSystem', 2 + 'FSCODE', 'FileCodewords', 11 + 'FSCTLH', 'FileControlAndHandling', 2 + 'FSREL', 'FileReleasingInstructions', 20 + 'FSDCTP', 'FileDeclassificationType', 2 + 'FSDCDT', 'FileDeclassificationDate', 8 + 'FSDCXM', 'FileDeclassificationExemption', 4 + 'FSDG', 'FileDowngrade', 1 + 'FSDGT', 'FileDowngradeDate', 8 + 'FSCLTX', 'FileClassificationText', 43 + 'FSCATP', 'FileClassificationAuthorityType', 1 + 'FSCAUT', 'FileClassificationAuthority', 40 + 'FSCRSN', 'FileClassificationReason', 1 + 'FSSRDT', 'FileSecuritySourceDate', 8 + 'FSCTLN', 'FileSecurityControlNumber', 15 + 'FSCOP', 'FileCopyNumber', 5 + 'FSCPYS', 'FileNumberOfCopies', 5 + 'ENCRYP', 'Encryption', 1 + 'FBKGC', 'FileBackgroundColor', 3 + 'ONAME', 'OriginatorName', 24 + 'OPHONE', 'OriginatorPhoneNumber', 18 + 'FL', 'FileLength', 12 + 'HL', 'NITFFileHeaderLength', 6 + 'NUMI', 'NumberOfImages', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numi = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid); + +%Next field gives the number of graphics segments. +fields = {'NUMS', 'NumberOfGraphics', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +nums = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums, nitf_meta, fid); + +%Next field is reserved for future use but it is a required field +fields = {'NUMX', 'ReservedForFutureUse', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +%Next group is text files +fields = {'NUMT', 'NumberOfTextFiles', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numt = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid); + +%Next group is Data Extension Segments +fields = {'NUMDES', 'NumberOfDataExtensionSegments', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numdes = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid); + +%Next group is Reserved Extension Segments +fields = {'NUMRES', 'NumberOfReservedDataExtensionSegments', 3}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +numres = sscanf(nitf_meta(end).value, '%f'); +[nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid); + +%Next group is User Define Header Segments which contain tagged record extensions. +%Get the Header Data Length +fields = {'UDHDL', 'UserDefinedHeaderDataLength', 5}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +UDHDL = sscanf(nitf_meta(end).value, '%f'); +nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid); + +%Next group is Extended Header Segments which contain tagged record extensions. +%Get the Header Data Length +fields = {'XHDL', 'ExtendedHeaderDataLength', 5}; +nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +XHDL = sscanf(nitf_meta(end).value, '%f'); +nitf_meta = processtopxsubheadermeta(XHDL, nitf_meta, fid); + +%Call the image segment metadata parser for each image subheader + +% We want the image subheader(s) to be drilllable from the structure +% editor. Image subheader data will not be visible from command line +% feedback without explicitly accessing the structure. + +%First, we want the nitf_meta struct to have a ImageSubHeader field. This +%will have the total offset of the subheader(s), and the value will be +%a struct which contains a list of image subheaders. Each subheader in +%the list is displayed table form when the user clicks on the struct +%value. + +%Build the structure of ImageSubHeaders +if numi > 0 + nitf_meta = processImageSubheaders(nitf_meta,numi,fid, imLengths); +end + +%Build the structure of SymbolSubHeaders +if nums > 0 + nitf_meta = processGraphicSubheaders(nitf_meta,nums ,fid, grLengths); +end + +%Build the structure of TextSubHeaders +if numt > 0 + nitf_meta = processTextSubheaders(nitf_meta,numt ,fid, teLengths); +end + +%Build the structure of DataExtensionSubHeaders +if numdes > 0 + nitf_meta = processDESubheaders(nitf_meta,numdes ,fid, deLengths); +end + +%Build the structure of ReservedExtensionSubHeaders +if numres > 0 + nitf_meta = processRESubheaders(nitf_meta,numres ,fid, reLengths, reHeaderLengths); +end + + + +function [nitf_meta, imLengths] = processtopimagesubheadermeta(numi, nitf_meta, fid) +%Add the nth image subheader information to the nitf_meta struct +%This struct contains the image subheader length and image length +%for all the images in the file +imLengths(1).value = ''; + +if (numi > 0) + + % Preallocate the lengths structure. + imLengths(numi).value = ''; + + nitf_meta(end + 1).name = 'ImageAndSubHeaderLengths'; + nitf_meta(end).vname = 'ImageAndImageSubheaderLengths'; + + fields = {'LISH%03d', 'LengthOfNthImageSubheader', 6 + 'LI%010d', 'LengthOfNthImage', 10}; + + for currentImage = 1:numi + + % Setup. + nitf_metaISL(currentImage).name = sprintf('Image%03d', currentImage); + nitf_metaISL(currentImage).vname = [nitf_metaISL(currentImage).name 'ImageAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentImage); + nitf_metaISL(currentImage).value = tempStruct; + + % Update lengths with the value read. + imLengths(currentImage).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaISL; + +end + + + +function [nitf_meta, grLengths] = processtopgraphicsubheadermeta(nums,nitf_meta, fid) + +grLengths(1).value = ''; + +if (nums > 0) + + % Preallocate the lengths structure. + grLengths(nums).value = ''; + + nitf_meta(end + 1).name = 'GraphicAndSubHeaderLengths'; + nitf_meta(end).vname = 'GraphicAndGraphicSubheaderLengths'; + + fields = {'LSSH%03d', 'LengthOfNthGraphicSubheader', 4 + 'LS%06d', 'LengthOfNthGraphic', 6}; + + for currentGraphic = 1:nums + + % Setup. + nitf_metaGSL(currentGraphic).name = sprintf('Graphic%03d', currentGraphic); + nitf_metaGSL(currentGraphic).vname = [nitf_metaGSL(currentGraphic).name 'GraphicAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentGraphic); + nitf_metaGSL(currentGraphic).value = tempStruct; + + % Update lengths with the value read. + grLengths(currentGraphic).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaGSL; + +end + + + +function [nitf_meta, teLengths] = processtoptextsubheadermeta(numt, nitf_meta, fid) + +teLengths(1).value = ''; + +if (numt > 0) + + % Preallocate the lengths structure. + teLengths(numt).value = ''; + + nitf_meta(end + 1).name = 'TextAndSubHeaderLengths'; + nitf_meta(end).vname = 'TextAndTextSubheaderLengths'; + + fields = {'LTSH%03d', 'LengthOfNthTextSubheader', 4 + 'LT%03d', 'LengthOfNthText', 5}; + + for currentText = 1:numt + + % Setup. + nitf_metaLTL(currentText).name = sprintf('Text%03d', currentText); + nitf_metaLTL(currentText).vname = [nitf_metaLTL(currentText).name 'TextAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentText); + nitf_metaLTL(currentText).value = tempStruct; + + % Update lengths with the value read. + teLengths(currentText).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaLTL; + +end + + + +function [nitf_meta, deLengths] = processtopdesubheadermeta(numdes, nitf_meta, fid) + +deLengths(1).value = ''; + +if (numdes > 0) + + % Preallocate the lengths structure. + deLengths(numdes).value = ''; + + nitf_meta(end + 1).name = 'DataExtensionsAndSubHeaderLengths'; + nitf_meta(end).vname = 'DataExtensionAndDataExtensionSubheaderLengths'; + + fields = {'LDSH%03d', 'LengthOfNthDataExtensionSubheader', 4 + 'LD%03d', 'LengthOfNthDataExtension', 9}; + + for currentDES = 1:numdes + + % Setup. + nitf_metaDES(currentDES).name = sprintf('DataExtension%03d', currentDES); + nitf_metaDES(currentDES).vname = [nitf_metaDES(currentDES).name 'DataExtensionAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentDES); + nitf_metaDES(currentDES).value = tempStruct; + + % Update lengths with the value read. + deLengths(currentDES).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaDES; + +end + + + +function [nitf_meta, reLengths, reHeaderLengths] = processtopresubheadermeta(numres, nitf_meta, fid) + +reLengths(1).value = ''; +reHeaderLengths(1).value = ''; + +if (numres > 0) + + % Preallocate the lengths structure. + reLengths(numres).value = ''; + reHeaderLengths(numres).value = ''; + + nitf_meta(end + 1).name = 'ReservedExtensionsAndSubHeaderLengths'; + nitf_meta(end).vname = 'ReservedExtensionAndReservedExtensionSubheaderLengths'; + + fields = {'LRSH%03d', 'LengthOfNthReservedExtensionSegmentSubheader', 4 + 'LR%03d', 'LengthOfNthReservedExtensionSegmentData', 7}; + + for currentRES = 1:numres + + % Setup. + nitf_metaRES(currentRES).name = sprintf('ReservedExtension%03d', currentRES); + nitf_metaRES(currentRES).vname = [nitf_metaRES(currentRES).name 'ReservedExtensionAndSubheaderLengths']; + + % Parse the data. + tempStruct = struct([]); + tempStruct = nitfReadMetaMulti(tempStruct, fields, fid, currentRES); + nitf_metaRES(currentRES).value = tempStruct; + + % Update lengths with the value read. + reHeaderLengths(currentRES).value = tempStruct(1).value; + reLengths(currentRES).value = tempStruct(2).value; + + end + + nitf_meta(end).value = nitf_metaRES; + +end + + + +function nitf_meta = processtopudsubheadermeta(UDHDL, nitf_meta, fid) + +if (UDHDL > 0) + + fields = {'UDHOFL', 'UserDefinedHeaderOverflow', 3 + 'UDHDL', 'UserDefinedHeaderData', UDHDL - 3}; + nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +end + + + +function nitf_meta = processtopxsubheadermeta(XHDL, nitf_meta, fid) + +if (XHDL > 0) + + fields = {'XHDLOFL', 'UserHeaderDataOverflow', 3 + 'XHDL', 'ExtendedHeaderData', XHDL - 3}; + nitf_meta = nitfReadMeta(nitf_meta, fields, fid); + +end + + + +function nitf_meta = processImageSubheaders(nitf_meta, numi, fid, imLengths) +%Add the nth image subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'ImageSubHeaders'; +nitf_meta(end).vname = 'ImageSubheaderMetadata'; + +%Preallocate memory for ISnitf_meta +ISnitf_meta(numi).name = ''; + +for imageNumber = 1:numi + ISnitf_meta(imageNumber).name = sprintf('IS%03d', imageNumber); + ISnitf_meta(imageNumber).vname = sprintf('ImageSubheader%03d', imageNumber); + ISnitf_meta(imageNumber).value = parseimagesubheader( fid, imLengths(imageNumber).value); +end + +nitf_meta(end).value = ISnitf_meta; + + + +function nitf_meta = processGraphicSubheaders(nitf_meta, nums, fid, grLengths) +%Add the nth graphic subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'GraphicSubHeaders'; +nitf_meta(end).vname = 'GraphicSubheaderMetadata'; + +%Preallocate memory for ISnitf_meta +IGnitf_meta(nums).name = ''; + +for graphicNumber = 1:nums + IGnitf_meta(graphicNumber).name = sprintf('IG%03d', graphicNumber); + IGnitf_meta(graphicNumber).vname = sprintf('GraphicSubheader%03d', graphicNumber); + IGnitf_meta(graphicNumber).value = parsegraphicsubheader( fid, grLengths(graphicNumber).value); +end + +nitf_meta(end).value = IGnitf_meta; + + + +function nitf_meta = processTextSubheaders(nitf_meta, numt, fid, teLengths) +%Add the nth text subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'TextSubHeaders'; +nitf_meta(end).vname = 'TextSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta +ITnitf_meta(numt).value = ''; + +for textNumber = 1:numt + ITnitf_meta(textNumber).name = sprintf('IT%03d', textNumber); + ITnitf_meta(textNumber).vname = sprintf('TextSubheader%03d', textNumber); + ITnitf_meta(textNumber).value = parsetextsubheader( fid, teLengths(textNumber).value); +end + +nitf_meta(end).value = ITnitf_meta; + + + +function nitf_meta = processDESubheaders(nitf_meta, numdes, fid, deLengths) +%Add the nth data extension subheader to the nitf_meta struct + +nitf_meta(end + 1).name = 'DataExtensionSubHeaders'; +nitf_meta(end).vname = 'DataExtensionSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta +DEnitf_meta(numdes).value = ''; + +for deNumber = 1:numdes + DEnitf_meta(deNumber).name = sprintf('DE%03d', deNumber); + DEnitf_meta(deNumber).vname = sprintf('DataExtensionSubheader%03d', deNumber); + DEnitf_meta(deNumber).value = parseDEsubheader( fid, deLengths(deNumber).value); +end + +nitf_meta(end).value = DEnitf_meta; + + + +function nitf_meta = processRESubheaders(nitf_meta, numres, fid, reLengths, reHeaderLengths) +%Add the nth reserved extension header to the nitf_meta struct + +nitf_meta(end + 1).name = 'ReservedExtensionSubHeaders'; +nitf_meta(end).vname = 'ReservedExtensionSubheaderMetadata'; + +%Preallocate memory for ITnitf_meta..How to do this? +REnitf_meta(numres).value = ''; + +for reNumber = 1:numres + REnitf_meta(reNumber).name = sprintf('RE%03d', reNumber); + REnitf_meta(reNumber).vname = sprintf('ReserveExtensionSubheader%03d', reNumber); + REnitf_meta(reNumber).value = parseREsubheader20( fid, reLengths(reNumber).value, reHeaderLengths(reNumber).value); +end + +nitf_meta(end).value = REnitf_meta; diff --git a/CT/private/parseDEsubheader.m b/CT/private/parseDEsubheader.m index dd6b04b..0941b09 100644 --- a/CT/private/parseDEsubheader.m +++ b/CT/private/parseDEsubheader.m @@ -1,56 +1,56 @@ -function [ denitf_meta ] = parseDEsubheader( fid, dataLength ) -%PARSEDESUBHEADER Parse the Data Extension subheaders in an NITF file. -% DENITF_META = PARSEDESUBHEADER -% Parse the Data Extension Segment Subheader for an NITF 2.1 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -denitf_meta = struct([]); -fields = {'DE', 'FilePartType', 2 - 'DESTAG', 'UniqueDESTypeIdentifier', 25 - 'DESVER', 'VersionOfTheDataFieldDefinition', 2 - 'DESCLAS', 'DESecurityClassification', 1 - 'DESCLSY', 'DESecurityClassificationSystem', 2 - 'DESCODE', 'DECodewords', 11 - 'DESCTLH', 'DEControlAndHandling', 2 - 'DESREL', 'DEReleasingInstructions', 20 - 'DESDCTP', 'DEDeclassificationType', 2 - 'DESDCDT', 'DEDeclassificationDate', 8 - 'DESDCXM', 'DEDeclassificationExemption', 4 - 'DESDG', 'DEDowngrade', 1 - 'DESDGT', 'DEDowngradeDate', 8 - 'DESCLTX', 'DEClassificationText', 43 - 'DESCATP', 'DEClassificationAuthorityType' 1 - 'DESCAUT', 'DEClassificationAuthority', 40 - 'DESCRSN', 'DEClassificationReason', 1 - 'DESSRDT', 'DESecuritySourceDate', 8 - 'DESCTL', 'DESecurityControlNumber', 15}; -denitf_meta = nitfReadMeta(denitf_meta, fields, fid); - -% DESOFLW is present if DESTAG = TRE_OVERFLOW. -if (isequal(deblank(denitf_meta(2).value), 'TRE_OVERFLOW')) - - fields = {'DESOFLW', 'OverflowedHeaderType', 6 - 'DESITEM', 'DataItemOverflowed', 3}; - denitf_meta = nitfReadMeta(denitf_meta, fields, fid); - -end - -%DESSHL -fields = {'DESSHL', 'LengthOfUserDefinedSubheaderFields', 4}; -denitf_meta = nitfReadMeta(denitf_meta, fields, fid); - -desshl = sscanf(denitf_meta(end).value, '%f'); -if desshl ~= 0 % The we'll have user defined fields - fields = {'DESSHF', 'UserDefinedSubheaderFields', desshl}; - denitf_meta = nitfReadMeta(denitf_meta, fields, fid); -end - -%DESDATA -%Contains either user-defined data or controlled/registered extensions -%Move the cursor through the Extension data. Return as raw bytes. -readforwardbytes = sscanf(dataLength, '%f'); - -denitf_meta(end + 1).name = 'DESDATA'; -denitf_meta(end).vname = 'UserDefinedData'; -denitf_meta(end).value = fread(fid, readforwardbytes, 'uint8=>uint8'); +function [ denitf_meta ] = parseDEsubheader( fid, dataLength ) +%PARSEDESUBHEADER Parse the Data Extension subheaders in an NITF file. +% DENITF_META = PARSEDESUBHEADER +% Parse the Data Extension Segment Subheader for an NITF 2.1 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +denitf_meta = struct([]); +fields = {'DE', 'FilePartType', 2 + 'DESTAG', 'UniqueDESTypeIdentifier', 25 + 'DESVER', 'VersionOfTheDataFieldDefinition', 2 + 'DESCLAS', 'DESecurityClassification', 1 + 'DESCLSY', 'DESecurityClassificationSystem', 2 + 'DESCODE', 'DECodewords', 11 + 'DESCTLH', 'DEControlAndHandling', 2 + 'DESREL', 'DEReleasingInstructions', 20 + 'DESDCTP', 'DEDeclassificationType', 2 + 'DESDCDT', 'DEDeclassificationDate', 8 + 'DESDCXM', 'DEDeclassificationExemption', 4 + 'DESDG', 'DEDowngrade', 1 + 'DESDGT', 'DEDowngradeDate', 8 + 'DESCLTX', 'DEClassificationText', 43 + 'DESCATP', 'DEClassificationAuthorityType' 1 + 'DESCAUT', 'DEClassificationAuthority', 40 + 'DESCRSN', 'DEClassificationReason', 1 + 'DESSRDT', 'DESecuritySourceDate', 8 + 'DESCTL', 'DESecurityControlNumber', 15}; +denitf_meta = nitfReadMeta(denitf_meta, fields, fid); + +% DESOFLW is present if DESTAG = TRE_OVERFLOW. +if (isequal(deblank(denitf_meta(2).value), 'TRE_OVERFLOW')) + + fields = {'DESOFLW', 'OverflowedHeaderType', 6 + 'DESITEM', 'DataItemOverflowed', 3}; + denitf_meta = nitfReadMeta(denitf_meta, fields, fid); + +end + +%DESSHL +fields = {'DESSHL', 'LengthOfUserDefinedSubheaderFields', 4}; +denitf_meta = nitfReadMeta(denitf_meta, fields, fid); + +desshl = sscanf(denitf_meta(end).value, '%f'); +if desshl ~= 0 % The we'll have user defined fields + fields = {'DESSHF', 'UserDefinedSubheaderFields', desshl}; + denitf_meta = nitfReadMeta(denitf_meta, fields, fid); +end + +%DESDATA +%Contains either user-defined data or controlled/registered extensions +%Move the cursor through the Extension data. Return as raw bytes. +readforwardbytes = sscanf(dataLength, '%f'); + +denitf_meta(end + 1).name = 'DESDATA'; +denitf_meta(end).vname = 'UserDefinedData'; +denitf_meta(end).value = fread(fid, readforwardbytes, 'uint8=>uint8'); diff --git a/CT/private/parseDEsubheader20.m b/CT/private/parseDEsubheader20.m index 403e172..634d16c 100644 --- a/CT/private/parseDEsubheader20.m +++ b/CT/private/parseDEsubheader20.m @@ -1,50 +1,50 @@ -function denitf_meta = parseDEsubheader20( fid, dataLength ) -%PARSEDESUBHEADER20 Parse the Data Extension subheaders in an NITF file. -% DENITF_META = PARSEDESUBHEADER20 -% Parse the Data Extension Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -denitf_meta = struct([]); -fields = {'DE', 'FilePartType', 2 - 'DESTAG', 'UniqueDESTypeIdentifier', 25 - 'DESVER', 'VersionOfTheDataFieldDefinition', 2 - 'DESCLAS', 'DESecurityClassification', 1 - 'DESCODE', 'DECodewords', 40 - 'DESCTLH', 'DEControlAndHandling', 40 - 'DESREL', 'DEReleasingInstructions', 40 - 'DESCAUT', 'DEClassificationAuthority', 20 - 'DESCTLN', 'DESecurityControlNumber', 20 - 'DESDWNG', 'DESecurityDowngrade', 6}; -denitf_meta = nitfReadMeta(denitf_meta, fields, fid); - -%DESDWNG is DENITF_META(10) and the last item extracted in the loop above. Depending -%on its value there will be an DESDEVT -desdwng = sscanf(denitf_meta(end).value, '%f'); -if desdwng == 999998 - fields = {'DESDEVT', 'DEDowngradingEvent', 40}; - denitf_meta = nitfReadMeta(denitf_meta, fields, fid); -end - -%The following is conditional on the value of DESTAG which is denitf_meta(2) -destag = deblank(denitf_meta(2).value); -if strcmp(destag, 'Registered Extensions') || strcmp(destag,'Controlled Extensions') - fields = {'DESOFLW', 'OverflowedHeaderType', 6 - 'DESITEM', 'DataItemOverflowed', 3}; - denitf_meta = nitfReadMeta(denitf_meta, fields, fid); -end - -fields = {'DESSHL', 'LengthOfUserDefinedSubheaderFields', 4}; -denitf_meta = nitfReadMeta(denitf_meta, fields, fid); - -desshl = sscanf(denitf_meta(end).value, '%f'); -if desshl ~= 0 % Then we'll have user defined fields - fields = {'DESSHF', 'UserDefinedSubheaderFields', desshl}; - denitf_meta = nitfReadMeta(denitf_meta, fields, fid); -end - -%DESDATA -%Contains either user-defined data or controlled/registered extensions -%Move the cursor through the Extension data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function denitf_meta = parseDEsubheader20( fid, dataLength ) +%PARSEDESUBHEADER20 Parse the Data Extension subheaders in an NITF file. +% DENITF_META = PARSEDESUBHEADER20 +% Parse the Data Extension Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +denitf_meta = struct([]); +fields = {'DE', 'FilePartType', 2 + 'DESTAG', 'UniqueDESTypeIdentifier', 25 + 'DESVER', 'VersionOfTheDataFieldDefinition', 2 + 'DESCLAS', 'DESecurityClassification', 1 + 'DESCODE', 'DECodewords', 40 + 'DESCTLH', 'DEControlAndHandling', 40 + 'DESREL', 'DEReleasingInstructions', 40 + 'DESCAUT', 'DEClassificationAuthority', 20 + 'DESCTLN', 'DESecurityControlNumber', 20 + 'DESDWNG', 'DESecurityDowngrade', 6}; +denitf_meta = nitfReadMeta(denitf_meta, fields, fid); + +%DESDWNG is DENITF_META(10) and the last item extracted in the loop above. Depending +%on its value there will be an DESDEVT +desdwng = sscanf(denitf_meta(end).value, '%f'); +if desdwng == 999998 + fields = {'DESDEVT', 'DEDowngradingEvent', 40}; + denitf_meta = nitfReadMeta(denitf_meta, fields, fid); +end + +%The following is conditional on the value of DESTAG which is denitf_meta(2) +destag = deblank(denitf_meta(2).value); +if strcmp(destag, 'Registered Extensions') || strcmp(destag,'Controlled Extensions') + fields = {'DESOFLW', 'OverflowedHeaderType', 6 + 'DESITEM', 'DataItemOverflowed', 3}; + denitf_meta = nitfReadMeta(denitf_meta, fields, fid); +end + +fields = {'DESSHL', 'LengthOfUserDefinedSubheaderFields', 4}; +denitf_meta = nitfReadMeta(denitf_meta, fields, fid); + +desshl = sscanf(denitf_meta(end).value, '%f'); +if desshl ~= 0 % Then we'll have user defined fields + fields = {'DESSHF', 'UserDefinedSubheaderFields', desshl}; + denitf_meta = nitfReadMeta(denitf_meta, fields, fid); +end + +%DESDATA +%Contains either user-defined data or controlled/registered extensions +%Move the cursor through the Extension data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parseParameterValuePairs.m b/CT/private/parseParameterValuePairs.m index 2e91ba9..35ec514 100644 --- a/CT/private/parseParameterValuePairs.m +++ b/CT/private/parseParameterValuePairs.m @@ -1,58 +1,58 @@ -function options = parseParameterValuePairs(funcName, knownParams, varargin) -%parseParameterValuePairs Get user-provided and default options. -% OPTIONS = parseParameterValuePairs(FUNCNAME, DETAILS, PARAM1, VALUE1, ...) -% parses and validates a set of parameter-value pairs. FUNCNAME is a -% character array containing the name of the function that accepts -% various parameters (PARAM1, etc.) and values (VALUE1, etc.). DETAILS -% is a cell array with one row per known parameter and has the following -% columns: -% -% 1 - Parameter name (character array) -% 2 - Output field name (character array) -% 3 - Default parameter value (any value) -% 4 - Acceptable value type (cell array of MATLAB types) -% 5 - Value parameters (cell array of validateattributes values) -% -% The output value OPTIONS is a structure array whose field names are -% the parameters and whose values are the corresponding values. The -% field names of OPTIONS may not actually match the parameter names -% expected by FUNCNAME, depending on the values in the second column of -% DETAILS. This allows convenient field names for internal use. -% - -% Copyright 2007-2010 The MathWorks, Inc. - -% Create a structure with default values, and map actual param-value pair -% names to convenient names for internal use. - -options = cell2struct(knownParams(:,3), knownParams(:,2), 1); - -if (rem(nargin, 2) ~= 0) - error(message('images:parseParameterValuePairs:paramValuePairs')) -end - -% Loop over the P-V pairs. -for p = 1:2:numel(varargin) - % Get the parameter name. - paramName = varargin{p}; - if (~ischar(paramName)) - error(message('images:parseParameterValuePairs:badParamName')) - end - - % Look for the parameter amongst the possible values. - idx = strmatch(lower(paramName), lower(knownParams(:,1))); - if (isempty(idx)) - error(message('images:parseParameterValuePairs:unknownParamName', paramName)); - elseif (numel(idx) > 1) - error(message('images:parseParameterValuePairs:ambiguousParamName', paramName)); - end - - % Validate the value. - options.(knownParams{idx, 2}) = varargin{p+1}; - validateattributes(varargin{p+1}, ... - knownParams{idx,4}, ... - knownParams{idx,5}, ... - funcName, ... - knownParams{idx,1}, ... - p+2); % p+2 = Param name + 1 + offset to first arg -end +function options = parseParameterValuePairs(funcName, knownParams, varargin) +%parseParameterValuePairs Get user-provided and default options. +% OPTIONS = parseParameterValuePairs(FUNCNAME, DETAILS, PARAM1, VALUE1, ...) +% parses and validates a set of parameter-value pairs. FUNCNAME is a +% character array containing the name of the function that accepts +% various parameters (PARAM1, etc.) and values (VALUE1, etc.). DETAILS +% is a cell array with one row per known parameter and has the following +% columns: +% +% 1 - Parameter name (character array) +% 2 - Output field name (character array) +% 3 - Default parameter value (any value) +% 4 - Acceptable value type (cell array of MATLAB types) +% 5 - Value parameters (cell array of validateattributes values) +% +% The output value OPTIONS is a structure array whose field names are +% the parameters and whose values are the corresponding values. The +% field names of OPTIONS may not actually match the parameter names +% expected by FUNCNAME, depending on the values in the second column of +% DETAILS. This allows convenient field names for internal use. +% + +% Copyright 2007-2010 The MathWorks, Inc. + +% Create a structure with default values, and map actual param-value pair +% names to convenient names for internal use. + +options = cell2struct(knownParams(:,3), knownParams(:,2), 1); + +if (rem(nargin, 2) ~= 0) + error(message('images:parseParameterValuePairs:paramValuePairs')) +end + +% Loop over the P-V pairs. +for p = 1:2:numel(varargin) + % Get the parameter name. + paramName = varargin{p}; + if (~ischar(paramName)) + error(message('images:parseParameterValuePairs:badParamName')) + end + + % Look for the parameter amongst the possible values. + idx = strmatch(lower(paramName), lower(knownParams(:,1))); + if (isempty(idx)) + error(message('images:parseParameterValuePairs:unknownParamName', paramName)); + elseif (numel(idx) > 1) + error(message('images:parseParameterValuePairs:ambiguousParamName', paramName)); + end + + % Validate the value. + options.(knownParams{idx, 2}) = varargin{p+1}; + validateattributes(varargin{p+1}, ... + knownParams{idx,4}, ... + knownParams{idx,5}, ... + funcName, ... + knownParams{idx,1}, ... + p+2); % p+2 = Param name + 1 + offset to first arg +end diff --git a/CT/private/parseREsubheader20.m b/CT/private/parseREsubheader20.m index a595bc4..bad5acd 100644 --- a/CT/private/parseREsubheader20.m +++ b/CT/private/parseREsubheader20.m @@ -1,20 +1,20 @@ -function renitf_meta = parseREsubheader20( fid, dataLength, headerLength ) -%PARSERESUBHEADER20 Process the Reserved Extensions subheaders in an NITF20 file. -% RENITF_META = PARSERESUBHEADER20 -% Parse the Reserved Extension Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -% Essentially we don't know anything about the extension so we just need -% to pull the header into a struct and read ahead over the data - -% Convert the length from a decimal string to an integer. -headerLength = sscanf(headerLength', '%d'); - -renitf_meta = struct([]); -fields = {'ReservedDataExtensionHeader', 'ReservedDataExtensionHeader', headerLength}; -renitf_meta = nitfReadMeta(renitf_meta, fields, fid); - -%Move the cursor through the Reserved Extension data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function renitf_meta = parseREsubheader20( fid, dataLength, headerLength ) +%PARSERESUBHEADER20 Process the Reserved Extensions subheaders in an NITF20 file. +% RENITF_META = PARSERESUBHEADER20 +% Parse the Reserved Extension Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +% Essentially we don't know anything about the extension so we just need +% to pull the header into a struct and read ahead over the data + +% Convert the length from a decimal string to an integer. +headerLength = sscanf(headerLength', '%d'); + +renitf_meta = struct([]); +fields = {'ReservedDataExtensionHeader', 'ReservedDataExtensionHeader', headerLength}; +renitf_meta = nitfReadMeta(renitf_meta, fields, fid); + +%Move the cursor through the Reserved Extension data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parsegraphicsubheader.m b/CT/private/parsegraphicsubheader.m index ab95200..1b2d56f 100644 --- a/CT/private/parsegraphicsubheader.m +++ b/CT/private/parsegraphicsubheader.m @@ -1,57 +1,57 @@ -function gsnitf_meta = parsegraphicsubheader( fid, dataLength ) -%PARSEGRAPHICSUBHEADER Parse the Graphic subheaders in an NITF file. -% GSNITF_META = PARSEGRAPHICSUBHEADER -% Parse the Graphic Segment Subheader for an NITF 2.1 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -% Initialize the graphic subheader structure -gsnitf_meta = struct([]); -fields = {'SY', 'FilePartType', 2 - 'SID', 'GraphicID', 10 - 'SNAME', 'GraphicName', 20 - 'SSCLAS', 'GraphicSecurityClassification', 1 - 'SSCLSY', 'GraphicSecurityClassificationSystem', 2 - 'SSCODE', 'GraphicCodewords', 11 - 'SSCTLH', 'GraphicControlAndHandling', 2 - 'SSREL', 'GraphicReleasingInstructions', 20 - 'SSDCTP', 'GraphicDeclassificationType', 2 - 'SSDCDT', 'GraphicDeclassificationDate', 8 - 'SSDCXM', 'GraphicDeclassificationExemption', 4 - 'SSDG', 'GraphicDowngrade', 1 - 'SSDGT', 'GraphicDowngradeDate', 8 - 'SSCLTX', 'GraphicClassificationText', 43 - 'SSCATP', 'GraphicClassificationAuthorityType', 1 - 'SSCAUT', 'GraphicClassificationAuthority', 40 - 'SSCRSN', 'GraphicClassificationReason', 1 - 'SSSRDT', 'GraphicSecuritySourceDate', 8 - 'SSCTLN', 'GraphicSecurityControlNumber', 15 - 'ENCRYP', 'Encryption', 1 - 'STYPE', 'GraphicType', 1 - 'SRES1', 'ReservedForFutureUse', 13 - 'SDLVL', 'DisplayLevel', 3 - 'SALVL', 'GraphicAttachmentLevel', 3 - 'SLOC', 'GraphicLocation', 10 - 'SBND1', 'FirstGraphicBoundLocation', 10 - 'SCOLOR', 'GraphicColor', 1 - 'SBND2', 'SecondGraphicBoundLocation', 10 - 'SRES2', 'ReservedForFutureUse', 2 - 'SXSHDL', 'ExtendedSubheaderDataLength', 5}; -gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); - -%SXSHDL is GSNITF_META(30) and the last item extracted in the loop above. Depending -%on its value there will be an SXSOFL or Extended Subheader Overflow field and SXSHD field. -%If SXSHDL is not zeros, add the SXSOFL and SXSHD fields to the meta data struct -%and insert values. - -sxshdl = sscanf(gsnitf_meta(30).value, '%f'); -if sxshdl ~= 0 - fields = {'SXSOFL', 'ExtendedSubheaderOverflow', 3 - 'SXSHD', 'ExtendedSubheaderData', sxshdl - 3}; - gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); -end - - -%Move the cursor through the graphic data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function gsnitf_meta = parsegraphicsubheader( fid, dataLength ) +%PARSEGRAPHICSUBHEADER Parse the Graphic subheaders in an NITF file. +% GSNITF_META = PARSEGRAPHICSUBHEADER +% Parse the Graphic Segment Subheader for an NITF 2.1 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +% Initialize the graphic subheader structure +gsnitf_meta = struct([]); +fields = {'SY', 'FilePartType', 2 + 'SID', 'GraphicID', 10 + 'SNAME', 'GraphicName', 20 + 'SSCLAS', 'GraphicSecurityClassification', 1 + 'SSCLSY', 'GraphicSecurityClassificationSystem', 2 + 'SSCODE', 'GraphicCodewords', 11 + 'SSCTLH', 'GraphicControlAndHandling', 2 + 'SSREL', 'GraphicReleasingInstructions', 20 + 'SSDCTP', 'GraphicDeclassificationType', 2 + 'SSDCDT', 'GraphicDeclassificationDate', 8 + 'SSDCXM', 'GraphicDeclassificationExemption', 4 + 'SSDG', 'GraphicDowngrade', 1 + 'SSDGT', 'GraphicDowngradeDate', 8 + 'SSCLTX', 'GraphicClassificationText', 43 + 'SSCATP', 'GraphicClassificationAuthorityType', 1 + 'SSCAUT', 'GraphicClassificationAuthority', 40 + 'SSCRSN', 'GraphicClassificationReason', 1 + 'SSSRDT', 'GraphicSecuritySourceDate', 8 + 'SSCTLN', 'GraphicSecurityControlNumber', 15 + 'ENCRYP', 'Encryption', 1 + 'STYPE', 'GraphicType', 1 + 'SRES1', 'ReservedForFutureUse', 13 + 'SDLVL', 'DisplayLevel', 3 + 'SALVL', 'GraphicAttachmentLevel', 3 + 'SLOC', 'GraphicLocation', 10 + 'SBND1', 'FirstGraphicBoundLocation', 10 + 'SCOLOR', 'GraphicColor', 1 + 'SBND2', 'SecondGraphicBoundLocation', 10 + 'SRES2', 'ReservedForFutureUse', 2 + 'SXSHDL', 'ExtendedSubheaderDataLength', 5}; +gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); + +%SXSHDL is GSNITF_META(30) and the last item extracted in the loop above. Depending +%on its value there will be an SXSOFL or Extended Subheader Overflow field and SXSHD field. +%If SXSHDL is not zeros, add the SXSOFL and SXSHD fields to the meta data struct +%and insert values. + +sxshdl = sscanf(gsnitf_meta(30).value, '%f'); +if sxshdl ~= 0 + fields = {'SXSOFL', 'ExtendedSubheaderOverflow', 3 + 'SXSHD', 'ExtendedSubheaderData', sxshdl - 3}; + gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); +end + + +%Move the cursor through the graphic data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parsegraphicsubheader20.m b/CT/private/parsegraphicsubheader20.m index bf78e99..ee9fdc3 100644 --- a/CT/private/parsegraphicsubheader20.m +++ b/CT/private/parsegraphicsubheader20.m @@ -1,78 +1,78 @@ -function gsnitf_meta = parsegraphicsubheader20( fid, dataLength ) -%PARSEGRAPHICSUBHEADER Parse the Graphic subheaders in an NITF file. -% GSNITF_META = PARSEGRAPHICSUBHEADER20 -% Parse the Graphic Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -% Initialize the graphic subheader structure -gsnitf_meta = struct([]); -fields = {'SY', 'FilePartType', 2 - 'SID', 'SymbolID', 10 - 'SNAME', 'SymbolName', 20 - 'SSCLAS', 'SymbolSecurityClassification', 1 - 'SSCODE', 'SymbolCodewords', 40 - 'SSCTLH', 'SymbolControlAndHandling', 40 - 'SSREL', 'SymbolReleasingInstructions', 40 - 'SSCAUT', 'SymbolClassificationAuthority', 20 - 'SSCTLN', 'SymbolSecurityControlNumber', 20 - 'SSDWNG', 'SymbolSecurityDowngrade', 6}; -gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); - -%SSDWNG is GSNITF_META(10) and the last item extracted in the loop above. Depending -%on its value there will be an SSDEVT -ssdwng = sscanf(gsnitf_meta(end).value, '%f'); -if ssdwng == 999998 - - fields = {'SSDEVT', 'SymbolDowngradingEvent', 40}; - gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); - -end - -fields = {'ENCRYP', 'Encryption', 1 - 'STYPE', 'SymbolType', 1 - 'NLIPS', 'NumberOfLinesPerSymbol', 4 - 'NPIXPL', 'NumberOfPixelsPerLine' 4 - 'NWDTH', 'LineWidth', 4 - 'NBPP', 'NumberOfBitsPerPixel', 1 - 'SDLVL', 'DisplayLevel', 3 - 'SALVL', 'SymbolAttachmentLevel', 3 - 'SLOC', 'SymbolLocation', 10 - 'SLOC2', 'SecondSymbolLocation', 10 - 'SCOLOR', 'SymbolColor', 1 - 'SNUM', 'SymbolNumber', 6 - 'SROT', 'SymbolRotation', 3 - 'NELUT', 'NumberOfLUTEntries', 3}; -gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); - - -%DLUT -- This is the actual look-up table which I think constitutes data -% Should we expose it as metadata? -nelut = sscanf(gsnitf_meta(end).value, '%f'); -if nelut ~= 0 - if strcmp(scolor,'C') %Color look-up table - dlutsize = 3 * nelut; - elseif strcmp(scolor,'G') %Gray scale look-up table - dlutsize = nelut; - else - dlutsize = 0; - end - - fields = {'DLUT', 'SymbolLUTData', dlutsize}; - gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); -end - -fields = {'SXSHDL', 'ExtendedSubheaderDataLength', 5}; -gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); - -%SXSOFL and SCSHD -sxshdl = sscanf(gsnitf_meta(end).value, '%f'); -if sxshdl ~= 0 - fields = {'SXSOFL', 'ExtendedSubheaderOverflow', 3 - 'SXSHD', 'ExtendedSubheaderData', sxshdl - 3}; - gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); -end - -%Move the cursor through the graphic data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function gsnitf_meta = parsegraphicsubheader20( fid, dataLength ) +%PARSEGRAPHICSUBHEADER Parse the Graphic subheaders in an NITF file. +% GSNITF_META = PARSEGRAPHICSUBHEADER20 +% Parse the Graphic Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +% Initialize the graphic subheader structure +gsnitf_meta = struct([]); +fields = {'SY', 'FilePartType', 2 + 'SID', 'SymbolID', 10 + 'SNAME', 'SymbolName', 20 + 'SSCLAS', 'SymbolSecurityClassification', 1 + 'SSCODE', 'SymbolCodewords', 40 + 'SSCTLH', 'SymbolControlAndHandling', 40 + 'SSREL', 'SymbolReleasingInstructions', 40 + 'SSCAUT', 'SymbolClassificationAuthority', 20 + 'SSCTLN', 'SymbolSecurityControlNumber', 20 + 'SSDWNG', 'SymbolSecurityDowngrade', 6}; +gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); + +%SSDWNG is GSNITF_META(10) and the last item extracted in the loop above. Depending +%on its value there will be an SSDEVT +ssdwng = sscanf(gsnitf_meta(end).value, '%f'); +if ssdwng == 999998 + + fields = {'SSDEVT', 'SymbolDowngradingEvent', 40}; + gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); + +end + +fields = {'ENCRYP', 'Encryption', 1 + 'STYPE', 'SymbolType', 1 + 'NLIPS', 'NumberOfLinesPerSymbol', 4 + 'NPIXPL', 'NumberOfPixelsPerLine' 4 + 'NWDTH', 'LineWidth', 4 + 'NBPP', 'NumberOfBitsPerPixel', 1 + 'SDLVL', 'DisplayLevel', 3 + 'SALVL', 'SymbolAttachmentLevel', 3 + 'SLOC', 'SymbolLocation', 10 + 'SLOC2', 'SecondSymbolLocation', 10 + 'SCOLOR', 'SymbolColor', 1 + 'SNUM', 'SymbolNumber', 6 + 'SROT', 'SymbolRotation', 3 + 'NELUT', 'NumberOfLUTEntries', 3}; +gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); + + +%DLUT -- This is the actual look-up table which I think constitutes data +% Should we expose it as metadata? +nelut = sscanf(gsnitf_meta(end).value, '%f'); +if nelut ~= 0 + if strcmp(scolor,'C') %Color look-up table + dlutsize = 3 * nelut; + elseif strcmp(scolor,'G') %Gray scale look-up table + dlutsize = nelut; + else + dlutsize = 0; + end + + fields = {'DLUT', 'SymbolLUTData', dlutsize}; + gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); +end + +fields = {'SXSHDL', 'ExtendedSubheaderDataLength', 5}; +gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); + +%SXSOFL and SCSHD +sxshdl = sscanf(gsnitf_meta(end).value, '%f'); +if sxshdl ~= 0 + fields = {'SXSOFL', 'ExtendedSubheaderOverflow', 3 + 'SXSHD', 'ExtendedSubheaderData', sxshdl - 3}; + gsnitf_meta = nitfReadMeta(gsnitf_meta, fields, fid); +end + +%Move the cursor through the graphic data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parseimagesubheader.m b/CT/private/parseimagesubheader.m index 2ea635a..dad684a 100644 --- a/CT/private/parseimagesubheader.m +++ b/CT/private/parseimagesubheader.m @@ -1,280 +1,280 @@ -function isnitf_meta = parseimagesubheader( fid, dataLength ) -%PARSEIMAGESUBHEADER Parse the Image subheaders in an NITF file. -% ISNITF_META = PARSEIMAGESUBHEADER -% Parse the Image Segment Subheader for an NITF 2.1 file. - -% Copyright 2007-2009 The MathWorks, Inc. - -% TODO: refactor common functions between 2.0 and 2.1 into an external file - -% Initialize the image subheader structure -isnitf_meta = struct([]); -fields = {'IM', 'FilePartType', 2 - 'IID1', 'ImageID1', 10 - 'IDATIM', 'ImageDateAndTime', 14 - 'TGTID', 'TargetID', 17 - 'IID2', 'ImageIID2', 80 - 'ISCLAS', 'ImageSecurityClassification', 1 - 'ISCLSY', 'ImageSecurityClassificationSystem', 2 - 'ISCODE', 'ImageCodewords', 11 - 'ISCTLH', 'ImageControlAndHandling', 2 - 'ISREL', 'ImageReleasingInstructions', 20 - 'ISDCTP', 'ImageDeclassificationType', 2 - 'ISDCDT', 'ImageDeclassificationDate', 8 - 'ISDCXM', 'ImageDeclassificationExemption', 4 - 'ISDG', 'ImageDowngrade', 1 - 'ISDGT', 'ImageDowngradeDate', 8 - 'ISCLTX', 'ImageClassificationText', 43 - 'ISCATP', 'ImageClassificationAuthorityType', 1 - 'ISCAUT', 'ImageClassificationAuthority', 40 - 'ISCRSN', 'ImageClassificationReason', 1 - 'ISSRDT', 'ImageSecuritySourceDate', 8 - 'ISCTLN', 'ImageSecurityControlNumber', 15 - 'ENCRYP', 'Encryption', 1 - 'ISORCE', 'ImageSource', 42 - 'NROWS', 'NumberOfSignificantRowsInImage', 8 - 'NCOLS', 'NumberOfSignificantColumnsInImage', 8 - 'PVTYPE', 'PixelValueType', 3 - 'IREP', 'ImageRepresentation', 8 - 'ICAT', 'ImageCategory', 8 - 'ABPP', 'ActualBitsPerPixelPerBand', 2 - 'PJUST', 'PixelJustification', 1 - 'ICORDS', 'ImageCoordinateSystem', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%ICORDS is the last item extracted in the loop above. Depending -%on its value there will be an IGEOLO or Image Geographic Location field. -%If ICORDS is not a space, add the IGEOLO field to the meta data struct -%and insert values. -icords = deblank(isnitf_meta(end).value); - -isnitf_meta = checkIcords(icords, fid, isnitf_meta); - -%NICOM -%Depending on its value there will be ICOM fields (ICOM1 through ICOMn). -%If NICOM is not zero, add the comment fields to the meta data struct -%and insert their values. - -fields = {'NICOM', 'NumberOfImageComments', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -nicom = sscanf(isnitf_meta(end).value, '%f'); -isnitf_meta = checkNicom(nicom, fid, isnitf_meta); - -%Next field is Image Compression -fields = {'IC', 'ImageCompression', 2}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%Next field is COMRAT or Compression Rate Code -%If the IC field is not NC or NM the field will have a value. -ic = isnitf_meta(end).value; -if ~strcmp(ic, 'NC') && ~strcmp(ic, 'NM') - fields = {'COMRAT', 'CompressionRateCode', 4}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); -end - -fields = {'NBANDS', 'NumberOfBands', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -nbands = sscanf(isnitf_meta(end).value, '%f'); - -%XBANDS -%If the NBANDS field is 0 XBANDS is the number of bands in a multi-spectral image. -if nbands == 0 - fields = {'XBANDS', 'NumberOfMultiSpectralBands', 5}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - bands = sscanf(isnitf_meta(end).value, '%f'); -else - bands = nbands; -end - -%Set up Band substructure - -isnitf_meta(end + 1).name = 'Band Meta'; -isnitf_meta(end).vname = 'BandMeta'; -%The value will be a structure of band data - -%The next seven fields occur for each band as indicated by BANDS -% Lengths: -% IREPBAND 2, ISUBCAT 6, IFC 1, IMFLT 3, NLUTS 1, NELUT 5 [omitted if NLUTS = 0] -% LUTD contains data for the mth LUT of the nnth band - -nitf_metaBand(bands).value = ''; - -for currentBand = 1: bands - - isBandnitf_meta = struct([]); - - nitf_metaBand(currentBand).name = sprintf('Band%03d', currentBand); - nitf_metaBand(currentBand).vname = [nitf_metaBand(currentBand).name 'Meta']; - nitf_metaBand(currentBand).value = ''; - - fields = {'IREPBAND%02d', 'BandRepresentation%02d', 2 - 'ISUBCAT%02d', 'BandSubcategory%02d', 6 - 'IFC%02d', 'BandImageFilterCondition%02d', 1 - 'IMFLT%02d', 'BandImageFilterCode%02d', 3 - 'NLUTS%02d', 'BandNumberOfLUTS%02d', 1}; - - isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); - numluts = sscanf(isBandnitf_meta(end).value, '%f'); - - if numluts ~= 0 - %nnth Band Number of LUT Entries - - fields = {'NELUT%02d', 'BandNumberOfLUTEntries%02d', 5}; - isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); - - numlutentries = sscanf(isBandnitf_meta(end).value', '%f'); - - %The value will be a struct of LUT data - for currentLut = 1 : numluts - isBandnitf_meta(end + 1).name = sprintf('LUTData%1d', currentLut); - isBandnitf_meta(end).vname = sprintf('LUTData%1d', currentLut); - % Note: not converted to char. - isBandnitf_meta(end).value = fread(fid, numlutentries, 'uint8'); - end - end - - nitf_metaBand(currentBand).value = isBandnitf_meta; - -end -isnitf_meta(end).value = nitf_metaBand; - - -fields = {'ISYNC', 'ImageSyncCode', 1 - 'IMODE', 'ImageMode' 1 - 'NBPR', 'NumberOfBlocksPerRow', 4}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); -NBPR = sscanf(isnitf_meta(end).value, '%f'); - -fields = {'NBPC', 'NumberOfBlocksPerColumn', 4}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); -NBPC = sscanf(isnitf_meta(end).value, '%f'); - -fields = {'NPPBH', 'NumberOfPixelsPerBlockHorizontal', 4 - 'NPPBV', 'NumberOfPixelsPerBlockVertical', 4 - 'NBPP', 'NumberOfBitsPerPixelPerBand', 2 - 'IDLVL', 'DisplayLevel', 3 - 'IALVL', 'ImageAttachmentLevel', 3 - 'ILOC', 'ImageLocation', 10 - 'IMAG', 'ImageMagnification', 4}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -% The IMAG value should be converted to a number from the decimal string. -isnitf_meta(end).value = sscanf(isnitf_meta(end).value, '%f'); - -fields = {'UDIDL', 'UserDefinedImageDataLength', 5}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%If UDIDL is not zeros we'll have values for UDOFL and UDID -UDIDL = sscanf(isnitf_meta(end).value, '%f'); -if UDIDL ~= 0 - - fields = {'UDOFL', 'UserDefinedOverflow', 3 - 'UDID', 'UserDefinedImageData', UDIDL - 3}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - -fields = {'IXSHDL', 'ExtendedSubheaderDataLength', 5}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%If IXSHDL is not zeros we'll have values for IXSOFL and IXSHD -IXSHDL = sscanf(isnitf_meta(end).value, '%f'); -if IXSHDL ~= 0 - - fields = {'IXOFL', 'ExtendedSubheaderOverflow', 3 - 'IXSHD', 'ExtendedSubheaderData', IXSHDL - 3}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - -% If the image contains data masking, read those values. (See -% MIL-STD-2500B Table A-3(A). -dataMaskTableLength = 0; -if (hasDataMaskTable(ic)) - - fields = {'IMDATOFF', 'BlockedImageDataOffset', 1, 'int32' - 'BMRLNTH', 'BlockedMaskRecordLength', 1, 'uint16'}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - BMRLNTH = isnitf_meta(end).value; - - fields = {'TMRLNTH', 'PadPixelMaskRecordLength', 1, 'uint16'}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - TMRLNTH = isnitf_meta(end).value; - - fields = {'TPXCDLNTH', 'TransparentOutputPixelCodeLength', 1, 'uint16'}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - TPXCDLNTH = isnitf_meta(end).value; - - dataMaskTableLength = dataMaskTableLength + 4 + 2 + 2 + 2; - - if (TPXCDLNTH ~= 0) - - if (TPXCDLNTH <= 8) - fmt = 'uint8'; - dataMaskTableLength = dataMaskTableLength + 1; - else - fmt = 'uint16'; - dataMaskTableLength = dataMaskTableLength + 2; - end - - fields = {'TPXCD', 'PadOutputPixelCode', 1, fmt}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - - end - - numBlocks = NBPR * NBPC * max(bands, nbands); - - if (BMRLNTH ~= 0) - fields = {'BMRnBNDm', 'BlockNBandMOffset', numBlocks, 'uint32'}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - dataMaskTableLength = dataMaskTableLength + 4 * numBlocks; - end - - if (TMRLNTH ~= 0) - fields = {'TMRnBNDm', 'PadPixelNBandMOffset', numBlocks, 'uint32'}; - isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); - dataMaskTableLength = dataMaskTableLength + 4 * numBlocks; - end - -end - -%Move the cursor through the image data -readforwardbytes = sscanf(dataLength, '%f') - dataMaskTableLength; -fseek(fid, readforwardbytes, 'cof'); - - - -function isnitf_meta = checkIcords(icords, fid, isnitf_meta) -if ~strcmp(icords, '') - - fields = {'IGEOLO', 'ImageGeographicLocation', 60}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - - - -function isnitf_meta = checkNicom(nicom, fid, isnitf_meta) - -% Parse the Image Comment fields in the image subheader -% The length of the ICOM field is 80. -for currentComment = 1:nicom - - %Pull the Image Comment - fields = {'ICOM%1d', 'ImageComment%1d', 80}; - isnitf_meta = nitfReadMetaMulti(isnitf_meta, fields, fid, currentComment); - -end - - - -function tf = hasDataMaskTable(IC) - -switch (IC) -case {'NM', 'M1', 'M3', 'M4', 'M5'} - tf = true; -otherwise - tf = false; -end +function isnitf_meta = parseimagesubheader( fid, dataLength ) +%PARSEIMAGESUBHEADER Parse the Image subheaders in an NITF file. +% ISNITF_META = PARSEIMAGESUBHEADER +% Parse the Image Segment Subheader for an NITF 2.1 file. + +% Copyright 2007-2009 The MathWorks, Inc. + +% TODO: refactor common functions between 2.0 and 2.1 into an external file + +% Initialize the image subheader structure +isnitf_meta = struct([]); +fields = {'IM', 'FilePartType', 2 + 'IID1', 'ImageID1', 10 + 'IDATIM', 'ImageDateAndTime', 14 + 'TGTID', 'TargetID', 17 + 'IID2', 'ImageIID2', 80 + 'ISCLAS', 'ImageSecurityClassification', 1 + 'ISCLSY', 'ImageSecurityClassificationSystem', 2 + 'ISCODE', 'ImageCodewords', 11 + 'ISCTLH', 'ImageControlAndHandling', 2 + 'ISREL', 'ImageReleasingInstructions', 20 + 'ISDCTP', 'ImageDeclassificationType', 2 + 'ISDCDT', 'ImageDeclassificationDate', 8 + 'ISDCXM', 'ImageDeclassificationExemption', 4 + 'ISDG', 'ImageDowngrade', 1 + 'ISDGT', 'ImageDowngradeDate', 8 + 'ISCLTX', 'ImageClassificationText', 43 + 'ISCATP', 'ImageClassificationAuthorityType', 1 + 'ISCAUT', 'ImageClassificationAuthority', 40 + 'ISCRSN', 'ImageClassificationReason', 1 + 'ISSRDT', 'ImageSecuritySourceDate', 8 + 'ISCTLN', 'ImageSecurityControlNumber', 15 + 'ENCRYP', 'Encryption', 1 + 'ISORCE', 'ImageSource', 42 + 'NROWS', 'NumberOfSignificantRowsInImage', 8 + 'NCOLS', 'NumberOfSignificantColumnsInImage', 8 + 'PVTYPE', 'PixelValueType', 3 + 'IREP', 'ImageRepresentation', 8 + 'ICAT', 'ImageCategory', 8 + 'ABPP', 'ActualBitsPerPixelPerBand', 2 + 'PJUST', 'PixelJustification', 1 + 'ICORDS', 'ImageCoordinateSystem', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%ICORDS is the last item extracted in the loop above. Depending +%on its value there will be an IGEOLO or Image Geographic Location field. +%If ICORDS is not a space, add the IGEOLO field to the meta data struct +%and insert values. +icords = deblank(isnitf_meta(end).value); + +isnitf_meta = checkIcords(icords, fid, isnitf_meta); + +%NICOM +%Depending on its value there will be ICOM fields (ICOM1 through ICOMn). +%If NICOM is not zero, add the comment fields to the meta data struct +%and insert their values. + +fields = {'NICOM', 'NumberOfImageComments', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +nicom = sscanf(isnitf_meta(end).value, '%f'); +isnitf_meta = checkNicom(nicom, fid, isnitf_meta); + +%Next field is Image Compression +fields = {'IC', 'ImageCompression', 2}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%Next field is COMRAT or Compression Rate Code +%If the IC field is not NC or NM the field will have a value. +ic = isnitf_meta(end).value; +if ~strcmp(ic, 'NC') && ~strcmp(ic, 'NM') + fields = {'COMRAT', 'CompressionRateCode', 4}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); +end + +fields = {'NBANDS', 'NumberOfBands', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +nbands = sscanf(isnitf_meta(end).value, '%f'); + +%XBANDS +%If the NBANDS field is 0 XBANDS is the number of bands in a multi-spectral image. +if nbands == 0 + fields = {'XBANDS', 'NumberOfMultiSpectralBands', 5}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + bands = sscanf(isnitf_meta(end).value, '%f'); +else + bands = nbands; +end + +%Set up Band substructure + +isnitf_meta(end + 1).name = 'Band Meta'; +isnitf_meta(end).vname = 'BandMeta'; +%The value will be a structure of band data + +%The next seven fields occur for each band as indicated by BANDS +% Lengths: +% IREPBAND 2, ISUBCAT 6, IFC 1, IMFLT 3, NLUTS 1, NELUT 5 [omitted if NLUTS = 0] +% LUTD contains data for the mth LUT of the nnth band + +nitf_metaBand(bands).value = ''; + +for currentBand = 1: bands + + isBandnitf_meta = struct([]); + + nitf_metaBand(currentBand).name = sprintf('Band%03d', currentBand); + nitf_metaBand(currentBand).vname = [nitf_metaBand(currentBand).name 'Meta']; + nitf_metaBand(currentBand).value = ''; + + fields = {'IREPBAND%02d', 'BandRepresentation%02d', 2 + 'ISUBCAT%02d', 'BandSubcategory%02d', 6 + 'IFC%02d', 'BandImageFilterCondition%02d', 1 + 'IMFLT%02d', 'BandImageFilterCode%02d', 3 + 'NLUTS%02d', 'BandNumberOfLUTS%02d', 1}; + + isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); + numluts = sscanf(isBandnitf_meta(end).value, '%f'); + + if numluts ~= 0 + %nnth Band Number of LUT Entries + + fields = {'NELUT%02d', 'BandNumberOfLUTEntries%02d', 5}; + isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); + + numlutentries = sscanf(isBandnitf_meta(end).value', '%f'); + + %The value will be a struct of LUT data + for currentLut = 1 : numluts + isBandnitf_meta(end + 1).name = sprintf('LUTData%1d', currentLut); + isBandnitf_meta(end).vname = sprintf('LUTData%1d', currentLut); + % Note: not converted to char. + isBandnitf_meta(end).value = fread(fid, numlutentries, 'uint8'); + end + end + + nitf_metaBand(currentBand).value = isBandnitf_meta; + +end +isnitf_meta(end).value = nitf_metaBand; + + +fields = {'ISYNC', 'ImageSyncCode', 1 + 'IMODE', 'ImageMode' 1 + 'NBPR', 'NumberOfBlocksPerRow', 4}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); +NBPR = sscanf(isnitf_meta(end).value, '%f'); + +fields = {'NBPC', 'NumberOfBlocksPerColumn', 4}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); +NBPC = sscanf(isnitf_meta(end).value, '%f'); + +fields = {'NPPBH', 'NumberOfPixelsPerBlockHorizontal', 4 + 'NPPBV', 'NumberOfPixelsPerBlockVertical', 4 + 'NBPP', 'NumberOfBitsPerPixelPerBand', 2 + 'IDLVL', 'DisplayLevel', 3 + 'IALVL', 'ImageAttachmentLevel', 3 + 'ILOC', 'ImageLocation', 10 + 'IMAG', 'ImageMagnification', 4}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +% The IMAG value should be converted to a number from the decimal string. +isnitf_meta(end).value = sscanf(isnitf_meta(end).value, '%f'); + +fields = {'UDIDL', 'UserDefinedImageDataLength', 5}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%If UDIDL is not zeros we'll have values for UDOFL and UDID +UDIDL = sscanf(isnitf_meta(end).value, '%f'); +if UDIDL ~= 0 + + fields = {'UDOFL', 'UserDefinedOverflow', 3 + 'UDID', 'UserDefinedImageData', UDIDL - 3}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + +fields = {'IXSHDL', 'ExtendedSubheaderDataLength', 5}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%If IXSHDL is not zeros we'll have values for IXSOFL and IXSHD +IXSHDL = sscanf(isnitf_meta(end).value, '%f'); +if IXSHDL ~= 0 + + fields = {'IXOFL', 'ExtendedSubheaderOverflow', 3 + 'IXSHD', 'ExtendedSubheaderData', IXSHDL - 3}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + +% If the image contains data masking, read those values. (See +% MIL-STD-2500B Table A-3(A). +dataMaskTableLength = 0; +if (hasDataMaskTable(ic)) + + fields = {'IMDATOFF', 'BlockedImageDataOffset', 1, 'int32' + 'BMRLNTH', 'BlockedMaskRecordLength', 1, 'uint16'}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + BMRLNTH = isnitf_meta(end).value; + + fields = {'TMRLNTH', 'PadPixelMaskRecordLength', 1, 'uint16'}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + TMRLNTH = isnitf_meta(end).value; + + fields = {'TPXCDLNTH', 'TransparentOutputPixelCodeLength', 1, 'uint16'}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + TPXCDLNTH = isnitf_meta(end).value; + + dataMaskTableLength = dataMaskTableLength + 4 + 2 + 2 + 2; + + if (TPXCDLNTH ~= 0) + + if (TPXCDLNTH <= 8) + fmt = 'uint8'; + dataMaskTableLength = dataMaskTableLength + 1; + else + fmt = 'uint16'; + dataMaskTableLength = dataMaskTableLength + 2; + end + + fields = {'TPXCD', 'PadOutputPixelCode', 1, fmt}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + + end + + numBlocks = NBPR * NBPC * max(bands, nbands); + + if (BMRLNTH ~= 0) + fields = {'BMRnBNDm', 'BlockNBandMOffset', numBlocks, 'uint32'}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + dataMaskTableLength = dataMaskTableLength + 4 * numBlocks; + end + + if (TMRLNTH ~= 0) + fields = {'TMRnBNDm', 'PadPixelNBandMOffset', numBlocks, 'uint32'}; + isnitf_meta = nitfReadMetaNumeric(isnitf_meta, fields, fid); + dataMaskTableLength = dataMaskTableLength + 4 * numBlocks; + end + +end + +%Move the cursor through the image data +readforwardbytes = sscanf(dataLength, '%f') - dataMaskTableLength; +fseek(fid, readforwardbytes, 'cof'); + + + +function isnitf_meta = checkIcords(icords, fid, isnitf_meta) +if ~strcmp(icords, '') + + fields = {'IGEOLO', 'ImageGeographicLocation', 60}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + + + +function isnitf_meta = checkNicom(nicom, fid, isnitf_meta) + +% Parse the Image Comment fields in the image subheader +% The length of the ICOM field is 80. +for currentComment = 1:nicom + + %Pull the Image Comment + fields = {'ICOM%1d', 'ImageComment%1d', 80}; + isnitf_meta = nitfReadMetaMulti(isnitf_meta, fields, fid, currentComment); + +end + + + +function tf = hasDataMaskTable(IC) + +switch (IC) +case {'NM', 'M1', 'M3', 'M4', 'M5'} + tf = true; +otherwise + tf = false; +end diff --git a/CT/private/parseimagesubheader20.m b/CT/private/parseimagesubheader20.m index 4070ccf..31bb476 100644 --- a/CT/private/parseimagesubheader20.m +++ b/CT/private/parseimagesubheader20.m @@ -1,214 +1,214 @@ -function isnitf_meta = parseimagesubheader20( fid, dataLength ) -%PARSEIMAGESUBHEADER Parse the Image subheaders in an NITF file. -% ISNITF_META = PARSEIMAGESUBHEADER -% Parse the Image Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -% TODO: complete refactorization around conditionals - -% Initialize the image subheader structure -isnitf_meta = struct([]); -fields = {'IM', 'FilePartType', 2 - 'IID', 'ImageID', 10 - 'IDATIM', 'ImageDateAndTime', 14 - 'TGTID', 'TargetID', 17 - 'ITITLE', 'ImageTitle', 80 - 'ISCLAS', 'ImageSecurityClassification', 1 - 'ISCODE', 'ImageCodewords', 40 - 'ISCTLH', 'ImageControlAndHandling', 40 - 'ISREL', 'ImageReleasingInstructions', 40 - 'ISCAUT', 'ImageClassificationAuthority', 20 - 'ISCTLN', 'ImageSecurityControlNumber', 20 - 'ISDWNG', 'ImageSecurityDowngrade', 6}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%ISDWNG is the last item extracted in the loop above. Depending -%on its value there will be an ISDEVT or Image Downgrading Event field. -%If ISDWNG is "999998, add the ISDEVT field to the meta data struct -%and insert values. -isdwng = sscanf(isnitf_meta(end).value, '%f'); - -%File downgrade event is conditional on fsdwng -isnitf_meta = checkISDWNG(fid, isnitf_meta, isdwng); - -fields = {'ENCRYP', 'Encryption', 1 - 'ISORCE', 'ImageSource', 42 - 'NROWS', 'NumberOfSignificantRowsInImage', 8 - 'NCOLS', 'NumberOfSignificantColumnsInImage', 8 - 'PVTYPE', 'PixelValueType', 3 - 'IREP', 'ImageRepresentation', 8 - 'ICAT', 'ImageCategory', 8 - 'ABPP', 'ActualBitsPerPixelPerBand', 2 - 'PJUST', 'PixelJustification', 1 - 'ICORDS', 'ImageCoordinateSystem', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%ICORDS Depending on its value there will be an IGEOLO or Image Geographic Location field. -%If ICORDS is not a space, add the IGEOLO field to the meta data struct -%and insert values. -icords = isnitf_meta(end).value; -isnitf_meta = checkIcords(icords, fid, isnitf_meta); - -%NICOM -%Depending on its value there will be ICOM fields (ICOM1 through ICOMn). -%If NICOM is not zero, add the comment fields to the meta data struct -%and insert their values. - -fields = {'NICOM', 'NumberOfImageComments', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -nicom = sscanf(isnitf_meta(end).value, '%f'); -isnitf_meta = checkNicom(nicom, fid, isnitf_meta); - -%Next field is Image Compression -fields = {'IC', 'ImageCompression', 2}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%Next field is COMRAT or Compression Rate Code -%If the IC field is not NC or NM the field will have a value. -ic = isnitf_meta(end).value; -if ~strcmp(ic, 'NC') && ~strcmp(ic, 'NM') - fields = {'COMRAT', 'CompressionRateCode', 4}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); -end - -fields = {'NBANDS', 'NumberOfBands', 1}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -bands = sscanf(isnitf_meta(end).value, '%f'); - -%Set up Band substructure - -isnitf_meta(end + 1).name = 'Band Meta'; -isnitf_meta(end).vname = 'BandMeta'; -%The value will be a structure of band data - -%The next seven fields occur for each band as indicated by BANDS -% Lengths: -% IREPBAND 2, ISUBCAT 6, IFC 1, IMFLT 3, NLUTS 1, NELUT 5 [omitted if NLUTS = 0] -% LUTD contains data for the mth LUT of the nnth band - -nitf_metaBand(bands).value = ''; - -for currentBand = 1: bands - - isBandnitf_meta = struct([]); - - nitf_metaBand(currentBand).name = sprintf('Band%03d', currentBand); - nitf_metaBand(currentBand).vname = [nitf_metaBand(currentBand).name 'Meta']; - nitf_metaBand(currentBand).value = ''; - - fields = {'IREPBAND%02d', 'BandRepresentation%02d', 2 - 'ISUBCAT%02d', 'BandSubcategory%02d', 6 - 'IFC%02d', 'BandImageFilterCondition%02d', 1 - 'IMFLT%02d', 'BandImageFilterCode%02d', 3 - 'NLUTS%02d', 'BandNumberOfLUTS%02d', 1}; - - isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); - numluts = sscanf(isBandnitf_meta(end).value, '%f'); - - if numluts ~= 0 - %nnth Band Number of LUT Entries - - fields = {'NELUT%02d', 'BandNumberOfLUTEntries%02d', 5}; - isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); - - numlutentries = sscanf(isBandnitf_meta(end).value', '%f'); - - %The value will be a struct of LUT data - for currentLut = 1 : numluts - isBandnitf_meta(end + 1).name = sprintf('LUTData%1d', currentLut); - isBandnitf_meta(end).vname = sprintf('LUTData%1d', currentLut); - % Note: not converted to char. - isBandnitf_meta(end).value = fread(fid, numlutentries, 'uint8'); - end - end - - nitf_metaBand(currentBand).value = isBandnitf_meta; - -end -isnitf_meta(end).value = nitf_metaBand; - - -fields = {'ISYNC', 'ImageSyncCode', 1 - 'IMODE', 'ImageMode' 1 - 'NBPR', 'NumberOfBlocksPerRow', 4 - 'NBPC', 'NumberOfBlocksPerColumn', 4 - 'NPPBH', 'NumberOfPixelsPerBlockHorizontal', 4 - 'NPPBV', 'NumberOfPixelsPerBlockVertical', 4 - 'NBPP', 'NumberOfBitsPerPixelPerBand', 2 - 'IDLVL', 'DisplayLevel', 3 - 'IALVL', 'ImageAttachmentLevel', 3 - 'ILOC', 'ImageLocation', 10 - 'IMAG', 'ImageMagnification', 4}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -% The IMAG value should be converted to a number from the decimal string. -isnitf_meta(end).value = sscanf(isnitf_meta(end).value, '%f'); - -fields = {'UDIDL', 'UserDefinedImageDataLength', 5}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%If UDIDL is not zeros we'll have values for UDOFL and UDID -UDIDL = sscanf(isnitf_meta(end).value, '%f'); -if UDIDL ~= 0 - - fields = {'UDOFL', 'UserDefinedOverflow', 3 - 'UDID', 'UserDefinedImageData', UDIDL - 3}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - -fields = {'IXSHDL', 'ExtendedSubheaderDataLength', 5}; -isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -%If IXSHDL is not zeros we'll have values for IXSOFL and IXSHD -IXSHDL = sscanf(isnitf_meta(end).value, '%f'); -if IXSHDL ~= 0 - - fields = {'IXOFL', 'ExtendedSubheaderOverflow', 3 - 'IXSHD', 'ExtendedSubheaderData', IXSHDL - 3}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - -%Move the cursor through the image data -readforwardbytes = sscanf(dataLength, '%f'); -fseek(fid, readforwardbytes, 'cof'); - - - -function isnitf_meta = checkISDWNG(fid, isnitf_meta, fsdwng) - -if fsdwng == 999998 - - fields = {'ISDEVT', 'ImageDowngradingEvent', 40}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - - - -function isnitf_meta = checkIcords(icords, fid, isnitf_meta) - -if ~strcmp(icords, 'N') - - fields = {'IGEOLO', 'ImageGeographicLocation', 60}; - isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); - -end - - - -function isnitf_meta = checkNicom(nicom, fid, isnitf_meta) - -% Parse the Image Comment fields in the image subheader -% The length of the ICOM field is 80. -for currentComment = 1:nicom - - %Pull the Image Comment - fields = {'ICOM%1d', 'ImageComment%1d', 80}; - isnitf_meta = nitfReadMetaMulti(isnitf_meta, fields, fid, currentComment); - -end +function isnitf_meta = parseimagesubheader20( fid, dataLength ) +%PARSEIMAGESUBHEADER Parse the Image subheaders in an NITF file. +% ISNITF_META = PARSEIMAGESUBHEADER +% Parse the Image Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +% TODO: complete refactorization around conditionals + +% Initialize the image subheader structure +isnitf_meta = struct([]); +fields = {'IM', 'FilePartType', 2 + 'IID', 'ImageID', 10 + 'IDATIM', 'ImageDateAndTime', 14 + 'TGTID', 'TargetID', 17 + 'ITITLE', 'ImageTitle', 80 + 'ISCLAS', 'ImageSecurityClassification', 1 + 'ISCODE', 'ImageCodewords', 40 + 'ISCTLH', 'ImageControlAndHandling', 40 + 'ISREL', 'ImageReleasingInstructions', 40 + 'ISCAUT', 'ImageClassificationAuthority', 20 + 'ISCTLN', 'ImageSecurityControlNumber', 20 + 'ISDWNG', 'ImageSecurityDowngrade', 6}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%ISDWNG is the last item extracted in the loop above. Depending +%on its value there will be an ISDEVT or Image Downgrading Event field. +%If ISDWNG is "999998, add the ISDEVT field to the meta data struct +%and insert values. +isdwng = sscanf(isnitf_meta(end).value, '%f'); + +%File downgrade event is conditional on fsdwng +isnitf_meta = checkISDWNG(fid, isnitf_meta, isdwng); + +fields = {'ENCRYP', 'Encryption', 1 + 'ISORCE', 'ImageSource', 42 + 'NROWS', 'NumberOfSignificantRowsInImage', 8 + 'NCOLS', 'NumberOfSignificantColumnsInImage', 8 + 'PVTYPE', 'PixelValueType', 3 + 'IREP', 'ImageRepresentation', 8 + 'ICAT', 'ImageCategory', 8 + 'ABPP', 'ActualBitsPerPixelPerBand', 2 + 'PJUST', 'PixelJustification', 1 + 'ICORDS', 'ImageCoordinateSystem', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%ICORDS Depending on its value there will be an IGEOLO or Image Geographic Location field. +%If ICORDS is not a space, add the IGEOLO field to the meta data struct +%and insert values. +icords = isnitf_meta(end).value; +isnitf_meta = checkIcords(icords, fid, isnitf_meta); + +%NICOM +%Depending on its value there will be ICOM fields (ICOM1 through ICOMn). +%If NICOM is not zero, add the comment fields to the meta data struct +%and insert their values. + +fields = {'NICOM', 'NumberOfImageComments', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +nicom = sscanf(isnitf_meta(end).value, '%f'); +isnitf_meta = checkNicom(nicom, fid, isnitf_meta); + +%Next field is Image Compression +fields = {'IC', 'ImageCompression', 2}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%Next field is COMRAT or Compression Rate Code +%If the IC field is not NC or NM the field will have a value. +ic = isnitf_meta(end).value; +if ~strcmp(ic, 'NC') && ~strcmp(ic, 'NM') + fields = {'COMRAT', 'CompressionRateCode', 4}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); +end + +fields = {'NBANDS', 'NumberOfBands', 1}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +bands = sscanf(isnitf_meta(end).value, '%f'); + +%Set up Band substructure + +isnitf_meta(end + 1).name = 'Band Meta'; +isnitf_meta(end).vname = 'BandMeta'; +%The value will be a structure of band data + +%The next seven fields occur for each band as indicated by BANDS +% Lengths: +% IREPBAND 2, ISUBCAT 6, IFC 1, IMFLT 3, NLUTS 1, NELUT 5 [omitted if NLUTS = 0] +% LUTD contains data for the mth LUT of the nnth band + +nitf_metaBand(bands).value = ''; + +for currentBand = 1: bands + + isBandnitf_meta = struct([]); + + nitf_metaBand(currentBand).name = sprintf('Band%03d', currentBand); + nitf_metaBand(currentBand).vname = [nitf_metaBand(currentBand).name 'Meta']; + nitf_metaBand(currentBand).value = ''; + + fields = {'IREPBAND%02d', 'BandRepresentation%02d', 2 + 'ISUBCAT%02d', 'BandSubcategory%02d', 6 + 'IFC%02d', 'BandImageFilterCondition%02d', 1 + 'IMFLT%02d', 'BandImageFilterCode%02d', 3 + 'NLUTS%02d', 'BandNumberOfLUTS%02d', 1}; + + isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); + numluts = sscanf(isBandnitf_meta(end).value, '%f'); + + if numluts ~= 0 + %nnth Band Number of LUT Entries + + fields = {'NELUT%02d', 'BandNumberOfLUTEntries%02d', 5}; + isBandnitf_meta = nitfReadMetaMulti(isBandnitf_meta, fields, fid, currentBand); + + numlutentries = sscanf(isBandnitf_meta(end).value', '%f'); + + %The value will be a struct of LUT data + for currentLut = 1 : numluts + isBandnitf_meta(end + 1).name = sprintf('LUTData%1d', currentLut); + isBandnitf_meta(end).vname = sprintf('LUTData%1d', currentLut); + % Note: not converted to char. + isBandnitf_meta(end).value = fread(fid, numlutentries, 'uint8'); + end + end + + nitf_metaBand(currentBand).value = isBandnitf_meta; + +end +isnitf_meta(end).value = nitf_metaBand; + + +fields = {'ISYNC', 'ImageSyncCode', 1 + 'IMODE', 'ImageMode' 1 + 'NBPR', 'NumberOfBlocksPerRow', 4 + 'NBPC', 'NumberOfBlocksPerColumn', 4 + 'NPPBH', 'NumberOfPixelsPerBlockHorizontal', 4 + 'NPPBV', 'NumberOfPixelsPerBlockVertical', 4 + 'NBPP', 'NumberOfBitsPerPixelPerBand', 2 + 'IDLVL', 'DisplayLevel', 3 + 'IALVL', 'ImageAttachmentLevel', 3 + 'ILOC', 'ImageLocation', 10 + 'IMAG', 'ImageMagnification', 4}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +% The IMAG value should be converted to a number from the decimal string. +isnitf_meta(end).value = sscanf(isnitf_meta(end).value, '%f'); + +fields = {'UDIDL', 'UserDefinedImageDataLength', 5}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%If UDIDL is not zeros we'll have values for UDOFL and UDID +UDIDL = sscanf(isnitf_meta(end).value, '%f'); +if UDIDL ~= 0 + + fields = {'UDOFL', 'UserDefinedOverflow', 3 + 'UDID', 'UserDefinedImageData', UDIDL - 3}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + +fields = {'IXSHDL', 'ExtendedSubheaderDataLength', 5}; +isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +%If IXSHDL is not zeros we'll have values for IXSOFL and IXSHD +IXSHDL = sscanf(isnitf_meta(end).value, '%f'); +if IXSHDL ~= 0 + + fields = {'IXOFL', 'ExtendedSubheaderOverflow', 3 + 'IXSHD', 'ExtendedSubheaderData', IXSHDL - 3}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + +%Move the cursor through the image data +readforwardbytes = sscanf(dataLength, '%f'); +fseek(fid, readforwardbytes, 'cof'); + + + +function isnitf_meta = checkISDWNG(fid, isnitf_meta, fsdwng) + +if fsdwng == 999998 + + fields = {'ISDEVT', 'ImageDowngradingEvent', 40}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + + + +function isnitf_meta = checkIcords(icords, fid, isnitf_meta) + +if ~strcmp(icords, 'N') + + fields = {'IGEOLO', 'ImageGeographicLocation', 60}; + isnitf_meta = nitfReadMeta(isnitf_meta, fields, fid); + +end + + + +function isnitf_meta = checkNicom(nicom, fid, isnitf_meta) + +% Parse the Image Comment fields in the image subheader +% The length of the ICOM field is 80. +for currentComment = 1:nicom + + %Pull the Image Comment + fields = {'ICOM%1d', 'ImageComment%1d', 80}; + isnitf_meta = nitfReadMetaMulti(isnitf_meta, fields, fid, currentComment); + +end diff --git a/CT/private/parselabelsubheader20.m b/CT/private/parselabelsubheader20.m index 0bbcb7b..8160301 100644 --- a/CT/private/parselabelsubheader20.m +++ b/CT/private/parselabelsubheader20.m @@ -1,50 +1,50 @@ -function lsnitf_meta = parselabelsubheader20( fid, dataLength ) -%PARSELABELSUBHEADER20 Parse the Label subheaders in an NITF file. -% LSNITF_META = PARSELABELSUBHEADER20 -% Parse the Label Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -lsnitf_meta = struct([]); -fields = {'LA', 'FilePartType', 2 - 'LID', 'LabelID', 10 - 'LSCLAS', 'LabelSecurityClassification', 1 - 'LSCODE', 'LabelCodewords', 40 - 'LSCTLH', 'LabelControlAndHandling', 40 - 'LSREL', 'LabelReleasingInstructions', 40 - 'LSCAUT', 'LabelClassificationAuthority', 20 - 'LSCTLN', 'LabelSecurityControlNumber', 20 - 'LSDWNG', 'LabelSecurityDowngrade', 6}; -lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); - -%LSDWNG is LSnitf_meta(9) and the last item extracted in the loop above. Depending -%on its value there will be an SSDEVT -lsdwng = sscanf(lsnitf_meta(end).value, '%f'); -if lsdwng == 999998 - fields = {'LSDEVT', 'LabelDowngradingEvent', 40}; - lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); -end - -fields = {'ENCRYP', 'Encryption', 1 - 'LFS', 'LabelFontStyle', 1 - 'LCW', 'LabelCellWidth', 2 - 'LCH', 'LabelCellHeight', 2 - 'LDLVL', 'DisplayLevel', 3 - 'LALVL', 'LabelAttachmentLevel', 3 - 'LLOC', 'LabelLocation', 10 - 'LTC', 'LabelTextColor', 3 - 'LBC', 'LabelBackgroundColor', 3 - 'LXSHDL', 'ExtendedSubheaderDataLength', 5}; -lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); - -%LXSOFL and LCSHD -lxshdl = sscanf(lsnitf_meta(end).value, '%f'); -if lxshdl ~= 0 - fields = {'LXSOFL', 'ExtendedSubheaderOverflow', 3 - 'LXSHD', 'ExtendedSubheaderData', lxshdl - 3}; - lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); -end - -%Move the cursor through the Label data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function lsnitf_meta = parselabelsubheader20( fid, dataLength ) +%PARSELABELSUBHEADER20 Parse the Label subheaders in an NITF file. +% LSNITF_META = PARSELABELSUBHEADER20 +% Parse the Label Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +lsnitf_meta = struct([]); +fields = {'LA', 'FilePartType', 2 + 'LID', 'LabelID', 10 + 'LSCLAS', 'LabelSecurityClassification', 1 + 'LSCODE', 'LabelCodewords', 40 + 'LSCTLH', 'LabelControlAndHandling', 40 + 'LSREL', 'LabelReleasingInstructions', 40 + 'LSCAUT', 'LabelClassificationAuthority', 20 + 'LSCTLN', 'LabelSecurityControlNumber', 20 + 'LSDWNG', 'LabelSecurityDowngrade', 6}; +lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); + +%LSDWNG is LSnitf_meta(9) and the last item extracted in the loop above. Depending +%on its value there will be an SSDEVT +lsdwng = sscanf(lsnitf_meta(end).value, '%f'); +if lsdwng == 999998 + fields = {'LSDEVT', 'LabelDowngradingEvent', 40}; + lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); +end + +fields = {'ENCRYP', 'Encryption', 1 + 'LFS', 'LabelFontStyle', 1 + 'LCW', 'LabelCellWidth', 2 + 'LCH', 'LabelCellHeight', 2 + 'LDLVL', 'DisplayLevel', 3 + 'LALVL', 'LabelAttachmentLevel', 3 + 'LLOC', 'LabelLocation', 10 + 'LTC', 'LabelTextColor', 3 + 'LBC', 'LabelBackgroundColor', 3 + 'LXSHDL', 'ExtendedSubheaderDataLength', 5}; +lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); + +%LXSOFL and LCSHD +lxshdl = sscanf(lsnitf_meta(end).value, '%f'); +if lxshdl ~= 0 + fields = {'LXSOFL', 'ExtendedSubheaderOverflow', 3 + 'LXSHD', 'ExtendedSubheaderData', lxshdl - 3}; + lsnitf_meta = nitfReadMeta(lsnitf_meta, fields, fid); +end + +%Move the cursor through the Label data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parsetextsubheader.m b/CT/private/parsetextsubheader.m index d6f3bc8..2fd27d9 100644 --- a/CT/private/parsetextsubheader.m +++ b/CT/private/parsetextsubheader.m @@ -1,48 +1,48 @@ -function tsnitf_meta = parsetextsubheader( fid, dataLength ) -%PARSETEXTSUBHEADER Parse the Text subheaders in an NITF file. -% TSNITF_META = PARSETEXTSUBHEADER -% Parse the Text Segment Subheader for an NITF 2.1 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -tsnitf_meta = struct([]); -fields = {'TE', 'FilePartType', 2 - 'TEXTID', 'TextID', 7 - 'TXTALVAL', 'TextAttachmentLevel', 3 - 'TXTDT', 'TextDateAndTime', 14 - 'TXTITL', 'TextTitle', 80 - 'TSCLAS', 'TextSecurityClassification', 1 - 'TSCLSY', 'TextSecurityClassificationSystem', 2 - 'TSCODE', 'TextCodewords', 11 - 'TSCTLH', 'TextControlAndHandling', 2 - 'TSREL', 'TextReleasingInstructions', 20 - 'TSDCTP', 'TextDeclassificationType', 2 - 'TSDCDT', 'TextDeclassificationDate', 8 - 'TSDCXM', 'TextDeclassificationExemption', 4 - 'TSDG', 'TextDowngrade', 1 - 'TSDGT', 'TextDowngradeDate', 8 - 'TSCLTX', 'TextClassificationText', 43 - 'TSCATP', 'TextClassificationAuthorityType', 1 - 'TSCAUT', 'TextClassificationAuthority', 40 - 'TSCRSN', 'TextClassificationReason', 1 - 'TSSRDT', 'TextSecuritySourceDate', 8 - 'TSCTLN', 'TextSecurityControlNumber', 15 - 'ENCRYP', 'Encryption', 1 - 'TXTFMT', 'TextFormat', 3 - 'TXSHDL', 'ExtendedSubheaderDataLength', 5}; -tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); - -%TXSHDL is TSMETA_INFO(24) and the last item extracted in the loop above. Depending -%on its value there will be an TXSOFL or Extended Subheader Overflow field and TXSHD field. -%If TXSHDL is not zeros, add the TXSOFL and TXSHD fields to the meta data struct -%and insert values. -txshdl = sscanf(tsnitf_meta(24).value, '%f'); -if txshdl ~= 0 - fields = {'TXSOFL', 'ExtendedSubheaderOverflow', 3 - 'TXSHD', 'ExtendedSubheaderData', txshdl - 3}; - tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); -end - -%Move the cursor through the Text data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function tsnitf_meta = parsetextsubheader( fid, dataLength ) +%PARSETEXTSUBHEADER Parse the Text subheaders in an NITF file. +% TSNITF_META = PARSETEXTSUBHEADER +% Parse the Text Segment Subheader for an NITF 2.1 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +tsnitf_meta = struct([]); +fields = {'TE', 'FilePartType', 2 + 'TEXTID', 'TextID', 7 + 'TXTALVAL', 'TextAttachmentLevel', 3 + 'TXTDT', 'TextDateAndTime', 14 + 'TXTITL', 'TextTitle', 80 + 'TSCLAS', 'TextSecurityClassification', 1 + 'TSCLSY', 'TextSecurityClassificationSystem', 2 + 'TSCODE', 'TextCodewords', 11 + 'TSCTLH', 'TextControlAndHandling', 2 + 'TSREL', 'TextReleasingInstructions', 20 + 'TSDCTP', 'TextDeclassificationType', 2 + 'TSDCDT', 'TextDeclassificationDate', 8 + 'TSDCXM', 'TextDeclassificationExemption', 4 + 'TSDG', 'TextDowngrade', 1 + 'TSDGT', 'TextDowngradeDate', 8 + 'TSCLTX', 'TextClassificationText', 43 + 'TSCATP', 'TextClassificationAuthorityType', 1 + 'TSCAUT', 'TextClassificationAuthority', 40 + 'TSCRSN', 'TextClassificationReason', 1 + 'TSSRDT', 'TextSecuritySourceDate', 8 + 'TSCTLN', 'TextSecurityControlNumber', 15 + 'ENCRYP', 'Encryption', 1 + 'TXTFMT', 'TextFormat', 3 + 'TXSHDL', 'ExtendedSubheaderDataLength', 5}; +tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); + +%TXSHDL is TSMETA_INFO(24) and the last item extracted in the loop above. Depending +%on its value there will be an TXSOFL or Extended Subheader Overflow field and TXSHD field. +%If TXSHDL is not zeros, add the TXSOFL and TXSHD fields to the meta data struct +%and insert values. +txshdl = sscanf(tsnitf_meta(24).value, '%f'); +if txshdl ~= 0 + fields = {'TXSOFL', 'ExtendedSubheaderOverflow', 3 + 'TXSHD', 'ExtendedSubheaderData', txshdl - 3}; + tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); +end + +%Move the cursor through the Text data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/parsetextsubheader20.m b/CT/private/parsetextsubheader20.m index f61430c..d7e98b4 100644 --- a/CT/private/parsetextsubheader20.m +++ b/CT/private/parsetextsubheader20.m @@ -1,45 +1,45 @@ -function tsnitf_meta = parsetextsubheader20( fid, dataLength ) -%PARSETEXTSUBHEADER20 Parse the Text subheaders in an NITF file. -% TSNITF_META = PARSETEXTSUBHEADER20 -% Parse the Text Segment Subheader for an NITF 2.0 file. - -% Copyright 2007-2008 The MathWorks, Inc. - -tsnitf_meta = struct([]); -fields = {'TE', 'FilePartType', 2 - 'TEXTID', 'TextID', 10 - 'TXTDT', 'TextDateAndTime', 14 - 'TXTITL', 'TextTitle', 80 - 'TSCLAS', 'TextSecurityClassification', 1 - 'TSCODE', 'TextCodewords', 40 - 'TSCTLH', 'TextControlAndHandling', 40 - 'TSREL', 'TextReleasingInstructions', 40 - 'TSCAUT', 'TextClassificationAuthority', 20 - 'TSCTLN', 'TextSecurityControlNumber', 20 - 'TSDWNG', 'TextSecurityDowngrade', 6}; -tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); - -%TSDWNG is TSNITF_META(11) and the last item extracted in the loop above. Depending -%on its value there will be an TSDEVT -tsdwng = sscanf(tsnitf_meta(end).value, '%f'); -if tsdwng == 999998 - fields = {'TSDEVT', 'TextDowngradingEvent', 40}; - tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); -end - -%ENCRYP -fields = {'ENCRYP', 'Encryption', 1 - 'TXTFMT', 'TextFormat', 3 - 'TXSHDL', 'ExtendedSubheaderDataLength', 5}; -tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); - -txshdl = sscanf(tsnitf_meta(end).value, '%f'); -if txshdl ~= 0 - fields = {'TXSOFL', 'ExtendedSubheaderOverflow', 3 - 'TXSHD', 'ExtendedSubheaderData', txshdl - 3}; - tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); -end - -%Move the cursor through the Text data -readforwardbytes = sscanf(dataLength, '%f'); -fread(fid, readforwardbytes, 'uint8=>char'); +function tsnitf_meta = parsetextsubheader20( fid, dataLength ) +%PARSETEXTSUBHEADER20 Parse the Text subheaders in an NITF file. +% TSNITF_META = PARSETEXTSUBHEADER20 +% Parse the Text Segment Subheader for an NITF 2.0 file. + +% Copyright 2007-2008 The MathWorks, Inc. + +tsnitf_meta = struct([]); +fields = {'TE', 'FilePartType', 2 + 'TEXTID', 'TextID', 10 + 'TXTDT', 'TextDateAndTime', 14 + 'TXTITL', 'TextTitle', 80 + 'TSCLAS', 'TextSecurityClassification', 1 + 'TSCODE', 'TextCodewords', 40 + 'TSCTLH', 'TextControlAndHandling', 40 + 'TSREL', 'TextReleasingInstructions', 40 + 'TSCAUT', 'TextClassificationAuthority', 20 + 'TSCTLN', 'TextSecurityControlNumber', 20 + 'TSDWNG', 'TextSecurityDowngrade', 6}; +tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); + +%TSDWNG is TSNITF_META(11) and the last item extracted in the loop above. Depending +%on its value there will be an TSDEVT +tsdwng = sscanf(tsnitf_meta(end).value, '%f'); +if tsdwng == 999998 + fields = {'TSDEVT', 'TextDowngradingEvent', 40}; + tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); +end + +%ENCRYP +fields = {'ENCRYP', 'Encryption', 1 + 'TXTFMT', 'TextFormat', 3 + 'TXSHDL', 'ExtendedSubheaderDataLength', 5}; +tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); + +txshdl = sscanf(tsnitf_meta(end).value, '%f'); +if txshdl ~= 0 + fields = {'TXSOFL', 'ExtendedSubheaderOverflow', 3 + 'TXSHD', 'ExtendedSubheaderData', txshdl - 3}; + tsnitf_meta = nitfReadMeta(tsnitf_meta, fields, fid); +end + +%Move the cursor through the Text data +readforwardbytes = sscanf(dataLength, '%f'); +fread(fid, readforwardbytes, 'uint8=>char'); diff --git a/CT/private/rgb2rgbe.m b/CT/private/rgb2rgbe.m index f48442a..40c03cf 100644 --- a/CT/private/rgb2rgbe.m +++ b/CT/private/rgb2rgbe.m @@ -1,22 +1,22 @@ -function rgbe = rgb2rgbe(rgb) -%rgb2rgbe Convert RGB HDR pixels to 8-bit RGBE components. -% RGBE = RGB2RGBE(RGB) converts an arry of floating-point, high dynamic -% range (R,G,B) values to an encoded UINT8 array of (R,G,B,E) values. -% -% Reference: Ward, "Real Pixels" (pp. 80-83) in Arvo "Graphics Gems II," 1991. - -% Copyright 2007-2013 The MathWorks, Inc. - -% Reshape the m-by-n-by-3 RGB array into a m*n-by-3 array and find the -% maximum value of each RGB triple. -rgb = reshape(rgb, numel(rgb)/3, 3); - -maxRGB = max(rgb,[],2); -[f,e] = log2(maxRGB); -tmp = f*256./maxRGB; % Reusing maxRGB causes NaN values. -rgbm = bsxfun(@times,rgb,tmp); -rgbe = uint8([rgbm, e+128]); - -% Pixels where max(rgb) < 1e-38 must become (0,0,0,0). -mask = find(maxRGB < 1e-38); -rgbe(mask, :) = repmat([0 0 0 0], [numel(mask), 1]); +function rgbe = rgb2rgbe(rgb) +%rgb2rgbe Convert RGB HDR pixels to 8-bit RGBE components. +% RGBE = RGB2RGBE(RGB) converts an arry of floating-point, high dynamic +% range (R,G,B) values to an encoded UINT8 array of (R,G,B,E) values. +% +% Reference: Ward, "Real Pixels" (pp. 80-83) in Arvo "Graphics Gems II," 1991. + +% Copyright 2007-2013 The MathWorks, Inc. + +% Reshape the m-by-n-by-3 RGB array into a m*n-by-3 array and find the +% maximum value of each RGB triple. +rgb = reshape(rgb, numel(rgb)/3, 3); + +maxRGB = max(rgb,[],2); +[f,e] = log2(maxRGB); +tmp = f*256./maxRGB; % Reusing maxRGB causes NaN values. +rgbm = bsxfun(@times,rgb,tmp); +rgbe = uint8([rgbm, e+128]); + +% Pixels where max(rgb) < 1e-38 must become (0,0,0,0). +mask = find(maxRGB < 1e-38); +rgbe(mask, :) = repmat([0 0 0 0], [numel(mask), 1]); diff --git a/CT/private/rgbe2rgb.m b/CT/private/rgbe2rgb.m index 7727fee..f7bcc58 100644 --- a/CT/private/rgbe2rgb.m +++ b/CT/private/rgbe2rgb.m @@ -1,23 +1,23 @@ -function rgb = rgbe2rgb(rgbe) -%rgbe2rgb Convert RGBE values to floating-point RGB. -% RGB = RGBE2RGB(RGBE) converts an arry of (R,G,B,E) encoded values to -% an array of floating-point (R,G,B) high dynamic range values. -% -% Reference: Ward, "Real Pixels" (pp. 80-83) in Arvo "Graphics Gems II," 1991. - -% Copyright 2007-2013 The MathWorks, Inc. - -dims = size(rgbe); - -% Separate the 1-D scanline into separate columns of (R,G,B,E) values. -rgbe = reshape(rgbe, dims(1), 4); - -rgb = bsxfun(@times, single(rgbe(:, 1:3)) ./ 256, ... - 2.^(single(rgbe(:,4)) - 128)); - -% Esnure that (0,0,0,0) maps to (0,0,0). -mask = max(rgbe, [], 2) == 0; -rgb(mask,:) = 0; - -% Separate into color planes suitable for storage. -rgb = reshape(rgb, [dims(1), 1, 3]); +function rgb = rgbe2rgb(rgbe) +%rgbe2rgb Convert RGBE values to floating-point RGB. +% RGB = RGBE2RGB(RGBE) converts an arry of (R,G,B,E) encoded values to +% an array of floating-point (R,G,B) high dynamic range values. +% +% Reference: Ward, "Real Pixels" (pp. 80-83) in Arvo "Graphics Gems II," 1991. + +% Copyright 2007-2013 The MathWorks, Inc. + +dims = size(rgbe); + +% Separate the 1-D scanline into separate columns of (R,G,B,E) values. +rgbe = reshape(rgbe, dims(1), 4); + +rgb = bsxfun(@times, single(rgbe(:, 1:3)) ./ 256, ... + 2.^(single(rgbe(:,4)) - 128)); + +% Esnure that (0,0,0,0) maps to (0,0,0). +mask = max(rgbe, [], 2) == 0; +rgb(mask,:) = 0; + +% Separate into color planes suitable for storage. +rgb = reshape(rgb, [dims(1), 1, 3]); diff --git a/CT/private/rleCoder.m b/CT/private/rleCoder.m index 499a323..d98c8dd 100644 --- a/CT/private/rleCoder.m +++ b/CT/private/rleCoder.m @@ -1,14 +1,14 @@ -function scanline = rleCoder(data, dataLength) -%rleCoder Compress data using HDR adaptive run-length encoding. - -% Unlike the description in Graphics Gems II.8 (p.89), code values greater -% than 128 correspond to repeated runs, while smaller code values are -% literal dumps. See the following web site for more details: -% . -% The adaptive RLE used by HDR also does not shift code values by one. -% There can be only 127 elements in a run. - -scanline = rleCoder_mex(uint8(data), dataLength); - -% Crop the scanline to exclude the unused buffer. +function scanline = rleCoder(data, dataLength) +%rleCoder Compress data using HDR adaptive run-length encoding. + +% Unlike the description in Graphics Gems II.8 (p.89), code values greater +% than 128 correspond to repeated runs, while smaller code values are +% literal dumps. See the following web site for more details: +% . +% The adaptive RLE used by HDR also does not shift code values by one. +% There can be only 127 elements in a run. + +scanline = rleCoder_mex(uint8(data), dataLength); + +% Crop the scanline to exclude the unused buffer. scanline(isnan(scanline)) = []; \ No newline at end of file diff --git a/CT/private/rleDecoder.m b/CT/private/rleDecoder.m index 0d6a144..931f1f2 100644 --- a/CT/private/rleDecoder.m +++ b/CT/private/rleDecoder.m @@ -1,74 +1,74 @@ -function [decodedData, decodedBytes] = rleDecoder(encodedData, ... - scanlineWidth) -%rleDecoder Decompress data using HDR adaptive run-length encoding. -% [decodedData, decodedBytes] = rleCoder(encodedData, scanlineWidth) -% decompresses the RLE-compressed values in encodedData and places the -% result in decodedData. At most scanlineWidth values are decoded, -% and decodedBytes contains the number of values in encodedData that -% were used during decompression. - -% Copyright 2007-2013 The MathWorks, Inc. - -% Unlike the description in Graphics Gems II.8 (p.89), code values greater -% than 128 correspond to repeated runs, while smaller code values are -% literal dumps. See the following web site for more details: -% . -% The adaptive RLE used by HDR also does not shift code values by one. - -if (isempty(encodedData) || (scanlineWidth == 0)) - decodedData = []; - decodedBytes = 0; - return -end - -% Create a temporary buffer for the decoded data. -decodedData = zeros(1, scanlineWidth, class(encodedData)); - -% Loop over the compressed data until this part of the scanline is full. -inputPtr = 1; -outputPtr = 1; -while (outputPtr <= scanlineWidth) - if (encodedData(inputPtr) > 128) - - % A run of the same value. - runLength = double(encodedData(inputPtr)) - 128; - - if ((runLength - 1) > (scanlineWidth - outputPtr)) - - warning(message('images:rleDecoder:badRunLength')); - - end - - decodedData(outputPtr:(outputPtr + runLength - 1)) = ... - encodedData(inputPtr + 1); - - inputPtr = inputPtr + 2; - outputPtr = outputPtr + runLength; - - else - - % A run of literal data. - runLength = double(encodedData(inputPtr)); - inputPtr = inputPtr + 1; - - if ((runLength == 0) || ... - ((runLength - 1) > (scanlineWidth - outputPtr))) - - warning(message('images:rleDecoder:badRunLength')); - - end - - if ((inputPtr + runLength - 1) > numel(encodedData)) - error(message('images:rleDecoder:notEnoughEncodedData')) - end - - decodedData(outputPtr:(outputPtr + runLength - 1)) = ... - encodedData(inputPtr:(inputPtr + runLength - 1)); - - inputPtr = inputPtr + runLength; - outputPtr = outputPtr + runLength; - - end -end - -decodedBytes = inputPtr - 1; +function [decodedData, decodedBytes] = rleDecoder(encodedData, ... + scanlineWidth) +%rleDecoder Decompress data using HDR adaptive run-length encoding. +% [decodedData, decodedBytes] = rleCoder(encodedData, scanlineWidth) +% decompresses the RLE-compressed values in encodedData and places the +% result in decodedData. At most scanlineWidth values are decoded, +% and decodedBytes contains the number of values in encodedData that +% were used during decompression. + +% Copyright 2007-2013 The MathWorks, Inc. + +% Unlike the description in Graphics Gems II.8 (p.89), code values greater +% than 128 correspond to repeated runs, while smaller code values are +% literal dumps. See the following web site for more details: +% . +% The adaptive RLE used by HDR also does not shift code values by one. + +if (isempty(encodedData) || (scanlineWidth == 0)) + decodedData = []; + decodedBytes = 0; + return +end + +% Create a temporary buffer for the decoded data. +decodedData = zeros(1, scanlineWidth, class(encodedData)); + +% Loop over the compressed data until this part of the scanline is full. +inputPtr = 1; +outputPtr = 1; +while (outputPtr <= scanlineWidth) + if (encodedData(inputPtr) > 128) + + % A run of the same value. + runLength = double(encodedData(inputPtr)) - 128; + + if ((runLength - 1) > (scanlineWidth - outputPtr)) + + warning(message('images:rleDecoder:badRunLength')); + + end + + decodedData(outputPtr:(outputPtr + runLength - 1)) = ... + encodedData(inputPtr + 1); + + inputPtr = inputPtr + 2; + outputPtr = outputPtr + runLength; + + else + + % A run of literal data. + runLength = double(encodedData(inputPtr)); + inputPtr = inputPtr + 1; + + if ((runLength == 0) || ... + ((runLength - 1) > (scanlineWidth - outputPtr))) + + warning(message('images:rleDecoder:badRunLength')); + + end + + if ((inputPtr + runLength - 1) > numel(encodedData)) + error(message('images:rleDecoder:notEnoughEncodedData')) + end + + decodedData(outputPtr:(outputPtr + runLength - 1)) = ... + encodedData(inputPtr:(inputPtr + runLength - 1)); + + inputPtr = inputPtr + runLength; + outputPtr = outputPtr + runLength; + + end +end + +decodedBytes = inputPtr - 1; diff --git a/CT/private/tokenize.m b/CT/private/tokenize.m index 5fc0509..33babf4 100644 --- a/CT/private/tokenize.m +++ b/CT/private/tokenize.m @@ -1,22 +1,22 @@ -function tokens = tokenize( input_string, delimiters ) -%TOKENIZE Divide a string into tokens. -% TOKENS = TOKENIZE(STRING, DELIMITERS) divides STRING into tokens -% using the characters in the string DELIMITERS. The result is stored -% in a single-column cell array of strings. -% -% Examples: -% -% tokenize('The quick fox jumped',' ') returns {'The'; 'quick'; 'fox'; 'jumped'}. -% -% tokenize('Ann, Barry, Charlie',' ,') returns {'Ann'; 'Barry'; 'Charlie'}. -% -% tokenize('George E. Forsyth,Michael A. Malcolm,Cleve B. Moler',',') returns -% {'George E. Forsyth'; 'Michael A. Malcolm'; 'Cleve B. Moler'} - -% Copyright 1993-2005 The MathWorks, Inc. - -if (~isempty(input_string)) - tokens = strread(input_string,'%s',-1,'delimiter',delimiters); -else - tokens = {}; -end +function tokens = tokenize( input_string, delimiters ) +%TOKENIZE Divide a string into tokens. +% TOKENS = TOKENIZE(STRING, DELIMITERS) divides STRING into tokens +% using the characters in the string DELIMITERS. The result is stored +% in a single-column cell array of strings. +% +% Examples: +% +% tokenize('The quick fox jumped',' ') returns {'The'; 'quick'; 'fox'; 'jumped'}. +% +% tokenize('Ann, Barry, Charlie',' ,') returns {'Ann'; 'Barry'; 'Charlie'}. +% +% tokenize('George E. Forsyth,Michael A. Malcolm,Cleve B. Moler',',') returns +% {'George E. Forsyth'; 'Michael A. Malcolm'; 'Cleve B. Moler'} + +% Copyright 1993-2005 The MathWorks, Inc. + +if (~isempty(input_string)) + tokens = strread(input_string,'%s',-1,'delimiter',delimiters); +else + tokens = {}; +end diff --git a/CT/readme.txt b/CT/readme.txt new file mode 100755 index 0000000..02077e5 --- /dev/null +++ b/CT/readme.txt @@ -0,0 +1 @@ +The matlab function infoCT in this folder only works with matlab R2015a on a Windows system. diff --git a/XLS_MySQL_DB/CTfromExcel.m b/XLS_MySQL_DB/CTfromExcel.m index 5e93c40..970bd75 100644 --- a/XLS_MySQL_DB/CTfromExcel.m +++ b/XLS_MySQL_DB/CTfromExcel.m @@ -1,457 +1,457 @@ -function [CT, scapula, glenoid, glenoid_density, humerus, muscle] = CTfromExcel(shoulder, patient, rawExcel) -% ctFromExcel build the ct structure array from the excelRaw, patient and -% shoulder structure array. -% ct contains all data directly related to the imaging - -fprintf('\nGet CT & glenoid from Excel...'); - -% define CT structure array -CT = struct(... - 'CT_id', [], ... - 'shoulder_id', [], ... - 'date', [], ... - 'kernel', [], ... - 'pixel_size', [], ... - 'slice_spacing', [], ... - 'slice_thickness', [], ... - 'tension', [], ... - 'current', [], ... - 'manufacturer', [], ... - 'model', [], ... - 'institution', [], ... - 'folder_name', [], ... - 'comment', [] ... - ); - -% define scapula array -scapula = struct(... - 'CT_id', [], ... - 'CTangle', [], ... - 'AI', [], ... - 'comment', [] ... - ); - -% define glenoid structure array -glenoid = struct(... - 'CT_id', [], ... - 'radius', [], ... - 'sphericity', [], ... - 'biconcave', [], ... - 'depth', [], ... - 'width', [], ... - 'height', [], ... - 'version_ampl', [], ... - 'version_orient', [], ... - 'center_PA', [], ... - 'center_IS', [], ... - 'center_ML', [], ... - 'version', [], ... - 'inclination', [], ... - 'version_2D', [], ... - 'walch_class', [], ... - 'comment', [] ... - ); - -% define glenoid_density structure array -glenoid_density = struct(... - 'CT_id',[], ... - 'CO' ,[], ... - 'SC' ,[], ... - 'ST' ,[], ... - 'T1' ,[], ... - 'T2' ,[], ... - 'T3' ,[], ... - 'RE' ,[], ... - 'comment', [] ... - ); - -% define humerus structure array -humerus = struct(... - 'CT_id' ,[], ... - 'joint_radius' ,[], ... - 'head_radius' ,[], ... - 'SHsublux_ampl' ,[], ... - 'SHsublux_orient',[], ... - 'GHsublux_ampl' ,[], ... - 'GHsublux_orient',[], ... - 'SHsublux_2D' ,[], ... - 'GHsublux_2D' ,[], ... - 'comment', [] ... - ); - -%define muscle structure array -muscle = struct(... - 'CT_id',[], ... - 'SSA' ,[],... - 'SSI' ,[],... - 'SSO' ,[],... - 'SSD' ,[],... - 'ISA' ,[],... - 'ISI' ,[],... - 'ISO' ,[],... - 'ISD' ,[],... - 'SCA' ,[],... - 'SCI' ,[],... - 'SCO' ,[],... - 'SCD' ,[],... - 'TMA' ,[],... - 'TMI' ,[],... - 'TMO' ,[],... - 'TMD' ,[], ... - 'comment', [] ... - ); - -% get column of variables -header = rawExcel(1,:); -sCase_id_col = find(strcmp([header],'sCase.id')); -IPP_col = find(strcmp([header],'anonymity.IPP')); -side_col = find(strcmp([header],'shoulder.side')); - -CT_date_col = find(strcmp([header],'CT.date')); -CT_kernel_col = find(strcmp([header],'CT.kernel')); -CT_pixel_size_col = find(strcmp([header],'CT.pixel_size')); -CT_slice_spacing_col = find(strcmp([header],'CT.slice_spacing')); -CT_slice_thickness_col = find(strcmp([header],'CT.slice_thickness')); -CT_tension_col = find(strcmp([header],'CT.tension')); -CT_current_col = find(strcmp([header],'CT.current')); -CT_manufacturer_col = find(strcmp([header],'CT.manufacturer')); -CT_model_col = find(strcmp([header],'CT.model')); -CT_institution_col = find(strcmp([header],'CT.institution')); -CTcom_col = find(strcmp([header],'CT.comment')); - -scapula_CTangle_col = find(strcmp([header],'scapula.CTangle')); -scapula_AI_col = find(strcmp([header],'scapula.AI')); -scapulacom_col = find(strcmp([header],'scapula.comment')); - -glenoid_radius_col = find(strcmp([header],'glenoid.radius')); -% glenoid_sphericity_col = find(strcmp([header],'glenoid.sphericity')); -% glenoid_biconcave_col = find(strcmp([header],'glenoid.biconcave')); -glenoid_depth_col = find(strcmp([header],'glenoid.depth')); -glenoid_width_col = find(strcmp([header],'glenoid.width')); -glenoid_height_col = find(strcmp([header],'glenoid.height')); -glenoid_version_ampl_col = find(strcmp([header],'glenoid.version_ampl')); -glenoid_version_orient_col = find(strcmp([header],'glenoid.version_orient')); -glenoid_center_PA_col = find(strcmp([header],'glenoid.center_PA')); -glenoid_center_IS_col = find(strcmp([header],'glenoid.center_IS')); -glenoid_center_ML_col = find(strcmp([header],'glenoid.center_ML')); -glenoid_version_col = find(strcmp([header],'glenoid.version')); -glenoid_inclination_col = find(strcmp([header],'glenoid.inclination')); -glenoid_version_2D_col = find(strcmp([header],'glenoid.version_2D')); -glenoid_walch_class_col = find(strcmp([header],'glenoid.walch_class')); -glenoidcom_col = find(strcmp([header],'glenoid.comment')); - -glenoid_density_CO_col = find(strcmp([header],'glenoid_density.CO')); -glenoid_density_SC_col = find(strcmp([header],'glenoid_density.SC')); -glenoid_density_ST_col = find(strcmp([header],'glenoid_density.ST')); -glenoid_density_T1_col = find(strcmp([header],'glenoid_density.T1')); -glenoid_density_T2_col = find(strcmp([header],'glenoid_density.T2')); -glenoid_density_T3_col = find(strcmp([header],'glenoid_density.T3')); -glenoid_density_RE_col = find(strcmp([header],'glenoid_density.RE')); -glenoid_densitycom_col = find(strcmp([header],'glenoid_density.comment')); - -humerus_joint_radius_col = find(strcmp([header],'humerus.joint_radius')); -humerus_head_radius_col = find(strcmp([header],'humerus.head_radius')); -humerus_SHsublux_ampl_col = find(strcmp([header],'humerus.SHsublux_ampl')); -humerus_SHsublux_orient_col = find(strcmp([header],'humerus.SHsublux_orient')); -humerus_GHsublux_ampl_col = find(strcmp([header],'humerus.GHsublux_ampl')); -humerus_GHsublux_orient_col = find(strcmp([header],'humerus.GHsublux_orient')); -humerus_SHsublux_2D_col = find(strcmp([header],'humerus.SHsublux_2D')); -humerus_GHsublux_2D_col = find(strcmp([header],'humerus.GHsublux_2D')); -humeruscom_col = find(strcmp([header],'humerus.comment')); - -muscle_SSA_col = find(strcmp([header],'muscle.SSA')); -muscle_SSI_col = find(strcmp([header],'muscle.SSI')); -muscle_SSO_col = find(strcmp([header],'muscle.SSO')); -muscle_SSD_col = find(strcmp([header],'muscle.SSD')); -muscle_ISA_col = find(strcmp([header],'muscle.ISA')); -muscle_ISI_col = find(strcmp([header],'muscle.ISI')); -muscle_ISO_col = find(strcmp([header],'muscle.ISO')); -muscle_ISD_col = find(strcmp([header],'muscle.ISD')); -muscle_SCA_col = find(strcmp([header],'muscle.SCA')); -muscle_SCI_col = find(strcmp([header],'muscle.SCI')); -muscle_SCO_col = find(strcmp([header],'muscle.SCO')); -muscle_SCD_col = find(strcmp([header],'muscle.SCD')); -muscle_TMA_col = find(strcmp([header],'muscle.TMA')); -muscle_TMI_col = find(strcmp([header],'muscle.TMI')); -muscle_TMO_col = find(strcmp([header],'muscle.TMO')); -muscle_TMD_col = find(strcmp([header],'muscle.TMD')); -musclecom_col = find(strcmp([header],'muscle.comment')); - -% initialize counters -[rowN, ~] = size(rawExcel); -CT_id = 0; -CT_idx = 0; -scapula_idx = 0; -glenoid_idx = 0; -glenoid_density_idx = 0; -humerus_idx = 0; -muscle_idx = 0; - -% loop over rows of Excel table -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - sCaseE = row{sCase_id_col}; - if ~isnan(sCaseE) % check that this sCase is not empty - IPP = row{IPP_col}; % IPP of the patient - patient_idx = find([patient.IPP] == IPP); - % get patient index of this sCase - % this patient should already exist in patient structure array and - % should be unique - if ~isempty(patient_idx) - patient_id = patient(patient_idx).patient_id; % get patient_id - - shoulder_idx = find([shoulder.patient_id] == patient_id); % get shoulder_id - % should be 1 or 2 (R/L), but not 0 (test it however) - shoulder_idxN = length(shoulder_idx); - side = row{side_col}; - - switch shoulder_idxN - case 0 - % this sCase patient is not in the shoulder array - % report an error (probably not side identified) - fprintf('\nsCase %s not in shoulder array', sCaseE); - addCT = 0; - case 1 - % this sCase patient has 1 match in the shoulder array - % so we have to check side - if side == shoulder(shoulder_idx).side - shoulder_id = shoulder(shoulder_idx).shoulder_id; - addCT = 1; - else - % not same side so report a problem, probably side not - % defined - fprintf('\nsCase %s in shoulder array but not same side', sCaseE); - end - case 2 - % both shoulder sides of this patient are in the - % shoulder array, so we have to check side - if side == shoulder(shoulder_idx(1)).side - shoulder_id = shoulder(shoulder_idx(1)).shoulder_id; - addCT = 1; - else - if side == shoulder(shoulder_idx(2)).side - shoulder_id = shoulder(shoulder_idx(2)).shoulder_id; - addCT = 1; - end - end - end - % check if CT kernel is defined - kernel = row{CT_kernel_col}; - if isnan(kernel) - addCT = 0; - end - - if addCT % add CT to the array - CT_id = CT_id + 1; - CT_idx = CT_id; - - CT_date = row{CT_date_col}; - CT_date = datetime(CT_date,'ConvertFrom','excel','InputFormat','dd.MM.yyyy'); - CT_date.Format = 'yyyy-MM-dd'; - - % get CT comment - CT_comment = row{CTcom_col}; - if isnan(CT_comment) - CT_comment = ''; - end - - CT(CT_idx).CT_id = CT_id; - CT(CT_idx).shoulder_id = shoulder_id; - CT(CT_idx).date = CT_date; - CT(CT_idx).kernel = row{CT_kernel_col}; - CT(CT_idx).pixel_size = row{CT_pixel_size_col}; - CT(CT_idx).slice_spacing = row{CT_slice_spacing_col}; - CT(CT_idx).slice_thickness = row{CT_slice_thickness_col}; - CT(CT_idx).tension = row{CT_tension_col}; -% CT(CT_idx).current = row{CT_current_col}; - CT(CT_idx).manufacturer = row{CT_manufacturer_col}; - CT(CT_idx).model = row{CT_model_col}; - CT(CT_idx).institution = row{CT_institution_col}; - CT(CT_idx).folder_name = sCaseE; - CT(CT_idx).comment = CT_comment; - - % check for adding in scapula structure array - CTangle = row{scapula_CTangle_col}; - AI = row{scapula_AI_col}; - % get scapula comment - scapula_comment = row{scapulacom_col}; - if isnan(scapula_comment) - scapula_comment = ''; - end - - if ~isnan(CTangle) - scapula_idx = scapula_idx + 1; - - scapula(scapula_idx).CT_id = CT_id; - scapula(scapula_idx).CTangle = CTangle; - scapula(scapula_idx).AI = AI; - scapula(scapula_idx).comment = scapula_comment; - end - - % check for adding in glenoid structure array - radius = row{glenoid_radius_col}; - if ~isnan(radius) - % Assumes that all glenoid data are present & valid is - % radius is defined - glenoid_idx = glenoid_idx + 1; - - % sphericity = row{glenoid_sphericity_col}; - % biconcave = row{glenoid_biconcave_col}; - depth = row{glenoid_depth_col}; - width = row{glenoid_width_col}; - height = row{glenoid_height_col}; - version_ampl = row{glenoid_version_ampl_col}; - version_orient = row{glenoid_version_orient_col}; - center_PA = row{glenoid_center_PA_col}; - center_IS = row{glenoid_center_IS_col}; - center_ML = row{glenoid_center_ML_col}; - version = row{glenoid_version_col}; - inclination = row{glenoid_inclination_col}; - version_2D = row{glenoid_version_2D_col}; - walch_class = row{glenoid_walch_class_col}; - glenoid_comment = row{glenoidcom_col}; - if isnan(glenoid_comment) - glenoid_comment = ''; - end - - glenoid(glenoid_idx).CT_id = CT_id; - glenoid(glenoid_idx).radius = radius; - % glenoid(glenoid_idx).sphericity = sphericity; - % glenoid(glenoid_idx).biconcave = biconcave; - glenoid(glenoid_idx).depth = depth; - glenoid(glenoid_idx).width = width; - glenoid(glenoid_idx).height = height; - glenoid(glenoid_idx).version_ampl = version_ampl; - glenoid(glenoid_idx).version_orient = version_orient; - glenoid(glenoid_idx).center_PA = center_PA; - glenoid(glenoid_idx).center_IS = center_IS; - glenoid(glenoid_idx).center_ML = center_ML; - glenoid(glenoid_idx).version = version; - glenoid(glenoid_idx).inclination = inclination; - glenoid(glenoid_idx).version_2D = version_2D; - glenoid(glenoid_idx).walch_class = walch_class; - glenoid(glenoid_idx).comment = glenoid_comment; - - end - - % check for adding in glenoid_density structure array - CO = row{glenoid_density_CO_col}; - if ~isnan(CO) - % Assumes that all glenoid_density data are present & valid is - % CO is defined. RE can be empty - glenoid_density_idx = glenoid_density_idx + 1; - - SC = row{glenoid_density_SC_col}; - ST = row{glenoid_density_ST_col}; - T1 = row{glenoid_density_T1_col}; - T2 = row{glenoid_density_T2_col}; - T3 = row{glenoid_density_T3_col}; - RE = row{glenoid_density_RE_col}; - % get glenoid_density comment - glenoid_density_comment = row{glenoid_densitycom_col}; - if isnan(glenoid_density_comment) - glenoid_density_comment = ''; - end - - glenoid_density(glenoid_density_idx).CT_id = CT_id; - glenoid_density(glenoid_density_idx).CO = CO; - glenoid_density(glenoid_density_idx).SC = SC; - glenoid_density(glenoid_density_idx).ST = ST; - glenoid_density(glenoid_density_idx).T1 = T1; - glenoid_density(glenoid_density_idx).T2 = T2; - glenoid_density(glenoid_density_idx).T3 = T3; - glenoid_density(glenoid_density_idx).RE = RE; - glenoid_density(glenoid_density_idx).comment = glenoid_density_comment; - - end - - % check for adding in humerus structure array - head_radius = row{humerus_head_radius_col}; - if ~isnan(head_radius) - % Assumes that all humerus data are present & valid is - % head_radius is defined. - humerus_idx = humerus_idx + 1; - - joint_radius = row{humerus_joint_radius_col}; - SHsublux_ampl = row{humerus_SHsublux_ampl_col}; - SHsublux_orient = row{humerus_SHsublux_orient_col}; - GHsublux_ampl = row{humerus_GHsublux_ampl_col}; - GHsublux_orient = row{humerus_GHsublux_orient_col}; - SHsublux_2D = row{humerus_SHsublux_2D_col}; - GHsublux_2D = row{humerus_GHsublux_2D_col}; - % get humerus comment - humerus_comment = row{humeruscom_col}; - if isnan(humerus_comment) - humerus_comment = ''; - end - - humerus(humerus_idx).CT_id = CT_id; - humerus(humerus_idx).head_radius = head_radius; - humerus(humerus_idx).joint_radius = joint_radius; - humerus(humerus_idx).SHsublux_ampl = SHsublux_ampl; - humerus(humerus_idx).SHsublux_orient = SHsublux_orient; - humerus(humerus_idx).GHsublux_ampl = GHsublux_ampl; - humerus(humerus_idx).GHsublux_orient = GHsublux_orient; - humerus(humerus_idx).SHsublux_2D = SHsublux_2D; - humerus(humerus_idx).GHsublux_2D = GHsublux_2D; - humerus(humerus_idx).comment = humerus_comment; - end - - % check for adding in muscle structure array - SSA = row{muscle_SSA_col}; - if ~isnan(SSA) - % Assumes that all muscle data are present & valid is - % SSA is defined. - muscle_idx = muscle_idx + 1; - - SSA = row{muscle_SSA_col}; - SSI = row{muscle_SSI_col}; - SSO = row{muscle_SSO_col}; - SSD = row{muscle_SSD_col}; - ISA = row{muscle_ISA_col}; - ISI = row{muscle_ISI_col}; - ISO = row{muscle_ISO_col}; - ISD = row{muscle_ISD_col}; - SCA = row{muscle_SCA_col}; - SCI = row{muscle_SCI_col}; - SCO = row{muscle_SCO_col}; - SCD = row{muscle_SCD_col}; - TMA = row{muscle_ISA_col}; - TMI = row{muscle_ISI_col}; - TMO = row{muscle_ISO_col}; - TMD = row{muscle_ISD_col}; - % get muscle comment - muscle_comment = row{musclecom_col}; - if isnan(muscle_comment) - muscle_comment = ''; - end - - muscle(muscle_idx).CT_id = CT_id; - muscle(muscle_idx).SSA = SSA; - muscle(muscle_idx).SSI = SSI; - muscle(muscle_idx).SSO = SSO; - muscle(muscle_idx).SSD = SSD; - muscle(muscle_idx).ISA = ISA; - muscle(muscle_idx).ISI = ISI; - muscle(muscle_idx).ISO = ISO; - muscle(muscle_idx).ISD = ISD; - muscle(muscle_idx).SCA = SCA; - muscle(muscle_idx).SCI = SCI; - muscle(muscle_idx).SCO = SCO; - muscle(muscle_idx).SCD = SCD; - muscle(muscle_idx).TMA = TMA; - muscle(muscle_idx).TMI = TMI; - muscle(muscle_idx).TMO = TMO; - muscle(muscle_idx).TMD = TMD; - muscle(muscle_idx).comment = muscle_comment; - - end - end - end - end -end - -fprintf(' Done\n'); - -end - +function [CT, scapula, glenoid, glenoid_density, humerus, muscle] = CTfromExcel(shoulder, patient, rawExcel) +% ctFromExcel build the ct structure array from the excelRaw, patient and +% shoulder structure array. +% ct contains all data directly related to the imaging + +fprintf('\nGet CT & glenoid from Excel...'); + +% define CT structure array +CT = struct(... + 'CT_id', [], ... + 'shoulder_id', [], ... + 'date', [], ... + 'kernel', [], ... + 'pixel_size', [], ... + 'slice_spacing', [], ... + 'slice_thickness', [], ... + 'tension', [], ... + 'current', [], ... + 'manufacturer', [], ... + 'model', [], ... + 'institution', [], ... + 'folder_name', [], ... + 'comment', [] ... + ); + +% define scapula array +scapula = struct(... + 'CT_id', [], ... + 'CTangle', [], ... + 'AI', [], ... + 'comment', [] ... + ); + +% define glenoid structure array +glenoid = struct(... + 'CT_id', [], ... + 'radius', [], ... + 'sphericity', [], ... + 'biconcave', [], ... + 'depth', [], ... + 'width', [], ... + 'height', [], ... + 'version_ampl', [], ... + 'version_orient', [], ... + 'center_PA', [], ... + 'center_IS', [], ... + 'center_ML', [], ... + 'version', [], ... + 'inclination', [], ... + 'version_2D', [], ... + 'walch_class', [], ... + 'comment', [] ... + ); + +% define glenoid_density structure array +glenoid_density = struct(... + 'CT_id',[], ... + 'CO' ,[], ... + 'SC' ,[], ... + 'ST' ,[], ... + 'T1' ,[], ... + 'T2' ,[], ... + 'T3' ,[], ... + 'RE' ,[], ... + 'comment', [] ... + ); + +% define humerus structure array +humerus = struct(... + 'CT_id' ,[], ... + 'joint_radius' ,[], ... + 'head_radius' ,[], ... + 'SHsublux_ampl' ,[], ... + 'SHsublux_orient',[], ... + 'GHsublux_ampl' ,[], ... + 'GHsublux_orient',[], ... + 'SHsublux_2D' ,[], ... + 'GHsublux_2D' ,[], ... + 'comment', [] ... + ); + +%define muscle structure array +muscle = struct(... + 'CT_id',[], ... + 'SSA' ,[],... + 'SSI' ,[],... + 'SSO' ,[],... + 'SSD' ,[],... + 'ISA' ,[],... + 'ISI' ,[],... + 'ISO' ,[],... + 'ISD' ,[],... + 'SCA' ,[],... + 'SCI' ,[],... + 'SCO' ,[],... + 'SCD' ,[],... + 'TMA' ,[],... + 'TMI' ,[],... + 'TMO' ,[],... + 'TMD' ,[], ... + 'comment', [] ... + ); + +% get column of variables +header = rawExcel(1,:); +sCase_id_col = find(strcmp([header],'sCase.id')); +IPP_col = find(strcmp([header],'anonymity.IPP')); +side_col = find(strcmp([header],'shoulder.side')); + +CT_date_col = find(strcmp([header],'CT.date')); +CT_kernel_col = find(strcmp([header],'CT.kernel')); +CT_pixel_size_col = find(strcmp([header],'CT.pixel_size')); +CT_slice_spacing_col = find(strcmp([header],'CT.slice_spacing')); +CT_slice_thickness_col = find(strcmp([header],'CT.slice_thickness')); +CT_tension_col = find(strcmp([header],'CT.tension')); +CT_current_col = find(strcmp([header],'CT.current')); +CT_manufacturer_col = find(strcmp([header],'CT.manufacturer')); +CT_model_col = find(strcmp([header],'CT.model')); +CT_institution_col = find(strcmp([header],'CT.institution')); +CTcom_col = find(strcmp([header],'CT.comment')); + +scapula_CTangle_col = find(strcmp([header],'scapula.CTangle')); +scapula_AI_col = find(strcmp([header],'scapula.AI')); +scapulacom_col = find(strcmp([header],'scapula.comment')); + +glenoid_radius_col = find(strcmp([header],'glenoid.radius')); +% glenoid_sphericity_col = find(strcmp([header],'glenoid.sphericity')); +% glenoid_biconcave_col = find(strcmp([header],'glenoid.biconcave')); +glenoid_depth_col = find(strcmp([header],'glenoid.depth')); +glenoid_width_col = find(strcmp([header],'glenoid.width')); +glenoid_height_col = find(strcmp([header],'glenoid.height')); +glenoid_version_ampl_col = find(strcmp([header],'glenoid.version_ampl')); +glenoid_version_orient_col = find(strcmp([header],'glenoid.version_orient')); +glenoid_center_PA_col = find(strcmp([header],'glenoid.center_PA')); +glenoid_center_IS_col = find(strcmp([header],'glenoid.center_IS')); +glenoid_center_ML_col = find(strcmp([header],'glenoid.center_ML')); +glenoid_version_col = find(strcmp([header],'glenoid.version')); +glenoid_inclination_col = find(strcmp([header],'glenoid.inclination')); +glenoid_version_2D_col = find(strcmp([header],'glenoid.version_2D')); +glenoid_walch_class_col = find(strcmp([header],'glenoid.walch_class')); +glenoidcom_col = find(strcmp([header],'glenoid.comment')); + +glenoid_density_CO_col = find(strcmp([header],'glenoid_density.CO')); +glenoid_density_SC_col = find(strcmp([header],'glenoid_density.SC')); +glenoid_density_ST_col = find(strcmp([header],'glenoid_density.ST')); +glenoid_density_T1_col = find(strcmp([header],'glenoid_density.T1')); +glenoid_density_T2_col = find(strcmp([header],'glenoid_density.T2')); +glenoid_density_T3_col = find(strcmp([header],'glenoid_density.T3')); +glenoid_density_RE_col = find(strcmp([header],'glenoid_density.RE')); +glenoid_densitycom_col = find(strcmp([header],'glenoid_density.comment')); + +humerus_joint_radius_col = find(strcmp([header],'humerus.joint_radius')); +humerus_head_radius_col = find(strcmp([header],'humerus.head_radius')); +humerus_SHsublux_ampl_col = find(strcmp([header],'humerus.SHsublux_ampl')); +humerus_SHsublux_orient_col = find(strcmp([header],'humerus.SHsublux_orient')); +humerus_GHsublux_ampl_col = find(strcmp([header],'humerus.GHsublux_ampl')); +humerus_GHsublux_orient_col = find(strcmp([header],'humerus.GHsublux_orient')); +humerus_SHsublux_2D_col = find(strcmp([header],'humerus.SHsublux_2D')); +humerus_GHsublux_2D_col = find(strcmp([header],'humerus.GHsublux_2D')); +humeruscom_col = find(strcmp([header],'humerus.comment')); + +muscle_SSA_col = find(strcmp([header],'muscle.SSA')); +muscle_SSI_col = find(strcmp([header],'muscle.SSI')); +muscle_SSO_col = find(strcmp([header],'muscle.SSO')); +muscle_SSD_col = find(strcmp([header],'muscle.SSD')); +muscle_ISA_col = find(strcmp([header],'muscle.ISA')); +muscle_ISI_col = find(strcmp([header],'muscle.ISI')); +muscle_ISO_col = find(strcmp([header],'muscle.ISO')); +muscle_ISD_col = find(strcmp([header],'muscle.ISD')); +muscle_SCA_col = find(strcmp([header],'muscle.SCA')); +muscle_SCI_col = find(strcmp([header],'muscle.SCI')); +muscle_SCO_col = find(strcmp([header],'muscle.SCO')); +muscle_SCD_col = find(strcmp([header],'muscle.SCD')); +muscle_TMA_col = find(strcmp([header],'muscle.TMA')); +muscle_TMI_col = find(strcmp([header],'muscle.TMI')); +muscle_TMO_col = find(strcmp([header],'muscle.TMO')); +muscle_TMD_col = find(strcmp([header],'muscle.TMD')); +musclecom_col = find(strcmp([header],'muscle.comment')); + +% initialize counters +[rowN, ~] = size(rawExcel); +CT_id = 0; +CT_idx = 0; +scapula_idx = 0; +glenoid_idx = 0; +glenoid_density_idx = 0; +humerus_idx = 0; +muscle_idx = 0; + +% loop over rows of Excel table +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + sCaseE = row{sCase_id_col}; + if ~isnan(sCaseE) % check that this sCase is not empty + IPP = row{IPP_col}; % IPP of the patient + patient_idx = find([patient.IPP] == IPP); + % get patient index of this sCase + % this patient should already exist in patient structure array and + % should be unique + if ~isempty(patient_idx) + patient_id = patient(patient_idx).patient_id; % get patient_id + + shoulder_idx = find([shoulder.patient_id] == patient_id); % get shoulder_id + % should be 1 or 2 (R/L), but not 0 (test it however) + shoulder_idxN = length(shoulder_idx); + side = row{side_col}; + + switch shoulder_idxN + case 0 + % this sCase patient is not in the shoulder array + % report an error (probably not side identified) + fprintf('\nsCase %s not in shoulder array', sCaseE); + addCT = 0; + case 1 + % this sCase patient has 1 match in the shoulder array + % so we have to check side + if side == shoulder(shoulder_idx).side + shoulder_id = shoulder(shoulder_idx).shoulder_id; + addCT = 1; + else + % not same side so report a problem, probably side not + % defined + fprintf('\nsCase %s in shoulder array but not same side', sCaseE); + end + case 2 + % both shoulder sides of this patient are in the + % shoulder array, so we have to check side + if side == shoulder(shoulder_idx(1)).side + shoulder_id = shoulder(shoulder_idx(1)).shoulder_id; + addCT = 1; + else + if side == shoulder(shoulder_idx(2)).side + shoulder_id = shoulder(shoulder_idx(2)).shoulder_id; + addCT = 1; + end + end + end + % check if CT kernel is defined + kernel = row{CT_kernel_col}; + if isnan(kernel) + addCT = 0; + end + + if addCT % add CT to the array + CT_id = CT_id + 1; + CT_idx = CT_id; + + CT_date = row{CT_date_col}; + CT_date = datetime(CT_date,'ConvertFrom','excel','InputFormat','dd.MM.yyyy'); + CT_date.Format = 'yyyy-MM-dd'; + + % get CT comment + CT_comment = row{CTcom_col}; + if isnan(CT_comment) + CT_comment = ''; + end + + CT(CT_idx).CT_id = CT_id; + CT(CT_idx).shoulder_id = shoulder_id; + CT(CT_idx).date = CT_date; + CT(CT_idx).kernel = row{CT_kernel_col}; + CT(CT_idx).pixel_size = row{CT_pixel_size_col}; + CT(CT_idx).slice_spacing = row{CT_slice_spacing_col}; + CT(CT_idx).slice_thickness = row{CT_slice_thickness_col}; + CT(CT_idx).tension = row{CT_tension_col}; +% CT(CT_idx).current = row{CT_current_col}; + CT(CT_idx).manufacturer = row{CT_manufacturer_col}; + CT(CT_idx).model = row{CT_model_col}; + CT(CT_idx).institution = row{CT_institution_col}; + CT(CT_idx).folder_name = sCaseE; + CT(CT_idx).comment = CT_comment; + + % check for adding in scapula structure array + CTangle = row{scapula_CTangle_col}; + AI = row{scapula_AI_col}; + % get scapula comment + scapula_comment = row{scapulacom_col}; + if isnan(scapula_comment) + scapula_comment = ''; + end + + if ~isnan(CTangle) + scapula_idx = scapula_idx + 1; + + scapula(scapula_idx).CT_id = CT_id; + scapula(scapula_idx).CTangle = CTangle; + scapula(scapula_idx).AI = AI; + scapula(scapula_idx).comment = scapula_comment; + end + + % check for adding in glenoid structure array + radius = row{glenoid_radius_col}; + if ~isnan(radius) + % Assumes that all glenoid data are present & valid is + % radius is defined + glenoid_idx = glenoid_idx + 1; + + % sphericity = row{glenoid_sphericity_col}; + % biconcave = row{glenoid_biconcave_col}; + depth = row{glenoid_depth_col}; + width = row{glenoid_width_col}; + height = row{glenoid_height_col}; + version_ampl = row{glenoid_version_ampl_col}; + version_orient = row{glenoid_version_orient_col}; + center_PA = row{glenoid_center_PA_col}; + center_IS = row{glenoid_center_IS_col}; + center_ML = row{glenoid_center_ML_col}; + version = row{glenoid_version_col}; + inclination = row{glenoid_inclination_col}; + version_2D = row{glenoid_version_2D_col}; + walch_class = row{glenoid_walch_class_col}; + glenoid_comment = row{glenoidcom_col}; + if isnan(glenoid_comment) + glenoid_comment = ''; + end + + glenoid(glenoid_idx).CT_id = CT_id; + glenoid(glenoid_idx).radius = radius; + % glenoid(glenoid_idx).sphericity = sphericity; + % glenoid(glenoid_idx).biconcave = biconcave; + glenoid(glenoid_idx).depth = depth; + glenoid(glenoid_idx).width = width; + glenoid(glenoid_idx).height = height; + glenoid(glenoid_idx).version_ampl = version_ampl; + glenoid(glenoid_idx).version_orient = version_orient; + glenoid(glenoid_idx).center_PA = center_PA; + glenoid(glenoid_idx).center_IS = center_IS; + glenoid(glenoid_idx).center_ML = center_ML; + glenoid(glenoid_idx).version = version; + glenoid(glenoid_idx).inclination = inclination; + glenoid(glenoid_idx).version_2D = version_2D; + glenoid(glenoid_idx).walch_class = walch_class; + glenoid(glenoid_idx).comment = glenoid_comment; + + end + + % check for adding in glenoid_density structure array + CO = row{glenoid_density_CO_col}; + if ~isnan(CO) + % Assumes that all glenoid_density data are present & valid is + % CO is defined. RE can be empty + glenoid_density_idx = glenoid_density_idx + 1; + + SC = row{glenoid_density_SC_col}; + ST = row{glenoid_density_ST_col}; + T1 = row{glenoid_density_T1_col}; + T2 = row{glenoid_density_T2_col}; + T3 = row{glenoid_density_T3_col}; + RE = row{glenoid_density_RE_col}; + % get glenoid_density comment + glenoid_density_comment = row{glenoid_densitycom_col}; + if isnan(glenoid_density_comment) + glenoid_density_comment = ''; + end + + glenoid_density(glenoid_density_idx).CT_id = CT_id; + glenoid_density(glenoid_density_idx).CO = CO; + glenoid_density(glenoid_density_idx).SC = SC; + glenoid_density(glenoid_density_idx).ST = ST; + glenoid_density(glenoid_density_idx).T1 = T1; + glenoid_density(glenoid_density_idx).T2 = T2; + glenoid_density(glenoid_density_idx).T3 = T3; + glenoid_density(glenoid_density_idx).RE = RE; + glenoid_density(glenoid_density_idx).comment = glenoid_density_comment; + + end + + % check for adding in humerus structure array + head_radius = row{humerus_head_radius_col}; + if ~isnan(head_radius) + % Assumes that all humerus data are present & valid is + % head_radius is defined. + humerus_idx = humerus_idx + 1; + + joint_radius = row{humerus_joint_radius_col}; + SHsublux_ampl = row{humerus_SHsublux_ampl_col}; + SHsublux_orient = row{humerus_SHsublux_orient_col}; + GHsublux_ampl = row{humerus_GHsublux_ampl_col}; + GHsublux_orient = row{humerus_GHsublux_orient_col}; + SHsublux_2D = row{humerus_SHsublux_2D_col}; + GHsublux_2D = row{humerus_GHsublux_2D_col}; + % get humerus comment + humerus_comment = row{humeruscom_col}; + if isnan(humerus_comment) + humerus_comment = ''; + end + + humerus(humerus_idx).CT_id = CT_id; + humerus(humerus_idx).head_radius = head_radius; + humerus(humerus_idx).joint_radius = joint_radius; + humerus(humerus_idx).SHsublux_ampl = SHsublux_ampl; + humerus(humerus_idx).SHsublux_orient = SHsublux_orient; + humerus(humerus_idx).GHsublux_ampl = GHsublux_ampl; + humerus(humerus_idx).GHsublux_orient = GHsublux_orient; + humerus(humerus_idx).SHsublux_2D = SHsublux_2D; + humerus(humerus_idx).GHsublux_2D = GHsublux_2D; + humerus(humerus_idx).comment = humerus_comment; + end + + % check for adding in muscle structure array + SSA = row{muscle_SSA_col}; + if ~isnan(SSA) + % Assumes that all muscle data are present & valid is + % SSA is defined. + muscle_idx = muscle_idx + 1; + + SSA = row{muscle_SSA_col}; + SSI = row{muscle_SSI_col}; + SSO = row{muscle_SSO_col}; + SSD = row{muscle_SSD_col}; + ISA = row{muscle_ISA_col}; + ISI = row{muscle_ISI_col}; + ISO = row{muscle_ISO_col}; + ISD = row{muscle_ISD_col}; + SCA = row{muscle_SCA_col}; + SCI = row{muscle_SCI_col}; + SCO = row{muscle_SCO_col}; + SCD = row{muscle_SCD_col}; + TMA = row{muscle_ISA_col}; + TMI = row{muscle_ISI_col}; + TMO = row{muscle_ISO_col}; + TMD = row{muscle_ISD_col}; + % get muscle comment + muscle_comment = row{musclecom_col}; + if isnan(muscle_comment) + muscle_comment = ''; + end + + muscle(muscle_idx).CT_id = CT_id; + muscle(muscle_idx).SSA = SSA; + muscle(muscle_idx).SSI = SSI; + muscle(muscle_idx).SSO = SSO; + muscle(muscle_idx).SSD = SSD; + muscle(muscle_idx).ISA = ISA; + muscle(muscle_idx).ISI = ISI; + muscle(muscle_idx).ISO = ISO; + muscle(muscle_idx).ISD = ISD; + muscle(muscle_idx).SCA = SCA; + muscle(muscle_idx).SCI = SCI; + muscle(muscle_idx).SCO = SCO; + muscle(muscle_idx).SCD = SCD; + muscle(muscle_idx).TMA = TMA; + muscle(muscle_idx).TMI = TMI; + muscle(muscle_idx).TMO = TMO; + muscle(muscle_idx).TMD = TMD; + muscle(muscle_idx).comment = muscle_comment; + + end + end + end + end +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/CTtoSQL.m b/XLS_MySQL_DB/CTtoSQL.m index bb29943..b17778c 100644 --- a/XLS_MySQL_DB/CTtoSQL.m +++ b/XLS_MySQL_DB/CTtoSQL.m @@ -1,53 +1,53 @@ -function CTtoSQL(CT, conn) -%CTTOSQL fill ct structure array in mySQL -% Detailed explanation goes here - -fprintf('\nCT to mySQL... '); - -testquery= 'SET FOREIGN_KEY_CHECKS = 0'; -exec(conn,testquery); - -% Empty CT table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE CT'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -testquery= 'SET FOREIGN_KEY_CHECKS = 1'; -exec(conn,testquery); - -colnames = {'CT_id','shoulder_id','date','kernel','pixel_size',... - 'slice_spacing','slice_thickness','tension','current',... - 'manufacturer','model','institution','folder_name','comment'}; - -% Loop on ct structure array -for i=1:numel(CT) - CT_id = CT(i).CT_id; - shoulder_id = CT(i).shoulder_id; - date = datestr(CT(i).date,'yyyy-mm-dd'); - kernel = CT(i).kernel; - pixel_size = CT(i).pixel_size; - slice_spacing = CT(i).slice_spacing; - slice_thickness = CT(i).slice_thickness; - tension = CT(i).tension; - current = 0; - manufacturer = CT(i).manufacturer; - model = CT(i).model; - institution = CT(i).institution; - if isempty (institution) - institution = ' '; - end - folder_name = CT(i).folder_name; - comment = CT(i).comment; - exdata = {CT_id,shoulder_id,date,kernel,pixel_size,... - slice_spacing,slice_thickness,tension,current,... - manufacturer,model,institution,folder_name,comment}; - % insert data in SQL - datainsert(conn,'CT',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function CTtoSQL(CT, conn) +%CTTOSQL fill ct structure array in mySQL +% Detailed explanation goes here + +fprintf('\nCT to mySQL... '); + +testquery= 'SET FOREIGN_KEY_CHECKS = 0'; +exec(conn,testquery); + +% Empty CT table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE CT'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +testquery= 'SET FOREIGN_KEY_CHECKS = 1'; +exec(conn,testquery); + +colnames = {'CT_id','shoulder_id','date','kernel','pixel_size',... + 'slice_spacing','slice_thickness','tension','current',... + 'manufacturer','model','institution','folder_name','comment'}; + +% Loop on ct structure array +for i=1:numel(CT) + CT_id = CT(i).CT_id; + shoulder_id = CT(i).shoulder_id; + date = datestr(CT(i).date,'yyyy-mm-dd'); + kernel = CT(i).kernel; + pixel_size = CT(i).pixel_size; + slice_spacing = CT(i).slice_spacing; + slice_thickness = CT(i).slice_thickness; + tension = CT(i).tension; + current = 0; + manufacturer = CT(i).manufacturer; + model = CT(i).model; + institution = CT(i).institution; + if isempty (institution) + institution = ' '; + end + folder_name = CT(i).folder_name; + comment = CT(i).comment; + exdata = {CT_id,shoulder_id,date,kernel,pixel_size,... + slice_spacing,slice_thickness,tension,current,... + manufacturer,model,institution,folder_name,comment}; + % insert data in SQL + datainsert(conn,'CT',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/Copy_of_sCaseToSQL.m b/XLS_MySQL_DB/Copy_of_sCaseToSQL.m index 6e16b00..d799040 100644 --- a/XLS_MySQL_DB/Copy_of_sCaseToSQL.m +++ b/XLS_MySQL_DB/Copy_of_sCaseToSQL.m @@ -1,38 +1,38 @@ -function sCaseToSQL(sCase, conn) -%PCASETOSQL fill sCase structure array in mySQL -% Detailed explanation goes here - -fprintf('\nsCase to mySQL... '); - -% Empty sCase table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE sCase'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'sCase_id','shoulder_id','folder_name'}; - -% Loop on pCase structure array -for i=1:numel(sCase) - sCase_id = sCase(i).sCase_id; - shoulder_id = sCase(i).shoulder_id; - pathology_id = sCase(i).pathology_id; - treatment_id = pCase(i).treatment_id; - operation_date = pCase(i).operation_date; - if ~isnat(operation_date) - operation_date = datestr(pCase(i).operation_date,'yyyy-mm-dd'); - else - operation_date = ''; - end - folder_name = pCase(i).folder_name; - - exdata = {pCase_id,shoulder_id,pathology_id,treatment_id,operation_date,folder_name}; - % insert data in SQL - datainsert(conn,'pCase',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function sCaseToSQL(sCase, conn) +%PCASETOSQL fill sCase structure array in mySQL +% Detailed explanation goes here + +fprintf('\nsCase to mySQL... '); + +% Empty sCase table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE sCase'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'sCase_id','shoulder_id','folder_name'}; + +% Loop on pCase structure array +for i=1:numel(sCase) + sCase_id = sCase(i).sCase_id; + shoulder_id = sCase(i).shoulder_id; + pathology_id = sCase(i).pathology_id; + treatment_id = pCase(i).treatment_id; + operation_date = pCase(i).operation_date; + if ~isnat(operation_date) + operation_date = datestr(pCase(i).operation_date,'yyyy-mm-dd'); + else + operation_date = ''; + end + folder_name = pCase(i).folder_name; + + exdata = {pCase_id,shoulder_id,pathology_id,treatment_id,operation_date,folder_name}; + % insert data in SQL + datainsert(conn,'pCase',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/MainExcel2SQL.m b/XLS_MySQL_DB/MainExcel2SQL.m index 4a29ea3..3d3af22 100644 --- a/XLS_MySQL_DB/MainExcel2SQL.m +++ b/XLS_MySQL_DB/MainExcel2SQL.m @@ -1,134 +1,134 @@ -%% MainExcel2SQL -% -% This script reads the shoulder database Excel file and -% write in the mySQL database. The existing tables are overwritten and -% auto-counter are reset. - -% Author: Alexandre Terrier, EPFL-LBO -% Date: 2016-09-29 -% - -%% -% Initialization - -clearvars; % Clear all matlab variables - -%% -%************************************************************************** -% -% Read data from Excel -% - -excelRaw = rawFromExcel(); - -%% -% Read from Excel: pathology array - -diagnosisList = diagnosisListFromExcel(excelRaw); - -%% -% Read from Excel: treatement array - -treatmentList = treatmentListFromExcel(excelRaw); - -%% -% Read from Excel: patient structure array - -patient = patientFromExcel(excelRaw); - -%% -% Read from Excel: shoulder structure array - -shoulder = shoulderFromExcel(patient, excelRaw); - -%% -% Read from Excel: sCase struture array, and associated - -[sCase, diagnosis, treatment, outcome, study] = sCaseFromExcel(... - shoulder, patient, diagnosisList, treatmentList, excelRaw); - -%% -% Read from Excel: ct structure array, nd associated - -[CT, scapula, glenoid, glenoid_density, humerus, muscle] = CTfromExcel(... - shoulder, patient, excelRaw); - -%% -%************************************************************************** -% -% Write data to mySQL database - -%% -% open mySQL connection - -conn = openSQL(); - -%% -% Initialize tables of mySQL database - -initializeSQL(conn); - -%% -% Write in mySQL: patient table - -patientToSQL(patient, conn); - -%% -% Write in mySQL: anonymity table - -anonymityToSQL(patient, conn); - -%% -% Write in mySQL: shoulder table - -shoulderToSQL(shoulder, conn); - -%% -% Write in mySQL: sCase - -sCaseToSQL(sCase, conn); - -%% -% Write in mySQL: CT - -CTtoSQL(CT, conn); - -%% -% Write in mySQL: glenoid - -scapulaToSQL(scapula, conn); - -%% -% Write in mySQL: glenoid - -glenoidToSQL(glenoid, conn); - -%% -% Write in mySQL: diagnosisList - -diagnosisListToSQL(diagnosisList, conn); - -%% -% Write in mySQL: diagnosis - -diagnosisToSQL(diagnosis, conn); - -%% -% Write in mySQL: treatmentList - -treatmentListToSQL(treatmentList, conn); - -%% -% Write in mySQL: treatment - -treatmentToSQL(treatment, conn); - -%% -% Write in mySQL: outcome - -outcomeToSQL(outcome, conn); - -%% -% Close mySQL database - -closeSQL(conn); +%% MainExcel2SQL +% +% This script reads the shoulder database Excel file and +% write in the mySQL database. The existing tables are overwritten and +% auto-counter are reset. + +% Author: Alexandre Terrier, EPFL-LBO +% Date: 2016-09-29 +% + +%% +% Initialization + +clearvars; % Clear all matlab variables + +%% +%************************************************************************** +% +% Read data from Excel +% + +excelRaw = rawFromExcel(); + +%% +% Read from Excel: pathology array + +diagnosisList = diagnosisListFromExcel(excelRaw); + +%% +% Read from Excel: treatement array + +treatmentList = treatmentListFromExcel(excelRaw); + +%% +% Read from Excel: patient structure array + +patient = patientFromExcel(excelRaw); + +%% +% Read from Excel: shoulder structure array + +shoulder = shoulderFromExcel(patient, excelRaw); + +%% +% Read from Excel: sCase struture array, and associated + +[sCase, diagnosis, treatment, outcome, study] = sCaseFromExcel(... + shoulder, patient, diagnosisList, treatmentList, excelRaw); + +%% +% Read from Excel: ct structure array, nd associated + +[CT, scapula, glenoid, glenoid_density, humerus, muscle] = CTfromExcel(... + shoulder, patient, excelRaw); + +%% +%************************************************************************** +% +% Write data to mySQL database + +%% +% open mySQL connection + +conn = openSQL(); + +%% +% Initialize tables of mySQL database + +initializeSQL(conn); + +%% +% Write in mySQL: patient table + +patientToSQL(patient, conn); + +%% +% Write in mySQL: anonymity table + +anonymityToSQL(patient, conn); + +%% +% Write in mySQL: shoulder table + +shoulderToSQL(shoulder, conn); + +%% +% Write in mySQL: sCase + +sCaseToSQL(sCase, conn); + +%% +% Write in mySQL: CT + +CTtoSQL(CT, conn); + +%% +% Write in mySQL: glenoid + +scapulaToSQL(scapula, conn); + +%% +% Write in mySQL: glenoid + +glenoidToSQL(glenoid, conn); + +%% +% Write in mySQL: diagnosisList + +diagnosisListToSQL(diagnosisList, conn); + +%% +% Write in mySQL: diagnosis + +diagnosisToSQL(diagnosis, conn); + +%% +% Write in mySQL: treatmentList + +treatmentListToSQL(treatmentList, conn); + +%% +% Write in mySQL: treatment + +treatmentToSQL(treatment, conn); + +%% +% Write in mySQL: outcome + +outcomeToSQL(outcome, conn); + +%% +% Close mySQL database + +closeSQL(conn); diff --git a/XLS_MySQL_DB/anonymityToSQL.m b/XLS_MySQL_DB/anonymityToSQL.m index d50820c..916bea6 100644 --- a/XLS_MySQL_DB/anonymityToSQL.m +++ b/XLS_MySQL_DB/anonymityToSQL.m @@ -1,30 +1,30 @@ -function anonymityToSQL(patient, conn) -%PATIENDBUPDATE fill patient structure array in mySQL -% Detailed explanation goes here - -fprintf('\nanonymity to mySQL... '); - -% Empty anonymity table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE anonymity'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'ipp','initials','birth_date'}; - -% Loop on patient structure array -for i=1:numel(patient) - ipp = patient(i).IPP; - initials = patient(i).initials; - birth_date = datestr(patient(i).anonym_birth_date,'yyyy-mm-dd'); - - exdata = {ipp,initials,birth_date}; - % insert data in SQL - datainsert(conn,'anonymity',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function anonymityToSQL(patient, conn) +%PATIENDBUPDATE fill patient structure array in mySQL +% Detailed explanation goes here + +fprintf('\nanonymity to mySQL... '); + +% Empty anonymity table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE anonymity'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'ipp','initials','birth_date'}; + +% Loop on patient structure array +for i=1:numel(patient) + ipp = patient(i).IPP; + initials = patient(i).initials; + birth_date = datestr(patient(i).anonym_birth_date,'yyyy-mm-dd'); + + exdata = {ipp,initials,birth_date}; + % insert data in SQL + datainsert(conn,'anonymity',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/closeSQL.m b/XLS_MySQL_DB/closeSQL.m index 438f213..c7cb917 100644 --- a/XLS_MySQL_DB/closeSQL.m +++ b/XLS_MySQL_DB/closeSQL.m @@ -1,12 +1,12 @@ -function closeSQL(conn) -%CLOSESQL Close SQL connection -% Detailed explanation goes here - -fprintf('\nClose mySQL... '); - -close(conn); % close SQL connection - -fprintf('Done\n'); - -end - +function closeSQL(conn) +%CLOSESQL Close SQL connection +% Detailed explanation goes here + +fprintf('\nClose mySQL... '); + +close(conn); % close SQL connection + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/diagnosisListFromExcel.m b/XLS_MySQL_DB/diagnosisListFromExcel.m index 515c573..3d49f85 100644 --- a/XLS_MySQL_DB/diagnosisListFromExcel.m +++ b/XLS_MySQL_DB/diagnosisListFromExcel.m @@ -1,51 +1,51 @@ -function [ diagnosisList ] = diagnosisListFromExcel( rawExcel ) -%DIAGNOSISLISTFROMEXCEL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\nGet diagnosisList from Excel... '); - -% define diagnosis structure array -diagnosisList = struct(... - 'diagnosisList_id' ,[], ... - 'name' ,[], ... - 'description' ,[] ... - ); - -% set normal subnormaland diagnosis names -diagnosisList(1).diagnosisList_id = 1; -diagnosisList(1).name = 'normal'; -diagnosisList(1).description = 'fully normal'; - -diagnosisList(2).diagnosisList_id = 2; -diagnosisList(2).name = 'subnormal'; -diagnosisList(2).description = 'no OA but small abnormalities'; - -% get column of variables -header = rawExcel(1,:); -diagnosisName_col = find(strcmp([header],'diagnosis.name')); - -% loop over rows of Excel table -[rowN, ~] = size(rawExcel); -diagnosisList_id = 2; -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - diagnosisName = row(diagnosisName_col); - diagnosisName = diagnosisName{1}; - if ~isempty(diagnosisName) & ~isnan(diagnosisName) - sameDiagnosis = find(strcmp({diagnosisList.name}, diagnosisName)==1); - % check if this diagnosis is already present in the array - if isempty(sameDiagnosis) - % add this diagnosis to the array - diagnosisList_id = diagnosisList_id + 1; - diagnosisList_idx = diagnosisList_id; - diagnosisList(diagnosisList_idx).diagnosisList_id = diagnosisList_id; - diagnosisList(diagnosisList_idx).name = diagnosisName; - diagnosisList(diagnosisList_idx).description = ''; - end - end -end - -fprintf('Done\n'); - -end - +function [ diagnosisList ] = diagnosisListFromExcel( rawExcel ) +%DIAGNOSISLISTFROMEXCEL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\nGet diagnosisList from Excel... '); + +% define diagnosis structure array +diagnosisList = struct(... + 'diagnosisList_id' ,[], ... + 'name' ,[], ... + 'description' ,[] ... + ); + +% set normal subnormaland diagnosis names +diagnosisList(1).diagnosisList_id = 1; +diagnosisList(1).name = 'normal'; +diagnosisList(1).description = 'fully normal'; + +diagnosisList(2).diagnosisList_id = 2; +diagnosisList(2).name = 'subnormal'; +diagnosisList(2).description = 'no OA but small abnormalities'; + +% get column of variables +header = rawExcel(1,:); +diagnosisName_col = find(strcmp([header],'diagnosis.name')); + +% loop over rows of Excel table +[rowN, ~] = size(rawExcel); +diagnosisList_id = 2; +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + diagnosisName = row(diagnosisName_col); + diagnosisName = diagnosisName{1}; + if ~isempty(diagnosisName) & ~isnan(diagnosisName) + sameDiagnosis = find(strcmp({diagnosisList.name}, diagnosisName)==1); + % check if this diagnosis is already present in the array + if isempty(sameDiagnosis) + % add this diagnosis to the array + diagnosisList_id = diagnosisList_id + 1; + diagnosisList_idx = diagnosisList_id; + diagnosisList(diagnosisList_idx).diagnosisList_id = diagnosisList_id; + diagnosisList(diagnosisList_idx).name = diagnosisName; + diagnosisList(diagnosisList_idx).description = ''; + end + end +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/diagnosisListToSQL.m b/XLS_MySQL_DB/diagnosisListToSQL.m index f1244c1..8911434 100644 --- a/XLS_MySQL_DB/diagnosisListToSQL.m +++ b/XLS_MySQL_DB/diagnosisListToSQL.m @@ -1,36 +1,36 @@ -function diagnosisListToSQL(diagnosisList, conn) -%diagnosisTOSQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\ndiagnosisList to mySQL...'); - -testquery= 'SET FOREIGN_KEY_CHECKS = 0'; -exec(conn,testquery); - -% Empty diagnosisList table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE diagnosisList'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -testquery= 'SET FOREIGN_KEY_CHECKS = 1'; -exec(conn,testquery); - -colnames = {'diagnosisList_id','name','description'}; - -% Loop on pCase structure array -for i=1:numel(diagnosisList) - diagnosisList_id = diagnosisList(i).diagnosisList_id; - name = diagnosisList(i).name; - description = diagnosisList(i).description; - - exdata = {diagnosisList_id,name,description}; - % insert data in SQL - datainsert(conn,'diagnosisList',colnames,exdata); -end - -fprintf(' Done\n'); - -end - +function diagnosisListToSQL(diagnosisList, conn) +%diagnosisTOSQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\ndiagnosisList to mySQL...'); + +testquery= 'SET FOREIGN_KEY_CHECKS = 0'; +exec(conn,testquery); + +% Empty diagnosisList table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE diagnosisList'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +testquery= 'SET FOREIGN_KEY_CHECKS = 1'; +exec(conn,testquery); + +colnames = {'diagnosisList_id','name','description'}; + +% Loop on pCase structure array +for i=1:numel(diagnosisList) + diagnosisList_id = diagnosisList(i).diagnosisList_id; + name = diagnosisList(i).name; + description = diagnosisList(i).description; + + exdata = {diagnosisList_id,name,description}; + % insert data in SQL + datainsert(conn,'diagnosisList',colnames,exdata); +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/diagnosisToSQL.m b/XLS_MySQL_DB/diagnosisToSQL.m index ed124b8..61ccf4e 100644 --- a/XLS_MySQL_DB/diagnosisToSQL.m +++ b/XLS_MySQL_DB/diagnosisToSQL.m @@ -1,32 +1,32 @@ -function diagnosisToSQL(diagnosis, conn) -%diagnosisTOSQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\ndiagnosis to mySQL...'); - -% Empty diagnosis table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE diagnosis'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'diagnosis_id','sCase_id','diagnosisList_id','date','comment'}; - -% Loop on pCase structure array -for i=1:numel(diagnosis) - diagnosis_id = diagnosis(i).diagnosis_id; - sCase_id = diagnosis(i).sCase_id; - diagnosisList_id = diagnosis(i).diagnosisList_id; - date = diagnosis(i).date; - comment = diagnosis(i).comment; - - exdata = {diagnosis_id,sCase_id,diagnosisList_id,date,comment}; - % insert data in SQL - datainsert(conn,'diagnosis',colnames,exdata); -end - -fprintf(' Done\n'); - -end - +function diagnosisToSQL(diagnosis, conn) +%diagnosisTOSQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\ndiagnosis to mySQL...'); + +% Empty diagnosis table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE diagnosis'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'diagnosis_id','sCase_id','diagnosisList_id','date','comment'}; + +% Loop on pCase structure array +for i=1:numel(diagnosis) + diagnosis_id = diagnosis(i).diagnosis_id; + sCase_id = diagnosis(i).sCase_id; + diagnosisList_id = diagnosis(i).diagnosisList_id; + date = diagnosis(i).date; + comment = diagnosis(i).comment; + + exdata = {diagnosis_id,sCase_id,diagnosisList_id,date,comment}; + % insert data in SQL + datainsert(conn,'diagnosis',colnames,exdata); +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/glenoidToSQL.m b/XLS_MySQL_DB/glenoidToSQL.m index 9072365..7f41b38 100644 --- a/XLS_MySQL_DB/glenoidToSQL.m +++ b/XLS_MySQL_DB/glenoidToSQL.m @@ -1,70 +1,70 @@ -function glenoidToSQL(glenoid, conn) -%GLENOIDTOSQL fill glenoid structure array in mySQL -% Detailed explanation goes here - -fprintf('\nglenoid to mySQL... '); - -% Empty glenoid table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE glenoid'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'CT_id','radius','sphericity','biconcave','depth',... - 'width','height','version_ampl','version_orient',... - 'center_PA','center_IS','center_ML','version','inclination',... - 'version_2D','walch_class','comment'}; - - -% Loop on glenoid structure array -for i=1:numel(glenoid) - - CT_id = glenoid(i).CT_id; - radius = glenoid(i).radius; - biconcave = ''; - sphericity = 0; -% sphericity = glenoid(i).sphericity; -% biconcave = glenoid(i).biconcave; - depth = glenoid(i).depth; - width = glenoid(i).width; - height = glenoid(i).height; - version_ampl = glenoid(i).version_ampl; - version_orient = glenoid(i).version_orient; - center_PA = glenoid(i).center_PA; - center_IS = glenoid(i).center_IS; - center_ML = glenoid(i).center_ML; - version = glenoid(i).version; - inclination = glenoid(i).inclination; - version_2D = glenoid(i).version_2D; - walch_class = glenoid(i).walch_class; - comment = glenoid(i).comment; - - if isnan(version_2D) - version_2D = ''; - end - if isnan(walch_class) - walch_class = ''; - end - -% if isnan(biconcave) -% biconcave = ''; -% end - if isnan(width) - width = ''; - height = ''; - end - - exdata = {CT_id,radius,sphericity,biconcave,depth,... - width,height,version_ampl,version_orient,... - center_PA,center_IS,center_ML,version,inclination,... - version_2D,walch_class,comment}; - - % insert data in SQL - - datainsert(conn,'glenoid',colnames,exdata); -end - -fprintf('Done\n'); - +function glenoidToSQL(glenoid, conn) +%GLENOIDTOSQL fill glenoid structure array in mySQL +% Detailed explanation goes here + +fprintf('\nglenoid to mySQL... '); + +% Empty glenoid table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE glenoid'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'CT_id','radius','sphericity','biconcave','depth',... + 'width','height','version_ampl','version_orient',... + 'center_PA','center_IS','center_ML','version','inclination',... + 'version_2D','walch_class','comment'}; + + +% Loop on glenoid structure array +for i=1:numel(glenoid) + + CT_id = glenoid(i).CT_id; + radius = glenoid(i).radius; + biconcave = ''; + sphericity = 0; +% sphericity = glenoid(i).sphericity; +% biconcave = glenoid(i).biconcave; + depth = glenoid(i).depth; + width = glenoid(i).width; + height = glenoid(i).height; + version_ampl = glenoid(i).version_ampl; + version_orient = glenoid(i).version_orient; + center_PA = glenoid(i).center_PA; + center_IS = glenoid(i).center_IS; + center_ML = glenoid(i).center_ML; + version = glenoid(i).version; + inclination = glenoid(i).inclination; + version_2D = glenoid(i).version_2D; + walch_class = glenoid(i).walch_class; + comment = glenoid(i).comment; + + if isnan(version_2D) + version_2D = ''; + end + if isnan(walch_class) + walch_class = ''; + end + +% if isnan(biconcave) +% biconcave = ''; +% end + if isnan(width) + width = ''; + height = ''; + end + + exdata = {CT_id,radius,sphericity,biconcave,depth,... + width,height,version_ampl,version_orient,... + center_PA,center_IS,center_ML,version,inclination,... + version_2D,walch_class,comment}; + + % insert data in SQL + + datainsert(conn,'glenoid',colnames,exdata); +end + +fprintf('Done\n'); + end \ No newline at end of file diff --git a/XLS_MySQL_DB/initializeSQL.m b/XLS_MySQL_DB/initializeSQL.m index 53a8cf5..89f9056 100644 --- a/XLS_MySQL_DB/initializeSQL.m +++ b/XLS_MySQL_DB/initializeSQL.m @@ -1,134 +1,134 @@ -function [ curs ] = initializeSQL( conn ) -%INITIALIZESQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\nInitialize mySQL database... '); - -testquery= 'SET FOREIGN_KEY_CHECKS = 0'; -exec(conn,testquery); - -% Empty diagnosis table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE diagnosis'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty diagnosisList table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE diagnosisList'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty treatment table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE treatment'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty treatmentList table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE treatmentList'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty outcome table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE outcome'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty study table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE study'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty studyList table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE studyList'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty glenoid table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE glenoid'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty glenoid_density table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE glenoid_density'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty humerus table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE humerus'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty muscle table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE muscle'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty scapula table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE scapula'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty CT table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE CT'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty shoulder table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE shoulder'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty patient table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE patient'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty anonymity table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE anonymity'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -% Empty sCase table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE sCase'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -testquery= 'SET FOREIGN_KEY_CHECKS = 1'; -exec(conn,testquery); - -fprintf('Done\n'); - +function [ curs ] = initializeSQL( conn ) +%INITIALIZESQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\nInitialize mySQL database... '); + +testquery= 'SET FOREIGN_KEY_CHECKS = 0'; +exec(conn,testquery); + +% Empty diagnosis table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE diagnosis'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty diagnosisList table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE diagnosisList'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty treatment table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE treatment'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty treatmentList table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE treatmentList'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty outcome table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE outcome'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty study table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE study'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty studyList table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE studyList'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty glenoid table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE glenoid'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty glenoid_density table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE glenoid_density'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty humerus table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE humerus'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty muscle table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE muscle'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty scapula table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE scapula'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty CT table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE CT'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty shoulder table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE shoulder'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty patient table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE patient'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty anonymity table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE anonymity'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +% Empty sCase table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE sCase'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +testquery= 'SET FOREIGN_KEY_CHECKS = 1'; +exec(conn,testquery); + +fprintf('Done\n'); + end \ No newline at end of file diff --git a/XLS_MySQL_DB/openSQL.m b/XLS_MySQL_DB/openSQL.m index f836de4..819dcbb 100644 --- a/XLS_MySQL_DB/openSQL.m +++ b/XLS_MySQL_DB/openSQL.m @@ -1,25 +1,25 @@ -function [ conn ] = openSQL( ~ ) -% OPENSQL open the mySQL database -% The JDBC driver is required to connect MATLAB to the MySQL database. -% Refer to documentation at: -% https://ch.mathworks.com/help/database/ug/mysql-jdbc-windows.html - -fprintf('\nOpen mySQL connection... '); - -instance = 'shoulder_test'; -username = 'matlabA'; -password = 'M@tL@b1,'; -url = 'lbovenus.epfl.ch'; - -% Open SQL databse -conn = database(instance,username,password,'Vendor','MySQL',... - 'Server',url); - -if ~isempty(conn.Message) - fprintf('\nMessage: %s\n', conn.Message); -end - -fprintf('Done\n'); - -end - +function [ conn ] = openSQL( ~ ) +% OPENSQL open the mySQL database +% The JDBC driver is required to connect MATLAB to the MySQL database. +% Refer to documentation at: +% https://ch.mathworks.com/help/database/ug/mysql-jdbc-windows.html + +fprintf('\nOpen mySQL connection... '); + +instance = 'shoulder_test'; +username = 'matlabA'; +password = 'M@tL@b1,'; +url = 'lbovenus.epfl.ch'; + +% Open SQL databse +conn = database(instance,username,password,'Vendor','MySQL',... + 'Server',url); + +if ~isempty(conn.Message) + fprintf('\nMessage: %s\n', conn.Message); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/outcomeToSQL.m b/XLS_MySQL_DB/outcomeToSQL.m index 90af358..7af485b 100644 --- a/XLS_MySQL_DB/outcomeToSQL.m +++ b/XLS_MySQL_DB/outcomeToSQL.m @@ -1,32 +1,32 @@ -function outcomeToSQL( outcome, conn ) -%OUTCOMETOSQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\noutcome to mySQL...'); - -% Empty outcome table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE outcome'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'outcome_id','sCase_id','date','comment','loosening'}; - -% Loop on pCase structure array -for i=1:numel(outcome) - - outcome_id = outcome(i).outcome_id; - sCase_id = outcome(i).sCase_id; - date = outcome(i).date; - comment = outcome(i).comment; - loosening = outcome(i).loosening; - - exdata = {outcome_id, sCase_id, date, comment, loosening}; - % insert data in SQL - datainsert(conn,'outcome',colnames,exdata); -end - -fprintf(' Done\n'); - -end +function outcomeToSQL( outcome, conn ) +%OUTCOMETOSQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\noutcome to mySQL...'); + +% Empty outcome table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE outcome'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'outcome_id','sCase_id','date','comment','loosening'}; + +% Loop on pCase structure array +for i=1:numel(outcome) + + outcome_id = outcome(i).outcome_id; + sCase_id = outcome(i).sCase_id; + date = outcome(i).date; + comment = outcome(i).comment; + loosening = outcome(i).loosening; + + exdata = {outcome_id, sCase_id, date, comment, loosening}; + % insert data in SQL + datainsert(conn,'outcome',colnames,exdata); +end + +fprintf(' Done\n'); + +end diff --git a/XLS_MySQL_DB/patientFromExcel.m b/XLS_MySQL_DB/patientFromExcel.m index ffca24b..6564400 100644 --- a/XLS_MySQL_DB/patientFromExcel.m +++ b/XLS_MySQL_DB/patientFromExcel.m @@ -1,106 +1,106 @@ -function [patient] = patientFromExcel(rawExcel) -%PATIENT builds the structure array patients from -% Detailed explanation goes here - -fprintf('\nGet patient from Excel... '); - -% define the patient structure array -patient = struct(... - 'patient_id', [], ... - 'IPP', [], ... % to be later move to another table for anonymous constrain - 'initials', [], ... % to be later move to another table for anonymous constrain - 'anonym_birth_date', [], ... % to be later move to another table for anonymous constrain - 'gender', [], ... - 'birth_date', [], ... - 'height', [], ... - 'weight', [],... % should we put weight here since it can vary? - 'comment', []... - ); - -% get column of variables -header = rawExcel(1,:); -sCase_id_col = find(strcmp([header],'sCase.id')); -IPP_col = find(strcmp([header],'anonymity.IPP')); -initials_col = find(strcmp([header],'anonymity.initials')); -anonym_birth_date_col = find(strcmp([header],'anonymity.birth_date')); -gender_col = find(strcmp([header],'patient.gender')); -birth_date_col = find(strcmp([header],'patient.birth_date')); -height_col = find(strcmp([header],'patient.height')); -weight_col = find(strcmp([header],'patient.weight')); -comment_col = find(strcmp([header],'patient.comment')); -% loop over rows of Excel table to build patient structure -% patients are not included if caseExcel, IPP, initials, gender, birth_date are not -% defined -[rowN, ~] = size(rawExcel); -patient_id = 0; -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - - % check that there is a sCase for this row - sCase_id = row{sCase_id_col}; % sCase_id from Excel - if ~isempty(sCase_id) - - % check validity of data - IPP = row{IPP_col}; - initials = row{initials_col}; - gender = row{gender_col}; - anonym_birth_date = row{anonym_birth_date_col}; - anonym_birth_date = datetime(anonym_birth_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); - anonym_birth_date.Format = 'yyyy-MM-dd'; - birth_date = row{birth_date_col}; - birth_date = datetime(birth_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); - birth_date.Format = 'yyyy-MM-dd'; - % get muscle comment - comment = row{comment_col}; - if isnan(comment) - comment = ''; - end - - if ~isnan(IPP) & ... - ~isnan(initials) & ... - ~isnan(gender) & ... - ~isnat(birth_date) & ... - ~isnat(anonym_birth_date) - - % Check that patient in row sCase is not already in structure - % We asume here that IPP uniquely identifies a patient - sameIPP = find([patient.IPP] == IPP); - if ~isempty(sameIPP) - % IPP is allready in patient array, extra-checks to avoid false same patient - if birth_date ~= patient(sameIPP).birth_date % not same birthdate - fprintf('\nSame IPP (%i) but different birth date in case %s !', IPP, sCase_id); - end - if gender ~= patient(sameIPP).gender % not same gender - fprintf('\nSame IPP (%i) but different gender in case %s !', IPP, sCase_id); - end - if ~strcmp(initials, patient(sameIPP).initials) % not same initials - fprintf('\nSame IPP (%i) but different initials in case %s !', IPP, sCase_id); - end - else % patient is not in patient structure array, so add it - patient_id = patient_id + 1; - patient_idx = patient_id; - - height = row{height_col}; - if height == ' ' % to avoid strange behavior of the excel import - height = NaN; - end - weight = row{weight_col}; - - patient(patient_idx).patient_id = patient_id; - patient(patient_idx).IPP = IPP; - patient(patient_idx).initials = initials; - patient(patient_idx).gender = gender; - patient(patient_idx).birth_date = birth_date; - patient(patient_idx).height = height; - patient(patient_idx).weight = weight; - patient(patient_idx).anonym_birth_date = anonym_birth_date; - patient(patient_idx).comment = comment; - end - end - end -end - -fprintf('Done\n'); - -end - +function [patient] = patientFromExcel(rawExcel) +%PATIENT builds the structure array patients from +% Detailed explanation goes here + +fprintf('\nGet patient from Excel... '); + +% define the patient structure array +patient = struct(... + 'patient_id', [], ... + 'IPP', [], ... % to be later move to another table for anonymous constrain + 'initials', [], ... % to be later move to another table for anonymous constrain + 'anonym_birth_date', [], ... % to be later move to another table for anonymous constrain + 'gender', [], ... + 'birth_date', [], ... + 'height', [], ... + 'weight', [],... % should we put weight here since it can vary? + 'comment', []... + ); + +% get column of variables +header = rawExcel(1,:); +sCase_id_col = find(strcmp([header],'sCase.id')); +IPP_col = find(strcmp([header],'anonymity.IPP')); +initials_col = find(strcmp([header],'anonymity.initials')); +anonym_birth_date_col = find(strcmp([header],'anonymity.birth_date')); +gender_col = find(strcmp([header],'patient.gender')); +birth_date_col = find(strcmp([header],'patient.birth_date')); +height_col = find(strcmp([header],'patient.height')); +weight_col = find(strcmp([header],'patient.weight')); +comment_col = find(strcmp([header],'patient.comment')); +% loop over rows of Excel table to build patient structure +% patients are not included if caseExcel, IPP, initials, gender, birth_date are not +% defined +[rowN, ~] = size(rawExcel); +patient_id = 0; +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + + % check that there is a sCase for this row + sCase_id = row{sCase_id_col}; % sCase_id from Excel + if ~isempty(sCase_id) + + % check validity of data + IPP = row{IPP_col}; + initials = row{initials_col}; + gender = row{gender_col}; + anonym_birth_date = row{anonym_birth_date_col}; + anonym_birth_date = datetime(anonym_birth_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); + anonym_birth_date.Format = 'yyyy-MM-dd'; + birth_date = row{birth_date_col}; + birth_date = datetime(birth_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); + birth_date.Format = 'yyyy-MM-dd'; + % get muscle comment + comment = row{comment_col}; + if isnan(comment) + comment = ''; + end + + if ~isnan(IPP) & ... + ~isnan(initials) & ... + ~isnan(gender) & ... + ~isnat(birth_date) & ... + ~isnat(anonym_birth_date) + + % Check that patient in row sCase is not already in structure + % We asume here that IPP uniquely identifies a patient + sameIPP = find([patient.IPP] == IPP); + if ~isempty(sameIPP) + % IPP is allready in patient array, extra-checks to avoid false same patient + if birth_date ~= patient(sameIPP).birth_date % not same birthdate + fprintf('\nSame IPP (%i) but different birth date in case %s !', IPP, sCase_id); + end + if gender ~= patient(sameIPP).gender % not same gender + fprintf('\nSame IPP (%i) but different gender in case %s !', IPP, sCase_id); + end + if ~strcmp(initials, patient(sameIPP).initials) % not same initials + fprintf('\nSame IPP (%i) but different initials in case %s !', IPP, sCase_id); + end + else % patient is not in patient structure array, so add it + patient_id = patient_id + 1; + patient_idx = patient_id; + + height = row{height_col}; + if height == ' ' % to avoid strange behavior of the excel import + height = NaN; + end + weight = row{weight_col}; + + patient(patient_idx).patient_id = patient_id; + patient(patient_idx).IPP = IPP; + patient(patient_idx).initials = initials; + patient(patient_idx).gender = gender; + patient(patient_idx).birth_date = birth_date; + patient(patient_idx).height = height; + patient(patient_idx).weight = weight; + patient(patient_idx).anonym_birth_date = anonym_birth_date; + patient(patient_idx).comment = comment; + end + end + end +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/patientToSQL.m b/XLS_MySQL_DB/patientToSQL.m index 28aced9..1952641 100644 --- a/XLS_MySQL_DB/patientToSQL.m +++ b/XLS_MySQL_DB/patientToSQL.m @@ -1,45 +1,45 @@ -function patientToSQL(patient, conn) -%PATIENDBUPDATE fill patient structure array in mySQL -% Detailed explanation goes here - -fprintf('\npatient to mySQL... '); - -testquery= 'SET FOREIGN_KEY_CHECKS = 0'; -exec(conn,testquery); - -% Empty patient table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE patient'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -testquery= 'SET FOREIGN_KEY_CHECKS = 1'; -exec(conn,testquery); - -colnames = {'gender','birth_date','height','weight','comment'}; - -% Loop on patient structure array -for i=1:numel(patient) - gender = patient(i).gender; - birth_date = datestr(patient(i).birth_date,'yyyy-mm-dd'); - height = patient(i).height; - comment = patient(i).comment; - - if isnan(height) - height = ''; - end - weight = patient(i).weight; - if isnan(weight) - weight = ''; - end - - exdata = {gender,birth_date,height,weight,comment}; - % insert data in SQL - datainsert(conn,'patient',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function patientToSQL(patient, conn) +%PATIENDBUPDATE fill patient structure array in mySQL +% Detailed explanation goes here + +fprintf('\npatient to mySQL... '); + +testquery= 'SET FOREIGN_KEY_CHECKS = 0'; +exec(conn,testquery); + +% Empty patient table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE patient'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +testquery= 'SET FOREIGN_KEY_CHECKS = 1'; +exec(conn,testquery); + +colnames = {'gender','birth_date','height','weight','comment'}; + +% Loop on patient structure array +for i=1:numel(patient) + gender = patient(i).gender; + birth_date = datestr(patient(i).birth_date,'yyyy-mm-dd'); + height = patient(i).height; + comment = patient(i).comment; + + if isnan(height) + height = ''; + end + weight = patient(i).weight; + if isnan(weight) + weight = ''; + end + + exdata = {gender,birth_date,height,weight,comment}; + % insert data in SQL + datainsert(conn,'patient',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/rawFromExcel.m b/XLS_MySQL_DB/rawFromExcel.m index 643e44d..58664c0 100644 --- a/XLS_MySQL_DB/rawFromExcel.m +++ b/XLS_MySQL_DB/rawFromExcel.m @@ -1,37 +1,37 @@ -function [ rawExcel ] = rawFromExcel() -%RAWFROMEXCERL read the Excel file filename and output content in raw -% Set the sheet and the range -% Returns the raw data - -fprintf('\nGet rawExcel from Excel... '); - -% Set Excel file name & directory -directory = '../../../../data/Excel/'; -filename = 'ShoulderDataBase.xlsx';filename = strcat(directory,filename); -sheet = 'sCase'; % 'Normal' & 'TSA' -xlRange = 'A1:CX999'; % range of column and row to be imported - -[~,~,rawExcel] = xlsread(filename,sheet,xlRange); - -% get column of variables -header = rawExcel(1,:); -sCase_id_col = find(strcmp([header],'sCase.id')); - -% delete rows without sCase) -row_idx = 2; -[rowN, ~] = size(rawExcel); -while row_idx <= rowN - row = rawExcel(row_idx, :); % get the entire row - sCase_id = row{sCase_id_col}; - if isnan(sCase_id) - rawExcel(row_idx, :) = []; - rowN = rowN - 1; - else - row_idx = row_idx + 1; - end -end - -fprintf('Done\n'); - -end - +function [ rawExcel ] = rawFromExcel() +%RAWFROMEXCERL read the Excel file filename and output content in raw +% Set the sheet and the range +% Returns the raw data + +fprintf('\nGet rawExcel from Excel... '); + +% Set Excel file name & directory +directory = '../../../../data/Excel/'; +filename = 'ShoulderDataBase.xlsx';filename = strcat(directory,filename); +sheet = 'sCase'; % 'Normal' & 'TSA' +xlRange = 'A1:CX999'; % range of column and row to be imported + +[~,~,rawExcel] = xlsread(filename,sheet,xlRange); + +% get column of variables +header = rawExcel(1,:); +sCase_id_col = find(strcmp([header],'sCase.id')); + +% delete rows without sCase) +row_idx = 2; +[rowN, ~] = size(rawExcel); +while row_idx <= rowN + row = rawExcel(row_idx, :); % get the entire row + sCase_id = row{sCase_id_col}; + if isnan(sCase_id) + rawExcel(row_idx, :) = []; + rowN = rowN - 1; + else + row_idx = row_idx + 1; + end +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/sCaseFromExcel.m b/XLS_MySQL_DB/sCaseFromExcel.m index 4db2053..4df5531 100644 --- a/XLS_MySQL_DB/sCaseFromExcel.m +++ b/XLS_MySQL_DB/sCaseFromExcel.m @@ -1,222 +1,222 @@ -function [ sCase, diagnosis, treatment, outcome, study ] = sCaseFromExcel(shoulder, patient, diagnosisList, treatmentList, rawExcel) -% PCASEFROMEXCEL build the sCase (shoulder case) structure array from Excel and from -% patient and shoulder structure. A sCase is associated to a shoulder. The -% same shoulder can be associated to more than one sCase. -% pCase stand for patient case. The variable case could not be used since -% alrewad usd by Matlab - -fprintf('\nGet sCase from Excel... '); - -% define sCase structure array -sCase = struct(... - 'sCase_id' ,[],... - 'shoulder_id' ,[],... - 'folder_name' ,[],... - 'comment' ,[] ... - ); - -% defne diagnosis structure -diagnosis = struct(... - 'diagnosis_id' ,[],... - 'sCase_id' ,[],... - 'diagnosisList_id',[],... - 'date' ,[],... - 'comment' ,[]... - ); - -% defne treatment structure -treatment = struct(... - 'treatment_id' ,[],... - 'sCase_id' ,[],... - 'treatmentList_id',[],... - 'date' ,[],... - 'comment' ,[]... - ); - -% defne outcome structure -outcome = struct(... - 'outcome_id' ,[],... - 'sCase_id' ,[],... - 'date' ,[],... - 'comment' ,[],... - 'loosening' ,[]... - ); - -% define study structure -study = struct(... - 'study_id' ,[],... - 'sCase_id' ,[],... - 'name' ,[],... - 'comment' ,[]... - ); - -% get column of variables -header = rawExcel(1,:); -sCase_id_col = find(strcmp([header],'sCase.id')); -sCaseComm_col = find(strcmp([header],'sCase.comment')); -diagnosisName_col = find(strcmp([header],'diagnosis.name')); -diagnosisDate_col = find(strcmp([header],'diagnosis.date')); -diagnosisComm_col = find(strcmp([header],'diagnosis.comment')); -treatmentName_col = find(strcmp([header],'treatment.name')); -treatmentDate_col = find(strcmp([header],'treatment.date')); -treatmentComm_col = find(strcmp([header],'treatment.comment')); -outcomeDate_col = find(strcmp([header],'outcome.date')); -outcomeComm_col = find(strcmp([header],'outcome.comment')); -outcomeLoos_col = find(strcmp([header],'outcome.loosening')); -IPP_col = find(strcmp([header],'anonymity.IPP')); -side_col = find(strcmp([header],'shoulder.side')); - -% loop over rows of Excel table -[rowN, ~] = size(rawExcel); -sCase_id = 0; -diagnosis_id = 0; -treatment_id = 0; -outcome_id = 0; -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - sCaseE = row{sCase_id_col}; % sCase id from Excel -> folder_name - side = row{side_col}; - if ~isnan(sCaseE) & ~isnan(side) % check that sCase & side are defined - IPP = row{IPP_col}; - patient_idx = find([patient.IPP] == IPP); - % get patient index of this caseExcel - if ~isempty(patient_idx) - % there is a patient with same IPP - patient_idx = patient(patient_idx).patient_id; % get patient_id - shoulder_idx = find([shoulder.patient_id] == patient_idx); % get shoulder_id - % should be 1 or 2 (R/L), but not 0 (test it however) - shoulder_idxN = length(shoulder_idx); - switch shoulder_idxN - case 0 - % this sCase patient is not in the shoulder array - % report an error - fprintf('\nsCase %s not in shoulder array', sCaseE); - addCase = 0; - case 1 - % this sCase patient has 1 match in the shoulder array - % so we have to check side - if side == shoulder(shoulder_idx).side - shoulder_id = shoulder(shoulder_idx).shoulder_id; - addCase = 1; - else - % not same side so report a problem - fprintf('\nsCase in soulder array but not same side'); - end - case 2 - % both shoulder sides of this patient are in the - % shoulder array, so we have to check side - if side == shoulder(shoulder_idx(1)).side - shoulder_id = shoulder(shoulder_idx(1)).shoulder_id; - addCase = 1; - else - if side == shoulder(shoulder_idx(2)).side - shoulder_id = shoulder(shoulder_idx(2)).shoulder_id; - addCase = 1; - end - end - end - if addCase - % add sCase - sCase_id = sCase_id + 1; - sCase_idx = sCase_id; - folder_name = sCaseE; - % get sCase comment - sCase_comment = row{sCaseComm_col}; - if isnan(sCase_comment) - sCase_comment = ''; - end - - sCase(sCase_idx).sCase_id = sCase_id; - sCase(sCase_idx).shoulder_id = shoulder_id; - sCase(sCase_idx).folder_name = folder_name; - sCase(sCase_idx).comment = sCase_comment; - - - - % check & add diagnosis - diagnosisName = row{diagnosisName_col}; - diagnosisList_idx = find(strcmp({diagnosisList.name}, diagnosisName) == 1); - if isempty(diagnosisList_idx) - fprintf('\nsCase %s has not associated diagnosisList_id', sCaseE); - else - diagnosis_id = diagnosis_id + 1; - diagnosis_idx = diagnosis_id; - % get diagnosis date - diagnosis_date = row{diagnosisDate_col}; - diagnosis_date = datetime(diagnosis_date,'ConvertFrom','excel'); - diagnosis_date.Format = 'yyyy-MM-dd'; - % get diagnosis comment - diagnosis_comment = row{diagnosisComm_col}; - if isnan(diagnosis_comment) - diagnosis_comment = ''; - end - - diagnosis(diagnosis_idx).diagnosis_id = diagnosis_id; - diagnosis(diagnosis_idx).sCase_id = sCase_id; - diagnosis(diagnosis_idx).diagnosisList_id = diagnosisList(diagnosisList_idx).diagnosisList_id; - diagnosis(diagnosis_idx).date = diagnosis_date; - diagnosis(diagnosis_idx).comment = diagnosis_comment; - end - - % check & add treatment - treatmentName = row{treatmentName_col}; - treatmentList_idx = find(strcmp({treatmentList.name}, treatmentName) == 1); - if isempty(treatmentList_idx) - fprintf('\nsCase %s has not associated treatmentList_id', sCaseE); - else - treatment_id = treatment_id + 1; - treatment_idx = treatment_id; - % get treatment date - treatment_date = row{treatmentDate_col}; - treatment_date = datetime(treatment_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); - treatment_date.Format = 'yyyy-MM-dd'; - % get treatment comment - treatment_comment = row{treatmentComm_col}; - - treatment(treatment_idx).treatment_id = treatment_id; - treatment(treatment_idx).sCase_id = sCase_id; - treatment(treatment_idx).treatmentList_id = treatmentList(treatmentList_idx).treatmentList_id; - treatment(treatment_idx).date = treatment_date; - treatment(treatment_idx).comment = treatment_comment; - end - - % check & add outcome - % get outcome date - outcome_date = row{outcomeDate_col}; - if ~isempty(outcome_date) - outcome_date = datetime(outcome_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); - outcome_date.Format = 'yyyy-MM-dd'; - end - % get outcome comment - outcome_comment = row{outcomeComm_col}; - % get outcome loosening - outcome_loosening = row{outcomeLoos_col}; - - if isempty(outcome_comment) || isempty(outcome_loosening) - fprintf('\nsCase %s has not associated outcome', sCaseE); - else - outcome_id = outcome_id + 1; - outcome_idx = outcome_id; - - outcome(outcome_idx).outcome_id = outcome_id; - outcome(outcome_idx).sCase_id = sCase_id; - outcome(outcome_idx).date = outcome_date; - outcome(outcome_idx).comment = outcome_comment; - outcome(outcome_idx).loosening = outcome_loosening; - end - - % check & add study - % to be done - study(1).study_id = 1; - study(1).sCase_id = 1; - study(1).name = 'studyTest'; - study(1).comment = 'studyComment'; - end - end - end -end - -fprintf(' Done\n'); - -end - +function [ sCase, diagnosis, treatment, outcome, study ] = sCaseFromExcel(shoulder, patient, diagnosisList, treatmentList, rawExcel) +% PCASEFROMEXCEL build the sCase (shoulder case) structure array from Excel and from +% patient and shoulder structure. A sCase is associated to a shoulder. The +% same shoulder can be associated to more than one sCase. +% pCase stand for patient case. The variable case could not be used since +% alrewad usd by Matlab + +fprintf('\nGet sCase from Excel... '); + +% define sCase structure array +sCase = struct(... + 'sCase_id' ,[],... + 'shoulder_id' ,[],... + 'folder_name' ,[],... + 'comment' ,[] ... + ); + +% defne diagnosis structure +diagnosis = struct(... + 'diagnosis_id' ,[],... + 'sCase_id' ,[],... + 'diagnosisList_id',[],... + 'date' ,[],... + 'comment' ,[]... + ); + +% defne treatment structure +treatment = struct(... + 'treatment_id' ,[],... + 'sCase_id' ,[],... + 'treatmentList_id',[],... + 'date' ,[],... + 'comment' ,[]... + ); + +% defne outcome structure +outcome = struct(... + 'outcome_id' ,[],... + 'sCase_id' ,[],... + 'date' ,[],... + 'comment' ,[],... + 'loosening' ,[]... + ); + +% define study structure +study = struct(... + 'study_id' ,[],... + 'sCase_id' ,[],... + 'name' ,[],... + 'comment' ,[]... + ); + +% get column of variables +header = rawExcel(1,:); +sCase_id_col = find(strcmp([header],'sCase.id')); +sCaseComm_col = find(strcmp([header],'sCase.comment')); +diagnosisName_col = find(strcmp([header],'diagnosis.name')); +diagnosisDate_col = find(strcmp([header],'diagnosis.date')); +diagnosisComm_col = find(strcmp([header],'diagnosis.comment')); +treatmentName_col = find(strcmp([header],'treatment.name')); +treatmentDate_col = find(strcmp([header],'treatment.date')); +treatmentComm_col = find(strcmp([header],'treatment.comment')); +outcomeDate_col = find(strcmp([header],'outcome.date')); +outcomeComm_col = find(strcmp([header],'outcome.comment')); +outcomeLoos_col = find(strcmp([header],'outcome.loosening')); +IPP_col = find(strcmp([header],'anonymity.IPP')); +side_col = find(strcmp([header],'shoulder.side')); + +% loop over rows of Excel table +[rowN, ~] = size(rawExcel); +sCase_id = 0; +diagnosis_id = 0; +treatment_id = 0; +outcome_id = 0; +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + sCaseE = row{sCase_id_col}; % sCase id from Excel -> folder_name + side = row{side_col}; + if ~isnan(sCaseE) & ~isnan(side) % check that sCase & side are defined + IPP = row{IPP_col}; + patient_idx = find([patient.IPP] == IPP); + % get patient index of this caseExcel + if ~isempty(patient_idx) + % there is a patient with same IPP + patient_idx = patient(patient_idx).patient_id; % get patient_id + shoulder_idx = find([shoulder.patient_id] == patient_idx); % get shoulder_id + % should be 1 or 2 (R/L), but not 0 (test it however) + shoulder_idxN = length(shoulder_idx); + switch shoulder_idxN + case 0 + % this sCase patient is not in the shoulder array + % report an error + fprintf('\nsCase %s not in shoulder array', sCaseE); + addCase = 0; + case 1 + % this sCase patient has 1 match in the shoulder array + % so we have to check side + if side == shoulder(shoulder_idx).side + shoulder_id = shoulder(shoulder_idx).shoulder_id; + addCase = 1; + else + % not same side so report a problem + fprintf('\nsCase in soulder array but not same side'); + end + case 2 + % both shoulder sides of this patient are in the + % shoulder array, so we have to check side + if side == shoulder(shoulder_idx(1)).side + shoulder_id = shoulder(shoulder_idx(1)).shoulder_id; + addCase = 1; + else + if side == shoulder(shoulder_idx(2)).side + shoulder_id = shoulder(shoulder_idx(2)).shoulder_id; + addCase = 1; + end + end + end + if addCase + % add sCase + sCase_id = sCase_id + 1; + sCase_idx = sCase_id; + folder_name = sCaseE; + % get sCase comment + sCase_comment = row{sCaseComm_col}; + if isnan(sCase_comment) + sCase_comment = ''; + end + + sCase(sCase_idx).sCase_id = sCase_id; + sCase(sCase_idx).shoulder_id = shoulder_id; + sCase(sCase_idx).folder_name = folder_name; + sCase(sCase_idx).comment = sCase_comment; + + + + % check & add diagnosis + diagnosisName = row{diagnosisName_col}; + diagnosisList_idx = find(strcmp({diagnosisList.name}, diagnosisName) == 1); + if isempty(diagnosisList_idx) + fprintf('\nsCase %s has not associated diagnosisList_id', sCaseE); + else + diagnosis_id = diagnosis_id + 1; + diagnosis_idx = diagnosis_id; + % get diagnosis date + diagnosis_date = row{diagnosisDate_col}; + diagnosis_date = datetime(diagnosis_date,'ConvertFrom','excel'); + diagnosis_date.Format = 'yyyy-MM-dd'; + % get diagnosis comment + diagnosis_comment = row{diagnosisComm_col}; + if isnan(diagnosis_comment) + diagnosis_comment = ''; + end + + diagnosis(diagnosis_idx).diagnosis_id = diagnosis_id; + diagnosis(diagnosis_idx).sCase_id = sCase_id; + diagnosis(diagnosis_idx).diagnosisList_id = diagnosisList(diagnosisList_idx).diagnosisList_id; + diagnosis(diagnosis_idx).date = diagnosis_date; + diagnosis(diagnosis_idx).comment = diagnosis_comment; + end + + % check & add treatment + treatmentName = row{treatmentName_col}; + treatmentList_idx = find(strcmp({treatmentList.name}, treatmentName) == 1); + if isempty(treatmentList_idx) + fprintf('\nsCase %s has not associated treatmentList_id', sCaseE); + else + treatment_id = treatment_id + 1; + treatment_idx = treatment_id; + % get treatment date + treatment_date = row{treatmentDate_col}; + treatment_date = datetime(treatment_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); + treatment_date.Format = 'yyyy-MM-dd'; + % get treatment comment + treatment_comment = row{treatmentComm_col}; + + treatment(treatment_idx).treatment_id = treatment_id; + treatment(treatment_idx).sCase_id = sCase_id; + treatment(treatment_idx).treatmentList_id = treatmentList(treatmentList_idx).treatmentList_id; + treatment(treatment_idx).date = treatment_date; + treatment(treatment_idx).comment = treatment_comment; + end + + % check & add outcome + % get outcome date + outcome_date = row{outcomeDate_col}; + if ~isempty(outcome_date) + outcome_date = datetime(outcome_date,'InputFormat','dd.MM.yyyy','ConvertFrom','excel'); + outcome_date.Format = 'yyyy-MM-dd'; + end + % get outcome comment + outcome_comment = row{outcomeComm_col}; + % get outcome loosening + outcome_loosening = row{outcomeLoos_col}; + + if isempty(outcome_comment) || isempty(outcome_loosening) + fprintf('\nsCase %s has not associated outcome', sCaseE); + else + outcome_id = outcome_id + 1; + outcome_idx = outcome_id; + + outcome(outcome_idx).outcome_id = outcome_id; + outcome(outcome_idx).sCase_id = sCase_id; + outcome(outcome_idx).date = outcome_date; + outcome(outcome_idx).comment = outcome_comment; + outcome(outcome_idx).loosening = outcome_loosening; + end + + % check & add study + % to be done + study(1).study_id = 1; + study(1).sCase_id = 1; + study(1).name = 'studyTest'; + study(1).comment = 'studyComment'; + end + end + end +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/sCaseToSQL.m b/XLS_MySQL_DB/sCaseToSQL.m index 038d207..0959d68 100644 --- a/XLS_MySQL_DB/sCaseToSQL.m +++ b/XLS_MySQL_DB/sCaseToSQL.m @@ -1,37 +1,37 @@ -function sCaseToSQL(sCase, conn) -%PCASETOSQL fill sCase structure array in mySQL -% Detailed explanation goes here - -fprintf('\nsCase to mySQL... '); - -testquery= 'SET FOREIGN_KEY_CHECKS = 0'; -exec(conn,testquery); - -% Empty sCase table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE sCase'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -testquery= 'SET FOREIGN_KEY_CHECKS = 1'; -exec(conn,testquery); - -colnames = {'sCase_id','shoulder_id','folder_name','comment'}; - -% Loop on pCase structure array -for i=1:numel(sCase) - sCase_id = sCase(i).sCase_id; - shoulder_id = sCase(i).shoulder_id; - folder_name = sCase(i).folder_name; - comment = sCase(i).comment; - - exdata = {sCase_id,shoulder_id,folder_name,comment}; - % insert data in SQL - datainsert(conn,'sCase',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function sCaseToSQL(sCase, conn) +%PCASETOSQL fill sCase structure array in mySQL +% Detailed explanation goes here + +fprintf('\nsCase to mySQL... '); + +testquery= 'SET FOREIGN_KEY_CHECKS = 0'; +exec(conn,testquery); + +% Empty sCase table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE sCase'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +testquery= 'SET FOREIGN_KEY_CHECKS = 1'; +exec(conn,testquery); + +colnames = {'sCase_id','shoulder_id','folder_name','comment'}; + +% Loop on pCase structure array +for i=1:numel(sCase) + sCase_id = sCase(i).sCase_id; + shoulder_id = sCase(i).shoulder_id; + folder_name = sCase(i).folder_name; + comment = sCase(i).comment; + + exdata = {sCase_id,shoulder_id,folder_name,comment}; + % insert data in SQL + datainsert(conn,'sCase',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/scapulaToSQL.m b/XLS_MySQL_DB/scapulaToSQL.m index c963278..ec90f76 100644 --- a/XLS_MySQL_DB/scapulaToSQL.m +++ b/XLS_MySQL_DB/scapulaToSQL.m @@ -1,31 +1,31 @@ -function scapulaToSQL(scapula, conn) -% fill scapula structure array in mySQL -% Detailed explanation goes here - -fprintf('\nscapula to mySQL... '); - -% Empty scapula table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE scapula'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'CT_id','CT_angle','AI','comment'}; - -% Loop on glenoid structure array -for i=1:numel(scapula) - - CT_id = scapula(i).CT_id; - CT_angle = scapula(i).CTangle; - AI = scapula(i).AI; - comment = scapula(i).comment; - - exdata = {CT_id,CT_angle,AI,comment}; - % insert data in SQL - datainsert(conn,'scapula',colnames,exdata); -end - -fprintf('Done\n'); - +function scapulaToSQL(scapula, conn) +% fill scapula structure array in mySQL +% Detailed explanation goes here + +fprintf('\nscapula to mySQL... '); + +% Empty scapula table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE scapula'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'CT_id','CT_angle','AI','comment'}; + +% Loop on glenoid structure array +for i=1:numel(scapula) + + CT_id = scapula(i).CT_id; + CT_angle = scapula(i).CTangle; + AI = scapula(i).AI; + comment = scapula(i).comment; + + exdata = {CT_id,CT_angle,AI,comment}; + % insert data in SQL + datainsert(conn,'scapula',colnames,exdata); +end + +fprintf('Done\n'); + end \ No newline at end of file diff --git a/XLS_MySQL_DB/shoulderFromExcel.m b/XLS_MySQL_DB/shoulderFromExcel.m index 138daec..852073a 100644 --- a/XLS_MySQL_DB/shoulderFromExcel.m +++ b/XLS_MySQL_DB/shoulderFromExcel.m @@ -1,88 +1,88 @@ -function [ shoulder ] = shoulderFromExcel(patient, rawExcel) -%SHOULDERFROMEXCEL build the shoulder structure array from Excel and from -%the patient structure -% The output is a shoulder strucure array that has the same form as the -% shoulder table of the mySQL database - -fprintf('\nGet shoulder from Excel... '); - -% define houlder structure array -shoulder = struct(... - 'shoulder_id', [], ... - 'patient_id', [], ... - 'side', [], ... - 'comment', [] ... - ); - -% get column of variables -header = rawExcel(1,:); -sCase_id_col = find(strcmp([header],'sCase.id')); -IPP_col = find(strcmp([header],'anonymity.IPP')); -side_col = find(strcmp([header],'shoulder.side')); -comment_col = find(strcmp([header],'shoulder.comment')); - -% loop over rows of Excel table -[rowN, ~] = size(rawExcel); -shoulder_id = 0; -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - sCaseE = row{sCase_id_col}; % sCase id from Excel - side = row{side_col}; - % get shoulder comment - comment = row{comment_col}; - if isnan(comment) - comment = ''; - end - if ~isnan(sCaseE) & ~isnan(side) % check that sCase & side are defined - IPP = row{IPP_col}; - patient_idx = find([patient.IPP] == IPP); - % get patient index of this sCase - if ~isempty(patient_idx) - % there is a patient with same IPP - % check that this shoulder patient_id & side is already in the shoulder array - patient_id = patient(patient_idx).patient_id; % patient_id of existing patient - % find this patient_id in shoulder array - shoulder_idx = find([shoulder.patient_id] == patient_id); - % might be 0, 1, or 2 (R/L) - shoulder_idxN = length(shoulder_idx) == 1; % number of matching patient_id in shoulder - addShoulder = 0; % 1 to add the shoulder in the shoulder array - switch shoulder_idxN - case 0 - % this sCase patient is not in the shoulder array - % so the shoulder of this sCase is also not, so add it - addShoulder = 1; - case 1 - % this sCase patient is in the shoulder array - % so we have to check the shoulder side - if side ~= shoulder(shoulder_idx).side - % this shoulder side in not yet in the shoulder array - % so add it - addShoulder = 1; - end - % fprintf('\nSecond shoulder in %s', sCase); - case 2 - % both shoulder sides of this patient are in the - % shoulder array, meaning that this sCase is either - % a duplicate, or a multiple case of the same shoulder - % Nothing to add, but report for control - fprintf('\nDuplicate or multiple case in %s', sCaseE); - otherwise - % should not happend, so report in this case - fprintf('\nMore than 2 shoulders in %s', sCaseE); - end - if addShoulder - shoulder_id = shoulder_id + 1; - shoulder_idx = shoulder_id; - shoulder(shoulder_idx).shoulder_id = shoulder_id; - shoulder(shoulder_idx).patient_id = patient_id; - shoulder(shoulder_idx).side = side; - shoulder(shoulder_idx).comment = comment; - end - end - end -end - -fprintf('Done\n'); - -end - +function [ shoulder ] = shoulderFromExcel(patient, rawExcel) +%SHOULDERFROMEXCEL build the shoulder structure array from Excel and from +%the patient structure +% The output is a shoulder strucure array that has the same form as the +% shoulder table of the mySQL database + +fprintf('\nGet shoulder from Excel... '); + +% define houlder structure array +shoulder = struct(... + 'shoulder_id', [], ... + 'patient_id', [], ... + 'side', [], ... + 'comment', [] ... + ); + +% get column of variables +header = rawExcel(1,:); +sCase_id_col = find(strcmp([header],'sCase.id')); +IPP_col = find(strcmp([header],'anonymity.IPP')); +side_col = find(strcmp([header],'shoulder.side')); +comment_col = find(strcmp([header],'shoulder.comment')); + +% loop over rows of Excel table +[rowN, ~] = size(rawExcel); +shoulder_id = 0; +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + sCaseE = row{sCase_id_col}; % sCase id from Excel + side = row{side_col}; + % get shoulder comment + comment = row{comment_col}; + if isnan(comment) + comment = ''; + end + if ~isnan(sCaseE) & ~isnan(side) % check that sCase & side are defined + IPP = row{IPP_col}; + patient_idx = find([patient.IPP] == IPP); + % get patient index of this sCase + if ~isempty(patient_idx) + % there is a patient with same IPP + % check that this shoulder patient_id & side is already in the shoulder array + patient_id = patient(patient_idx).patient_id; % patient_id of existing patient + % find this patient_id in shoulder array + shoulder_idx = find([shoulder.patient_id] == patient_id); + % might be 0, 1, or 2 (R/L) + shoulder_idxN = length(shoulder_idx) == 1; % number of matching patient_id in shoulder + addShoulder = 0; % 1 to add the shoulder in the shoulder array + switch shoulder_idxN + case 0 + % this sCase patient is not in the shoulder array + % so the shoulder of this sCase is also not, so add it + addShoulder = 1; + case 1 + % this sCase patient is in the shoulder array + % so we have to check the shoulder side + if side ~= shoulder(shoulder_idx).side + % this shoulder side in not yet in the shoulder array + % so add it + addShoulder = 1; + end + % fprintf('\nSecond shoulder in %s', sCase); + case 2 + % both shoulder sides of this patient are in the + % shoulder array, meaning that this sCase is either + % a duplicate, or a multiple case of the same shoulder + % Nothing to add, but report for control + fprintf('\nDuplicate or multiple case in %s', sCaseE); + otherwise + % should not happend, so report in this case + fprintf('\nMore than 2 shoulders in %s', sCaseE); + end + if addShoulder + shoulder_id = shoulder_id + 1; + shoulder_idx = shoulder_id; + shoulder(shoulder_idx).shoulder_id = shoulder_id; + shoulder(shoulder_idx).patient_id = patient_id; + shoulder(shoulder_idx).side = side; + shoulder(shoulder_idx).comment = comment; + end + end + end +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/shoulderToSQL.m b/XLS_MySQL_DB/shoulderToSQL.m index d8d90f0..6b0af78 100644 --- a/XLS_MySQL_DB/shoulderToSQL.m +++ b/XLS_MySQL_DB/shoulderToSQL.m @@ -1,24 +1,24 @@ -function shoulderToSQL(shoulder, conn) -%SHOULDERTOSQL fill shoulder structure array in mySQL -% Detailed explanation goes here - -fprintf('\nshoulder to mySQL... '); - -colnames = {'shoulder_id','patient_id','side','comment'}; - -% Loop on patient structure array -for i=1:numel(shoulder) - shoulder_id = shoulder(i).shoulder_id; - patient_id = shoulder(i).patient_id; - side = shoulder(i).side; - comment = shoulder(i).comment; - - exdata = {shoulder_id,patient_id,side,comment}; - % insert data in SQL - datainsert(conn,'shoulder',colnames,exdata); -end - -fprintf('Done\n'); - -end - +function shoulderToSQL(shoulder, conn) +%SHOULDERTOSQL fill shoulder structure array in mySQL +% Detailed explanation goes here + +fprintf('\nshoulder to mySQL... '); + +colnames = {'shoulder_id','patient_id','side','comment'}; + +% Loop on patient structure array +for i=1:numel(shoulder) + shoulder_id = shoulder(i).shoulder_id; + patient_id = shoulder(i).patient_id; + side = shoulder(i).side; + comment = shoulder(i).comment; + + exdata = {shoulder_id,patient_id,side,comment}; + % insert data in SQL + datainsert(conn,'shoulder',colnames,exdata); +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/treatmentListFromExcel.m b/XLS_MySQL_DB/treatmentListFromExcel.m index 9381fe7..7650b7c 100644 --- a/XLS_MySQL_DB/treatmentListFromExcel.m +++ b/XLS_MySQL_DB/treatmentListFromExcel.m @@ -1,50 +1,50 @@ -function [ treatmentList ] = treatmentListFromExcel( rawExcel ) -%TREATEMENT built the tratement structure array that identify the treatments -% Detailed explanation goes here - -fprintf('\nGet treatmentList from Excel... '); - -% define treatment structure array -treatmentList = struct(... - 'treatmentList_id',[], ... - 'name' ,[], ... - 'description' ,[] ... - ); - -% define undefined treatment -treatmentList(1).treatmentList_id = 1; -treatmentList(1).name = 'undefined'; -treatmentList(1).description = 'The treatement is not defined yet'; - -% define no treatment -treatmentList(2).treatmentList_id = 2; -treatmentList(2).name = 'none'; -treatmentList(2).description = 'No planed treatment, for healthy shoulders'; - -% get column of variables -header = rawExcel(1,:); -treatmentName_col = find(strcmp([header],'treatment.name')); - -% loop over rows of Excel table -[rowN, ~] = size(rawExcel); -treatmentList_id = 2; -for row_idx = 2:rowN - row = rawExcel(row_idx, :); % get the entire row - treatmentName = row(treatmentName_col); - treatmentName = treatmentName{1}; - if ~isempty(treatmentName) & ~isnan(treatmentName) - sameTreatment = find(strcmp({treatmentList.name}, treatmentName)==1); - if isempty(sameTreatment) - treatmentList_id = treatmentList_id + 1; - treatmentList_idx = treatmentList_id; - treatmentList(treatmentList_idx).treatmentList_id = treatmentList_id; - treatmentList(treatmentList_idx).name = treatmentName; - treatmentList(treatmentList_idx).description = ''; - end - end -end - -fprintf('Done\n'); - -end - +function [ treatmentList ] = treatmentListFromExcel( rawExcel ) +%TREATEMENT built the tratement structure array that identify the treatments +% Detailed explanation goes here + +fprintf('\nGet treatmentList from Excel... '); + +% define treatment structure array +treatmentList = struct(... + 'treatmentList_id',[], ... + 'name' ,[], ... + 'description' ,[] ... + ); + +% define undefined treatment +treatmentList(1).treatmentList_id = 1; +treatmentList(1).name = 'undefined'; +treatmentList(1).description = 'The treatement is not defined yet'; + +% define no treatment +treatmentList(2).treatmentList_id = 2; +treatmentList(2).name = 'none'; +treatmentList(2).description = 'No planed treatment, for healthy shoulders'; + +% get column of variables +header = rawExcel(1,:); +treatmentName_col = find(strcmp([header],'treatment.name')); + +% loop over rows of Excel table +[rowN, ~] = size(rawExcel); +treatmentList_id = 2; +for row_idx = 2:rowN + row = rawExcel(row_idx, :); % get the entire row + treatmentName = row(treatmentName_col); + treatmentName = treatmentName{1}; + if ~isempty(treatmentName) & ~isnan(treatmentName) + sameTreatment = find(strcmp({treatmentList.name}, treatmentName)==1); + if isempty(sameTreatment) + treatmentList_id = treatmentList_id + 1; + treatmentList_idx = treatmentList_id; + treatmentList(treatmentList_idx).treatmentList_id = treatmentList_id; + treatmentList(treatmentList_idx).name = treatmentName; + treatmentList(treatmentList_idx).description = ''; + end + end +end + +fprintf('Done\n'); + +end + diff --git a/XLS_MySQL_DB/treatmentListToSQL.m b/XLS_MySQL_DB/treatmentListToSQL.m index ff58fee..1a59940 100644 --- a/XLS_MySQL_DB/treatmentListToSQL.m +++ b/XLS_MySQL_DB/treatmentListToSQL.m @@ -1,23 +1,23 @@ -function treatmentListToSQL(treatmentList, conn) -%TREATMENTTOSQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\ntreatmentList to mySQL...'); - -colnames = {'treatmentList_id','name','description'}; - -% Loop on pCase structure array -for i=1:numel(treatmentList) - treatmentList_id = treatmentList(i).treatmentList_id; - name = treatmentList(i).name; - description = treatmentList(i).description; - - exdata = {treatmentList_id,name,description}; - % insert data in SQL - datainsert(conn,'treatmentList',colnames,exdata); -end - -fprintf(' Done\n'); - -end - +function treatmentListToSQL(treatmentList, conn) +%TREATMENTTOSQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\ntreatmentList to mySQL...'); + +colnames = {'treatmentList_id','name','description'}; + +% Loop on pCase structure array +for i=1:numel(treatmentList) + treatmentList_id = treatmentList(i).treatmentList_id; + name = treatmentList(i).name; + description = treatmentList(i).description; + + exdata = {treatmentList_id,name,description}; + % insert data in SQL + datainsert(conn,'treatmentList',colnames,exdata); +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/treatmentToSQL.m b/XLS_MySQL_DB/treatmentToSQL.m index f8ad801..860ba3d 100644 --- a/XLS_MySQL_DB/treatmentToSQL.m +++ b/XLS_MySQL_DB/treatmentToSQL.m @@ -1,33 +1,33 @@ -function treatmentToSQL(treatment, conn) -%TREATMENTTOSQL Summary of this function goes here -% Detailed explanation goes here - -fprintf('\ntreatment to mySQL...'); - -% Empty treatment table and reset AUTO_INCREMENT -sqlquery = 'TRUNCATE treatment'; -curs = exec(conn,sqlquery); -if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); -end - -colnames = {'treatment_id','sCase_id','treatmentList_id','date','comment'}; - -% Loop on pCase structure array -for i=1:numel(treatment) - treatment_id = treatment(i).treatment_id; - sCase_id = treatment(i).sCase_id; - treatmentList_id = treatment(i).treatmentList_id; - date = treatment(i).date; - comment = treatment(i).comment; - - - exdata = {treatment_id,sCase_id,treatmentList_id,date,comment}; - % insert data in SQL - datainsert(conn,'treatment',colnames,exdata); -end - -fprintf(' Done\n'); - -end - +function treatmentToSQL(treatment, conn) +%TREATMENTTOSQL Summary of this function goes here +% Detailed explanation goes here + +fprintf('\ntreatment to mySQL...'); + +% Empty treatment table and reset AUTO_INCREMENT +sqlquery = 'TRUNCATE treatment'; +curs = exec(conn,sqlquery); +if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); +end + +colnames = {'treatment_id','sCase_id','treatmentList_id','date','comment'}; + +% Loop on pCase structure array +for i=1:numel(treatment) + treatment_id = treatment(i).treatment_id; + sCase_id = treatment(i).sCase_id; + treatmentList_id = treatment(i).treatmentList_id; + date = treatment(i).date; + comment = treatment(i).comment; + + + exdata = {treatment_id,sCase_id,treatmentList_id,date,comment}; + % insert data in SQL + datainsert(conn,'treatment',colnames,exdata); +end + +fprintf(' Done\n'); + +end + diff --git a/XLS_MySQL_DB/upsert/license.txt b/XLS_MySQL_DB/upsert/license.txt index 4e65529..c154413 100644 --- a/XLS_MySQL_DB/upsert/license.txt +++ b/XLS_MySQL_DB/upsert/license.txt @@ -1,24 +1,24 @@ -Copyright (c) 2015, Sven -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the distribution - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +Copyright (c) 2015, Sven +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/XLS_MySQL_DB/upsert/upsert.m b/XLS_MySQL_DB/upsert/upsert.m index 8ae2b3f..cee6bff 100644 --- a/XLS_MySQL_DB/upsert/upsert.m +++ b/XLS_MySQL_DB/upsert/upsert.m @@ -1,247 +1,247 @@ -function [insertMask, returnedKeys] = upsert(conn,tableName,fieldNames,keyFields,data, varargin) -% UPSERT inserts new and updates old data to a database table -% -% UPSERT(CONNECT,TABLENAME,FIELDNAMES,KEYFIELDS,DATA). -% CONNECT is a database connection object. TABLENAME is the database -% table. FIELDNAMES is a string array of database column names. KEYFIELDS -% is the list of primary key fields that must be matched to perform an -% UPDATE rather than an INSERT. It may be given as a logical (or 0s, 1s) -% array the same length as FIELDNAMES, or a string or cell array of -% strings of key column names (in which case KEYFIELDS must be a subset -% of FIELDNAMES). DATA is a MATLAB cell array. -% -% INSERTEDMASK = UPSERT(...) returns a logical vector with one element for -% each row of DATA, indicating whether the "upsert" operation meant that -% corresponding row of DATA was inserted (TRUE) or merely updated (FALSE). -% -% UPSERT(...,'dateFields',DATEFIELDS) allows a DATE type field to be used -% as one of the primary key fields. DATEFIELDS is specified equivalently to -% KEYFIELDS. Each primary key DATE type field's data MUST be given as an -% ANSI string literal (i.e., '1998-12-25'), rather than a MATLAB datenum -% number or a differently formatted date string. -% (see http://docs.oracle.com/cd/E11882_01/server.112/e26088/sql_elements003.htm#SQLRF51062) -% -% UPSERT(...,'updateFcn',FUNCTION_HANDLE) -% UPSERT(...,'insertFcn',FUNCTION_HANDLE) optionally allows replacement -% functions for the default use of MATLAB's "update" and "fastinsert". -% -% UPSERT(...,'debug',true) prints out diagnostic information. -% -% Example: -% -% Imagine a database table "PHONE_NOS" with data like: -% PERSONID | TYPE | NUMBER -% 1 'HOME' 1234567 -% 1 'MOB' 1222222 -% 2 'HOME' 9888888 -% -% Then the MATLAB commands: -% newNos = {1 'MOB' 4444444 -% 2 'MOB' 5555555}; -% INS = upsert(conn, 'PHONE_NOS', {'PERSONID','TYPE','NUMBER'}, [1 1 0], newNos) -% -% Would result in the table having contents: -% PERSONID | TYPE | NUMBER -% 1 'HOME' 1234567 -% 1 'MOB' 4444444 -% 2 'HOME' 9888888 -% 2 'MOB' 5555555 -% -% The returned variable (INS) would be [0; 1], meaning the second row was -% updated, the first row was inserted. - -% Author: Sven Holcombe, 2015-09-01 - -% Handle user configuration input -IP = inputParser; -IP.addParameter('updateFcn', @update, @(x)isa(x,'function_handle')); -IP.addParameter('insertFcn', @fastinsert, @(x)isa(x,'function_handle')); -IP.addParameter('dateFields', []); -IP.addParameter('returnKeys',false,@(x)iscellstr(x)||(isscalar(x)&&nnz(x==[0 1])==1)); -IP.addParameter('debug', false); -IP.parse(varargin{:}); -doPrint = IP.Results.debug; -updateFcn = IP.Results.updateFcn; -insertFcn = IP.Results.insertFcn; -doReturnKeys = iscellstr(IP.Results.returnKeys) || IP.Results.returnKeys; -if ~doReturnKeys && nargout>1 - warning('upsert:noKeys', 'Second output (returned keys) will be empty because ''returnKeys'' option was not set') -end - -% Firstly, handle large data sets. Later we will use an IN clause with a -% comma-separated list to check key fields. Oracle has a limit of 1000 -% items in a list, so let's process 1000 at a time at most. Not an optimal -% solution but best solutions will differ by database flavour so this is -% adequate for the moment. -numRows = size(data,1); -returnedKeys = zeros(numRows,0); -if numRows>1000 - insertMask = true(numRows,1); - chunks = unique([1:999:numRows numRows+1]); - for i = 1:length(chunks)-1 - dataInds = chunks(i):chunks(i+1)-1; - [insertMask(dataInds), rk] = ... - upsert(conn,tableName,fieldNames,keyFields,data(dataInds,:), varargin{:}); - if doReturnKeys - if i==1 - returnedKeys = zeros(numRows,size(rk,2)); - end - returnedKeys(dataInds,:) = rk; - end - end - return; -end - -% keyFields may be input as: -% - A string: 'id' -% - A cellstr: {'id','groupNo'} -% - A logical mask the same size as fieldNames (true where field is a key) -% - Indices into fieldNames of the key fields -% keyFields will be transformed to the indices representation below -keyFields = convertSubsetOfFieldsToIndices(keyFields, fieldNames); -% Get a numeric array of which fields are DATE types for comparison -dateFields = convertSubsetOfFieldsToIndices(IP.Results.dateFields, fieldNames); - -if isempty(data) - insertMask = true(0,1); - return; -end - -% Currently it's easier (if perhaps slightly slower) to treat data as a -% cell regardless of what format it was provided as. -if isnumeric(data) - data = num2cell(data); -end - -% Which fields are keyFields? Build lists of them for an SQL fetch -keyFieldsCell = fieldNames(keyFields); -keyFieldsIsnumeric = cellfun(@(x)isnumeric(x)||islogical(x), data(1,keyFields)); -keyFieldsIsdatestr = ismember(keyFields, dateFields); -keyFieldsListStr = sprintf('%s,',keyFieldsCell{:}); - -% We don't want to gather the whole table. Only the rows matching the -% primary key fields. This is most generalised by building IN () lists from -% a single database query, rather than sending/recieving one query for -% every row of "data" being upserted. -inClauses = cell(length(keyFields),1); -for i=1:length(keyFields) - if keyFieldsIsnumeric(i) - inSet = unique([data{:,keyFields(i)}]); - if all(inSet==round(inSet)) - inStr = sprintf('%d,',inSet); - else - inStr = sprintf('%g,',inSet); - end - elseif ischar(data{1,keyFields(i)}) - inSet = unique(data(:,keyFields(i))); - if keyFieldsIsdatestr(i) - inStr = sprintf('date ''%s'',',inSet{:}); - else - inStr = sprintf('''%s'',',inSet{:}); - end - else - error('upsert:badKey', 'Primary key field cannot contain %s data',class(data{1,keyFields(i)})) - end - if length(inSet)>1 - inClauses{i} = sprintf('%s IN (%s)', keyFieldsCell{i}, inStr(1:end-1)); - else - inClauses{i} = sprintf('%s = %s', keyFieldsCell{i}, inStr(1:end-1)); - end -end - -% Fetch all table rows potentially matching the data we want to upsert -fetchWhereClause = sprintf(' %s AND', inClauses{:}); -fetchSqlStr = sprintf('SELECT %s FROM %s WHERE %s', keyFieldsListStr(1:end-1), tableName, fetchWhereClause(1:end-3)); -if doPrint, fprintf('Fetching %s data in %s matching given data...', keyFieldsListStr(1:end-1), tableName), end -fetchedData = fetch(conn, fetchSqlStr); -if doPrint, fprintf(' done. (%d potential matches found)\n', size(fetchedData,1)), end - -% Build a map of which rows to be upserted already exist in the table. -insertMask = true(size(data,1),1); % One -if ~isempty(fetchedData) - eqMap = false(size(data,1), size(fetchedData,1), length(keyFields)); - for i = 1:length(keyFields) - if keyFieldsIsnumeric(i) - thisUpsertData = cell2mat(data(:,keyFields(i))); - thisFetchedData = cast(cell2mat(fetchedData(:,i)), 'like',thisUpsertData); - eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); - elseif keyFieldsIsdatestr(i) - thisUpsertData = datenum(data(:,keyFields(i))); - thisFetchedData = datenum(fetchedData(:,i)); - eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); - else - thisUpsertData = data(:,keyFields(i)); - thisFetchedData = fetchedData(:,i)'; - eqCell = cellfun(@(x)strcmp(x, thisFetchedData), thisUpsertData, 'Un',0); - eqMap(:,:,i) = cat(1, eqCell{:}); - end - end - pkeysMatchMap = all(eqMap,3); - insertMask = ~any(pkeysMatchMap,2); -end - -% First find any data rows that do NOT yet exist in table. Insert them. -if any(insertMask) - if doPrint, fprintf('Inserting %d data rows not currently in %s...', nnz(insertMask), tableName), end - if doReturnKeys - insertedRK = insertFcn(conn,tableName,fieldNames,data(insertMask,:),'returnKeys',IP.Results.returnKeys); - else - insertFcn(conn,tableName,fieldNames,data(insertMask,:)); - end - if doPrint, fprintf(' done.\n'), end -else - insertedRK = []; -end - -% Next, update ALL rows to the values given in data. First build WHERE. -whereEqClauses = cell(numRows, length(keyFields)); -for i=1:length(keyFields) - if keyFieldsIsnumeric(i) - whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = %g', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); - elseif keyFieldsIsdatestr(i) - whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = date ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); - else - whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); - end -end -dataWhereClauses = cellfun(@(strs)sprintf(' %s AND',strs{:}), num2cell(whereEqClauses,2),'Un',0); -dataWhereClauses = cellfun(@(str)['WHERE ' str(1:end-3)], dataWhereClauses, 'Un',0); - -% Next, run the update on all the NON-keyField fields (since the key fields -% themselves are being matched, so won't change). Note that the "update" -% function can be replaced by a user's modified update function. -otherFields = setdiff(1:length(fieldNames), keyFields); -if doPrint, fprintf('Updating %d data rows (%d new, %d old) in %s...', length(insertMask), nnz(insertMask), nnz(~insertMask), tableName), end -if doReturnKeys - updatedRK = updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask),'returnKeys',IP.Results.returnKeys); - returnedKeys = zeros(size(cat(1,insertedRK, updatedRK))); - if any(insertMask) - returnedKeys(insertMask,:) = insertedRK; - end - if any(~insertMask) - returnedKeys(~insertMask,:) = updatedRK; - end -else - updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask)); -end - -if doPrint, fprintf(' done.\n'); end - - -function subFields = convertSubsetOfFieldsToIndices(subFields, allFields) -% convert subFields from various classes to a numeric index of allFields -% may be input as: -% - A string: 'id' -% - A cellstr: {'id','groupNo'} -% - A logical (or 0,1) mask the same size as fieldNames -% - Indices into fieldNames of the key fields -% keyFields will be transformed to the indices representation below -if ischar(subFields) || iscellstr(subFields) - subFields = ismember(upper(allFields), upper(subFields)); -end -if isnumeric(subFields) && (any(subFields==0) || nnz(subFields==1)>1) - subFields = logical(subFields); -end -if islogical(subFields) - subFields = find(subFields); +function [insertMask, returnedKeys] = upsert(conn,tableName,fieldNames,keyFields,data, varargin) +% UPSERT inserts new and updates old data to a database table +% +% UPSERT(CONNECT,TABLENAME,FIELDNAMES,KEYFIELDS,DATA). +% CONNECT is a database connection object. TABLENAME is the database +% table. FIELDNAMES is a string array of database column names. KEYFIELDS +% is the list of primary key fields that must be matched to perform an +% UPDATE rather than an INSERT. It may be given as a logical (or 0s, 1s) +% array the same length as FIELDNAMES, or a string or cell array of +% strings of key column names (in which case KEYFIELDS must be a subset +% of FIELDNAMES). DATA is a MATLAB cell array. +% +% INSERTEDMASK = UPSERT(...) returns a logical vector with one element for +% each row of DATA, indicating whether the "upsert" operation meant that +% corresponding row of DATA was inserted (TRUE) or merely updated (FALSE). +% +% UPSERT(...,'dateFields',DATEFIELDS) allows a DATE type field to be used +% as one of the primary key fields. DATEFIELDS is specified equivalently to +% KEYFIELDS. Each primary key DATE type field's data MUST be given as an +% ANSI string literal (i.e., '1998-12-25'), rather than a MATLAB datenum +% number or a differently formatted date string. +% (see http://docs.oracle.com/cd/E11882_01/server.112/e26088/sql_elements003.htm#SQLRF51062) +% +% UPSERT(...,'updateFcn',FUNCTION_HANDLE) +% UPSERT(...,'insertFcn',FUNCTION_HANDLE) optionally allows replacement +% functions for the default use of MATLAB's "update" and "fastinsert". +% +% UPSERT(...,'debug',true) prints out diagnostic information. +% +% Example: +% +% Imagine a database table "PHONE_NOS" with data like: +% PERSONID | TYPE | NUMBER +% 1 'HOME' 1234567 +% 1 'MOB' 1222222 +% 2 'HOME' 9888888 +% +% Then the MATLAB commands: +% newNos = {1 'MOB' 4444444 +% 2 'MOB' 5555555}; +% INS = upsert(conn, 'PHONE_NOS', {'PERSONID','TYPE','NUMBER'}, [1 1 0], newNos) +% +% Would result in the table having contents: +% PERSONID | TYPE | NUMBER +% 1 'HOME' 1234567 +% 1 'MOB' 4444444 +% 2 'HOME' 9888888 +% 2 'MOB' 5555555 +% +% The returned variable (INS) would be [0; 1], meaning the second row was +% updated, the first row was inserted. + +% Author: Sven Holcombe, 2015-09-01 + +% Handle user configuration input +IP = inputParser; +IP.addParameter('updateFcn', @update, @(x)isa(x,'function_handle')); +IP.addParameter('insertFcn', @fastinsert, @(x)isa(x,'function_handle')); +IP.addParameter('dateFields', []); +IP.addParameter('returnKeys',false,@(x)iscellstr(x)||(isscalar(x)&&nnz(x==[0 1])==1)); +IP.addParameter('debug', false); +IP.parse(varargin{:}); +doPrint = IP.Results.debug; +updateFcn = IP.Results.updateFcn; +insertFcn = IP.Results.insertFcn; +doReturnKeys = iscellstr(IP.Results.returnKeys) || IP.Results.returnKeys; +if ~doReturnKeys && nargout>1 + warning('upsert:noKeys', 'Second output (returned keys) will be empty because ''returnKeys'' option was not set') +end + +% Firstly, handle large data sets. Later we will use an IN clause with a +% comma-separated list to check key fields. Oracle has a limit of 1000 +% items in a list, so let's process 1000 at a time at most. Not an optimal +% solution but best solutions will differ by database flavour so this is +% adequate for the moment. +numRows = size(data,1); +returnedKeys = zeros(numRows,0); +if numRows>1000 + insertMask = true(numRows,1); + chunks = unique([1:999:numRows numRows+1]); + for i = 1:length(chunks)-1 + dataInds = chunks(i):chunks(i+1)-1; + [insertMask(dataInds), rk] = ... + upsert(conn,tableName,fieldNames,keyFields,data(dataInds,:), varargin{:}); + if doReturnKeys + if i==1 + returnedKeys = zeros(numRows,size(rk,2)); + end + returnedKeys(dataInds,:) = rk; + end + end + return; +end + +% keyFields may be input as: +% - A string: 'id' +% - A cellstr: {'id','groupNo'} +% - A logical mask the same size as fieldNames (true where field is a key) +% - Indices into fieldNames of the key fields +% keyFields will be transformed to the indices representation below +keyFields = convertSubsetOfFieldsToIndices(keyFields, fieldNames); +% Get a numeric array of which fields are DATE types for comparison +dateFields = convertSubsetOfFieldsToIndices(IP.Results.dateFields, fieldNames); + +if isempty(data) + insertMask = true(0,1); + return; +end + +% Currently it's easier (if perhaps slightly slower) to treat data as a +% cell regardless of what format it was provided as. +if isnumeric(data) + data = num2cell(data); +end + +% Which fields are keyFields? Build lists of them for an SQL fetch +keyFieldsCell = fieldNames(keyFields); +keyFieldsIsnumeric = cellfun(@(x)isnumeric(x)||islogical(x), data(1,keyFields)); +keyFieldsIsdatestr = ismember(keyFields, dateFields); +keyFieldsListStr = sprintf('%s,',keyFieldsCell{:}); + +% We don't want to gather the whole table. Only the rows matching the +% primary key fields. This is most generalised by building IN () lists from +% a single database query, rather than sending/recieving one query for +% every row of "data" being upserted. +inClauses = cell(length(keyFields),1); +for i=1:length(keyFields) + if keyFieldsIsnumeric(i) + inSet = unique([data{:,keyFields(i)}]); + if all(inSet==round(inSet)) + inStr = sprintf('%d,',inSet); + else + inStr = sprintf('%g,',inSet); + end + elseif ischar(data{1,keyFields(i)}) + inSet = unique(data(:,keyFields(i))); + if keyFieldsIsdatestr(i) + inStr = sprintf('date ''%s'',',inSet{:}); + else + inStr = sprintf('''%s'',',inSet{:}); + end + else + error('upsert:badKey', 'Primary key field cannot contain %s data',class(data{1,keyFields(i)})) + end + if length(inSet)>1 + inClauses{i} = sprintf('%s IN (%s)', keyFieldsCell{i}, inStr(1:end-1)); + else + inClauses{i} = sprintf('%s = %s', keyFieldsCell{i}, inStr(1:end-1)); + end +end + +% Fetch all table rows potentially matching the data we want to upsert +fetchWhereClause = sprintf(' %s AND', inClauses{:}); +fetchSqlStr = sprintf('SELECT %s FROM %s WHERE %s', keyFieldsListStr(1:end-1), tableName, fetchWhereClause(1:end-3)); +if doPrint, fprintf('Fetching %s data in %s matching given data...', keyFieldsListStr(1:end-1), tableName), end +fetchedData = fetch(conn, fetchSqlStr); +if doPrint, fprintf(' done. (%d potential matches found)\n', size(fetchedData,1)), end + +% Build a map of which rows to be upserted already exist in the table. +insertMask = true(size(data,1),1); % One +if ~isempty(fetchedData) + eqMap = false(size(data,1), size(fetchedData,1), length(keyFields)); + for i = 1:length(keyFields) + if keyFieldsIsnumeric(i) + thisUpsertData = cell2mat(data(:,keyFields(i))); + thisFetchedData = cast(cell2mat(fetchedData(:,i)), 'like',thisUpsertData); + eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); + elseif keyFieldsIsdatestr(i) + thisUpsertData = datenum(data(:,keyFields(i))); + thisFetchedData = datenum(fetchedData(:,i)); + eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); + else + thisUpsertData = data(:,keyFields(i)); + thisFetchedData = fetchedData(:,i)'; + eqCell = cellfun(@(x)strcmp(x, thisFetchedData), thisUpsertData, 'Un',0); + eqMap(:,:,i) = cat(1, eqCell{:}); + end + end + pkeysMatchMap = all(eqMap,3); + insertMask = ~any(pkeysMatchMap,2); +end + +% First find any data rows that do NOT yet exist in table. Insert them. +if any(insertMask) + if doPrint, fprintf('Inserting %d data rows not currently in %s...', nnz(insertMask), tableName), end + if doReturnKeys + insertedRK = insertFcn(conn,tableName,fieldNames,data(insertMask,:),'returnKeys',IP.Results.returnKeys); + else + insertFcn(conn,tableName,fieldNames,data(insertMask,:)); + end + if doPrint, fprintf(' done.\n'), end +else + insertedRK = []; +end + +% Next, update ALL rows to the values given in data. First build WHERE. +whereEqClauses = cell(numRows, length(keyFields)); +for i=1:length(keyFields) + if keyFieldsIsnumeric(i) + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = %g', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + elseif keyFieldsIsdatestr(i) + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = date ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + else + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + end +end +dataWhereClauses = cellfun(@(strs)sprintf(' %s AND',strs{:}), num2cell(whereEqClauses,2),'Un',0); +dataWhereClauses = cellfun(@(str)['WHERE ' str(1:end-3)], dataWhereClauses, 'Un',0); + +% Next, run the update on all the NON-keyField fields (since the key fields +% themselves are being matched, so won't change). Note that the "update" +% function can be replaced by a user's modified update function. +otherFields = setdiff(1:length(fieldNames), keyFields); +if doPrint, fprintf('Updating %d data rows (%d new, %d old) in %s...', length(insertMask), nnz(insertMask), nnz(~insertMask), tableName), end +if doReturnKeys + updatedRK = updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask),'returnKeys',IP.Results.returnKeys); + returnedKeys = zeros(size(cat(1,insertedRK, updatedRK))); + if any(insertMask) + returnedKeys(insertMask,:) = insertedRK; + end + if any(~insertMask) + returnedKeys(~insertMask,:) = updatedRK; + end +else + updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask)); +end + +if doPrint, fprintf(' done.\n'); end + + +function subFields = convertSubsetOfFieldsToIndices(subFields, allFields) +% convert subFields from various classes to a numeric index of allFields +% may be input as: +% - A string: 'id' +% - A cellstr: {'id','groupNo'} +% - A logical (or 0,1) mask the same size as fieldNames +% - Indices into fieldNames of the key fields +% keyFields will be transformed to the indices representation below +if ischar(subFields) || iscellstr(subFields) + subFields = ismember(upper(allFields), upper(subFields)); +end +if isnumeric(subFields) && (any(subFields==0) || nnz(subFields==1)>1) + subFields = logical(subFields); +end +if islogical(subFields) + subFields = find(subFields); end \ No newline at end of file diff --git a/anatomy/HumeruSphere.m b/anatomy/HumeruSphere.m index 335bd84..0892dfd 100644 --- a/anatomy/HumeruSphere.m +++ b/anatomy/HumeruSphere.m @@ -1,118 +1,118 @@ -%% Humerusphere -% -% This script creates a tcl text file named "SphereScript.tcl" to display the humerus sphere in -% Amira. -% This script is used in the "Anatomical measurements with Amira". - -% Subject is the patient number -% Type is 'normal' or 'pathologic' - - - -function HumeruSphere(subject,type) - -directory = '../../../data'; % Define directory - -switch type - - - case 'pathologic' - type = 'P'; - folder = sprintf('P%03d',subject); % format the patient name to P### - subjectString = num2str(subject); - levelDir1 = str2num(subjectString(1)); - levelDir2 = str2num(subjectString(2)); - - % Loop over the list of patients and extract the complete name of the specific patient - listing = dir(sprintf('%s/%s/%d/%d', directory, type, levelDir1, levelDir2)); - for i = 1:1:numel(listing) - name = listing(i).name; - t = strfind (name, folder); - if t == 1; - patientName = listing(i).name; - end - end - - % Import HumeralHead landmarks - if exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 - newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); - HH = newData1.data; - - elseif exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 - warning('Using old humeral landmarks definition') - newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); - HH = newData1.data; - else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find humeral head landmarks file: %s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder) - end - - - - case 'normal' - type = 'N'; - folder = sprintf('N%03d',subject); % format the patient name to N### - subjectString = num2str(subject); - levelDir1 = str2num(subjectString(1)); - levelDir2 = str2num(subjectString(2)); - - % Loop over the list of patients and extract the complete name of the specific patient - listing = dir(sprintf('%s/%s/%d/%d', directory, type, levelDir1, levelDir2)); - for i = 1:1:numel(listing) - name = listing(i).name; - t = strfind (name, folder); - if t == 1; - patientName = listing(i).name; - end - end - - if exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 - newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); - HH = newData1.data; - - elseif exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 - warning('Using old humeral landmarks definition') - newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); - HH = newData1.data; - else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find humeral head landmarks file: %s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder) - end - - otherwise - error('Unexpected shoulder type.'); -end - -%% Fit sphere on Humeral Head landmarks -% [HC,HHRadius,HHResiduals] = fitSphere(HH); -[HC,HHRadius,~, ~] = fitSphere2(HH); - -mkdir('Generated_Amira_TCL_Scripts/HumerusSphereScripts') - - -fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\HumerusSphereScripts\\SphereScript_%s.tcl',folder),'w'); -fprintf(fid,'remove CreateSphere%s HumSphere%s.surf SphereView%s Intersect\n', folder, folder, folder); -fprintf(fid,'create HxCreateSphere {CreateSphere%s}\n', folder); -fprintf(fid,'CreateSphere%s setIconPosition 20 520\n', folder); -fprintf(fid,'CreateSphere%s radius setMinMax 0 50\n', folder); -fprintf(fid,'CreateSphere%s radius setValue %f\n', folder, HHRadius); -fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', folder, HC(1)); -fprintf(fid,'CreateSphere%s coords setValue 1 %f\n', folder, HC(2)); -fprintf(fid,'CreateSphere%s coords setValue 2 %f\n', folder, HC(3)); -fprintf(fid,'CreateSphere%s fire\n', folder); -fprintf(fid,'[ {CreateSphere%s} create\n ] setLabel {HumSphere%s.surf}\n', folder, folder); -fprintf(fid,'HumSphere%s.surf setIconPosition 200 520\n', folder); -fprintf(fid,'HumSphere%s.surf master connect CreateSphere%s\n', folder, folder); -fprintf(fid,'HumSphere%s.surf fire\n', folder); -fprintf(fid,'create HxDisplaySurface {SphereView%s}\n', folder); -fprintf(fid,'SphereView%s setIconPosition 400 520\n', folder); -fprintf(fid,'SphereView%s data connect HumSphere%s.surf\n', folder, folder); -fprintf(fid,'SphereView%s drawStyle setValue 4\n', folder); -fprintf(fid,'SphereView%s fire\n', folder); -fprintf(fid,'create HxOverlayGrid Intersect\n'); -fprintf(fid,'{Intersect} {data} connect HumSphere%s.surf\n', folder); -fprintf(fid,'{Intersect} {module} connect ObliqueSlice4\n'); -fprintf(fid,'{Intersect} {lineWidth} setIndex 0 2\n'); -fprintf(fid,'ObliqueSlice4 setPlane %f %f %f 1 0 0 0 1 0\n', HC); -fprintf(fid,'ObliqueSlice4 fire\n'); +%% Humerusphere +% +% This script creates a tcl text file named "SphereScript.tcl" to display the humerus sphere in +% Amira. +% This script is used in the "Anatomical measurements with Amira". + +% Subject is the patient number +% Type is 'normal' or 'pathologic' + + + +function HumeruSphere(subject,type) + +directory = '../../../data'; % Define directory + +switch type + + + case 'pathologic' + type = 'P'; + folder = sprintf('P%03d',subject); % format the patient name to P### + subjectString = num2str(subject); + levelDir1 = str2num(subjectString(1)); + levelDir2 = str2num(subjectString(2)); + + % Loop over the list of patients and extract the complete name of the specific patient + listing = dir(sprintf('%s/%s/%d/%d', directory, type, levelDir1, levelDir2)); + for i = 1:1:numel(listing) + name = listing(i).name; + t = strfind (name, folder); + if t == 1; + patientName = listing(i).name; + end + end + + % Import HumeralHead landmarks + if exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 + newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); + HH = newData1.data; + + elseif exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 + warning('Using old humeral landmarks definition') + newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); + HH = newData1.data; + else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find humeral head landmarks file: %s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder) + end + + + + case 'normal' + type = 'N'; + folder = sprintf('N%03d',subject); % format the patient name to N### + subjectString = num2str(subject); + levelDir1 = str2num(subjectString(1)); + levelDir2 = str2num(subjectString(2)); + + % Loop over the list of patients and extract the complete name of the specific patient + listing = dir(sprintf('%s/%s/%d/%d', directory, type, levelDir1, levelDir2)); + for i = 1:1:numel(listing) + name = listing(i).name; + t = strfind (name, folder); + if t == 1; + patientName = listing(i).name; + end + end + + if exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 + newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); + HH = newData1.data; + + elseif exist(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder),'file') == 2 + warning('Using old humeral landmarks definition') + newData1 = importdata(sprintf('%s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder), ' ', 14); + HH = newData1.data; + else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find humeral head landmarks file: %s/%s/%d/%d/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory, type, levelDir1, levelDir2, patientName,patientName,folder) + end + + otherwise + error('Unexpected shoulder type.'); +end + +%% Fit sphere on Humeral Head landmarks +% [HC,HHRadius,HHResiduals] = fitSphere(HH); +[HC,HHRadius,~, ~] = fitSphere2(HH); + +mkdir('Generated_Amira_TCL_Scripts/HumerusSphereScripts') + + +fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\HumerusSphereScripts\\SphereScript_%s.tcl',folder),'w'); +fprintf(fid,'remove CreateSphere%s HumSphere%s.surf SphereView%s Intersect\n', folder, folder, folder); +fprintf(fid,'create HxCreateSphere {CreateSphere%s}\n', folder); +fprintf(fid,'CreateSphere%s setIconPosition 20 520\n', folder); +fprintf(fid,'CreateSphere%s radius setMinMax 0 50\n', folder); +fprintf(fid,'CreateSphere%s radius setValue %f\n', folder, HHRadius); +fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', folder, HC(1)); +fprintf(fid,'CreateSphere%s coords setValue 1 %f\n', folder, HC(2)); +fprintf(fid,'CreateSphere%s coords setValue 2 %f\n', folder, HC(3)); +fprintf(fid,'CreateSphere%s fire\n', folder); +fprintf(fid,'[ {CreateSphere%s} create\n ] setLabel {HumSphere%s.surf}\n', folder, folder); +fprintf(fid,'HumSphere%s.surf setIconPosition 200 520\n', folder); +fprintf(fid,'HumSphere%s.surf master connect CreateSphere%s\n', folder, folder); +fprintf(fid,'HumSphere%s.surf fire\n', folder); +fprintf(fid,'create HxDisplaySurface {SphereView%s}\n', folder); +fprintf(fid,'SphereView%s setIconPosition 400 520\n', folder); +fprintf(fid,'SphereView%s data connect HumSphere%s.surf\n', folder, folder); +fprintf(fid,'SphereView%s drawStyle setValue 4\n', folder); +fprintf(fid,'SphereView%s fire\n', folder); +fprintf(fid,'create HxOverlayGrid Intersect\n'); +fprintf(fid,'{Intersect} {data} connect HumSphere%s.surf\n', folder); +fprintf(fid,'{Intersect} {module} connect ObliqueSlice4\n'); +fprintf(fid,'{Intersect} {lineWidth} setIndex 0 2\n'); +fprintf(fid,'ObliqueSlice4 setPlane %f %f %f 1 0 0 0 1 0\n', HC); +fprintf(fid,'ObliqueSlice4 fire\n'); fclose(fid); \ No newline at end of file diff --git a/anatomy/rebuildDatabaseScapulaMeasurements.m b/anatomy/rebuildDatabaseScapulaMeasurements.m index da26fe3..d364b4b 100644 --- a/anatomy/rebuildDatabaseScapulaMeasurements.m +++ b/anatomy/rebuildDatabaseScapulaMeasurements.m @@ -1,25 +1,25 @@ -% This script builds a cell array listing all caseIDs in the CT database -% for which an Amira folder exists and scapula measurements can be -% computed. Then computes scapula anatomical data for these cases. - -CTDatabaseLocation = 'Z://data'; % Location of the CT database -CaseType = ['N';'P']; % Folders to be reconstructed - -CaseIDsList = cell(0,0); - -for i=1:length(CaseType) %Loop - for j=0:9 - for k=0:9 - listDirInCurrDir = dir([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' CaseType(i) '*']); - if (~isempty(listDirInCurrDir)) - for m=1:length(listDirInCurrDir) - if exist([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' listDirInCurrDir(m).name '/CT-' listDirInCurrDir(m).name '-1/amira'],'dir') == 7 %if a folder "amira" exist, the case ID is added to the list - CaseIDsList{end+1,1} = strtok(listDirInCurrDir(m).name,'-'); - end - end - end - end - end -end - +% This script builds a cell array listing all caseIDs in the CT database +% for which an Amira folder exists and scapula measurements can be +% computed. Then computes scapula anatomical data for these cases. + +CTDatabaseLocation = 'Z://data'; % Location of the CT database +CaseType = ['N';'P']; % Folders to be reconstructed + +CaseIDsList = cell(0,0); + +for i=1:length(CaseType) %Loop + for j=0:9 + for k=0:9 + listDirInCurrDir = dir([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' CaseType(i) '*']); + if (~isempty(listDirInCurrDir)) + for m=1:length(listDirInCurrDir) + if exist([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' listDirInCurrDir(m).name '/CT-' listDirInCurrDir(m).name '-1/amira'],'dir') == 7 %if a folder "amira" exist, the case ID is added to the list + CaseIDsList{end+1,1} = strtok(listDirInCurrDir(m).name,'-'); + end + end + end + end + end +end + scapula_measure(CaseIDsList); \ No newline at end of file diff --git a/anatomy/scapula_addmeasure.m b/anatomy/scapula_addmeasure.m index ae8c44c..b9ffeb4 100644 --- a/anatomy/scapula_addmeasure.m +++ b/anatomy/scapula_addmeasure.m @@ -1,91 +1,91 @@ -%% scapula_measure -% -% This script returns additional measurements from the Amira files. -% 3 differents output are possible: - -% - 'References' creates a text file in References folder containing coordinates to built in Solidworks 2 Coordinates -% Systems. One for Abaqus and one for the scapula. There are two -% additional coordinates to built the glenoid centerline. -% -% - 'density' creates a textfile in CylinderReferences folder containing -% an Amira script used in the density measurements protocol. It is needed to adjust the Cylinder at the correct position and with the correct size. -% -% - 'obliqueSlice' creates a textfile in obliqueSlice folder containing an -% Amira script used in Amira to obtain an image of the Friedman plane (2D -% glenoid orientation measurement) and an image for muscles measurement. -% -% - 'display' creates a textfile in "display" folder containing an Amira -% script used to display the glenoid sphere and the scapula plane. - - -% Author: EPFL-LBO -% Date: 2016-09-05 -% -%% - -% Output is 'References' or 'obliqueSlice' 'display' or 'density' -% Type is 'normal' or 'pathologic' -% Subject is the patient number - - -function scapula_addmeasure(output,type,subject) - -directory = '../../../data'; % Define directory - -switch type - case 'pathologic' - location = 'P'; - CTn = sprintf('P%d',str2num(subject)); % format the patient name to P### - levelDir1 = str2num(subject(1)); - levelDir2 = str2num(subject(2)); - - - finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); - - listing = dir(sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2)); - for i = 1:1:numel(listing) - name = listing(i).name; - t = strfind(name, CTn); - if t == 1; - patientName = listing(i).name; - end - end - - - case 'normal' - location = 'N'; - CTn = sprintf('N%03d',subject); % format the patient name to N### - patient = num2str(subject); - levelDir1 = str2num(patient(1)); - levelDir2 = str2num(patient(2)); - finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); - - listing = dir(sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2)); - for i = 1:1:numel(listing) - name = listing(i).name; - t = strfind(name, CTn); - if t == 1; - patientName = listing(i).name; - end - end -end - -switch output - - case 'References' - output = 'References'; - scapula_calculation(patientName, finalDirectory, location, output); - - case 'obliqueSlice' - output = 'obliqueSlice'; - scapula_calculation(patientName, finalDirectory, location, output); - - case 'density' - output = 'density'; - scapula_calculation(patientName, finalDirectory, location, output); - - case 'display' - output = 'display'; - scapula_calculation(patientName, finalDirectory, location, output); -end +%% scapula_measure +% +% This script returns additional measurements from the Amira files. +% 3 differents output are possible: + +% - 'References' creates a text file in References folder containing coordinates to built in Solidworks 2 Coordinates +% Systems. One for Abaqus and one for the scapula. There are two +% additional coordinates to built the glenoid centerline. +% +% - 'density' creates a textfile in CylinderReferences folder containing +% an Amira script used in the density measurements protocol. It is needed to adjust the Cylinder at the correct position and with the correct size. +% +% - 'obliqueSlice' creates a textfile in obliqueSlice folder containing an +% Amira script used in Amira to obtain an image of the Friedman plane (2D +% glenoid orientation measurement) and an image for muscles measurement. +% +% - 'display' creates a textfile in "display" folder containing an Amira +% script used to display the glenoid sphere and the scapula plane. + + +% Author: EPFL-LBO +% Date: 2016-09-05 +% +%% + +% Output is 'References' or 'obliqueSlice' 'display' or 'density' +% Type is 'normal' or 'pathologic' +% Subject is the patient number + + +function scapula_addmeasure(output,type,subject) + +directory = '../../../data'; % Define directory + +switch type + case 'pathologic' + location = 'P'; + CTn = sprintf('P%d',str2num(subject)); % format the patient name to P### + levelDir1 = str2num(subject(1)); + levelDir2 = str2num(subject(2)); + + + finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); + + listing = dir(sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2)); + for i = 1:1:numel(listing) + name = listing(i).name; + t = strfind(name, CTn); + if t == 1; + patientName = listing(i).name; + end + end + + + case 'normal' + location = 'N'; + CTn = sprintf('N%03d',subject); % format the patient name to N### + patient = num2str(subject); + levelDir1 = str2num(patient(1)); + levelDir2 = str2num(patient(2)); + finalDirectory = sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2); + + listing = dir(sprintf('%s/%s/%d/%d', directory, location, levelDir1, levelDir2)); + for i = 1:1:numel(listing) + name = listing(i).name; + t = strfind(name, CTn); + if t == 1; + patientName = listing(i).name; + end + end +end + +switch output + + case 'References' + output = 'References'; + scapula_calculation(patientName, finalDirectory, location, output); + + case 'obliqueSlice' + output = 'obliqueSlice'; + scapula_calculation(patientName, finalDirectory, location, output); + + case 'density' + output = 'density'; + scapula_calculation(patientName, finalDirectory, location, output); + + case 'display' + output = 'display'; + scapula_calculation(patientName, finalDirectory, location, output); +end end \ No newline at end of file diff --git a/anatomy/scapula_calculation.m b/anatomy/scapula_calculation.m index 826febb..444e3fb 100644 --- a/anatomy/scapula_calculation.m +++ b/anatomy/scapula_calculation.m @@ -1,1155 +1,1155 @@ -%% scapula_calculation -% -% This script makes the anatomic calculation of the glenoid from the Amira files. -% It is used by the scripts scapula_measure.m and scapula_addmeasure. -% It should normally not be used directly. -% Author: EPFL-LBO -% Date: 2016-11-18 -% -%% - -% Subject is the patient number -% directory is the location of the datas -% location it 'Pathologic' or 'Normal' -% output is 'References', 'obliqueSlice', 'density' and 'display' - - - - - - -function [Result] = scapula_calculation(subject, directory, location, output) - -segmentedScapula = 0; - -CTn = strtok(subject,'-'); -CTn = strtok(CTn, location); -CTn = str2num(CTn); %#ok -CTn = sprintf('%s%03d',location,CTn); - - -% Import Data - - -% Import Glenoid Surface -if exist(sprintf('%s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn),'file') == 2 - [p,~,~]=importStl(sprintf('%s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn),1); -else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find glenoid surface file: %s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn) -end - -% Import Scapula Surface -if exist(sprintf('%s/%s/CT-%s-1/amira/scapula_%s.stl',directory,subject,subject,CTn),'file') == 2 - [pscapula,~,~]=importStl(sprintf('%s/%s/CT-%s-1/amira/scapula_%s.stl',directory,subject,subject,CTn),1); - segmentedScapula = 1; -end - -% Import HumeralHead landmarks -if exist(sprintf('%s/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 - newData1 = importdata(sprintf('%s/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); - HH = newData1.data; -else - warning('Using old humeral landmarks definition') - if exist(sprintf('%s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 - newData1 = importdata(sprintf('%s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); - HH = newData1.data; - else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find humeral head landmarks file: %s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn) - end -end - -% Import Scapula landmarks -% AI: Angulus Inferior -% TS: Trigonum Spinae Scapulae (the midpoint of the triangular surface on the medial border of the scapula in line with the scaoular spine -% PC: Processus Coracoideus (most lateral point) -% AL: Acromion Lateral (most lateral part on acromion) // Before, it was AC: Acromio-clavicular joint (most lateral part on acromion) -% AA: Angulus Acromialis (most laterodorsal point) -% AG: Acromion-Glenoid notch -if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 - newData2 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); - landmarks = newData2.data; -else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find scapula landmarks file: %s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn) - -end - -% Import Scapula Pillar -if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 - newData3 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); - SP = newData3.data; %Scapula Pillar points -else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find axillary border landmarks file: %s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn) -end - -% Import Scapula Groove -if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 - newData4 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); - SG = newData4.data; %Scapula Groove points -else - error('MATLAB:rmpath:DirNotFound',... - 'Could not find supraspinatus fossa landmarks file: %s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn) -end - -% Define anatomical axes - -MedLat = landmarks(6,:)-mean(SG); % Z ? -AntPos = landmarks(5,:)-landmarks(3,:); % X ? -InfSup = landmarks(6,:)-mean(SP); % Y ? - - -% Find scapula side - -A = cross(MedLat,AntPos); -B = vrrotvec(InfSup,A); %Comparing Z-axes -if rad2deg(B(4))>90 - Side = 0; %Right - p(:,1) = -p(:,1); - if segmentedScapula == 1 - pscapula(:,1) = -pscapula(:,1); - end - HH(:,1) = -HH(:,1); - landmarks(:,1) = -landmarks(:,1); - SG(:,1) = -SG(:,1); - MedLat = landmarks(6,:)-mean(SG); - AntPos = landmarks(5,:)-landmarks(3,:); -else - Side = 1; %Left -end - - -%% Fit plane and axis - -% Find scapular plane (fit plane to AI and TS and most distal SG) -% The 10 next line are used to determine the scapulaPlaneNormal regarding -% the 3 most distal points of the scapulagroove. - - -AI = landmarks(1,:); % Angulus inferioris point -TS = landmarks(2,:); % Trigonum Spinae point -EuclidDistance = zeros(5,1); -for i=1:5 % Calculate the distance between the TS point and each of the 5 groove points - EuclidDistance(i) = sqrt((landmarks(2,1)-SG(i,1))^2 + (landmarks(2,2)-SG(i,2))^2 + (landmarks(2,3)-SG(i,3))^2); -end - -maxEuclidianDistance = max(EuclidDistance); % Find the largest distance -pos = EuclidDistance == maxEuclidianDistance; -GRL = SG(pos,:); % set most lateral point of the groove -[ScapulaPlaneNormal, PlaneMean, ~, PlaneRMSE, ~] = fitPlane2([AI;TS;GRL]); - - -% Find scapular plane (fit plane to SP and SG) -%[ScapulaPlaneNormal, PlaneMean, ~, PlaneRMSE, R2Plane] = fitPlane2([SG;SP]); - -C = vrrotvec(ScapulaPlaneNormal,AntPos); -if rad2deg(C(4))>90 - ScapulaPlaneNormal = -ScapulaPlaneNormal; -end - -% First project, then fit -SG_proj = project2Plane(SG,ScapulaPlaneNormal',[0 0 0],size(SG,1)); % project SG points on scapular plane -[GrooveAxis, GrooveMean, ~, ~, ~] = fitLine2(SG_proj); -TransverseAxis = (GrooveAxis/norm(GrooveAxis))'; % TransverseAxis is the norm of the line that was created by the projection of SG points on scapular plane - -D = vrrotvec(TransverseAxis,MedLat); -if rad2deg(D(4))>90 - TransverseAxis = -TransverseAxis; -end - -% Project GrooveMean to scapular plane -TransverseAxisMean = project2Plane(GrooveMean,ScapulaPlaneNormal',PlaneMean,1); - -% Project AG to transverseAxis -Origin = TransverseAxisMean + dot((landmarks(6,:)-TransverseAxisMean),TransverseAxis)*TransverseAxis; - -% Scapula axial plane normal -AxialPlane = cross(TransverseAxis, ScapulaPlaneNormal); -E = vrrotvec(AxialPlane, [0 0 1]); -CTorientation = rad2deg(E(4)); -if CTorientation > 90 - CTorientation = 180 - CTorientation; -end - -% Closest point. GC is the closet point between the mean of the extracted -% surface and all the points of the extracted surface. -% GC = Glenoid Centre -GC = mean(p); -vect = zeros(size(p)); -distance3D = zeros(size(p,1),1); -for i=1:size(p,1) - vect(i,:) = p(i,:)-GC; - distance3D(i) = norm(vect(i,:)); -end -ClosestNode = distance3D == min(distance3D); -GC = p(ClosestNode,:); - - -% Fit Sphere on Glenoid Surface -[GlenoidSphere,GlenoidRadius,GlenoidResiduals, ~] = fitSphere2(p); -GlenoidRMSE = norm(GlenoidResiduals)/sqrt(length(GlenoidResiduals)); - - -% Fit sphere on Humeral Head landmarks -[HC,HHRadius, ~, ~] = fitSphere2(HH); - - -% Glenoid version 3D -GO = GlenoidSphere'-GC; - -Version3DRot = vrrotvec(TransverseAxis,GO); -Version3D = rad2deg(Version3DRot(4)); - -VersionDirectionRotX = vrrotvec(Version3DRot(1:3),ScapulaPlaneNormal); -VersionDirectionRotZ = vrrotvec(Version3DRot(1:3),cross(TransverseAxis,ScapulaPlaneNormal)); - -if rad2deg(VersionDirectionRotX(4)) > 90 - VersionDirection = rad2deg(VersionDirectionRotZ(4)); -else - VersionDirection = -rad2deg(VersionDirectionRotZ(4)); -end -if VersionDirection < -180 - VersionDirection = VersionDirection + 360; -end -if VersionDirection > 180 - VersionDirection = VersionDirection - 360; -end - - -% Maximum wear plane -MaxWearPlane = cross(GO, TransverseAxis); -F = vrrotvec(MaxWearPlane, [0 0 1]); -WearPlaneAngle = rad2deg(F(4)); -if WearPlaneAngle > 90 - WearPlaneAngle = 180 - WearPlaneAngle; -end - - -% Inclination : Angle between GO projected on the scapular plane and the medio-lateral axis -proj_GO = GO' - dot(GO', ScapulaPlaneNormal) * ScapulaPlaneNormal; -InclinationRot = vrrotvec(TransverseAxis,proj_GO); -Inclination = rad2deg(InclinationRot(4)); -if dot(InclinationRot(1:3), ScapulaPlaneNormal) > 0 - Inclination = -Inclination; -end - - -% Version 2D : Angle between GO projected on the scapular AXIAL plane and the medio-lateral axis -proj_GO2 = GO' - dot(GO', AxialPlane') * AxialPlane'; -Version2DRot = vrrotvec(TransverseAxis,proj_GO2); -Version2D = rad2deg(Version2DRot(4)); -if dot(Version2DRot(1:3), AxialPlane') > 0 - Version2D = -Version2D; -end - - -% Scapulo Humeral Subluxation Index -HO = HC'-GC; % glenoid center to humeral center -SHeccentricity = HO-(dot(HO,TransverseAxis)*TransverseAxis);% vector from HC to transverse axis (perpendicular) -SHSI = norm(SHeccentricity) / HHRadius; % Mod. on 04.06.2014 - -SHSIDirectionRotX = vrrotvec(SHeccentricity,ScapulaPlaneNormal); -SHSIDirectionRotZ = vrrotvec(SHeccentricity,cross(TransverseAxis,ScapulaPlaneNormal)); - -if rad2deg(SHSIDirectionRotZ(4)) < 90 - SHSIDirection = rad2deg(SHSIDirectionRotX(4)); -else - SHSIDirection = -rad2deg(SHSIDirectionRotX(4)); -end -if SHSIDirection < -180 - SHSIDirection = SHSIDirection+360; -end - -SHSPlane = cross(TransverseAxis, HO); -G = vrrotvec(SHSPlane, [0 0 1]); -SHSPlaneAngle = rad2deg(G(4)); -if SHSPlaneAngle > 90 - SHSPlaneAngle = 180 - SHSPlaneAngle; -end - - -% Gleno Humeral Subluxation Index -GHeccentricity = HO-dot(HO,GO/norm(GO))*(GO/norm(GO));%vector from glenoid sphere centre to glenoid centerline (perpendicular) -GHSI = norm(GHeccentricity) / HHRadius; % Mod. on 04.06.2014 - -ScapulaPlaneNormal2Glenoid = project2Plane(ScapulaPlaneNormal',GO,[0 0 0],1);% project y axis on glenoid plane - -GHSIDirectionRotX = vrrotvec(GHeccentricity,ScapulaPlaneNormal2Glenoid); -GHSIDirectionRotZ = vrrotvec(GHeccentricity,cross(GO,ScapulaPlaneNormal2Glenoid)); - -if rad2deg(GHSIDirectionRotZ(4)) < 90 - GHSIDirection = rad2deg(GHSIDirectionRotX(4)); -else - GHSIDirection = -rad2deg(GHSIDirectionRotX(4)); -end - -if GHSIDirection < -180 - GHSIDirection = GHSIDirection+360; -end - -GHSPlane = cross(GO, HO); -H = vrrotvec(GHSPlane, [0 0 1]); -GHSPlaneAngle = rad2deg(H(4)); -if GHSPlaneAngle > 90 - GHSPlaneAngle = 180 - GHSPlaneAngle; -end - - -% Height of the glenoid cap -% Project the points of the surface on the centerline -p_proj = zeros(size(p)); -for i = 1:size(p,1) - p_proj(i,:) = GC + (dot(GO,(p(i,:)-GC)) / dot(GO,GO)) * GO; - distance3D(i) = norm(GC-p_proj(i,:)); -end - -CapHeight = max(distance3D); - - -% Glenoid height and width - -% Scapula coordinate system in CT frame -ScapulaCSYS(1,:) = TransverseAxis; % X -ScapulaCSYS(2,:) = ScapulaPlaneNormal; % Y -ScapulaCSYS(3,:) = AxialPlane; % Z -ScapulaCSYS(4,:) = Origin; -ScapulaCSYS(5,:) = Origin + 10*ScapulaCSYS(1,:); -ScapulaCSYS(6,:) = Origin + 10*ScapulaCSYS(2,:); -ScapulaCSYS(7,:) = Origin + 10*ScapulaCSYS(3,:); - -% Contruction of transformation matrix from CT frame to Scapula -% frame -Osc = ScapulaCSYS(4,:); -Oscx = Origin + ScapulaCSYS(1,:); -Oscy = Origin + ScapulaCSYS(2,:); -Oscz = Origin + ScapulaCSYS(3,:); - -O = [0,0,0]; -Ox = [1,0,0]; -Oy = [0,1,0]; -Oz = [0,0,1]; - -A = [Osc(1),Osc(2),Osc(3),1; - Oscx(1),Oscx(2),Oscx(3),1; - Oscy(1),Oscy(2),Oscy(3),1; - Oscz(1),Oscz(2),Oscz(3),1]; - -bx = [O(1); - Ox(1); - Oy(1); - Oz(1)]; - -by = [O(2); - Ox(2); - Oy(2); - Oz(2)]; - - -bz = [O(3); - Ox(3); - Oy(3); - Oz(3)]; - - -B = [bx,by,bz]; -X = A\B; -T = [X';0,0,0,1]; % Transformation matrix from CT frame to scapula frame - - -psc = zeros(size(p,1),4); -for i = 1:size(p,1) - psc(i,:) = T*[p(i,:),1]'; -end - -psc_y = psc(:,2); -psc_z = psc(:,3); - -% Difference of extremal values -i_y_max=1; -for i = 2:1:size(psc_y) - - if psc_y(i) > psc_y(i_y_max) - i_y_max = i; - end -end - -i_y_min=1; -for i = 2:1:size(psc_y) - - if psc_y(i) < psc_y(i_y_min) - i_y_min = i; - end -end - -i_z_max=1; -for i = 2:1:size(psc_z) - - if psc_z(i) > psc_z(i_z_max) - i_z_max = i; - end -end - -i_z_min=1; -for i = 2:1:size(psc_z) - - if psc_z(i) < psc_z(i_z_min) - i_z_min = i; - end -end - -scapulaWidth = norm(p(i_y_max,:)-p(i_y_min,:)); -scapulaHeight = norm(p(i_z_max,:)-p(i_z_min,:)); - -% Acromion Index (AI) -% AI = GA/GH, where: -% GA: GlenoAcromial distance in scapula plane = distance from AL to glenoid principal axis -% GH: GlenoHumeral distance = 2*HHRadius, humeral head diameter (radius*2) - -%Calculate the correct AL landmarks if the scapula is segmented -if segmentedScapula == 1 - % Transform scapula, glenoid and acromion points to Scapula Coordinate System - ex = [1;0;0]; - ey = [0;1;0]; - ez = [0;0;1]; - - ev = AxialPlane'; % Y - ew = TransverseAxis'; %Z - eu = cross(ev, ew); % X - - R = eu * ex' + ev * ey' + ew * ez'; - - % Transform scapula point to Scapula Coordinate System - ScapulaPtsinScapulaCSYS = pscapula*R; - % Take the most lateral point (assumption: The most lateral point of the scaplua is always on the acromion - [~,ALpositionInScapula] = max(ScapulaPtsinScapulaCSYS(:,3)); - AL = pscapula(ALpositionInScapula,:);% AL: Acromion lateral (most lateral part on acromion) -else - AL = landmarks(4,:); % AL: Acromion lateral (most lateral part on acromion) -end - -% Project AL, glenoid points and GC in scapula plane -ALinScapulaPlane = project2Plane(AL,ScapulaPlaneNormal',PlaneMean,size(AL,1)); -GlenoidPtsinScapulaPlane = project2Plane(p,ScapulaPlaneNormal',PlaneMean,size(p,1)); -GCinScapulaPlane = project2Plane(GC,ScapulaPlaneNormal',PlaneMean,size(GC,1)); -if segmentedScapula == 1 - ScapulaPtsinScapulaPlane = project2Plane(pscapula,ScapulaPlaneNormal',PlaneMean,size(pscapula,1)); -end - -% Figure in 3D to show position of scapula plane, glenoid points and AC - -figure; -d = PlaneMean*ScapulaPlaneNormal; -if segmentedScapula == 1 - [xx,yy]=ndgrid(min(pscapula(:,1)):max(pscapula(:,1)),min(pscapula(:,2)):max(pscapula(:,2))); -else - [xx,yy]=ndgrid((min(p(:,1))-50):(max(p(:,1))+50),(min(p(:,2))-50):(max(p(:,2))+50)); -end -zz=(d-ScapulaPlaneNormal(1)*xx-ScapulaPlaneNormal(2)*yy)/ScapulaPlaneNormal(3); -hold on -surf(xx,yy,zz,'FaceColor','r','FaceAlpha',0.5, 'EdgeColor', 'none') -if segmentedScapula == 1 - scatter3(ScapulaPtsinScapulaPlane(:,1),ScapulaPtsinScapulaPlane(:,2),ScapulaPtsinScapulaPlane(:,3),200,[0.5 0.5 0.5],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) - hold on; -end -scatter3(ALinScapulaPlane(:,1),ALinScapulaPlane(:,2),ALinScapulaPlane(:,3),[],[0 0 1],'filled') -hold on -scatter3(GlenoidPtsinScapulaPlane(:,1),GlenoidPtsinScapulaPlane(:,2),GlenoidPtsinScapulaPlane(:,3),200,[0 0 1],'Marker','.') -hold on -scatter3(GCinScapulaPlane(:,1),GCinScapulaPlane(:,2),GCinScapulaPlane(:,3),[],'r','filled') -hold on -daspect([1 1 1]) -xlabel('X (Scapula CSYS)') -ylabel('Y (Scapula CSYS)') -zlabel('Z (Scapula CSYS)') -daspect([1 1 1]) -if segmentedScapula == 1 - savefig(sprintf('%s/%s/CT-%s-1/amira/Projection2ScapulaPlaneWithSegmentedScapula_%s',directory,subject,subject,CTn)) -else - savefig(sprintf('%s/%s/CT-%s-1/amira/Projection2ScapulaPlane_%s',directory,subject,subject,CTn)) -end - -% Transform scapula, glenoid and acromion points to Scapula Coordinate System - -ex = [1;0;0]; -ey = [0;1;0]; -ez = [0;0;1]; - -ev = AxialPlane'; % Y -ew = TransverseAxis'; %Z -eu = cross(ev, ew); % X - -R = eu * ex' + ev * ey' + ew * ez'; % Create rotation matrix to transform points to Scapula Coordinate System - -ALinScapulaCSYS = AL*R; -GlenoidPtsinScapulaCSYS = p*R; -GCinScapulaCSYS = GC*R; -TrinScapulaCSYS = TransverseAxis*R; -ScapulaPlaneNormalinScapulaCSYS = ScapulaPlaneNormal'*R; -PlaneMeaninScapulaCSYS = PlaneMean*R; -if segmentedScapula == 1 - ScapulaPtsinScapulaCSYS = pscapula*R; -end - -% Figure in 3D to show position of scapula plane, glenoid points and AC - -figure; -if segmentedScapula == 1 - scatter3(ScapulaPtsinScapulaCSYS(:,1),ScapulaPtsinScapulaCSYS(:,2),ScapulaPtsinScapulaCSYS(:,3),200,[0.5 0.5 0.5],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) - hold on; -end -scatter3(ALinScapulaCSYS(:,1),ALinScapulaCSYS(:,2),ALinScapulaCSYS(:,3),[],[0 0 1],'filled') -hold on -scatter3(GlenoidPtsinScapulaCSYS(:,1),GlenoidPtsinScapulaCSYS(:,2),GlenoidPtsinScapulaCSYS(:,3),200,[0 0 1],'Marker','.') -hold on -scatter3(GCinScapulaCSYS(:,1),GCinScapulaCSYS(:,2),GCinScapulaCSYS(:,3),[],'r','filled') -hold on -x=eu*linspace(0,50); -plot3(x(:,1)',x(:,2)',x(:,3)','r') -hold on -y=ev*linspace(0,50); -plot3(y(:,1)',y(:,2)',y(:,3)','g') -hold on -z=ew*linspace(0,50); -plot3(z(:,1)',z(:,2)',z(:,3)','b') -daspect([1 1 1]) -d= ScapulaPlaneNormalinScapulaCSYS*PlaneMeaninScapulaCSYS'; -if segmentedScapula == 1 - [yy,zz]=ndgrid(min(ScapulaPtsinScapulaCSYS(:,2)):max(ScapulaPtsinScapulaCSYS(:,2)),min(ScapulaPtsinScapulaCSYS(:,3)):max(ScapulaPtsinScapulaCSYS(:,3))); -else - [yy,zz]=ndgrid((min(GlenoidPtsinScapulaCSYS(:,2))-50):(max(GlenoidPtsinScapulaCSYS(:,2))+50),(min(GlenoidPtsinScapulaCSYS(:,3))-50):(max(GlenoidPtsinScapulaCSYS(:,3))+50)); -end -xx = zeros(size(yy)); -xx(:,:) = d/ScapulaPlaneNormalinScapulaCSYS(1); %assuming normal(3)==0 and normal(2)==0 -hold on -surf(xx,yy,zz,'FaceColor','r','FaceAlpha',0.5, 'EdgeColor', 'none'); -xlabel('X (Scapula CSYS)') -ylabel('Y (Scapula CSYS)') -zlabel('Z (Scapula CSYS)') -daspect([1 1 1]) -if segmentedScapula == 1 - savefig(sprintf('%s/%s/CT-%s-1/amira/Transform2ScapulaCSYSwithScapulaSegmented_%s',directory,subject,subject,CTn)) -else - savefig(sprintf('%s/%s/CT-%s-1/amira/Transform2ScapulaCSYS_%s',directory,subject,subject,CTn)) -end - -% Project scapula, glenoid, AC, GC and scapula axis to scapula plane in the -% Scapula Coordinate System - -ALinScapulaPlane = project2Plane(ALinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(ALinScapulaCSYS,1)); -GlenoidPtsinScapulaPlane = project2Plane(GlenoidPtsinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(GlenoidPtsinScapulaCSYS,1)); -GCinScapulaPlane = project2Plane(GCinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(GCinScapulaCSYS,1)); -TrinScapulaPlane = project2Plane(TrinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(TrinScapulaCSYS,1)); -if segmentedScapula == 1 - ScapulaPtsinScapulaPlane = project2Plane(ScapulaPtsinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(ScapulaPtsinScapulaCSYS,1)); -end - -% Find glenoid principal axis (most superior and most inferior projected -% glenoid points) - -GlenoidPrincipalAxis = [GlenoidPtsinScapulaPlane(GlenoidPtsinScapulaPlane(:,2) == min(GlenoidPtsinScapulaPlane(:,2)),:) ;GlenoidPtsinScapulaPlane(GlenoidPtsinScapulaPlane(:,2) == max(GlenoidPtsinScapulaPlane(:,2)),:)]; - -% Compute GA (distance from AL to glenoid principal axis) - -GA = norm(cross(GlenoidPrincipalAxis(2,:)-GlenoidPrincipalAxis(1,:),ALinScapulaPlane-GlenoidPrincipalAxis(1,:)))/norm(GlenoidPrincipalAxis(2,:)-GlenoidPrincipalAxis(1,:)); -% Compute GH (Humeral head diameter) - -GH = 2*HHRadius; - -% Compute AI - -AI = GA/GH; - -% Figure in 2D showing projected glenoid points, glenoid principal axis and -% AC point - -figure; -daspect([1 1 1]) -if segmentedScapula == 1 - scatter(ScapulaPtsinScapulaPlane(:,3),ScapulaPtsinScapulaPlane(:,2),200,[0.8 0.8 0.8],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) - hold on -end -scatter(ALinScapulaPlane(:,3),ALinScapulaPlane(:,2),[],[0 0 1],'filled') -hold on -scatter(GlenoidPtsinScapulaPlane(:,3),GlenoidPtsinScapulaPlane(:,2),200,[0 0 1],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) -hold on -scatter(GlenoidPrincipalAxis(:,3),GlenoidPrincipalAxis(:,2),[],'w','filled','MarkerEdgeColor','k') -hold on -scatter(GCinScapulaPlane(:,3),GCinScapulaPlane(:,2),[],'r','filled') -hold on -a=TrinScapulaPlane(:,2)/TrinScapulaPlane(:,3); -b=GCinScapulaPlane(:,2)-a*GCinScapulaPlane(:,3); -if segmentedScapula == 1 - x=linspace(min(ScapulaPtsinScapulaPlane(:,3)),max(ScapulaPtsinScapulaPlane(:,3))); -else - x=linspace((min(GlenoidPtsinScapulaPlane(:,3))-50),(max(GlenoidPtsinScapulaPlane(:,3)))+50); -end -y=a*x+b; -hold on -plot(x,y,'--r') -hold on -a1=(GlenoidPrincipalAxis(2,2)-GlenoidPrincipalAxis(1,2))/(GlenoidPrincipalAxis(2,3)-GlenoidPrincipalAxis(1,3)); -b1=GlenoidPrincipalAxis(1,2)-a1*GlenoidPrincipalAxis(1,3); -x=linspace(min(GlenoidPrincipalAxis(:,3))-5,max(GlenoidPrincipalAxis(:,3))+5); -y1=a1*x+b1; -plot(x,y1,'--b') -daspect([1 1 1]) -xlabel('Z (Scapula CSYS)') -ylabel('Y (Scapula CSYS)') -title(['Acromion Index = ' num2str(AI) ' (GA = ' num2str(GA) ' GH = ' num2str(GH) ')']) -if segmentedScapula == 1 - saveas(gcf,sprintf('%s/%s/CT-%s-1/amira/AIwithSegmentedScapula_%s.png',directory,subject,subject,CTn)) -else - saveas(gcf,sprintf('%s/%s/CT-%s-1/amira/AI_%s.png',directory,subject,subject,CTn)) -end - -close all - -% Output - -CTtoXYZ = [TransverseAxis' ScapulaPlaneNormal cross(TransverseAxis, ScapulaPlaneNormal)']; -Cxyz = (GC - Origin) * CTtoXYZ; - -% Scapula coordinate system in CT frame -ScapulaCSYS(3,:) = TransverseAxis; % Z -ScapulaCSYS(1,:) = ScapulaPlaneNormal; % X -ScapulaCSYS(2,:) = cross(TransverseAxis,ScapulaPlaneNormal); % Y -ScapulaCSYS(4,:) = Origin; - -Result.sCase_id = strtok(subject,'-');%1 -Result.glenoid_Radius = GlenoidRadius;%2 -Result.glenoid_radiusRMSE = GlenoidRMSE;%3 -%Result.glenoidSpehricity = ' '; -%Result.glenoid_biconcave = ' '; -Result.glenoid_depth = CapHeight; %4 -Result.glenoid_width = scapulaWidth;%5 -Result.glenoid_height = scapulaHeight;%6 -Result.glenoid_center_PA = -Cxyz(2); %7 -Result.glenoid_center_IS = Cxyz(3); %8 -Result.glenoid_center_ML = Cxyz(1); %9 -Result.glenoid_version_ampl = Version3D;%10 -Result.glenoid_version_orient = VersionDirection;%11 -Result.glenoid_Version = Version2D; % 12 -Result.glenoid_Inclination = Inclination; %13 -% Result.glenoid_version2D = ' '; -% Result.glenoid_walch_class = ' '; -Result.humerus_joint_radius = ' '; %14 -Result.humeral_head_radius = HHRadius;%15 -% Result.humerus_GHsublux_2D = ' '; -% Result.humerus_SHsublux_2D = ' '; -Result.humerus_GHsubluxation_ampl = GHSI;%16 -Result.humerus_GHsubluxation_orient = GHSIDirection;%17 -Result.humerus_SHsubluxation_ampl = SHSI;%18 -Result.humerus_SHsubluxation_orient = SHSIDirection;%19 -% Result.scapula_CSA = ' '; -Result.scapula_CTangle = CTorientation; %20 -Result.scapula_CTangleVersion = WearPlaneAngle; %21 -Result.scapula_CTangleSHS = SHSPlaneAngle; % 22 -Result.scapula_CTangleGHS = GHSPlaneAngle; % 23 -Result.scapula_PlaneRMSE = PlaneRMSE;%24 -Result.scapula_AI = AI; - - - -%% Additional output - -if exist('output','var') == 1 - - % Scapula coordinate system in CT frame - ScapulaCSYS(1,:) = TransverseAxis; % X - ScapulaCSYS(2,:) = ScapulaPlaneNormal; % Y - ScapulaCSYS(3,:) = AxialPlane; % Z - ScapulaCSYS(4,:) = Origin; - ScapulaCSYS(5,:) = Origin + 10*ScapulaCSYS(1,:); - ScapulaCSYS(6,:) = Origin + 10*ScapulaCSYS(2,:); - ScapulaCSYS(7,:) = Origin + 10*ScapulaCSYS(3,:); - - %% ISB coordinate system in CT frame - - Xisb = (landmarks(4,:)-landmarks(2,:))/norm(landmarks(4,:)-landmarks(2,:)); %Med-Lat - Yisb = cross(Xisb,landmarks(1,:)-landmarks(2,:))/norm(cross(Xisb,landmarks(1,:)-landmarks(2,:)));%Ant-Pos - Zisb = cross(Xisb,Yisb);%Inf-Sup - - %% Locate abq coordinate system in CT frame - - Act2isb = [Xisb' Yisb' Zisb']; - Act2lbo = ScapulaCSYS(1:3,:)'; - Albo2isb = Act2isb/Act2lbo; - Athx2abq = SpinCalc('EA321toDCM',[40 0 0]);% scapular plane is 40� anterior to frontal plane - Aisb2thx = SpinCalc('EA321toDCM',[-35.6 -17.8 -2.5]);% initial position McClure et al. 2001 - - Act2abq = Athx2abq*Aisb2thx*Albo2isb*Act2lbo; - - %% Output initial position of scapula for abaqus simulation in CT coordinates - - % scaleFactor = 24/HHRadius; % =24mm / Humerus radius - ScapulaABQ(1,:) = HC; % Humerus center - ScapulaABQ(2,:) = 10*Act2abq(:,1)'+ScapulaABQ(1,:); - ScapulaABQ(3,:) = 10*Act2abq(:,2)'+ScapulaABQ(1,:); - ScapulaABQ(4,:) = 10*Act2abq(:,3)'+ScapulaABQ(1,:); - % ScapulaABQ(5,1) = scaleFactor; - - %% Output coordinates for obliqueSlice - % The ObliqueSlice can be defined as point + (normal OR u-vector and - % v-vector) - FriedmanPlane = [GC 1 0 0 0 1 0]; - obliqueSlice = [landmarks(6,:) ScapulaCSYS(2,:) -ScapulaCSYS(3,:)]; % Plane for muscle measurement - obliqueSlice2 = [GC ScapulaCSYS(3,:)]; % Scapula axial plane - obliqueSlice3 = [GC ScapulaCSYS(1,:) GO]; % Max wear plane - obliqueSlice4 = [GC ScapulaCSYS(2,:)]; % Scapular plane - obliqueSlice5 = [GC HO/norm(HO) GHeccentricity/norm(GHeccentricity)]; % Plane for GHS - obliqueSlice6 = [GC HO/norm(HO) SHeccentricity/norm(SHeccentricity)]; % Plane for SHS - - if Side == 0 - FriedmanPlane(1) = -FriedmanPlane(1); - obliqueSlice(1:3:end) = - obliqueSlice(1:3:end); - obliqueSlice2(1:3:end) = -obliqueSlice2(1:3:end); - obliqueSlice3(1:3:end) = -obliqueSlice3(1:3:end); - obliqueSlice4(1:3:end) = -obliqueSlice4(1:3:end); - obliqueSlice5(1:3:end) = -obliqueSlice5(1:3:end); - obliqueSlice6(1:3:end) = -obliqueSlice6(1:3:end); - end - - - - - switch output - case 'density' - %% Data for the calculations of the density in amira - % Needed variables are : - % - CylO: cylinder origin - % - CylA: cylinder alignment point - % - CylR_factor: scale factor for cylinder radius - % - Glenoid_sphere_center - % - Glenoid radius - % - CylO_shift5mm : cylinder origin shifted 5 mm behind - % glenoid surface center - % - CylA2 : cylinder alignment point n�2 - % - CylO_shift15mmfront: cylinder origin shifted 15 mm - % frontwards - % - CylA3 : cylinder alignemnt point n�2 - % - startcylpoint : origin of the new cylinder starting from - % the AG point - % - endcylpoint: end of cylinder, 40 mm from the startcylpoint - % in the scapula axis - - - CylO = GC; - Glenoid_sphere_center = GlenoidSphere'; - Glenoid_radius = GlenoidRadius; - - %% CylA - second point to form a line with glenoid surface - % center point which is parallel to scapula axis - CylA = CylO - 40 * TransverseAxis/norm(TransverseAxis); - - %% CylR - distance from glenoid surface center to most - % eccentric point of glenoid surface - - %Projection of surface points onto the same plane - PlanePoint = CylO; - PlaneNormal = (CylO-CylA)/norm(CylO-CylA); - - ProjectedPoints = zeros(size(p)); - DistanceToCylO = zeros(size(p,1),1); - for i = 1:size(p,1) - ProjectedPoints(i,:) = p(i,:) - dot(p(i,:) - PlanePoint, PlaneNormal) * PlaneNormal; - DistanceToCylO(i) = norm(ProjectedPoints(i,:) - CylO); - end - - %Selection of the most eccentric point -> radius of cylinder - CylR = max(DistanceToCylO); - - %Scale factor of the cylinder - OriginalCylR = 5; - CylR_factor = CylR/OriginalCylR; - - % Coordinates of start and end point of the D10 mm cylinder - Deepness = 5; %behind glenoid surface - smallCylR = 5; - CylO_shift5mm = GC - Deepness * TransverseAxis/norm(TransverseAxis); - CylA2 = CylO_shift5mm - smallCylR * TransverseAxis; - - % To place big cylinder (finer seleciton, exclusion of distant - % bones) - CylO_shift15mmfront = GC + 15 * TransverseAxis/norm(TransverseAxis); - CylA3 = CylO_shift15mmfront - 40 * TransverseAxis/norm(TransverseAxis); - - %%Projection of AG on scapula axis - AG = landmarks(6,:); - p1 = CylA3; - p2 = CylO_shift15mmfront; - - v1 = p2-p1; - v2 = AG-p1; - - E1 = v1; - E2 = cross(v1,v2); - E3 = cross(E1,E2); - - e1 = E1/norm(E1); - e2 = E2/norm(E2); - e3 = E3/norm(E3); - - Oc = p1; - Ocx = p1 + e1; - Ocy = p1 + e2; - Ocz = p1 + e3; - - O = [0,0,0]; - Ox = [1,0,0]; - Oy = [0,1,0]; - Oz = [0,0,1]; - - A = [Oc(1),Oc(2),Oc(3),1; - Ocx(1),Ocx(2),Ocx(3),1; - Ocy(1),Ocy(2),Ocy(3),1; - Ocz(1),Ocz(2),Ocz(3),1]; - - bx = [O(1); - Ox(1); - Oy(1); - Oz(1)]; - - - by = [O(2); - Ox(2); - Oy(2); - Oz(2)]; - - - bz = [O(3); - Ox(3); - Oy(3); - Oz(3)]; - - - B = [bx,by,bz]; - X = A\B; - T = [X'; - 0,0,0,1]; - - det(T); - - AG2 = [AG,1]; - AG3=AG2'; - diff = T*AG3; - projAGcyl=[diff(1),0,0,1]; - startcylpoint=T\projAGcyl'; - startcylpoint = [startcylpoint(1),startcylpoint(2),startcylpoint(3)]; - endcylpoint = startcylpoint + 40 * TransverseAxis/norm(TransverseAxis); - - %% Write data - if Side == 0 - CylO(1) = -CylO(1); - CylA(1) = -CylA(1); - Glenoid_sphere_center(1) = -Glenoid_sphere_center(1); - CylO_shift5mm(1) = -CylO_shift5mm(1); - CylA2(1) = -CylA2(1); - CylO_shift15mmfront(1) = -CylO_shift15mmfront(1); - CylA3(1) = -CylA3(1); - startcylpoint(1) = -startcylpoint(1); - endcylpoint(1) = -endcylpoint(1); - - - - end - mkdir('Generated_Amira_TCL_Scripts/CylinderReferences') - - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),'w'); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylR_factor, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),Glenoid_sphere_center, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),Glenoid_radius, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO_shift5mm, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA2, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO_shift15mmfront, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA3, '-append','precision',10); - fclose(fid); - - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s_AmiraCode.dat',CTn),'w'); - - fprintf(fid,'set lineset1 [create HxLineSet]\n'); - fprintf(fid,'set p1 [$lineset1 addPoint %f %f %f]\n',CylO_shift5mm); - fprintf(fid,'set p2 [$lineset1 addPoint %f %f %f]\n',CylA2); - fprintf(fid,'$lineset1 addLine $p1 $p2\n'); - fprintf(fid,'{Line-Set} setIconPosition 20 10\n'); - fprintf(fid,'Line-Set fire\n\n'); - - fprintf(fid,'set lineset2 [create HxLineSet]\n'); - fprintf(fid,'set p1 [$lineset2 addPoint 0 0 0]\n'); - fprintf(fid,'set p2 [$lineset2 addPoint 5 0 0]\n'); - fprintf(fid,'$lineset2 addLine $p1 $p2\n'); - fprintf(fid,'{Line-Set2} setIconPosition 20 30\n'); - fprintf(fid,'Line-Set2 fire\n\n'); - - fprintf(fid,'set hideNewModules 0\n'); - fprintf(fid,'create {HxCreateSphere} {GlenoidSphereFit}\n'); - fprintf(fid,'{GlenoidSphereFit} setIconPosition 20 330\n'); - fprintf(fid,'{GlenoidSphereFit} fire\n'); - fprintf(fid,'{GlenoidSphereFit} {type} setValue 0\n'); - fprintf(fid,'{GlenoidSphereFit} {radius} setMinMax 0 100\n'); - fprintf(fid,'{GlenoidSphereFit} {radius} setButtons 0\n'); - fprintf(fid,'{GlenoidSphereFit} {radius} setIncrement 0.666667\n'); - fprintf(fid,'{GlenoidSphereFit} {radius} setValue %f\n',Glenoid_radius); - fprintf(fid,'{GlenoidSphereFit} {radius} setSubMinMax 0 100\n'); - fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 0 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{GlenoidSphereFit} {coords} setValue 0 %f\n',Glenoid_sphere_center(1)); - fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 1 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{GlenoidSphereFit} {coords} setValue 1 %f\n',Glenoid_sphere_center(2)); - fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 2 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{GlenoidSphereFit} {coords} setValue 2 %f\n',Glenoid_sphere_center(3)); - fprintf(fid,'{GlenoidSphereFit} {edgeLength} setMinMax 0.00999999977648258 5\n'); - fprintf(fid,'{GlenoidSphereFit} {edgeLength} setButtons 0\n'); - fprintf(fid,'{GlenoidSphereFit} {edgeLength} setIncrement 0.332667\n'); - fprintf(fid,'{GlenoidSphereFit} {edgeLength} setValue 1.07457\n'); - fprintf(fid,'{GlenoidSphereFit} {edgeLength} setSubMinMax 0.00999999977648258 5\n'); - fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setMinMax 0.100000001490116 20\n'); - fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setButtons 0\n'); - fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setIncrement 1.32667\n'); - fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setValue 1\n'); - fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setSubMinMax 0.100000001490116 20\n'); - fprintf(fid,'GlenoidSphereFit fire\n'); - fprintf(fid,'GlenoidSphereFit setViewerMask 16383\n'); - fprintf(fid,'GlenoidSphereFit setPickable 1\n\n'); - - fprintf(fid,'set hideNewModules 0\n'); - fprintf(fid,'create {HxCreateSphere} {CorticalSphereFit}\n'); - fprintf(fid,'{CorticalSphereFit} setIconPosition 20 350\n'); - fprintf(fid,'{CorticalSphereFit} fire\n'); - fprintf(fid,'{CorticalSphereFit} {type} setValue 0\n'); - fprintf(fid,'{CorticalSphereFit} {radius} setMinMax 0 100\n'); - fprintf(fid,'{CorticalSphereFit} {radius} setButtons 0\n'); - fprintf(fid,'{CorticalSphereFit} {radius} setIncrement 0.666667\n'); - fprintf(fid,'{CorticalSphereFit} {radius} setValue %f\n',Glenoid_radius+3); - fprintf(fid,'{CorticalSphereFit} {radius} setSubMinMax 0 100\n'); - fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 0 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{CorticalSphereFit} {coords} setValue 0 %f\n',Glenoid_sphere_center(1)); - fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 1 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{CorticalSphereFit} {coords} setValue 1 %f\n',Glenoid_sphere_center(2)); - fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 2 -3.40282346638529e+038 3.40282346638529e+038\n'); - fprintf(fid,'{CorticalSphereFit} {coords} setValue 2 %f\n',Glenoid_sphere_center(3)); - fprintf(fid,'{CorticalSphereFit} {edgeLength} setMinMax 0.00999999977648258 5\n'); - fprintf(fid,'{CorticalSphereFit} {edgeLength} setButtons 0\n'); - fprintf(fid,'{CorticalSphereFit} {edgeLength} setIncrement 0.332667\n'); - fprintf(fid,'{CorticalSphereFit} {edgeLength} setValue 1.07457\n'); - fprintf(fid,'{CorticalSphereFit} {edgeLength} setSubMinMax 0.00999999977648258 5\n'); - fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setMinMax 0.100000001490116 20\n'); - fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setButtons 0\n'); - fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setIncrement 1.32667\n'); - fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setValue 1\n'); - fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setSubMinMax 0.100000001490116 20\n'); - fprintf(fid,'CorticalSphereFit fire\n'); - fprintf(fid,'CorticalSphereFit setViewerMask 16383\n'); - fprintf(fid,'CorticalSphereFit setPickable 1\n\n'); - - fprintf(fid,'set lineset3 [create HxLineSet]\n'); - fprintf(fid,'set p1 [$lineset3 addPoint %f %f %f]\n',startcylpoint); - fprintf(fid,'set p2 [$lineset3 addPoint %f %f %f]\n',endcylpoint); - fprintf(fid,'$lineset3 addLine $p1 $p2\n'); - fprintf(fid,'{Line-Set3} setIconPosition 20 520\n'); - fprintf(fid,'Line-Set3 fire\n\n'); - - fprintf(fid,'set lineset4 [create HxLineSet]\n'); - fprintf(fid,'set p1 [$lineset4 addPoint 0 0 0]\n'); - fprintf(fid,'set p2 [$lineset4 addPoint 40 0 0]\n'); - fprintf(fid,'$lineset4 addLine $p1 $p2\n'); - fprintf(fid,'{Line-Set4} setIconPosition 20 540\n'); - fprintf(fid,'Line-Set4 fire\n\n'); - - fprintf(fid,'{Cylinder2.STL} setScaleFactor 1 %f %f\n',CylR_factor,CylR_factor); - fprintf(fid,'{Cylinder2.STL} applyTransform\n\n'); - - fprintf(fid,'saveProject\n'); - fprintf(fid,'clear\n'); - fprintf(fid,'echo "Complete center coordinates of ReamingSphere"\n\n'); - - %fprintf(fid,' - - fclose(fid); - - case 'References' - - mkdir('Generated_Amira_TCL_Scripts/References') - - % References data for a study case - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'w'); - fprintf(fid,'# Scapula coordinate system\n\n'); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),ScapulaCSYS(4:7,:), '-append','precision',10); - fclose(fid); - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'a'); - fprintf(fid,'\n\n# Abaqus reference\n\n'); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),ScapulaABQ, '-append','precision',10); - fclose(fid); - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'a'); - fprintf(fid,'\n\n# Glenoid centerline\n\n'); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),GC, '-append','precision',10); - dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),GlenoidSphere', '-append','precision',10); - fclose(fid); - - case 'obliqueSlice' - - [token, remain] = strtok(directory,'../../..'); - directory = ['Z:/' token remain]; - pictureName = [CTn '_new']; - - mkdir('Generated_Amira_TCL_Scripts/obliqueSlice') - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\obliqueslice\\obliqueSlice_%s.tcl',CTn),'w'); - - fprintf(fid,'# Create an ObliqueSlice with Friedman plane #\n'); - fprintf(fid,'ObliqueSlice orientation hit 0\n'); - fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f %f %f %f\n',FriedmanPlane); - fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 0 item 2 1 item 3 0 item 4 0\n'); - fprintf(fid,'ObliqueSlice fire\n'); - fprintf(fid,'set planepos [ObliqueSlice translate getValue]\n'); - fprintf(fid,'set maxvalue [ObliqueSlice translate getMaxValue]\n'); - fprintf(fid,'set planepos [expr {$maxvalue - int($planepos)}]\n'); - fprintf(fid,'ObliqueSlice createImage %s_$planepos\n', CTn); - fprintf(fid,'create HxCastField {CastField}\n'); - fprintf(fid,'CastField data connect %s_$planepos\n', CTn); - fprintf(fid,'CastField fire\n'); - fprintf(fid,'CastField scaling setValue 0 0.21\n'); - fprintf(fid,'CastField scaling setValue 1 200\n'); - fprintf(fid,'CastField fire\n'); - fprintf(fid,'[ {CastField} create ] setLabel %s_$planepos.jpeg\n', CTn); - if strcmp(location, 'P') - fprintf(fid,'%s_$planepos.jpeg exportData "JPEG" %s/%s/CT-%s-1/amira/%s_$planepos.jpeg\n\n', CTn, directory, subject, subject, CTn ); - else - fprintf(fid,'%s_$planepos.jpeg exportData "JPEG" Z:/data/%s/%s/CT-%s-1/amira/%s_$planepos.jpeg\n\n', CTn, directory, subject, subject, CTn ); - end - - fprintf(fid,'# Get image for muscle measurement #\n'); - fprintf(fid,'ObliqueSlice orientation hit 0\n'); - fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice fire\n', obliqueSlice); % Muscle evaluation, through notch - fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n'); - fprintf(fid,'ObliqueSlice createImage {%s}\n', pictureName); - if strcmp(location, 'pathologic') - fprintf(fid,'%s exportData "2D Tiff" %s/%s/CT-%s-1/amira/%s.tif\n', pictureName, directory, subject, subject, pictureName ); - else - fprintf(fid,'%s exportData "2D Tiff" %s/%s/CT-%s-1/amira/%s.tif\n', pictureName, directory, subject, subject, pictureName ); - end - fprintf(fid,'saveProject\n\n'); - - fprintf(fid,'ObliqueSlice2 setPlane %f %f %f %f %f %f\nObliqueSlice2 fire\n', obliqueSlice2); % Scapular axial plane - fprintf(fid,'ObliqueSlice2 options setValue 0 1\n'); - fprintf(fid,'ObliqueSlice2 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); - - fprintf(fid,'ObliqueSlice3 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice3 fire\n', obliqueSlice3); % Max wear plane - fprintf(fid,'ObliqueSlice3 options setValue 0 1\n'); - fprintf(fid,'ObliqueSlice3 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); - - fprintf(fid,'ObliqueSlice4 setPlane %f %f %f %f %f %f\nObliqueSlice4 fire\n', obliqueSlice4); % Scapula plane - fprintf(fid,'ObliqueSlice4 options setValue 0 1\n'); - fprintf(fid,'ObliqueSlice4 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); - - fprintf(fid,'ObliqueSlice5 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice5 fire\n', obliqueSlice5); % Scapula plane - fprintf(fid,'ObliqueSlice5 options setValue 0 1\n'); - fprintf(fid,'ObliqueSlice5 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); - - fprintf(fid,'ObliqueSlice6 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice6 fire\n', obliqueSlice6); % Scapula plane - fprintf(fid,'ObliqueSlice6 options setValue 0 1\n'); - fprintf(fid,'ObliqueSlice6 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); - - fprintf(fid,'remove CreateSphere%s HumSphere%s.surf SphereView%s Intersection\n', CTn, CTn, CTn); - fprintf(fid,'remove CreateSphere%s_2 GlenoidSphere%s.surf SphereView%s_2 Intersection2\n', CTn, CTn, CTn); - fprintf(fid,'create HxCreateSphere {CreateSphere%s}\n', CTn); - fprintf(fid,'CreateSphere%s setIconPosition 20 500\n', CTn); - fprintf(fid,'CreateSphere%s radius setMinMax 0 50\n', CTn); - fprintf(fid,'CreateSphere%s radius setValue %f\n', CTn, HHRadius); - if Side == 0 - fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', CTn, -HC(1)); - else - fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', CTn, HC(1)); - end - fprintf(fid,'CreateSphere%s coords setValue 1 %f\n', CTn, HC(2)); - fprintf(fid,'CreateSphere%s coords setValue 2 %f\n', CTn, HC(3)); - fprintf(fid,'CreateSphere%s fire\n', CTn); - fprintf(fid,'[ {CreateSphere%s} create\n ] setLabel {HumSphere%s.surf}\n', CTn, CTn); - fprintf(fid,'HumSphere%s.surf setIconPosition 200 500\n', CTn); - fprintf(fid,'HumSphere%s.surf master connect CreateSphere%s\n', CTn, CTn); - fprintf(fid,'HumSphere%s.surf fire\n', CTn); - fprintf(fid,'create HxDisplaySurface {SphereView%s}\n', CTn); - fprintf(fid,'SphereView%s setIconPosition 400 500\n', CTn); - fprintf(fid,'SphereView%s data connect HumSphere%s.surf\n', CTn, CTn); - fprintf(fid,'SphereView%s drawStyle setValue 4\n', CTn); - fprintf(fid,'SphereView%s fire\n', CTn); - fprintf(fid,'create HxOverlayGrid {Intersection}\n'); - fprintf(fid,'Intersection module connect ObliqueSlice4\n'); - fprintf(fid,'Intersection data connect HumSphere%s.surf\n', CTn); - fprintf(fid,'Intersection lineWidth setValue 2\n'); - fprintf(fid,'ObliqueSlice4 setViewerMask 16383\n'); - fprintf(fid,'ObliqueSlice4 setPlane %f %f %f 1 0 0 0 1 0\n', HC); - fprintf(fid,'ObliqueSlice4 fire\n\n'); - - fprintf(fid,'create HxCreateSphere {CreateSphere%s_2}\n', CTn); - fprintf(fid,'CreateSphere%s_2 setIconPosition 20 520\n', CTn); - fprintf(fid,'CreateSphere%s_2 radius setMinMax 0 50\n', CTn); - fprintf(fid,'CreateSphere%s_2 radius setValue %f\n', CTn, GlenoidRadius); - if Side == 0 - fprintf(fid,'CreateSphere%s_2 coords setValue 0 %f\n', CTn, -GlenoidSphere(1)); - else - fprintf(fid,'CreateSphere%s_2 coords setValue 0 %f\n', CTn, GlenoidSphere(1)); - end - fprintf(fid,'CreateSphere%s_2 coords setValue 1 %f\n', CTn, GlenoidSphere(2)); - fprintf(fid,'CreateSphere%s_2 coords setValue 2 %f\n', CTn, GlenoidSphere(3)); - fprintf(fid,'CreateSphere%s_2 fire\n', CTn); - fprintf(fid,'[ {CreateSphere%s_2} create\n ] setLabel {GlenoidSphere%s.surf}\n', CTn, CTn); - fprintf(fid,'GlenoidSphere%s.surf setIconPosition 200 520\n', CTn); - fprintf(fid,'GlenoidSphere%s.surf master connect CreateSphere%s_2\n', CTn, CTn); - fprintf(fid,'GlenoidSphere%s.surf fire\n', CTn); - fprintf(fid,'create HxDisplaySurface {SphereView%s_2}\n', CTn); - fprintf(fid,'SphereView%s_2 setIconPosition 400 520\n', CTn); - fprintf(fid,'SphereView%s_2 data connect GlenoidSphere%s.surf\n', CTn, CTn); - fprintf(fid,'SphereView%s_2 drawStyle setValue 4\n', CTn); - fprintf(fid,'SphereView%s_2 fire\n', CTn); - fprintf(fid,'create HxOverlayGrid {Intersection2}\n'); - fprintf(fid,'Intersection2 module connect ObliqueSlice5\n'); - fprintf(fid,'Intersection2 data connect GlenoidSphere%s.surf\n', CTn); - fprintf(fid,'Intersection2 lineWidth setValue 2\n'); - fprintf(fid,'ObliqueSlice5 setViewerMask 16383\n'); - fprintf(fid,'ObliqueSlice5 setPlane %f %f %f 1 0 0 0 1 0\n', GlenoidSphere); - fprintf(fid,'ObliqueSlice5 fire\n'); - - fclose(fid); - - case 'display' - - mkdir('Generated_Amira_TCL_Scripts/display'); - fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\display\\display_%s.tcl',CTn),'w'); - % if Side == 0; - % PlaneMean(1) = -PlaneMean(1); - % end - obliqueSlice7 = [PlaneMean ScapulaCSYS(2,:)]; - if Side == 0 - obliqueSlice7(1:3:end) = -obliqueSlice7(1:3:end); - GlenoidSphere(1) = -GlenoidSphere(1); - end - fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f \nObliqueSlice fire\n', obliqueSlice7); % Muscle evaluation, through notch - fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n'); - fprintf(fid,'ObliqueSlice fire\n\n'); - - fprintf(fid,'create HxCreateSphere {GlenoidSphere%s}\n', CTn); - fprintf(fid,'GlenoidSphere%s setIconPosition 20 550\n', CTn); - fprintf(fid,'GlenoidSphere%s radius setMinMax 0 50\n', CTn); - fprintf(fid,'GlenoidSphere%s radius setValue %f\n', CTn, GlenoidRadius); - fprintf(fid,'GlenoidSphere%s coords setValue 0 %f\n', CTn, GlenoidSphere(1)); - fprintf(fid,'GlenoidSphere%s coords setValue 1 %f\n', CTn, GlenoidSphere(2)); - fprintf(fid,'GlenoidSphere%s coords setValue 2 %f\n', CTn, GlenoidSphere(3)); - fprintf(fid,'GlenoidSphere%s fire\n\n', CTn); - - fprintf(fid,'[ {GlenoidSphere%s} create ] setLabel {HumSphereGlenoid%s.surf}\n', CTn, CTn); - fprintf(fid,'HumSphereGlenoid%s.surf setIconPosition 200 550\n', CTn); - fprintf(fid,'HumSphereGlenoid%s.surf master connect GlenoidSphere%s\n', CTn, CTn); - fprintf(fid,'HumSphereGlenoid%s.surf fire\n\n', CTn); - - fprintf(fid,'create HxDisplaySurface {GlenoidSphereView%s}\n', CTn); - fprintf(fid,'GlenoidSphereView%s setIconPosition 400 550\n', CTn); - fprintf(fid,'GlenoidSphereView%s data connect HumSphereGlenoid%s.surf\n', CTn, CTn); - fprintf(fid,'GlenoidSphereView%s drawStyle setValue 4\n', CTn); - fprintf(fid,'GlenoidSphereView%s fire\n', CTn); - fclose(fid); - otherwise - disp('unknown output request') - end -end +%% scapula_calculation +% +% This script makes the anatomic calculation of the glenoid from the Amira files. +% It is used by the scripts scapula_measure.m and scapula_addmeasure. +% It should normally not be used directly. +% Author: EPFL-LBO +% Date: 2016-11-18 +% +%% + +% Subject is the patient number +% directory is the location of the datas +% location it 'Pathologic' or 'Normal' +% output is 'References', 'obliqueSlice', 'density' and 'display' + + + + + + +function [Result] = scapula_calculation(subject, directory, location, output) + +segmentedScapula = 0; + +CTn = strtok(subject,'-'); +CTn = strtok(CTn, location); +CTn = str2num(CTn); %#ok +CTn = sprintf('%s%03d',location,CTn); + + +% Import Data + + +% Import Glenoid Surface +if exist(sprintf('%s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn),'file') == 2 + [p,~,~]=importStl(sprintf('%s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn),1); +else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find glenoid surface file: %s/%s/CT-%s-1/amira/ExtractedSurface%s.stl',directory,subject,subject,CTn) +end + +% Import Scapula Surface +if exist(sprintf('%s/%s/CT-%s-1/amira/scapula_%s.stl',directory,subject,subject,CTn),'file') == 2 + [pscapula,~,~]=importStl(sprintf('%s/%s/CT-%s-1/amira/scapula_%s.stl',directory,subject,subject,CTn),1); + segmentedScapula = 1; +end + +% Import HumeralHead landmarks +if exist(sprintf('%s/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 + newData1 = importdata(sprintf('%s/%s/CT-%s-1/amira/newHHLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); + HH = newData1.data; +else + warning('Using old humeral landmarks definition') + if exist(sprintf('%s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 + newData1 = importdata(sprintf('%s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); + HH = newData1.data; + else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find humeral head landmarks file: %s/%s/CT-%s-1/amira/HHLandmarks%s.landmarkAscii',directory,subject,subject,CTn) + end +end + +% Import Scapula landmarks +% AI: Angulus Inferior +% TS: Trigonum Spinae Scapulae (the midpoint of the triangular surface on the medial border of the scapula in line with the scaoular spine +% PC: Processus Coracoideus (most lateral point) +% AL: Acromion Lateral (most lateral part on acromion) // Before, it was AC: Acromio-clavicular joint (most lateral part on acromion) +% AA: Angulus Acromialis (most laterodorsal point) +% AG: Acromion-Glenoid notch +if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 + newData2 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); + landmarks = newData2.data; +else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find scapula landmarks file: %s/%s/CT-%s-1/amira/ScapulaLandmarks%s.landmarkAscii',directory,subject,subject,CTn) + +end + +% Import Scapula Pillar +if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 + newData3 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); + SP = newData3.data; %Scapula Pillar points +else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find axillary border landmarks file: %s/%s/CT-%s-1/amira/ScapulaPillarLandmarks%s.landmarkAscii',directory,subject,subject,CTn) +end + +% Import Scapula Groove +if exist(sprintf('%s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn),'file') == 2 + newData4 = importdata(sprintf('%s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn), ' ', 14); + SG = newData4.data; %Scapula Groove points +else + error('MATLAB:rmpath:DirNotFound',... + 'Could not find supraspinatus fossa landmarks file: %s/%s/CT-%s-1/amira/ScapulaGrooveLandmarks%s.landmarkAscii',directory,subject,subject,CTn) +end + +% Define anatomical axes + +MedLat = landmarks(6,:)-mean(SG); % Z ? +AntPos = landmarks(5,:)-landmarks(3,:); % X ? +InfSup = landmarks(6,:)-mean(SP); % Y ? + + +% Find scapula side + +A = cross(MedLat,AntPos); +B = vrrotvec(InfSup,A); %Comparing Z-axes +if rad2deg(B(4))>90 + Side = 0; %Right + p(:,1) = -p(:,1); + if segmentedScapula == 1 + pscapula(:,1) = -pscapula(:,1); + end + HH(:,1) = -HH(:,1); + landmarks(:,1) = -landmarks(:,1); + SG(:,1) = -SG(:,1); + MedLat = landmarks(6,:)-mean(SG); + AntPos = landmarks(5,:)-landmarks(3,:); +else + Side = 1; %Left +end + + +%% Fit plane and axis + +% Find scapular plane (fit plane to AI and TS and most distal SG) +% The 10 next line are used to determine the scapulaPlaneNormal regarding +% the 3 most distal points of the scapulagroove. + + +AI = landmarks(1,:); % Angulus inferioris point +TS = landmarks(2,:); % Trigonum Spinae point +EuclidDistance = zeros(5,1); +for i=1:5 % Calculate the distance between the TS point and each of the 5 groove points + EuclidDistance(i) = sqrt((landmarks(2,1)-SG(i,1))^2 + (landmarks(2,2)-SG(i,2))^2 + (landmarks(2,3)-SG(i,3))^2); +end + +maxEuclidianDistance = max(EuclidDistance); % Find the largest distance +pos = EuclidDistance == maxEuclidianDistance; +GRL = SG(pos,:); % set most lateral point of the groove +[ScapulaPlaneNormal, PlaneMean, ~, PlaneRMSE, ~] = fitPlane2([AI;TS;GRL]); + + +% Find scapular plane (fit plane to SP and SG) +%[ScapulaPlaneNormal, PlaneMean, ~, PlaneRMSE, R2Plane] = fitPlane2([SG;SP]); + +C = vrrotvec(ScapulaPlaneNormal,AntPos); +if rad2deg(C(4))>90 + ScapulaPlaneNormal = -ScapulaPlaneNormal; +end + +% First project, then fit +SG_proj = project2Plane(SG,ScapulaPlaneNormal',[0 0 0],size(SG,1)); % project SG points on scapular plane +[GrooveAxis, GrooveMean, ~, ~, ~] = fitLine2(SG_proj); +TransverseAxis = (GrooveAxis/norm(GrooveAxis))'; % TransverseAxis is the norm of the line that was created by the projection of SG points on scapular plane + +D = vrrotvec(TransverseAxis,MedLat); +if rad2deg(D(4))>90 + TransverseAxis = -TransverseAxis; +end + +% Project GrooveMean to scapular plane +TransverseAxisMean = project2Plane(GrooveMean,ScapulaPlaneNormal',PlaneMean,1); + +% Project AG to transverseAxis +Origin = TransverseAxisMean + dot((landmarks(6,:)-TransverseAxisMean),TransverseAxis)*TransverseAxis; + +% Scapula axial plane normal +AxialPlane = cross(TransverseAxis, ScapulaPlaneNormal); +E = vrrotvec(AxialPlane, [0 0 1]); +CTorientation = rad2deg(E(4)); +if CTorientation > 90 + CTorientation = 180 - CTorientation; +end + +% Closest point. GC is the closet point between the mean of the extracted +% surface and all the points of the extracted surface. +% GC = Glenoid Centre +GC = mean(p); +vect = zeros(size(p)); +distance3D = zeros(size(p,1),1); +for i=1:size(p,1) + vect(i,:) = p(i,:)-GC; + distance3D(i) = norm(vect(i,:)); +end +ClosestNode = distance3D == min(distance3D); +GC = p(ClosestNode,:); + + +% Fit Sphere on Glenoid Surface +[GlenoidSphere,GlenoidRadius,GlenoidResiduals, ~] = fitSphere2(p); +GlenoidRMSE = norm(GlenoidResiduals)/sqrt(length(GlenoidResiduals)); + + +% Fit sphere on Humeral Head landmarks +[HC,HHRadius, ~, ~] = fitSphere2(HH); + + +% Glenoid version 3D +GO = GlenoidSphere'-GC; + +Version3DRot = vrrotvec(TransverseAxis,GO); +Version3D = rad2deg(Version3DRot(4)); + +VersionDirectionRotX = vrrotvec(Version3DRot(1:3),ScapulaPlaneNormal); +VersionDirectionRotZ = vrrotvec(Version3DRot(1:3),cross(TransverseAxis,ScapulaPlaneNormal)); + +if rad2deg(VersionDirectionRotX(4)) > 90 + VersionDirection = rad2deg(VersionDirectionRotZ(4)); +else + VersionDirection = -rad2deg(VersionDirectionRotZ(4)); +end +if VersionDirection < -180 + VersionDirection = VersionDirection + 360; +end +if VersionDirection > 180 + VersionDirection = VersionDirection - 360; +end + + +% Maximum wear plane +MaxWearPlane = cross(GO, TransverseAxis); +F = vrrotvec(MaxWearPlane, [0 0 1]); +WearPlaneAngle = rad2deg(F(4)); +if WearPlaneAngle > 90 + WearPlaneAngle = 180 - WearPlaneAngle; +end + + +% Inclination : Angle between GO projected on the scapular plane and the medio-lateral axis +proj_GO = GO' - dot(GO', ScapulaPlaneNormal) * ScapulaPlaneNormal; +InclinationRot = vrrotvec(TransverseAxis,proj_GO); +Inclination = rad2deg(InclinationRot(4)); +if dot(InclinationRot(1:3), ScapulaPlaneNormal) > 0 + Inclination = -Inclination; +end + + +% Version 2D : Angle between GO projected on the scapular AXIAL plane and the medio-lateral axis +proj_GO2 = GO' - dot(GO', AxialPlane') * AxialPlane'; +Version2DRot = vrrotvec(TransverseAxis,proj_GO2); +Version2D = rad2deg(Version2DRot(4)); +if dot(Version2DRot(1:3), AxialPlane') > 0 + Version2D = -Version2D; +end + + +% Scapulo Humeral Subluxation Index +HO = HC'-GC; % glenoid center to humeral center +SHeccentricity = HO-(dot(HO,TransverseAxis)*TransverseAxis);% vector from HC to transverse axis (perpendicular) +SHSI = norm(SHeccentricity) / HHRadius; % Mod. on 04.06.2014 + +SHSIDirectionRotX = vrrotvec(SHeccentricity,ScapulaPlaneNormal); +SHSIDirectionRotZ = vrrotvec(SHeccentricity,cross(TransverseAxis,ScapulaPlaneNormal)); + +if rad2deg(SHSIDirectionRotZ(4)) < 90 + SHSIDirection = rad2deg(SHSIDirectionRotX(4)); +else + SHSIDirection = -rad2deg(SHSIDirectionRotX(4)); +end +if SHSIDirection < -180 + SHSIDirection = SHSIDirection+360; +end + +SHSPlane = cross(TransverseAxis, HO); +G = vrrotvec(SHSPlane, [0 0 1]); +SHSPlaneAngle = rad2deg(G(4)); +if SHSPlaneAngle > 90 + SHSPlaneAngle = 180 - SHSPlaneAngle; +end + + +% Gleno Humeral Subluxation Index +GHeccentricity = HO-dot(HO,GO/norm(GO))*(GO/norm(GO));%vector from glenoid sphere centre to glenoid centerline (perpendicular) +GHSI = norm(GHeccentricity) / HHRadius; % Mod. on 04.06.2014 + +ScapulaPlaneNormal2Glenoid = project2Plane(ScapulaPlaneNormal',GO,[0 0 0],1);% project y axis on glenoid plane + +GHSIDirectionRotX = vrrotvec(GHeccentricity,ScapulaPlaneNormal2Glenoid); +GHSIDirectionRotZ = vrrotvec(GHeccentricity,cross(GO,ScapulaPlaneNormal2Glenoid)); + +if rad2deg(GHSIDirectionRotZ(4)) < 90 + GHSIDirection = rad2deg(GHSIDirectionRotX(4)); +else + GHSIDirection = -rad2deg(GHSIDirectionRotX(4)); +end + +if GHSIDirection < -180 + GHSIDirection = GHSIDirection+360; +end + +GHSPlane = cross(GO, HO); +H = vrrotvec(GHSPlane, [0 0 1]); +GHSPlaneAngle = rad2deg(H(4)); +if GHSPlaneAngle > 90 + GHSPlaneAngle = 180 - GHSPlaneAngle; +end + + +% Height of the glenoid cap +% Project the points of the surface on the centerline +p_proj = zeros(size(p)); +for i = 1:size(p,1) + p_proj(i,:) = GC + (dot(GO,(p(i,:)-GC)) / dot(GO,GO)) * GO; + distance3D(i) = norm(GC-p_proj(i,:)); +end + +CapHeight = max(distance3D); + + +% Glenoid height and width + +% Scapula coordinate system in CT frame +ScapulaCSYS(1,:) = TransverseAxis; % X +ScapulaCSYS(2,:) = ScapulaPlaneNormal; % Y +ScapulaCSYS(3,:) = AxialPlane; % Z +ScapulaCSYS(4,:) = Origin; +ScapulaCSYS(5,:) = Origin + 10*ScapulaCSYS(1,:); +ScapulaCSYS(6,:) = Origin + 10*ScapulaCSYS(2,:); +ScapulaCSYS(7,:) = Origin + 10*ScapulaCSYS(3,:); + +% Contruction of transformation matrix from CT frame to Scapula +% frame +Osc = ScapulaCSYS(4,:); +Oscx = Origin + ScapulaCSYS(1,:); +Oscy = Origin + ScapulaCSYS(2,:); +Oscz = Origin + ScapulaCSYS(3,:); + +O = [0,0,0]; +Ox = [1,0,0]; +Oy = [0,1,0]; +Oz = [0,0,1]; + +A = [Osc(1),Osc(2),Osc(3),1; + Oscx(1),Oscx(2),Oscx(3),1; + Oscy(1),Oscy(2),Oscy(3),1; + Oscz(1),Oscz(2),Oscz(3),1]; + +bx = [O(1); + Ox(1); + Oy(1); + Oz(1)]; + +by = [O(2); + Ox(2); + Oy(2); + Oz(2)]; + + +bz = [O(3); + Ox(3); + Oy(3); + Oz(3)]; + + +B = [bx,by,bz]; +X = A\B; +T = [X';0,0,0,1]; % Transformation matrix from CT frame to scapula frame + + +psc = zeros(size(p,1),4); +for i = 1:size(p,1) + psc(i,:) = T*[p(i,:),1]'; +end + +psc_y = psc(:,2); +psc_z = psc(:,3); + +% Difference of extremal values +i_y_max=1; +for i = 2:1:size(psc_y) + + if psc_y(i) > psc_y(i_y_max) + i_y_max = i; + end +end + +i_y_min=1; +for i = 2:1:size(psc_y) + + if psc_y(i) < psc_y(i_y_min) + i_y_min = i; + end +end + +i_z_max=1; +for i = 2:1:size(psc_z) + + if psc_z(i) > psc_z(i_z_max) + i_z_max = i; + end +end + +i_z_min=1; +for i = 2:1:size(psc_z) + + if psc_z(i) < psc_z(i_z_min) + i_z_min = i; + end +end + +scapulaWidth = norm(p(i_y_max,:)-p(i_y_min,:)); +scapulaHeight = norm(p(i_z_max,:)-p(i_z_min,:)); + +% Acromion Index (AI) +% AI = GA/GH, where: +% GA: GlenoAcromial distance in scapula plane = distance from AL to glenoid principal axis +% GH: GlenoHumeral distance = 2*HHRadius, humeral head diameter (radius*2) + +%Calculate the correct AL landmarks if the scapula is segmented +if segmentedScapula == 1 + % Transform scapula, glenoid and acromion points to Scapula Coordinate System + ex = [1;0;0]; + ey = [0;1;0]; + ez = [0;0;1]; + + ev = AxialPlane'; % Y + ew = TransverseAxis'; %Z + eu = cross(ev, ew); % X + + R = eu * ex' + ev * ey' + ew * ez'; + + % Transform scapula point to Scapula Coordinate System + ScapulaPtsinScapulaCSYS = pscapula*R; + % Take the most lateral point (assumption: The most lateral point of the scaplua is always on the acromion + [~,ALpositionInScapula] = max(ScapulaPtsinScapulaCSYS(:,3)); + AL = pscapula(ALpositionInScapula,:);% AL: Acromion lateral (most lateral part on acromion) +else + AL = landmarks(4,:); % AL: Acromion lateral (most lateral part on acromion) +end + +% Project AL, glenoid points and GC in scapula plane +ALinScapulaPlane = project2Plane(AL,ScapulaPlaneNormal',PlaneMean,size(AL,1)); +GlenoidPtsinScapulaPlane = project2Plane(p,ScapulaPlaneNormal',PlaneMean,size(p,1)); +GCinScapulaPlane = project2Plane(GC,ScapulaPlaneNormal',PlaneMean,size(GC,1)); +if segmentedScapula == 1 + ScapulaPtsinScapulaPlane = project2Plane(pscapula,ScapulaPlaneNormal',PlaneMean,size(pscapula,1)); +end + +% Figure in 3D to show position of scapula plane, glenoid points and AC + +figure; +d = PlaneMean*ScapulaPlaneNormal; +if segmentedScapula == 1 + [xx,yy]=ndgrid(min(pscapula(:,1)):max(pscapula(:,1)),min(pscapula(:,2)):max(pscapula(:,2))); +else + [xx,yy]=ndgrid((min(p(:,1))-50):(max(p(:,1))+50),(min(p(:,2))-50):(max(p(:,2))+50)); +end +zz=(d-ScapulaPlaneNormal(1)*xx-ScapulaPlaneNormal(2)*yy)/ScapulaPlaneNormal(3); +hold on +surf(xx,yy,zz,'FaceColor','r','FaceAlpha',0.5, 'EdgeColor', 'none') +if segmentedScapula == 1 + scatter3(ScapulaPtsinScapulaPlane(:,1),ScapulaPtsinScapulaPlane(:,2),ScapulaPtsinScapulaPlane(:,3),200,[0.5 0.5 0.5],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) + hold on; +end +scatter3(ALinScapulaPlane(:,1),ALinScapulaPlane(:,2),ALinScapulaPlane(:,3),[],[0 0 1],'filled') +hold on +scatter3(GlenoidPtsinScapulaPlane(:,1),GlenoidPtsinScapulaPlane(:,2),GlenoidPtsinScapulaPlane(:,3),200,[0 0 1],'Marker','.') +hold on +scatter3(GCinScapulaPlane(:,1),GCinScapulaPlane(:,2),GCinScapulaPlane(:,3),[],'r','filled') +hold on +daspect([1 1 1]) +xlabel('X (Scapula CSYS)') +ylabel('Y (Scapula CSYS)') +zlabel('Z (Scapula CSYS)') +daspect([1 1 1]) +if segmentedScapula == 1 + savefig(sprintf('%s/%s/CT-%s-1/amira/Projection2ScapulaPlaneWithSegmentedScapula_%s',directory,subject,subject,CTn)) +else + savefig(sprintf('%s/%s/CT-%s-1/amira/Projection2ScapulaPlane_%s',directory,subject,subject,CTn)) +end + +% Transform scapula, glenoid and acromion points to Scapula Coordinate System + +ex = [1;0;0]; +ey = [0;1;0]; +ez = [0;0;1]; + +ev = AxialPlane'; % Y +ew = TransverseAxis'; %Z +eu = cross(ev, ew); % X + +R = eu * ex' + ev * ey' + ew * ez'; % Create rotation matrix to transform points to Scapula Coordinate System + +ALinScapulaCSYS = AL*R; +GlenoidPtsinScapulaCSYS = p*R; +GCinScapulaCSYS = GC*R; +TrinScapulaCSYS = TransverseAxis*R; +ScapulaPlaneNormalinScapulaCSYS = ScapulaPlaneNormal'*R; +PlaneMeaninScapulaCSYS = PlaneMean*R; +if segmentedScapula == 1 + ScapulaPtsinScapulaCSYS = pscapula*R; +end + +% Figure in 3D to show position of scapula plane, glenoid points and AC + +figure; +if segmentedScapula == 1 + scatter3(ScapulaPtsinScapulaCSYS(:,1),ScapulaPtsinScapulaCSYS(:,2),ScapulaPtsinScapulaCSYS(:,3),200,[0.5 0.5 0.5],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) + hold on; +end +scatter3(ALinScapulaCSYS(:,1),ALinScapulaCSYS(:,2),ALinScapulaCSYS(:,3),[],[0 0 1],'filled') +hold on +scatter3(GlenoidPtsinScapulaCSYS(:,1),GlenoidPtsinScapulaCSYS(:,2),GlenoidPtsinScapulaCSYS(:,3),200,[0 0 1],'Marker','.') +hold on +scatter3(GCinScapulaCSYS(:,1),GCinScapulaCSYS(:,2),GCinScapulaCSYS(:,3),[],'r','filled') +hold on +x=eu*linspace(0,50); +plot3(x(:,1)',x(:,2)',x(:,3)','r') +hold on +y=ev*linspace(0,50); +plot3(y(:,1)',y(:,2)',y(:,3)','g') +hold on +z=ew*linspace(0,50); +plot3(z(:,1)',z(:,2)',z(:,3)','b') +daspect([1 1 1]) +d= ScapulaPlaneNormalinScapulaCSYS*PlaneMeaninScapulaCSYS'; +if segmentedScapula == 1 + [yy,zz]=ndgrid(min(ScapulaPtsinScapulaCSYS(:,2)):max(ScapulaPtsinScapulaCSYS(:,2)),min(ScapulaPtsinScapulaCSYS(:,3)):max(ScapulaPtsinScapulaCSYS(:,3))); +else + [yy,zz]=ndgrid((min(GlenoidPtsinScapulaCSYS(:,2))-50):(max(GlenoidPtsinScapulaCSYS(:,2))+50),(min(GlenoidPtsinScapulaCSYS(:,3))-50):(max(GlenoidPtsinScapulaCSYS(:,3))+50)); +end +xx = zeros(size(yy)); +xx(:,:) = d/ScapulaPlaneNormalinScapulaCSYS(1); %assuming normal(3)==0 and normal(2)==0 +hold on +surf(xx,yy,zz,'FaceColor','r','FaceAlpha',0.5, 'EdgeColor', 'none'); +xlabel('X (Scapula CSYS)') +ylabel('Y (Scapula CSYS)') +zlabel('Z (Scapula CSYS)') +daspect([1 1 1]) +if segmentedScapula == 1 + savefig(sprintf('%s/%s/CT-%s-1/amira/Transform2ScapulaCSYSwithScapulaSegmented_%s',directory,subject,subject,CTn)) +else + savefig(sprintf('%s/%s/CT-%s-1/amira/Transform2ScapulaCSYS_%s',directory,subject,subject,CTn)) +end + +% Project scapula, glenoid, AC, GC and scapula axis to scapula plane in the +% Scapula Coordinate System + +ALinScapulaPlane = project2Plane(ALinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(ALinScapulaCSYS,1)); +GlenoidPtsinScapulaPlane = project2Plane(GlenoidPtsinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(GlenoidPtsinScapulaCSYS,1)); +GCinScapulaPlane = project2Plane(GCinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(GCinScapulaCSYS,1)); +TrinScapulaPlane = project2Plane(TrinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(TrinScapulaCSYS,1)); +if segmentedScapula == 1 + ScapulaPtsinScapulaPlane = project2Plane(ScapulaPtsinScapulaCSYS,ScapulaPlaneNormalinScapulaCSYS,PlaneMeaninScapulaCSYS,size(ScapulaPtsinScapulaCSYS,1)); +end + +% Find glenoid principal axis (most superior and most inferior projected +% glenoid points) + +GlenoidPrincipalAxis = [GlenoidPtsinScapulaPlane(GlenoidPtsinScapulaPlane(:,2) == min(GlenoidPtsinScapulaPlane(:,2)),:) ;GlenoidPtsinScapulaPlane(GlenoidPtsinScapulaPlane(:,2) == max(GlenoidPtsinScapulaPlane(:,2)),:)]; + +% Compute GA (distance from AL to glenoid principal axis) + +GA = norm(cross(GlenoidPrincipalAxis(2,:)-GlenoidPrincipalAxis(1,:),ALinScapulaPlane-GlenoidPrincipalAxis(1,:)))/norm(GlenoidPrincipalAxis(2,:)-GlenoidPrincipalAxis(1,:)); +% Compute GH (Humeral head diameter) + +GH = 2*HHRadius; + +% Compute AI + +AI = GA/GH; + +% Figure in 2D showing projected glenoid points, glenoid principal axis and +% AC point + +figure; +daspect([1 1 1]) +if segmentedScapula == 1 + scatter(ScapulaPtsinScapulaPlane(:,3),ScapulaPtsinScapulaPlane(:,2),200,[0.8 0.8 0.8],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) + hold on +end +scatter(ALinScapulaPlane(:,3),ALinScapulaPlane(:,2),[],[0 0 1],'filled') +hold on +scatter(GlenoidPtsinScapulaPlane(:,3),GlenoidPtsinScapulaPlane(:,2),200,[0 0 1],'Marker','.','MarkerFaceAlpha',0.5,'MarkerEdgeAlpha',0.5) +hold on +scatter(GlenoidPrincipalAxis(:,3),GlenoidPrincipalAxis(:,2),[],'w','filled','MarkerEdgeColor','k') +hold on +scatter(GCinScapulaPlane(:,3),GCinScapulaPlane(:,2),[],'r','filled') +hold on +a=TrinScapulaPlane(:,2)/TrinScapulaPlane(:,3); +b=GCinScapulaPlane(:,2)-a*GCinScapulaPlane(:,3); +if segmentedScapula == 1 + x=linspace(min(ScapulaPtsinScapulaPlane(:,3)),max(ScapulaPtsinScapulaPlane(:,3))); +else + x=linspace((min(GlenoidPtsinScapulaPlane(:,3))-50),(max(GlenoidPtsinScapulaPlane(:,3)))+50); +end +y=a*x+b; +hold on +plot(x,y,'--r') +hold on +a1=(GlenoidPrincipalAxis(2,2)-GlenoidPrincipalAxis(1,2))/(GlenoidPrincipalAxis(2,3)-GlenoidPrincipalAxis(1,3)); +b1=GlenoidPrincipalAxis(1,2)-a1*GlenoidPrincipalAxis(1,3); +x=linspace(min(GlenoidPrincipalAxis(:,3))-5,max(GlenoidPrincipalAxis(:,3))+5); +y1=a1*x+b1; +plot(x,y1,'--b') +daspect([1 1 1]) +xlabel('Z (Scapula CSYS)') +ylabel('Y (Scapula CSYS)') +title(['Acromion Index = ' num2str(AI) ' (GA = ' num2str(GA) ' GH = ' num2str(GH) ')']) +if segmentedScapula == 1 + saveas(gcf,sprintf('%s/%s/CT-%s-1/amira/AIwithSegmentedScapula_%s.png',directory,subject,subject,CTn)) +else + saveas(gcf,sprintf('%s/%s/CT-%s-1/amira/AI_%s.png',directory,subject,subject,CTn)) +end + +close all + +% Output + +CTtoXYZ = [TransverseAxis' ScapulaPlaneNormal cross(TransverseAxis, ScapulaPlaneNormal)']; +Cxyz = (GC - Origin) * CTtoXYZ; + +% Scapula coordinate system in CT frame +ScapulaCSYS(3,:) = TransverseAxis; % Z +ScapulaCSYS(1,:) = ScapulaPlaneNormal; % X +ScapulaCSYS(2,:) = cross(TransverseAxis,ScapulaPlaneNormal); % Y +ScapulaCSYS(4,:) = Origin; + +Result.sCase_id = strtok(subject,'-');%1 +Result.glenoid_Radius = GlenoidRadius;%2 +Result.glenoid_radiusRMSE = GlenoidRMSE;%3 +%Result.glenoidSpehricity = ' '; +%Result.glenoid_biconcave = ' '; +Result.glenoid_depth = CapHeight; %4 +Result.glenoid_width = scapulaWidth;%5 +Result.glenoid_height = scapulaHeight;%6 +Result.glenoid_center_PA = -Cxyz(2); %7 +Result.glenoid_center_IS = Cxyz(3); %8 +Result.glenoid_center_ML = Cxyz(1); %9 +Result.glenoid_version_ampl = Version3D;%10 +Result.glenoid_version_orient = VersionDirection;%11 +Result.glenoid_Version = Version2D; % 12 +Result.glenoid_Inclination = Inclination; %13 +% Result.glenoid_version2D = ' '; +% Result.glenoid_walch_class = ' '; +Result.humerus_joint_radius = ' '; %14 +Result.humeral_head_radius = HHRadius;%15 +% Result.humerus_GHsublux_2D = ' '; +% Result.humerus_SHsublux_2D = ' '; +Result.humerus_GHsubluxation_ampl = GHSI;%16 +Result.humerus_GHsubluxation_orient = GHSIDirection;%17 +Result.humerus_SHsubluxation_ampl = SHSI;%18 +Result.humerus_SHsubluxation_orient = SHSIDirection;%19 +% Result.scapula_CSA = ' '; +Result.scapula_CTangle = CTorientation; %20 +Result.scapula_CTangleVersion = WearPlaneAngle; %21 +Result.scapula_CTangleSHS = SHSPlaneAngle; % 22 +Result.scapula_CTangleGHS = GHSPlaneAngle; % 23 +Result.scapula_PlaneRMSE = PlaneRMSE;%24 +Result.scapula_AI = AI; + + + +%% Additional output + +if exist('output','var') == 1 + + % Scapula coordinate system in CT frame + ScapulaCSYS(1,:) = TransverseAxis; % X + ScapulaCSYS(2,:) = ScapulaPlaneNormal; % Y + ScapulaCSYS(3,:) = AxialPlane; % Z + ScapulaCSYS(4,:) = Origin; + ScapulaCSYS(5,:) = Origin + 10*ScapulaCSYS(1,:); + ScapulaCSYS(6,:) = Origin + 10*ScapulaCSYS(2,:); + ScapulaCSYS(7,:) = Origin + 10*ScapulaCSYS(3,:); + + %% ISB coordinate system in CT frame + + Xisb = (landmarks(4,:)-landmarks(2,:))/norm(landmarks(4,:)-landmarks(2,:)); %Med-Lat + Yisb = cross(Xisb,landmarks(1,:)-landmarks(2,:))/norm(cross(Xisb,landmarks(1,:)-landmarks(2,:)));%Ant-Pos + Zisb = cross(Xisb,Yisb);%Inf-Sup + + %% Locate abq coordinate system in CT frame + + Act2isb = [Xisb' Yisb' Zisb']; + Act2lbo = ScapulaCSYS(1:3,:)'; + Albo2isb = Act2isb/Act2lbo; + Athx2abq = SpinCalc('EA321toDCM',[40 0 0]);% scapular plane is 40� anterior to frontal plane + Aisb2thx = SpinCalc('EA321toDCM',[-35.6 -17.8 -2.5]);% initial position McClure et al. 2001 + + Act2abq = Athx2abq*Aisb2thx*Albo2isb*Act2lbo; + + %% Output initial position of scapula for abaqus simulation in CT coordinates + + % scaleFactor = 24/HHRadius; % =24mm / Humerus radius + ScapulaABQ(1,:) = HC; % Humerus center + ScapulaABQ(2,:) = 10*Act2abq(:,1)'+ScapulaABQ(1,:); + ScapulaABQ(3,:) = 10*Act2abq(:,2)'+ScapulaABQ(1,:); + ScapulaABQ(4,:) = 10*Act2abq(:,3)'+ScapulaABQ(1,:); + % ScapulaABQ(5,1) = scaleFactor; + + %% Output coordinates for obliqueSlice + % The ObliqueSlice can be defined as point + (normal OR u-vector and + % v-vector) + FriedmanPlane = [GC 1 0 0 0 1 0]; + obliqueSlice = [landmarks(6,:) ScapulaCSYS(2,:) -ScapulaCSYS(3,:)]; % Plane for muscle measurement + obliqueSlice2 = [GC ScapulaCSYS(3,:)]; % Scapula axial plane + obliqueSlice3 = [GC ScapulaCSYS(1,:) GO]; % Max wear plane + obliqueSlice4 = [GC ScapulaCSYS(2,:)]; % Scapular plane + obliqueSlice5 = [GC HO/norm(HO) GHeccentricity/norm(GHeccentricity)]; % Plane for GHS + obliqueSlice6 = [GC HO/norm(HO) SHeccentricity/norm(SHeccentricity)]; % Plane for SHS + + if Side == 0 + FriedmanPlane(1) = -FriedmanPlane(1); + obliqueSlice(1:3:end) = - obliqueSlice(1:3:end); + obliqueSlice2(1:3:end) = -obliqueSlice2(1:3:end); + obliqueSlice3(1:3:end) = -obliqueSlice3(1:3:end); + obliqueSlice4(1:3:end) = -obliqueSlice4(1:3:end); + obliqueSlice5(1:3:end) = -obliqueSlice5(1:3:end); + obliqueSlice6(1:3:end) = -obliqueSlice6(1:3:end); + end + + + + + switch output + case 'density' + %% Data for the calculations of the density in amira + % Needed variables are : + % - CylO: cylinder origin + % - CylA: cylinder alignment point + % - CylR_factor: scale factor for cylinder radius + % - Glenoid_sphere_center + % - Glenoid radius + % - CylO_shift5mm : cylinder origin shifted 5 mm behind + % glenoid surface center + % - CylA2 : cylinder alignment point n�2 + % - CylO_shift15mmfront: cylinder origin shifted 15 mm + % frontwards + % - CylA3 : cylinder alignemnt point n�2 + % - startcylpoint : origin of the new cylinder starting from + % the AG point + % - endcylpoint: end of cylinder, 40 mm from the startcylpoint + % in the scapula axis + + + CylO = GC; + Glenoid_sphere_center = GlenoidSphere'; + Glenoid_radius = GlenoidRadius; + + %% CylA - second point to form a line with glenoid surface + % center point which is parallel to scapula axis + CylA = CylO - 40 * TransverseAxis/norm(TransverseAxis); + + %% CylR - distance from glenoid surface center to most + % eccentric point of glenoid surface + + %Projection of surface points onto the same plane + PlanePoint = CylO; + PlaneNormal = (CylO-CylA)/norm(CylO-CylA); + + ProjectedPoints = zeros(size(p)); + DistanceToCylO = zeros(size(p,1),1); + for i = 1:size(p,1) + ProjectedPoints(i,:) = p(i,:) - dot(p(i,:) - PlanePoint, PlaneNormal) * PlaneNormal; + DistanceToCylO(i) = norm(ProjectedPoints(i,:) - CylO); + end + + %Selection of the most eccentric point -> radius of cylinder + CylR = max(DistanceToCylO); + + %Scale factor of the cylinder + OriginalCylR = 5; + CylR_factor = CylR/OriginalCylR; + + % Coordinates of start and end point of the D10 mm cylinder + Deepness = 5; %behind glenoid surface + smallCylR = 5; + CylO_shift5mm = GC - Deepness * TransverseAxis/norm(TransverseAxis); + CylA2 = CylO_shift5mm - smallCylR * TransverseAxis; + + % To place big cylinder (finer seleciton, exclusion of distant + % bones) + CylO_shift15mmfront = GC + 15 * TransverseAxis/norm(TransverseAxis); + CylA3 = CylO_shift15mmfront - 40 * TransverseAxis/norm(TransverseAxis); + + %%Projection of AG on scapula axis + AG = landmarks(6,:); + p1 = CylA3; + p2 = CylO_shift15mmfront; + + v1 = p2-p1; + v2 = AG-p1; + + E1 = v1; + E2 = cross(v1,v2); + E3 = cross(E1,E2); + + e1 = E1/norm(E1); + e2 = E2/norm(E2); + e3 = E3/norm(E3); + + Oc = p1; + Ocx = p1 + e1; + Ocy = p1 + e2; + Ocz = p1 + e3; + + O = [0,0,0]; + Ox = [1,0,0]; + Oy = [0,1,0]; + Oz = [0,0,1]; + + A = [Oc(1),Oc(2),Oc(3),1; + Ocx(1),Ocx(2),Ocx(3),1; + Ocy(1),Ocy(2),Ocy(3),1; + Ocz(1),Ocz(2),Ocz(3),1]; + + bx = [O(1); + Ox(1); + Oy(1); + Oz(1)]; + + + by = [O(2); + Ox(2); + Oy(2); + Oz(2)]; + + + bz = [O(3); + Ox(3); + Oy(3); + Oz(3)]; + + + B = [bx,by,bz]; + X = A\B; + T = [X'; + 0,0,0,1]; + + det(T); + + AG2 = [AG,1]; + AG3=AG2'; + diff = T*AG3; + projAGcyl=[diff(1),0,0,1]; + startcylpoint=T\projAGcyl'; + startcylpoint = [startcylpoint(1),startcylpoint(2),startcylpoint(3)]; + endcylpoint = startcylpoint + 40 * TransverseAxis/norm(TransverseAxis); + + %% Write data + if Side == 0 + CylO(1) = -CylO(1); + CylA(1) = -CylA(1); + Glenoid_sphere_center(1) = -Glenoid_sphere_center(1); + CylO_shift5mm(1) = -CylO_shift5mm(1); + CylA2(1) = -CylA2(1); + CylO_shift15mmfront(1) = -CylO_shift15mmfront(1); + CylA3(1) = -CylA3(1); + startcylpoint(1) = -startcylpoint(1); + endcylpoint(1) = -endcylpoint(1); + + + + end + mkdir('Generated_Amira_TCL_Scripts/CylinderReferences') + + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),'w'); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylR_factor, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),Glenoid_sphere_center, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),Glenoid_radius, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO_shift5mm, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA2, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylO_shift15mmfront, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s.dat',CTn),CylA3, '-append','precision',10); + fclose(fid); + + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\CylinderReferences\\CylinderReferences_%s_AmiraCode.dat',CTn),'w'); + + fprintf(fid,'set lineset1 [create HxLineSet]\n'); + fprintf(fid,'set p1 [$lineset1 addPoint %f %f %f]\n',CylO_shift5mm); + fprintf(fid,'set p2 [$lineset1 addPoint %f %f %f]\n',CylA2); + fprintf(fid,'$lineset1 addLine $p1 $p2\n'); + fprintf(fid,'{Line-Set} setIconPosition 20 10\n'); + fprintf(fid,'Line-Set fire\n\n'); + + fprintf(fid,'set lineset2 [create HxLineSet]\n'); + fprintf(fid,'set p1 [$lineset2 addPoint 0 0 0]\n'); + fprintf(fid,'set p2 [$lineset2 addPoint 5 0 0]\n'); + fprintf(fid,'$lineset2 addLine $p1 $p2\n'); + fprintf(fid,'{Line-Set2} setIconPosition 20 30\n'); + fprintf(fid,'Line-Set2 fire\n\n'); + + fprintf(fid,'set hideNewModules 0\n'); + fprintf(fid,'create {HxCreateSphere} {GlenoidSphereFit}\n'); + fprintf(fid,'{GlenoidSphereFit} setIconPosition 20 330\n'); + fprintf(fid,'{GlenoidSphereFit} fire\n'); + fprintf(fid,'{GlenoidSphereFit} {type} setValue 0\n'); + fprintf(fid,'{GlenoidSphereFit} {radius} setMinMax 0 100\n'); + fprintf(fid,'{GlenoidSphereFit} {radius} setButtons 0\n'); + fprintf(fid,'{GlenoidSphereFit} {radius} setIncrement 0.666667\n'); + fprintf(fid,'{GlenoidSphereFit} {radius} setValue %f\n',Glenoid_radius); + fprintf(fid,'{GlenoidSphereFit} {radius} setSubMinMax 0 100\n'); + fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 0 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{GlenoidSphereFit} {coords} setValue 0 %f\n',Glenoid_sphere_center(1)); + fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 1 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{GlenoidSphereFit} {coords} setValue 1 %f\n',Glenoid_sphere_center(2)); + fprintf(fid,'{GlenoidSphereFit} {coords} setMinMax 2 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{GlenoidSphereFit} {coords} setValue 2 %f\n',Glenoid_sphere_center(3)); + fprintf(fid,'{GlenoidSphereFit} {edgeLength} setMinMax 0.00999999977648258 5\n'); + fprintf(fid,'{GlenoidSphereFit} {edgeLength} setButtons 0\n'); + fprintf(fid,'{GlenoidSphereFit} {edgeLength} setIncrement 0.332667\n'); + fprintf(fid,'{GlenoidSphereFit} {edgeLength} setValue 1.07457\n'); + fprintf(fid,'{GlenoidSphereFit} {edgeLength} setSubMinMax 0.00999999977648258 5\n'); + fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setMinMax 0.100000001490116 20\n'); + fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setButtons 0\n'); + fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setIncrement 1.32667\n'); + fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setValue 1\n'); + fprintf(fid,'{GlenoidSphereFit} {nopPerUnit2} setSubMinMax 0.100000001490116 20\n'); + fprintf(fid,'GlenoidSphereFit fire\n'); + fprintf(fid,'GlenoidSphereFit setViewerMask 16383\n'); + fprintf(fid,'GlenoidSphereFit setPickable 1\n\n'); + + fprintf(fid,'set hideNewModules 0\n'); + fprintf(fid,'create {HxCreateSphere} {CorticalSphereFit}\n'); + fprintf(fid,'{CorticalSphereFit} setIconPosition 20 350\n'); + fprintf(fid,'{CorticalSphereFit} fire\n'); + fprintf(fid,'{CorticalSphereFit} {type} setValue 0\n'); + fprintf(fid,'{CorticalSphereFit} {radius} setMinMax 0 100\n'); + fprintf(fid,'{CorticalSphereFit} {radius} setButtons 0\n'); + fprintf(fid,'{CorticalSphereFit} {radius} setIncrement 0.666667\n'); + fprintf(fid,'{CorticalSphereFit} {radius} setValue %f\n',Glenoid_radius+3); + fprintf(fid,'{CorticalSphereFit} {radius} setSubMinMax 0 100\n'); + fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 0 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{CorticalSphereFit} {coords} setValue 0 %f\n',Glenoid_sphere_center(1)); + fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 1 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{CorticalSphereFit} {coords} setValue 1 %f\n',Glenoid_sphere_center(2)); + fprintf(fid,'{CorticalSphereFit} {coords} setMinMax 2 -3.40282346638529e+038 3.40282346638529e+038\n'); + fprintf(fid,'{CorticalSphereFit} {coords} setValue 2 %f\n',Glenoid_sphere_center(3)); + fprintf(fid,'{CorticalSphereFit} {edgeLength} setMinMax 0.00999999977648258 5\n'); + fprintf(fid,'{CorticalSphereFit} {edgeLength} setButtons 0\n'); + fprintf(fid,'{CorticalSphereFit} {edgeLength} setIncrement 0.332667\n'); + fprintf(fid,'{CorticalSphereFit} {edgeLength} setValue 1.07457\n'); + fprintf(fid,'{CorticalSphereFit} {edgeLength} setSubMinMax 0.00999999977648258 5\n'); + fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setMinMax 0.100000001490116 20\n'); + fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setButtons 0\n'); + fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setIncrement 1.32667\n'); + fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setValue 1\n'); + fprintf(fid,'{CorticalSphereFit} {nopPerUnit2} setSubMinMax 0.100000001490116 20\n'); + fprintf(fid,'CorticalSphereFit fire\n'); + fprintf(fid,'CorticalSphereFit setViewerMask 16383\n'); + fprintf(fid,'CorticalSphereFit setPickable 1\n\n'); + + fprintf(fid,'set lineset3 [create HxLineSet]\n'); + fprintf(fid,'set p1 [$lineset3 addPoint %f %f %f]\n',startcylpoint); + fprintf(fid,'set p2 [$lineset3 addPoint %f %f %f]\n',endcylpoint); + fprintf(fid,'$lineset3 addLine $p1 $p2\n'); + fprintf(fid,'{Line-Set3} setIconPosition 20 520\n'); + fprintf(fid,'Line-Set3 fire\n\n'); + + fprintf(fid,'set lineset4 [create HxLineSet]\n'); + fprintf(fid,'set p1 [$lineset4 addPoint 0 0 0]\n'); + fprintf(fid,'set p2 [$lineset4 addPoint 40 0 0]\n'); + fprintf(fid,'$lineset4 addLine $p1 $p2\n'); + fprintf(fid,'{Line-Set4} setIconPosition 20 540\n'); + fprintf(fid,'Line-Set4 fire\n\n'); + + fprintf(fid,'{Cylinder2.STL} setScaleFactor 1 %f %f\n',CylR_factor,CylR_factor); + fprintf(fid,'{Cylinder2.STL} applyTransform\n\n'); + + fprintf(fid,'saveProject\n'); + fprintf(fid,'clear\n'); + fprintf(fid,'echo "Complete center coordinates of ReamingSphere"\n\n'); + + %fprintf(fid,' + + fclose(fid); + + case 'References' + + mkdir('Generated_Amira_TCL_Scripts/References') + + % References data for a study case + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'w'); + fprintf(fid,'# Scapula coordinate system\n\n'); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),ScapulaCSYS(4:7,:), '-append','precision',10); + fclose(fid); + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'a'); + fprintf(fid,'\n\n# Abaqus reference\n\n'); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),ScapulaABQ, '-append','precision',10); + fclose(fid); + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),'a'); + fprintf(fid,'\n\n# Glenoid centerline\n\n'); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),GC, '-append','precision',10); + dlmwrite(sprintf('Generated_Amira_TCL_Scripts\\References\\References_%s.dat',CTn),GlenoidSphere', '-append','precision',10); + fclose(fid); + + case 'obliqueSlice' + + [token, remain] = strtok(directory,'../../..'); + directory = ['Z:/' token remain]; + pictureName = [CTn '_new']; + + mkdir('Generated_Amira_TCL_Scripts/obliqueSlice') + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\obliqueslice\\obliqueSlice_%s.tcl',CTn),'w'); + + fprintf(fid,'# Create an ObliqueSlice with Friedman plane #\n'); + fprintf(fid,'ObliqueSlice orientation hit 0\n'); + fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f %f %f %f\n',FriedmanPlane); + fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 0 item 2 1 item 3 0 item 4 0\n'); + fprintf(fid,'ObliqueSlice fire\n'); + fprintf(fid,'set planepos [ObliqueSlice translate getValue]\n'); + fprintf(fid,'set maxvalue [ObliqueSlice translate getMaxValue]\n'); + fprintf(fid,'set planepos [expr {$maxvalue - int($planepos)}]\n'); + fprintf(fid,'ObliqueSlice createImage %s_$planepos\n', CTn); + fprintf(fid,'create HxCastField {CastField}\n'); + fprintf(fid,'CastField data connect %s_$planepos\n', CTn); + fprintf(fid,'CastField fire\n'); + fprintf(fid,'CastField scaling setValue 0 0.21\n'); + fprintf(fid,'CastField scaling setValue 1 200\n'); + fprintf(fid,'CastField fire\n'); + fprintf(fid,'[ {CastField} create ] setLabel %s_$planepos.jpeg\n', CTn); + if strcmp(location, 'P') + fprintf(fid,'%s_$planepos.jpeg exportData "JPEG" %s/%s/CT-%s-1/amira/%s_$planepos.jpeg\n\n', CTn, directory, subject, subject, CTn ); + else + fprintf(fid,'%s_$planepos.jpeg exportData "JPEG" Z:/data/%s/%s/CT-%s-1/amira/%s_$planepos.jpeg\n\n', CTn, directory, subject, subject, CTn ); + end + + fprintf(fid,'# Get image for muscle measurement #\n'); + fprintf(fid,'ObliqueSlice orientation hit 0\n'); + fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice fire\n', obliqueSlice); % Muscle evaluation, through notch + fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n'); + fprintf(fid,'ObliqueSlice createImage {%s}\n', pictureName); + if strcmp(location, 'pathologic') + fprintf(fid,'%s exportData "2D Tiff" %s/%s/CT-%s-1/amira/%s.tif\n', pictureName, directory, subject, subject, pictureName ); + else + fprintf(fid,'%s exportData "2D Tiff" %s/%s/CT-%s-1/amira/%s.tif\n', pictureName, directory, subject, subject, pictureName ); + end + fprintf(fid,'saveProject\n\n'); + + fprintf(fid,'ObliqueSlice2 setPlane %f %f %f %f %f %f\nObliqueSlice2 fire\n', obliqueSlice2); % Scapular axial plane + fprintf(fid,'ObliqueSlice2 options setValue 0 1\n'); + fprintf(fid,'ObliqueSlice2 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); + + fprintf(fid,'ObliqueSlice3 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice3 fire\n', obliqueSlice3); % Max wear plane + fprintf(fid,'ObliqueSlice3 options setValue 0 1\n'); + fprintf(fid,'ObliqueSlice3 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); + + fprintf(fid,'ObliqueSlice4 setPlane %f %f %f %f %f %f\nObliqueSlice4 fire\n', obliqueSlice4); % Scapula plane + fprintf(fid,'ObliqueSlice4 options setValue 0 1\n'); + fprintf(fid,'ObliqueSlice4 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); + + fprintf(fid,'ObliqueSlice5 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice5 fire\n', obliqueSlice5); % Scapula plane + fprintf(fid,'ObliqueSlice5 options setValue 0 1\n'); + fprintf(fid,'ObliqueSlice5 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); + + fprintf(fid,'ObliqueSlice6 setPlane %f %f %f %f %f %f %f %f %f\nObliqueSlice6 fire\n', obliqueSlice6); % Scapula plane + fprintf(fid,'ObliqueSlice6 options setValue 0 1\n'); + fprintf(fid,'ObliqueSlice6 sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n\n'); + + fprintf(fid,'remove CreateSphere%s HumSphere%s.surf SphereView%s Intersection\n', CTn, CTn, CTn); + fprintf(fid,'remove CreateSphere%s_2 GlenoidSphere%s.surf SphereView%s_2 Intersection2\n', CTn, CTn, CTn); + fprintf(fid,'create HxCreateSphere {CreateSphere%s}\n', CTn); + fprintf(fid,'CreateSphere%s setIconPosition 20 500\n', CTn); + fprintf(fid,'CreateSphere%s radius setMinMax 0 50\n', CTn); + fprintf(fid,'CreateSphere%s radius setValue %f\n', CTn, HHRadius); + if Side == 0 + fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', CTn, -HC(1)); + else + fprintf(fid,'CreateSphere%s coords setValue 0 %f\n', CTn, HC(1)); + end + fprintf(fid,'CreateSphere%s coords setValue 1 %f\n', CTn, HC(2)); + fprintf(fid,'CreateSphere%s coords setValue 2 %f\n', CTn, HC(3)); + fprintf(fid,'CreateSphere%s fire\n', CTn); + fprintf(fid,'[ {CreateSphere%s} create\n ] setLabel {HumSphere%s.surf}\n', CTn, CTn); + fprintf(fid,'HumSphere%s.surf setIconPosition 200 500\n', CTn); + fprintf(fid,'HumSphere%s.surf master connect CreateSphere%s\n', CTn, CTn); + fprintf(fid,'HumSphere%s.surf fire\n', CTn); + fprintf(fid,'create HxDisplaySurface {SphereView%s}\n', CTn); + fprintf(fid,'SphereView%s setIconPosition 400 500\n', CTn); + fprintf(fid,'SphereView%s data connect HumSphere%s.surf\n', CTn, CTn); + fprintf(fid,'SphereView%s drawStyle setValue 4\n', CTn); + fprintf(fid,'SphereView%s fire\n', CTn); + fprintf(fid,'create HxOverlayGrid {Intersection}\n'); + fprintf(fid,'Intersection module connect ObliqueSlice4\n'); + fprintf(fid,'Intersection data connect HumSphere%s.surf\n', CTn); + fprintf(fid,'Intersection lineWidth setValue 2\n'); + fprintf(fid,'ObliqueSlice4 setViewerMask 16383\n'); + fprintf(fid,'ObliqueSlice4 setPlane %f %f %f 1 0 0 0 1 0\n', HC); + fprintf(fid,'ObliqueSlice4 fire\n\n'); + + fprintf(fid,'create HxCreateSphere {CreateSphere%s_2}\n', CTn); + fprintf(fid,'CreateSphere%s_2 setIconPosition 20 520\n', CTn); + fprintf(fid,'CreateSphere%s_2 radius setMinMax 0 50\n', CTn); + fprintf(fid,'CreateSphere%s_2 radius setValue %f\n', CTn, GlenoidRadius); + if Side == 0 + fprintf(fid,'CreateSphere%s_2 coords setValue 0 %f\n', CTn, -GlenoidSphere(1)); + else + fprintf(fid,'CreateSphere%s_2 coords setValue 0 %f\n', CTn, GlenoidSphere(1)); + end + fprintf(fid,'CreateSphere%s_2 coords setValue 1 %f\n', CTn, GlenoidSphere(2)); + fprintf(fid,'CreateSphere%s_2 coords setValue 2 %f\n', CTn, GlenoidSphere(3)); + fprintf(fid,'CreateSphere%s_2 fire\n', CTn); + fprintf(fid,'[ {CreateSphere%s_2} create\n ] setLabel {GlenoidSphere%s.surf}\n', CTn, CTn); + fprintf(fid,'GlenoidSphere%s.surf setIconPosition 200 520\n', CTn); + fprintf(fid,'GlenoidSphere%s.surf master connect CreateSphere%s_2\n', CTn, CTn); + fprintf(fid,'GlenoidSphere%s.surf fire\n', CTn); + fprintf(fid,'create HxDisplaySurface {SphereView%s_2}\n', CTn); + fprintf(fid,'SphereView%s_2 setIconPosition 400 520\n', CTn); + fprintf(fid,'SphereView%s_2 data connect GlenoidSphere%s.surf\n', CTn, CTn); + fprintf(fid,'SphereView%s_2 drawStyle setValue 4\n', CTn); + fprintf(fid,'SphereView%s_2 fire\n', CTn); + fprintf(fid,'create HxOverlayGrid {Intersection2}\n'); + fprintf(fid,'Intersection2 module connect ObliqueSlice5\n'); + fprintf(fid,'Intersection2 data connect GlenoidSphere%s.surf\n', CTn); + fprintf(fid,'Intersection2 lineWidth setValue 2\n'); + fprintf(fid,'ObliqueSlice5 setViewerMask 16383\n'); + fprintf(fid,'ObliqueSlice5 setPlane %f %f %f 1 0 0 0 1 0\n', GlenoidSphere); + fprintf(fid,'ObliqueSlice5 fire\n'); + + fclose(fid); + + case 'display' + + mkdir('Generated_Amira_TCL_Scripts/display'); + fid = fopen(sprintf('Generated_Amira_TCL_Scripts\\display\\display_%s.tcl',CTn),'w'); + % if Side == 0; + % PlaneMean(1) = -PlaneMean(1); + % end + obliqueSlice7 = [PlaneMean ScapulaCSYS(2,:)]; + if Side == 0 + obliqueSlice7(1:3:end) = -obliqueSlice7(1:3:end); + GlenoidSphere(1) = -GlenoidSphere(1); + end + fprintf(fid,'ObliqueSlice setPlane %f %f %f %f %f %f \nObliqueSlice fire\n', obliqueSlice7); % Muscle evaluation, through notch + fprintf(fid,'ObliqueSlice sampling setState item 0 3 item 1 1 item 2 1 item 3 0 item 4 0\n'); + fprintf(fid,'ObliqueSlice fire\n\n'); + + fprintf(fid,'create HxCreateSphere {GlenoidSphere%s}\n', CTn); + fprintf(fid,'GlenoidSphere%s setIconPosition 20 550\n', CTn); + fprintf(fid,'GlenoidSphere%s radius setMinMax 0 50\n', CTn); + fprintf(fid,'GlenoidSphere%s radius setValue %f\n', CTn, GlenoidRadius); + fprintf(fid,'GlenoidSphere%s coords setValue 0 %f\n', CTn, GlenoidSphere(1)); + fprintf(fid,'GlenoidSphere%s coords setValue 1 %f\n', CTn, GlenoidSphere(2)); + fprintf(fid,'GlenoidSphere%s coords setValue 2 %f\n', CTn, GlenoidSphere(3)); + fprintf(fid,'GlenoidSphere%s fire\n\n', CTn); + + fprintf(fid,'[ {GlenoidSphere%s} create ] setLabel {HumSphereGlenoid%s.surf}\n', CTn, CTn); + fprintf(fid,'HumSphereGlenoid%s.surf setIconPosition 200 550\n', CTn); + fprintf(fid,'HumSphereGlenoid%s.surf master connect GlenoidSphere%s\n', CTn, CTn); + fprintf(fid,'HumSphereGlenoid%s.surf fire\n\n', CTn); + + fprintf(fid,'create HxDisplaySurface {GlenoidSphereView%s}\n', CTn); + fprintf(fid,'GlenoidSphereView%s setIconPosition 400 550\n', CTn); + fprintf(fid,'GlenoidSphereView%s data connect HumSphereGlenoid%s.surf\n', CTn, CTn); + fprintf(fid,'GlenoidSphereView%s drawStyle setValue 4\n', CTn); + fprintf(fid,'GlenoidSphereView%s fire\n', CTn); + fclose(fid); + otherwise + disp('unknown output request') + end +end diff --git a/anatomy/scapula_calculation_files/SpinCalc.m b/anatomy/scapula_calculation_files/SpinCalc.m index 92f9f7d..10d5a1d 100644 --- a/anatomy/scapula_calculation_files/SpinCalc.m +++ b/anatomy/scapula_calculation_files/SpinCalc.m @@ -1,400 +1,400 @@ -function OUTPUT=SpinCalc(CONVERSION,INPUT,tol,ichk) -%Function for the conversion of one rotation input type to desired output. -%Supported conversion input/output types are as follows: -% 1: Q Rotation Quaternions -% 2: EV Euler Vector and rotation angle (degrees) -% 3: DCM Orthogonal DCM Rotation Matrix -% 4: EA### Euler angles (12 possible sets) (degrees) -% -%Author: John Fuller -%National Institute of Aerospace -%Hampton, VA 23666 -%John.Fuller@nianet.org -% -%Version 1.3 -%June 30th, 2009 -% -%Version 1.3 updates -% SpinCalc now detects when input data is too close to Euler singularity, if user is choosing -% Euler angle output. Prohibits output if middle angle is within 0.1 degree of singularity value. -%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -% OUTPUT=SpinCalc(CONVERSION,INPUT,tol,ichk) -%Inputs: -%CONVERSION - Single string value that dictates the type of desired -% conversion. The conversion strings are listed below. -% -% 'DCMtoEA###' 'DCMtoEV' 'DCMtoQ' **for cases that involve -% 'EA###toDCM' 'EA###toEV' 'EA###toQ' euler angles, ### should be -% 'EVtoDCM' 'EVtoEA###' 'EVtoQ' replaced with the proper -% 'QtoDCM' 'QtoEA###' 'QtoEV' order desired. EA321 would -% 'EA###toEA###' be Z(yaw)-Y(pitch)-X(roll). -% -%INPUT - matrix or vector that corresponds to the first entry in the -% CONVERSION string, formatted as follows: -% -% DCM - 3x3xN multidimensional matrix which pre-multiplies by a coordinate -% frame vector to rotate it to the desired new frame. -% -% EA### - [psi,theta,phi] (Nx3) row vector list dictating to the first angle -% rotation (psi), the second (theta), and third (phi) (DEGREES) -% -% EV - [m1,m2,m3,MU] (Nx4) row vector list dictating the components of euler -% rotation vector (original coordinate frame) and the Euler -% rotation angle about that vector (MU) (DEGREES) -% -% Q - [q1,q2,q3,q4] (Nx4) row vector list defining quaternion of -% rotation. q4 = cos(MU/2) where MU is Euler rotation angle -% -%tol - tolerance value -%ichk - 0 disables warning flags -% 1 enables warning flags (near singularities) -%**NOTE: N corresponds to multiple orientations -%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -%Output: -%OUTPUT - matrix or vector corresponding to the second entry in the -% CONVERSION input string, formatted as shown above. -%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -%Pre-processer to determine type of conversion from CONVERSION string input -%Types are numbered as follows: -%Q=1 EV=2 DCM=3 EA=4 -i_type=strfind(lower(CONVERSION),'to'); -length=size(CONVERSION,2); -if length>12 || length<4, %no CONVERSION string can be shorter than 4 or longer than 12 chars - error('Error: Invalid entry for CONVERSION input string'); -end -o_type=length-i_type; -if i_type<5, - i_type=i_type-1; -else - i_type=i_type-2; -end -if o_type<5, - o_type=o_type-1; -else - o_type=o_type-2; -end -TYPES=cell(1,4); -TYPES{1,1}='Q'; TYPES{1,2}='EV'; TYPES{1,3}='DCM'; TYPES{1,4}='EA'; -INPUT_TYPE=TYPES{1,i_type}; -OUTPUT_TYPE=TYPES{1,o_type}; -clear TYPES -%Confirm input as compared to program interpretation -if i_type~=4 && o_type~=4, %if input/output are NOT Euler angles - CC=[INPUT_TYPE,'to',OUTPUT_TYPE]; - if strcmpi(CONVERSION,CC)==0; - error('Error: Invalid entry for CONVERSION input string'); - end -else - if i_type==4, %if input type is Euler angles, determine the order of rotations - EULER_order_in=str2double(CONVERSION(1,3:5)); - rot_1_in=floor(EULER_order_in/100); %first rotation - rot_2_in=floor((EULER_order_in-rot_1_in*100)/10); %second rotation - rot_3_in=(EULER_order_in-rot_1_in*100-rot_2_in*10); %third rotation - if rot_1_in<1 || rot_2_in<1 || rot_3_in<1 || rot_1_in>3 || rot_2_in>3 || rot_3_in>3, - error('Error: Invalid input Euler angle order type (conversion string).'); %check that all orders are between 1 and 3 - elseif rot_1_in==rot_2_in || rot_2_in==rot_3_in, - error('Error: Invalid input Euler angle order type (conversion string).'); %check that no 2 consecutive orders are equal (invalid) - end - %check input dimensions to be 1x3x1 - if size(INPUT,2)~=3 || size(INPUT,3)~=1 - error('Error: Input euler angle data vector is not Nx3') - end - %identify singularities - if rot_1_in==rot_3_in, %Type 2 rotation (first and third rotations about same axis) - if INPUT(:,2)<=zeros(size(INPUT,1),1) | INPUT(:,2)>=180*ones(size(INPUT,1),1), %confirm second angle within range - error('Error: Second input Euler angle(s) outside 0 to 180 degree range') - elseif abs(INPUT(:,2))<2*ones(size(INPUT,1),1) | abs(INPUT(:,2))>178*ones(size(INPUT,1),1), %check for singularity - if ichk==1, - errordlg('Warning: Input Euler angle rotation(s) near a singularity. Second angle near 0 or 180 degrees.') - end - end - else %Type 1 rotation (all rotations about each of three axes) - if abs(INPUT(:,2))>=90*ones(size(INPUT,1),1), %confirm second angle within range - error('Error: Second input Euler angle(s) outside -90 to 90 degree range') - elseif abs(INPUT(:,2))>88*ones(size(INPUT,1),1), %check for singularity - if ichk==1, - errordlg('Warning: Input Euler angle(s) rotation near a singularity. Second angle near -90 or 90 degrees.') - end - end - end - end - if o_type==4, %if output type is Euler angles, determine order of rotations - EULER_order_out=str2double(CONVERSION(1,length-2:length)); - rot_1_out=floor(EULER_order_out/100); %first rotation - rot_2_out=floor((EULER_order_out-rot_1_out*100)/10); %second rotation - rot_3_out=(EULER_order_out-rot_1_out*100-rot_2_out*10); %third rotation - if rot_1_out<1 || rot_2_out<1 || rot_3_out<1 || rot_1_out>3 || rot_2_out>3 || rot_3_out>3, - error('Error: Invalid output Euler angle order type (conversion string).'); %check that all orders are between 1 and 3 - elseif rot_1_out==rot_2_out || rot_2_out==rot_3_out, - error('Error: Invalid output Euler angle order type (conversion string).'); %check that no 2 consecutive orders are equal - end - end - if i_type==4 && o_type~=4, %if input are euler angles but not output - CC=['EA',num2str(EULER_order_in),'to',OUTPUT_TYPE]; %construct program conversion string for checking against user input - elseif o_type==4 && i_type~=4, %if output are euler angles but not input - CC=[INPUT_TYPE,'to','EA',num2str(EULER_order_out)]; %construct program conversion string for checking against user input - elseif i_type==4 && o_type==4, %if both input and output are euler angles - CC=['EA',num2str(EULER_order_in),'to','EA',num2str(EULER_order_out)]; %construct program conversion string - end - if strcmpi(CONVERSION,CC)==0; %check program conversion string against user input to confirm the conversion command - error('Error: Invalid entry for CONVERSION input string'); - end -end -clear i_type o_type CC - -%From the input, determine the quaternions that uniquely describe the -%rotation prescribed by that input. The output will be calculated in the -%second portion of the code from these quaternions. -switch INPUT_TYPE - case 'DCM' - if size(INPUT,1)~=3 || size(INPUT,2)~=3 %check DCM dimensions - error('Error: DCM matrix is not 3x3xN'); - end - N=size(INPUT,3); %number of orientations - %Check if matrix is indeed orthogonal - perturbed=NaN(3,3,N); - DCM_flag=0; - for ii=1:N, - perturbed(:,:,ii)=abs(INPUT(:,:,ii)*INPUT(:,:,ii)'-eye(3)); %perturbed array shows difference between DCM*DCM' and I - if abs(det(INPUT(:,:,ii))-1)>tol, %if determinant is off by one more than tol, user is warned. - if ichk==1, - DCM_flag=1; - end - end - if abs(det(INPUT(:,:,ii))+1)<0.05, %if determinant is near -1, DCM is improper - error('Error: Input DCM(s) improper'); - end - if DCM_flag==1, - errordlg('Warning: Input DCM matrix determinant(s) off from 1 by more than tolerance.') - end - end - DCM_flag=0; - if ichk==1, - for kk=1:N, - for ii=1:3, - for jj=1:3, - if perturbed(ii,jj,kk)>tol, %if any difference is larger than tol, user is warned. - DCM_flag=1; - end - end - end - end - if DCM_flag==1, - fprintf('Warning: Input DCM(s) matrix not orthogonal to precision tolerance.') - end - end - clear perturbed DCM_flag - Q=NaN(4,N); - for ii=1:N, - denom=NaN(4,1); - denom(1)=0.5*sqrt(1+INPUT(1,1,ii)-INPUT(2,2,ii)-INPUT(3,3,ii)); - denom(2)=0.5*sqrt(1-INPUT(1,1,ii)+INPUT(2,2,ii)-INPUT(3,3,ii)); - denom(3)=0.5*sqrt(1-INPUT(1,1,ii)-INPUT(2,2,ii)+INPUT(3,3,ii)); - denom(4)=0.5*sqrt(1+INPUT(1,1,ii)+INPUT(2,2,ii)+INPUT(3,3,ii)); - %determine which Q equations maximize denominator - switch find(denom==max(denom),1,'first') %determines max value of qtests to put in denominator - case 1 - Q(1,ii)=denom(1); - Q(2,ii)=(INPUT(1,2,ii)+INPUT(2,1,ii))/(4*Q(1,ii)); - Q(3,ii)=(INPUT(1,3,ii)+INPUT(3,1,ii))/(4*Q(1,ii)); - Q(4,ii)=(INPUT(2,3,ii)-INPUT(3,2,ii))/(4*Q(1,ii)); - case 2 - Q(2,ii)=denom(2); - Q(1,ii)=(INPUT(1,2,ii)+INPUT(2,1,ii))/(4*Q(2,ii)); - Q(3,ii)=(INPUT(2,3,ii)+INPUT(3,2,ii))/(4*Q(2,ii)); - Q(4,ii)=(INPUT(3,1,ii)-INPUT(1,3,ii))/(4*Q(2,ii)); - case 3 - Q(3,ii)=denom(3); - Q(1,ii)=(INPUT(1,3,ii)+INPUT(3,1,ii))/(4*Q(3,ii)); - Q(2,ii)=(INPUT(2,3,ii)+INPUT(3,2,ii))/(4*Q(3,ii)); - Q(4,ii)=(INPUT(1,2,ii)-INPUT(2,1,ii))/(4*Q(3,ii)); - case 4 - Q(4,ii)=denom(4); - Q(1,ii)=(INPUT(2,3,ii)-INPUT(3,2,ii))/(4*Q(4,ii)); - Q(2,ii)=(INPUT(3,1,ii)-INPUT(1,3,ii))/(4*Q(4,ii)); - Q(3,ii)=(INPUT(1,2,ii)-INPUT(2,1,ii))/(4*Q(4,ii)); - end - end - Q=Q'; - clear denom - case 'EV' %Euler Vector Input Type - if size(INPUT,2)~=4 || size(INPUT,3)~=1 %check dimensions - error('Error: Input euler vector and rotation data matrix is not Nx4') - end - N=size(INPUT,1); - MU=INPUT(:,4)*pi/180; %assign mu name for clarity - if sqrt(INPUT(:,1).^2+INPUT(:,2).^2+INPUT(:,3).^2)-ones(N,1)>tol*ones(N,1), %check that input m's constitute unit vector - error('Input euler vector(s) components do not constitute a unit vector') - end - if MU2*pi*ones(N,1), %check if rotation about euler vector is between 0 and 360 - error('Input euler rotation angle(s) not between 0 and 360 degrees') - end - Q=[INPUT(:,1).*sin(MU/2),INPUT(:,2).*sin(MU/2),INPUT(:,3).*sin(MU/2),cos(MU/2)]; %quaternion - clear m1 m2 m3 MU - case 'EA' - psi=INPUT(:,1)*pi/180; theta=INPUT(:,2)*pi/180; phi=INPUT(:,3)*pi/180; - N=size(INPUT,1); %number of orientations - %Pre-calculate cosines and sines of the half-angles for conversion. - c1=cos(psi./2); c2=cos(theta./2); c3=cos(phi./2); - s1=sin(psi./2); s2=sin(theta./2); s3=sin(phi./2); - c13=cos((psi+phi)./2); s13=sin((psi+phi)./2); - c1_3=cos((psi-phi)./2); s1_3=sin((psi-phi)./2); - c3_1=cos((phi-psi)./2); s3_1=sin((phi-psi)./2); - if EULER_order_in==121, - Q=[c2.*s13,s2.*c1_3,s2.*s1_3,c2.*c13]; - elseif EULER_order_in==232, - Q=[s2.*s1_3,c2.*s13,s2.*c1_3,c2.*c13]; - elseif EULER_order_in==313; - Q=[s2.*c1_3,s2.*s1_3,c2.*s13,c2.*c13]; - elseif EULER_order_in==131, - Q=[c2.*s13,s2.*s3_1,s2.*c3_1,c2.*c13]; - elseif EULER_order_in==212, - Q=[s2.*c3_1,c2.*s13,s2.*s3_1,c2.*c13]; - elseif EULER_order_in==323, - Q=[s2.*s3_1,s2.*c3_1,c2.*s13,c2.*c13]; - elseif EULER_order_in==123, - Q=[s1.*c2.*c3+c1.*s2.*s3,c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*s3+s1.*s2.*c3,c1.*c2.*c3-s1.*s2.*s3]; - elseif EULER_order_in==231, - Q=[c1.*c2.*s3+s1.*s2.*c3,s1.*c2.*c3+c1.*s2.*s3,c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*c3-s1.*s2.*s3]; - elseif EULER_order_in==312, - Q=[c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*s3+s1.*s2.*c3,s1.*c2.*c3+c1.*s2.*s3,c1.*c2.*c3-s1.*s2.*s3]; - elseif EULER_order_in==132, - Q=[s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*s3-s1.*s2.*c3,c1.*s2.*c3+s1.*c2.*s3,c1.*c2.*c3+s1.*s2.*s3]; - elseif EULER_order_in==213, - Q=[c1.*s2.*c3+s1.*c2.*s3,s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*s3-s1.*s2.*c3,c1.*c2.*c3+s1.*s2.*s3]; - elseif EULER_order_in==321, - Q=[c1.*c2.*s3-s1.*s2.*c3,c1.*s2.*c3+s1.*c2.*s3,s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*c3+s1.*s2.*s3]; - else - error('Error: Invalid input Euler angle order type (conversion string)'); - end - clear c1 s1 c2 s2 c3 s3 c13 s13 c1_3 s1_3 c3_1 s3_1 psi theta phi - case 'Q' - if size(INPUT,2)~=4 || size(INPUT,3)~=1 - error('Error: Input quaternion matrix is not Nx4'); - end - N=size(INPUT,1); %number of orientations - if ichk==1, - if abs(sqrt(INPUT(:,1).^2+INPUT(:,2).^2+INPUT(:,3).^2+INPUT(:,4).^2)-ones(N,1))>tol*ones(N,1) - errordlg('Warning: Input quaternion norm(s) deviate(s) from unity by more than tolerance') - end - end - Q=INPUT; -end -clear INPUT INPUT_TYPE EULER_order_in - -%Normalize quaternions in case of deviation from unity. User has already -%been warned of deviation. -Qnorms=sqrt(sum(Q.*Q,2)); -Q=[Q(:,1)./Qnorms,Q(:,2)./Qnorms,Q(:,3)./Qnorms,Q(:,4)./Qnorms]; - -switch OUTPUT_TYPE - case 'DCM' - Q=reshape(Q',1,4,N); - OUTPUT=[Q(1,1,:).^2-Q(1,2,:).^2-Q(1,3,:).^2+Q(1,4,:).^2,2*(Q(1,1,:).*Q(1,2,:)+Q(1,3,:).*Q(1,4,:)),2*(Q(1,1,:).*Q(1,3,:)-Q(1,2,:).*Q(1,4,:)); - 2*(Q(1,1,:).*Q(1,2,:)-Q(1,3,:).*Q(1,4,:)),-Q(1,1,:).^2+Q(1,2,:).^2-Q(1,3,:).^2+Q(1,4,:).^2,2*(Q(1,2,:).*Q(1,3,:)+Q(1,1,:).*Q(1,4,:)); - 2*(Q(1,1,:).*Q(1,3,:)+Q(1,2,:).*Q(1,4,:)),2*(Q(1,2,:).*Q(1,3,:)-Q(1,1,:).*Q(1,4,:)),-Q(1,1,:).^2-Q(1,2,:).^2+Q(1,3,:).^2+Q(1,4,:).^2]; - case 'EV' - MU=2*atan2(sqrt(sum(Q(:,1:3).*Q(:,1:3),2)),Q(:,4)); - if sin(MU/2)~=zeros(N,1), - OUTPUT=[Q(:,1)./sin(MU/2),Q(:,2)./sin(MU/2),Q(:,3)./sin(MU/2),MU*180/pi]; - else - OUTPUT=NaN(N,4); - for ii=1:N, - if sin(MU(ii,1)/2)~=0, - OUTPUT(ii,1:4)=[Q(ii,1)/sin(MU(ii,1)/2),Q(ii,2)/sin(MU(ii,1)/2),Q(ii,3)/sin(MU(ii,1)/2),MU(ii,1)*180/pi]; - else - OUTPUT(ii,1:4)=[1,0,0,MU(ii,1)*180/pi]; - end - end - end - case 'Q' - OUTPUT=Q; - case 'EA' - if EULER_order_out==121, - psi=atan2((Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); - theta=acos(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2); - phi=atan2((Q(:,1).*Q(:,2)-Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); - Euler_type=2; - elseif EULER_order_out==232; - psi=atan2((Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); - theta=acos(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2); - phi=atan2((Q(:,2).*Q(:,3)-Q(:,1).*Q(:,4)),(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); - Euler_type=2; - elseif EULER_order_out==313; - psi=atan2((Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); - theta=acos(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2); - phi=atan2((Q(:,1).*Q(:,3)-Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); - Euler_type=2; - elseif EULER_order_out==131; - psi=atan2((Q(:,1).*Q(:,3)-Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); - theta=acos(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2); - phi=atan2((Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); - Euler_type=2; - elseif EULER_order_out==212; - psi=atan2((Q(:,1).*Q(:,2)-Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); - theta=acos(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2); - phi=atan2((Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); - Euler_type=2; - elseif EULER_order_out==323; - psi=atan2((Q(:,2).*Q(:,3)-Q(:,1).*Q(:,4)),(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); - theta=acos(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2); - phi=atan2((Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); - Euler_type=2; - elseif EULER_order_out==123; - psi=atan2(2.*(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); - theta=asin(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); - phi=atan2(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); - Euler_type=1; - elseif EULER_order_out==231; - psi=atan2(2.*(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); - theta=asin(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); - phi=atan2(2.*(Q(:,1).*Q(:,4)-Q(:,3).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); - Euler_type=1; - elseif EULER_order_out==312; - psi=atan2(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); - theta=asin(2.*(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); - phi=atan2(2.*(Q(:,2).*Q(:,4)-Q(:,3).*Q(:,1)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); - Euler_type=1; - elseif EULER_order_out==132; - psi=atan2(2.*(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); - theta=asin(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); - phi=atan2(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); - Euler_type=1; - elseif EULER_order_out==213; - psi=atan2(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); - theta=asin(2.*(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); - phi=atan2(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); - Euler_type=1; - elseif EULER_order_out==321; - psi=atan2(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); - theta=asin(2.*(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); - phi=atan2(2.*(Q(:,1).*Q(:,4)+Q(:,3).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); - Euler_type=1; - else - error('Error: Invalid output Euler angle order type (conversion string).'); - end - if(isreal([psi,theta,phi]))==0, - error('Error: Unreal Euler output. Input resides too close to singularity. Please choose different output type.') - end - OUTPUT=mod([psi,theta,phi]*180/pi,360); %deg - if Euler_type==1, - sing_chk=find(abs(theta)*180/pi>89.9); - sing_chk=sort(sing_chk(sing_chk>0)); - if size(sing_chk,1)>=1, - error('Error: Input rotation #%s resides too close to Type 1 Euler singularity.\nType 1 Euler singularity occurs when second angle is -90 or 90 degrees.\nPlease choose different output type.',num2str(sing_chk(1,1))); - end - elseif Euler_type==2, - sing_chk=[find(abs(theta*180/pi)<0.1);find(abs(theta*180/pi-180)<0.1);find(abs(theta*180/pi-360))<0.1]; - sing_chk=sort(sing_chk(sing_chk>0)); - if size(sing_chk,1)>=1, - error('Error: Input rotation #%s resides too close to Type 2 Euler singularity.\nType 2 Euler singularity occurs when second angle is 0 or 180 degrees.\nPlease choose different output type.',num2str(sing_chk(1,1))); - end - end -end - - - - - - +function OUTPUT=SpinCalc(CONVERSION,INPUT,tol,ichk) +%Function for the conversion of one rotation input type to desired output. +%Supported conversion input/output types are as follows: +% 1: Q Rotation Quaternions +% 2: EV Euler Vector and rotation angle (degrees) +% 3: DCM Orthogonal DCM Rotation Matrix +% 4: EA### Euler angles (12 possible sets) (degrees) +% +%Author: John Fuller +%National Institute of Aerospace +%Hampton, VA 23666 +%John.Fuller@nianet.org +% +%Version 1.3 +%June 30th, 2009 +% +%Version 1.3 updates +% SpinCalc now detects when input data is too close to Euler singularity, if user is choosing +% Euler angle output. Prohibits output if middle angle is within 0.1 degree of singularity value. +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +% OUTPUT=SpinCalc(CONVERSION,INPUT,tol,ichk) +%Inputs: +%CONVERSION - Single string value that dictates the type of desired +% conversion. The conversion strings are listed below. +% +% 'DCMtoEA###' 'DCMtoEV' 'DCMtoQ' **for cases that involve +% 'EA###toDCM' 'EA###toEV' 'EA###toQ' euler angles, ### should be +% 'EVtoDCM' 'EVtoEA###' 'EVtoQ' replaced with the proper +% 'QtoDCM' 'QtoEA###' 'QtoEV' order desired. EA321 would +% 'EA###toEA###' be Z(yaw)-Y(pitch)-X(roll). +% +%INPUT - matrix or vector that corresponds to the first entry in the +% CONVERSION string, formatted as follows: +% +% DCM - 3x3xN multidimensional matrix which pre-multiplies by a coordinate +% frame vector to rotate it to the desired new frame. +% +% EA### - [psi,theta,phi] (Nx3) row vector list dictating to the first angle +% rotation (psi), the second (theta), and third (phi) (DEGREES) +% +% EV - [m1,m2,m3,MU] (Nx4) row vector list dictating the components of euler +% rotation vector (original coordinate frame) and the Euler +% rotation angle about that vector (MU) (DEGREES) +% +% Q - [q1,q2,q3,q4] (Nx4) row vector list defining quaternion of +% rotation. q4 = cos(MU/2) where MU is Euler rotation angle +% +%tol - tolerance value +%ichk - 0 disables warning flags +% 1 enables warning flags (near singularities) +%**NOTE: N corresponds to multiple orientations +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +%Output: +%OUTPUT - matrix or vector corresponding to the second entry in the +% CONVERSION input string, formatted as shown above. +%~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +%Pre-processer to determine type of conversion from CONVERSION string input +%Types are numbered as follows: +%Q=1 EV=2 DCM=3 EA=4 +i_type=strfind(lower(CONVERSION),'to'); +length=size(CONVERSION,2); +if length>12 || length<4, %no CONVERSION string can be shorter than 4 or longer than 12 chars + error('Error: Invalid entry for CONVERSION input string'); +end +o_type=length-i_type; +if i_type<5, + i_type=i_type-1; +else + i_type=i_type-2; +end +if o_type<5, + o_type=o_type-1; +else + o_type=o_type-2; +end +TYPES=cell(1,4); +TYPES{1,1}='Q'; TYPES{1,2}='EV'; TYPES{1,3}='DCM'; TYPES{1,4}='EA'; +INPUT_TYPE=TYPES{1,i_type}; +OUTPUT_TYPE=TYPES{1,o_type}; +clear TYPES +%Confirm input as compared to program interpretation +if i_type~=4 && o_type~=4, %if input/output are NOT Euler angles + CC=[INPUT_TYPE,'to',OUTPUT_TYPE]; + if strcmpi(CONVERSION,CC)==0; + error('Error: Invalid entry for CONVERSION input string'); + end +else + if i_type==4, %if input type is Euler angles, determine the order of rotations + EULER_order_in=str2double(CONVERSION(1,3:5)); + rot_1_in=floor(EULER_order_in/100); %first rotation + rot_2_in=floor((EULER_order_in-rot_1_in*100)/10); %second rotation + rot_3_in=(EULER_order_in-rot_1_in*100-rot_2_in*10); %third rotation + if rot_1_in<1 || rot_2_in<1 || rot_3_in<1 || rot_1_in>3 || rot_2_in>3 || rot_3_in>3, + error('Error: Invalid input Euler angle order type (conversion string).'); %check that all orders are between 1 and 3 + elseif rot_1_in==rot_2_in || rot_2_in==rot_3_in, + error('Error: Invalid input Euler angle order type (conversion string).'); %check that no 2 consecutive orders are equal (invalid) + end + %check input dimensions to be 1x3x1 + if size(INPUT,2)~=3 || size(INPUT,3)~=1 + error('Error: Input euler angle data vector is not Nx3') + end + %identify singularities + if rot_1_in==rot_3_in, %Type 2 rotation (first and third rotations about same axis) + if INPUT(:,2)<=zeros(size(INPUT,1),1) | INPUT(:,2)>=180*ones(size(INPUT,1),1), %confirm second angle within range + error('Error: Second input Euler angle(s) outside 0 to 180 degree range') + elseif abs(INPUT(:,2))<2*ones(size(INPUT,1),1) | abs(INPUT(:,2))>178*ones(size(INPUT,1),1), %check for singularity + if ichk==1, + errordlg('Warning: Input Euler angle rotation(s) near a singularity. Second angle near 0 or 180 degrees.') + end + end + else %Type 1 rotation (all rotations about each of three axes) + if abs(INPUT(:,2))>=90*ones(size(INPUT,1),1), %confirm second angle within range + error('Error: Second input Euler angle(s) outside -90 to 90 degree range') + elseif abs(INPUT(:,2))>88*ones(size(INPUT,1),1), %check for singularity + if ichk==1, + errordlg('Warning: Input Euler angle(s) rotation near a singularity. Second angle near -90 or 90 degrees.') + end + end + end + end + if o_type==4, %if output type is Euler angles, determine order of rotations + EULER_order_out=str2double(CONVERSION(1,length-2:length)); + rot_1_out=floor(EULER_order_out/100); %first rotation + rot_2_out=floor((EULER_order_out-rot_1_out*100)/10); %second rotation + rot_3_out=(EULER_order_out-rot_1_out*100-rot_2_out*10); %third rotation + if rot_1_out<1 || rot_2_out<1 || rot_3_out<1 || rot_1_out>3 || rot_2_out>3 || rot_3_out>3, + error('Error: Invalid output Euler angle order type (conversion string).'); %check that all orders are between 1 and 3 + elseif rot_1_out==rot_2_out || rot_2_out==rot_3_out, + error('Error: Invalid output Euler angle order type (conversion string).'); %check that no 2 consecutive orders are equal + end + end + if i_type==4 && o_type~=4, %if input are euler angles but not output + CC=['EA',num2str(EULER_order_in),'to',OUTPUT_TYPE]; %construct program conversion string for checking against user input + elseif o_type==4 && i_type~=4, %if output are euler angles but not input + CC=[INPUT_TYPE,'to','EA',num2str(EULER_order_out)]; %construct program conversion string for checking against user input + elseif i_type==4 && o_type==4, %if both input and output are euler angles + CC=['EA',num2str(EULER_order_in),'to','EA',num2str(EULER_order_out)]; %construct program conversion string + end + if strcmpi(CONVERSION,CC)==0; %check program conversion string against user input to confirm the conversion command + error('Error: Invalid entry for CONVERSION input string'); + end +end +clear i_type o_type CC + +%From the input, determine the quaternions that uniquely describe the +%rotation prescribed by that input. The output will be calculated in the +%second portion of the code from these quaternions. +switch INPUT_TYPE + case 'DCM' + if size(INPUT,1)~=3 || size(INPUT,2)~=3 %check DCM dimensions + error('Error: DCM matrix is not 3x3xN'); + end + N=size(INPUT,3); %number of orientations + %Check if matrix is indeed orthogonal + perturbed=NaN(3,3,N); + DCM_flag=0; + for ii=1:N, + perturbed(:,:,ii)=abs(INPUT(:,:,ii)*INPUT(:,:,ii)'-eye(3)); %perturbed array shows difference between DCM*DCM' and I + if abs(det(INPUT(:,:,ii))-1)>tol, %if determinant is off by one more than tol, user is warned. + if ichk==1, + DCM_flag=1; + end + end + if abs(det(INPUT(:,:,ii))+1)<0.05, %if determinant is near -1, DCM is improper + error('Error: Input DCM(s) improper'); + end + if DCM_flag==1, + errordlg('Warning: Input DCM matrix determinant(s) off from 1 by more than tolerance.') + end + end + DCM_flag=0; + if ichk==1, + for kk=1:N, + for ii=1:3, + for jj=1:3, + if perturbed(ii,jj,kk)>tol, %if any difference is larger than tol, user is warned. + DCM_flag=1; + end + end + end + end + if DCM_flag==1, + fprintf('Warning: Input DCM(s) matrix not orthogonal to precision tolerance.') + end + end + clear perturbed DCM_flag + Q=NaN(4,N); + for ii=1:N, + denom=NaN(4,1); + denom(1)=0.5*sqrt(1+INPUT(1,1,ii)-INPUT(2,2,ii)-INPUT(3,3,ii)); + denom(2)=0.5*sqrt(1-INPUT(1,1,ii)+INPUT(2,2,ii)-INPUT(3,3,ii)); + denom(3)=0.5*sqrt(1-INPUT(1,1,ii)-INPUT(2,2,ii)+INPUT(3,3,ii)); + denom(4)=0.5*sqrt(1+INPUT(1,1,ii)+INPUT(2,2,ii)+INPUT(3,3,ii)); + %determine which Q equations maximize denominator + switch find(denom==max(denom),1,'first') %determines max value of qtests to put in denominator + case 1 + Q(1,ii)=denom(1); + Q(2,ii)=(INPUT(1,2,ii)+INPUT(2,1,ii))/(4*Q(1,ii)); + Q(3,ii)=(INPUT(1,3,ii)+INPUT(3,1,ii))/(4*Q(1,ii)); + Q(4,ii)=(INPUT(2,3,ii)-INPUT(3,2,ii))/(4*Q(1,ii)); + case 2 + Q(2,ii)=denom(2); + Q(1,ii)=(INPUT(1,2,ii)+INPUT(2,1,ii))/(4*Q(2,ii)); + Q(3,ii)=(INPUT(2,3,ii)+INPUT(3,2,ii))/(4*Q(2,ii)); + Q(4,ii)=(INPUT(3,1,ii)-INPUT(1,3,ii))/(4*Q(2,ii)); + case 3 + Q(3,ii)=denom(3); + Q(1,ii)=(INPUT(1,3,ii)+INPUT(3,1,ii))/(4*Q(3,ii)); + Q(2,ii)=(INPUT(2,3,ii)+INPUT(3,2,ii))/(4*Q(3,ii)); + Q(4,ii)=(INPUT(1,2,ii)-INPUT(2,1,ii))/(4*Q(3,ii)); + case 4 + Q(4,ii)=denom(4); + Q(1,ii)=(INPUT(2,3,ii)-INPUT(3,2,ii))/(4*Q(4,ii)); + Q(2,ii)=(INPUT(3,1,ii)-INPUT(1,3,ii))/(4*Q(4,ii)); + Q(3,ii)=(INPUT(1,2,ii)-INPUT(2,1,ii))/(4*Q(4,ii)); + end + end + Q=Q'; + clear denom + case 'EV' %Euler Vector Input Type + if size(INPUT,2)~=4 || size(INPUT,3)~=1 %check dimensions + error('Error: Input euler vector and rotation data matrix is not Nx4') + end + N=size(INPUT,1); + MU=INPUT(:,4)*pi/180; %assign mu name for clarity + if sqrt(INPUT(:,1).^2+INPUT(:,2).^2+INPUT(:,3).^2)-ones(N,1)>tol*ones(N,1), %check that input m's constitute unit vector + error('Input euler vector(s) components do not constitute a unit vector') + end + if MU2*pi*ones(N,1), %check if rotation about euler vector is between 0 and 360 + error('Input euler rotation angle(s) not between 0 and 360 degrees') + end + Q=[INPUT(:,1).*sin(MU/2),INPUT(:,2).*sin(MU/2),INPUT(:,3).*sin(MU/2),cos(MU/2)]; %quaternion + clear m1 m2 m3 MU + case 'EA' + psi=INPUT(:,1)*pi/180; theta=INPUT(:,2)*pi/180; phi=INPUT(:,3)*pi/180; + N=size(INPUT,1); %number of orientations + %Pre-calculate cosines and sines of the half-angles for conversion. + c1=cos(psi./2); c2=cos(theta./2); c3=cos(phi./2); + s1=sin(psi./2); s2=sin(theta./2); s3=sin(phi./2); + c13=cos((psi+phi)./2); s13=sin((psi+phi)./2); + c1_3=cos((psi-phi)./2); s1_3=sin((psi-phi)./2); + c3_1=cos((phi-psi)./2); s3_1=sin((phi-psi)./2); + if EULER_order_in==121, + Q=[c2.*s13,s2.*c1_3,s2.*s1_3,c2.*c13]; + elseif EULER_order_in==232, + Q=[s2.*s1_3,c2.*s13,s2.*c1_3,c2.*c13]; + elseif EULER_order_in==313; + Q=[s2.*c1_3,s2.*s1_3,c2.*s13,c2.*c13]; + elseif EULER_order_in==131, + Q=[c2.*s13,s2.*s3_1,s2.*c3_1,c2.*c13]; + elseif EULER_order_in==212, + Q=[s2.*c3_1,c2.*s13,s2.*s3_1,c2.*c13]; + elseif EULER_order_in==323, + Q=[s2.*s3_1,s2.*c3_1,c2.*s13,c2.*c13]; + elseif EULER_order_in==123, + Q=[s1.*c2.*c3+c1.*s2.*s3,c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*s3+s1.*s2.*c3,c1.*c2.*c3-s1.*s2.*s3]; + elseif EULER_order_in==231, + Q=[c1.*c2.*s3+s1.*s2.*c3,s1.*c2.*c3+c1.*s2.*s3,c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*c3-s1.*s2.*s3]; + elseif EULER_order_in==312, + Q=[c1.*s2.*c3-s1.*c2.*s3,c1.*c2.*s3+s1.*s2.*c3,s1.*c2.*c3+c1.*s2.*s3,c1.*c2.*c3-s1.*s2.*s3]; + elseif EULER_order_in==132, + Q=[s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*s3-s1.*s2.*c3,c1.*s2.*c3+s1.*c2.*s3,c1.*c2.*c3+s1.*s2.*s3]; + elseif EULER_order_in==213, + Q=[c1.*s2.*c3+s1.*c2.*s3,s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*s3-s1.*s2.*c3,c1.*c2.*c3+s1.*s2.*s3]; + elseif EULER_order_in==321, + Q=[c1.*c2.*s3-s1.*s2.*c3,c1.*s2.*c3+s1.*c2.*s3,s1.*c2.*c3-c1.*s2.*s3,c1.*c2.*c3+s1.*s2.*s3]; + else + error('Error: Invalid input Euler angle order type (conversion string)'); + end + clear c1 s1 c2 s2 c3 s3 c13 s13 c1_3 s1_3 c3_1 s3_1 psi theta phi + case 'Q' + if size(INPUT,2)~=4 || size(INPUT,3)~=1 + error('Error: Input quaternion matrix is not Nx4'); + end + N=size(INPUT,1); %number of orientations + if ichk==1, + if abs(sqrt(INPUT(:,1).^2+INPUT(:,2).^2+INPUT(:,3).^2+INPUT(:,4).^2)-ones(N,1))>tol*ones(N,1) + errordlg('Warning: Input quaternion norm(s) deviate(s) from unity by more than tolerance') + end + end + Q=INPUT; +end +clear INPUT INPUT_TYPE EULER_order_in + +%Normalize quaternions in case of deviation from unity. User has already +%been warned of deviation. +Qnorms=sqrt(sum(Q.*Q,2)); +Q=[Q(:,1)./Qnorms,Q(:,2)./Qnorms,Q(:,3)./Qnorms,Q(:,4)./Qnorms]; + +switch OUTPUT_TYPE + case 'DCM' + Q=reshape(Q',1,4,N); + OUTPUT=[Q(1,1,:).^2-Q(1,2,:).^2-Q(1,3,:).^2+Q(1,4,:).^2,2*(Q(1,1,:).*Q(1,2,:)+Q(1,3,:).*Q(1,4,:)),2*(Q(1,1,:).*Q(1,3,:)-Q(1,2,:).*Q(1,4,:)); + 2*(Q(1,1,:).*Q(1,2,:)-Q(1,3,:).*Q(1,4,:)),-Q(1,1,:).^2+Q(1,2,:).^2-Q(1,3,:).^2+Q(1,4,:).^2,2*(Q(1,2,:).*Q(1,3,:)+Q(1,1,:).*Q(1,4,:)); + 2*(Q(1,1,:).*Q(1,3,:)+Q(1,2,:).*Q(1,4,:)),2*(Q(1,2,:).*Q(1,3,:)-Q(1,1,:).*Q(1,4,:)),-Q(1,1,:).^2-Q(1,2,:).^2+Q(1,3,:).^2+Q(1,4,:).^2]; + case 'EV' + MU=2*atan2(sqrt(sum(Q(:,1:3).*Q(:,1:3),2)),Q(:,4)); + if sin(MU/2)~=zeros(N,1), + OUTPUT=[Q(:,1)./sin(MU/2),Q(:,2)./sin(MU/2),Q(:,3)./sin(MU/2),MU*180/pi]; + else + OUTPUT=NaN(N,4); + for ii=1:N, + if sin(MU(ii,1)/2)~=0, + OUTPUT(ii,1:4)=[Q(ii,1)/sin(MU(ii,1)/2),Q(ii,2)/sin(MU(ii,1)/2),Q(ii,3)/sin(MU(ii,1)/2),MU(ii,1)*180/pi]; + else + OUTPUT(ii,1:4)=[1,0,0,MU(ii,1)*180/pi]; + end + end + end + case 'Q' + OUTPUT=Q; + case 'EA' + if EULER_order_out==121, + psi=atan2((Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); + theta=acos(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2); + phi=atan2((Q(:,1).*Q(:,2)-Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); + Euler_type=2; + elseif EULER_order_out==232; + psi=atan2((Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); + theta=acos(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2); + phi=atan2((Q(:,2).*Q(:,3)-Q(:,1).*Q(:,4)),(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); + Euler_type=2; + elseif EULER_order_out==313; + psi=atan2((Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); + theta=acos(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2); + phi=atan2((Q(:,1).*Q(:,3)-Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); + Euler_type=2; + elseif EULER_order_out==131; + psi=atan2((Q(:,1).*Q(:,3)-Q(:,2).*Q(:,4)),(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); + theta=acos(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2); + phi=atan2((Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); + Euler_type=2; + elseif EULER_order_out==212; + psi=atan2((Q(:,1).*Q(:,2)-Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); + theta=acos(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2); + phi=atan2((Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); + Euler_type=2; + elseif EULER_order_out==323; + psi=atan2((Q(:,2).*Q(:,3)-Q(:,1).*Q(:,4)),(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); + theta=acos(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2); + phi=atan2((Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); + Euler_type=2; + elseif EULER_order_out==123; + psi=atan2(2.*(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); + theta=asin(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4))); + phi=atan2(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); + Euler_type=1; + elseif EULER_order_out==231; + psi=atan2(2.*(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); + theta=asin(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4))); + phi=atan2(2.*(Q(:,1).*Q(:,4)-Q(:,3).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); + Euler_type=1; + elseif EULER_order_out==312; + psi=atan2(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); + theta=asin(2.*(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3))); + phi=atan2(2.*(Q(:,2).*Q(:,4)-Q(:,3).*Q(:,1)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); + Euler_type=1; + elseif EULER_order_out==132; + psi=atan2(2.*(Q(:,1).*Q(:,4)+Q(:,2).*Q(:,3)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); + theta=asin(2.*(Q(:,3).*Q(:,4)-Q(:,1).*Q(:,2))); + phi=atan2(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); + Euler_type=1; + elseif EULER_order_out==213; + psi=atan2(2.*(Q(:,1).*Q(:,3)+Q(:,2).*Q(:,4)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); + theta=asin(2.*(Q(:,1).*Q(:,4)-Q(:,2).*Q(:,3))); + phi=atan2(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,4).^2-Q(:,1).^2+Q(:,2).^2-Q(:,3).^2)); + Euler_type=1; + elseif EULER_order_out==321; + psi=atan2(2.*(Q(:,1).*Q(:,2)+Q(:,3).*Q(:,4)),(Q(:,4).^2+Q(:,1).^2-Q(:,2).^2-Q(:,3).^2)); + theta=asin(2.*(Q(:,2).*Q(:,4)-Q(:,1).*Q(:,3))); + phi=atan2(2.*(Q(:,1).*Q(:,4)+Q(:,3).*Q(:,2)),(Q(:,4).^2-Q(:,1).^2-Q(:,2).^2+Q(:,3).^2)); + Euler_type=1; + else + error('Error: Invalid output Euler angle order type (conversion string).'); + end + if(isreal([psi,theta,phi]))==0, + error('Error: Unreal Euler output. Input resides too close to singularity. Please choose different output type.') + end + OUTPUT=mod([psi,theta,phi]*180/pi,360); %deg + if Euler_type==1, + sing_chk=find(abs(theta)*180/pi>89.9); + sing_chk=sort(sing_chk(sing_chk>0)); + if size(sing_chk,1)>=1, + error('Error: Input rotation #%s resides too close to Type 1 Euler singularity.\nType 1 Euler singularity occurs when second angle is -90 or 90 degrees.\nPlease choose different output type.',num2str(sing_chk(1,1))); + end + elseif Euler_type==2, + sing_chk=[find(abs(theta*180/pi)<0.1);find(abs(theta*180/pi-180)<0.1);find(abs(theta*180/pi-360))<0.1]; + sing_chk=sort(sing_chk(sing_chk>0)); + if size(sing_chk,1)>=1, + error('Error: Input rotation #%s resides too close to Type 2 Euler singularity.\nType 2 Euler singularity occurs when second angle is 0 or 180 degrees.\nPlease choose different output type.',num2str(sing_chk(1,1))); + end + end +end + + + + + + diff --git a/anatomy/scapula_calculation_files/fitLine2.m b/anatomy/scapula_calculation_files/fitLine2.m index 452858c..5075b9b 100644 --- a/anatomy/scapula_calculation_files/fitLine2.m +++ b/anatomy/scapula_calculation_files/fitLine2.m @@ -1,22 +1,22 @@ -function [dirVect,meanX,residuals,rmse,R2] = fitLine2(X) - -% Fits a line to a group of points in X - -[coeff,score,~] = princomp(X); -dirVect = coeff(:,1); -[Xn,Xm] = size(X); -meanX = mean(X,1); -Xfit1 = repmat(meanX,Xn,1) + score(:,1)*coeff(:,1)'; -residuals = X-Xfit1; -error = diag(pdist2(residuals,zeros(Xn,Xm))); -sse = sum(error.^2); -rmse = norm(error)/sqrt(Xn); - -for i=1:Xn - tot(i) = norm(meanX-X(i,:)); -end - -sst = sum(tot.^2); - -R2 = 1-(sse/sst); %http://en.wikipedia.org/wiki/Coefficient_of_determination +function [dirVect,meanX,residuals,rmse,R2] = fitLine2(X) + +% Fits a line to a group of points in X + +[coeff,score,~] = princomp(X); +dirVect = coeff(:,1); +[Xn,Xm] = size(X); +meanX = mean(X,1); +Xfit1 = repmat(meanX,Xn,1) + score(:,1)*coeff(:,1)'; +residuals = X-Xfit1; +error = diag(pdist2(residuals,zeros(Xn,Xm))); +sse = sum(error.^2); +rmse = norm(error)/sqrt(Xn); + +for i=1:Xn + tot(i) = norm(meanX-X(i,:)); +end + +sst = sum(tot.^2); + +R2 = 1-(sse/sst); %http://en.wikipedia.org/wiki/Coefficient_of_determination end \ No newline at end of file diff --git a/anatomy/scapula_calculation_files/fitPlane2.m b/anatomy/scapula_calculation_files/fitPlane2.m index e314cfa..f0dce98 100644 --- a/anatomy/scapula_calculation_files/fitPlane2.m +++ b/anatomy/scapula_calculation_files/fitPlane2.m @@ -1,20 +1,20 @@ -function [normal,meanX,residuals,rmse,R2] = fitPlane2(X) - -[coeff,score,~] = princomp(X); -normal = coeff(:,3); -[Xn,Xm] = size(X); -meanX = mean(X,1); -Xfit = repmat(meanX,Xn,1) + score(:,1:2)*coeff(:,1:2)'; -residuals = X - Xfit; -error = diag(pdist2(residuals,zeros(Xn,Xm))); -sse = sum(error.^2); -rmse = norm(error)/sqrt(Xn); - -for i=1:Xn - tot(i) = norm(meanX-X(i,:)); -end - -sst = sum(tot.^2); - -R2 = 1-(sse/sst); %http://en.wikipedia.org/wiki/Coefficient_of_determination +function [normal,meanX,residuals,rmse,R2] = fitPlane2(X) + +[coeff,score,~] = princomp(X); +normal = coeff(:,3); +[Xn,Xm] = size(X); +meanX = mean(X,1); +Xfit = repmat(meanX,Xn,1) + score(:,1:2)*coeff(:,1:2)'; +residuals = X - Xfit; +error = diag(pdist2(residuals,zeros(Xn,Xm))); +sse = sum(error.^2); +rmse = norm(error)/sqrt(Xn); + +for i=1:Xn + tot(i) = norm(meanX-X(i,:)); +end + +sst = sum(tot.^2); + +R2 = 1-(sse/sst); %http://en.wikipedia.org/wiki/Coefficient_of_determination end \ No newline at end of file diff --git a/anatomy/scapula_calculation_files/fitSphere2.m b/anatomy/scapula_calculation_files/fitSphere2.m index 127da1a..c287a78 100644 --- a/anatomy/scapula_calculation_files/fitSphere2.m +++ b/anatomy/scapula_calculation_files/fitSphere2.m @@ -1,76 +1,76 @@ -function [center,radius,residuals,R2] = fitSphere2(x,y,z) -%SPHEREFIT find least squares sphere -% -% Fit a sphere to a set of xyz data points -% [center,radius,residuals] = shperefit(X) -% [center,radius,residuals] = spherefit(x,y,z); -% Input -% x,y,z Cartesian data, n x 3 matrix or three vectors (n x 1 or 1 x n) -% Output -% center: least squares sphere center coordinates, == [xc yc zc] -% radius: radius of curvature -% residuals: residuals in the radial direction -% -% Fit the equation of a sphere in Cartesian coordinates to a set of xyz -% data points by solving the overdetermined system of normal equations, -% ie, x^2 + y^2 + z^2 + a*x + b*y + c*z + d = 0 -% The least squares sphere has radius R = sqrt((a^2+b^2+c^2)/4-d) and -% center coordinates (x,y,z) = (-a/2,-b/2,-c/2) - -error(nargchk(1,3,nargin)); % check input arguments -if nargin == 1 % n x 3 matrix - if size(x,2) ~= 3 - error ('input data must have three columns') - else - z = x(:,3); % save columns as x,y,z vectors - y = x(:,2); - x = x(:,1); - end -elseif nargin == 3 % three x,y,z vectors - x = x(:); % force into columns - y = y(:); - z = z(:); - if ~isequal(length(x),length(y),length(z)) % same length ? - error('input vectors must be same length'); - end -else % must have one or three inputs - error('invalid input, n x 3 matrix or 3 n x 1 vectors expected'); -end - -% need four or more data points -if length(x) < 4 - error('must have at least four points to fit a unique sphere'); -end - -% solve linear system of normal equations -A = [x y z ones(size(x))]; -b = -(x.^2 + y.^2 + z.^2); -a = A \ b; - -% return center coordinates and sphere radius -center = -a(1:3)./2; -radius = sqrt(sum(center.^2)-a(4)); - -% calculate residuals -if nargout > 2 - residuals = radius - sqrt(sum(bsxfun(@minus,[x y z],center.').^2,2)); -end -% whichstats = {'adjrsquare' 'rsquare'}; -% stats = regstats(b,A(:,1:3),'linear',whichstats); -% R2 = stats.rsquare; -% R2adj = stats.adjrsquare; - -sse = sum(residuals.^2); - -meanX = [mean(x) mean(y) mean(z)]; - -for i=1:size(x) - X = [x(i) y(i) z(i)]; - tot(i) = norm(meanX-X); -end - -sst = sum(tot.^2); - -R2 = 1-sse/sst; - +function [center,radius,residuals,R2] = fitSphere2(x,y,z) +%SPHEREFIT find least squares sphere +% +% Fit a sphere to a set of xyz data points +% [center,radius,residuals] = shperefit(X) +% [center,radius,residuals] = spherefit(x,y,z); +% Input +% x,y,z Cartesian data, n x 3 matrix or three vectors (n x 1 or 1 x n) +% Output +% center: least squares sphere center coordinates, == [xc yc zc] +% radius: radius of curvature +% residuals: residuals in the radial direction +% +% Fit the equation of a sphere in Cartesian coordinates to a set of xyz +% data points by solving the overdetermined system of normal equations, +% ie, x^2 + y^2 + z^2 + a*x + b*y + c*z + d = 0 +% The least squares sphere has radius R = sqrt((a^2+b^2+c^2)/4-d) and +% center coordinates (x,y,z) = (-a/2,-b/2,-c/2) + +error(nargchk(1,3,nargin)); % check input arguments +if nargin == 1 % n x 3 matrix + if size(x,2) ~= 3 + error ('input data must have three columns') + else + z = x(:,3); % save columns as x,y,z vectors + y = x(:,2); + x = x(:,1); + end +elseif nargin == 3 % three x,y,z vectors + x = x(:); % force into columns + y = y(:); + z = z(:); + if ~isequal(length(x),length(y),length(z)) % same length ? + error('input vectors must be same length'); + end +else % must have one or three inputs + error('invalid input, n x 3 matrix or 3 n x 1 vectors expected'); +end + +% need four or more data points +if length(x) < 4 + error('must have at least four points to fit a unique sphere'); +end + +% solve linear system of normal equations +A = [x y z ones(size(x))]; +b = -(x.^2 + y.^2 + z.^2); +a = A \ b; + +% return center coordinates and sphere radius +center = -a(1:3)./2; +radius = sqrt(sum(center.^2)-a(4)); + +% calculate residuals +if nargout > 2 + residuals = radius - sqrt(sum(bsxfun(@minus,[x y z],center.').^2,2)); +end +% whichstats = {'adjrsquare' 'rsquare'}; +% stats = regstats(b,A(:,1:3),'linear',whichstats); +% R2 = stats.rsquare; +% R2adj = stats.adjrsquare; + +sse = sum(residuals.^2); + +meanX = [mean(x) mean(y) mean(z)]; + +for i=1:size(x) + X = [x(i) y(i) z(i)]; + tot(i) = norm(meanX-X); +end + +sst = sum(tot.^2); + +R2 = 1-sse/sst; + end \ No newline at end of file diff --git a/anatomy/scapula_calculation_files/importStl.m b/anatomy/scapula_calculation_files/importStl.m index 033ca43..1747250 100644 --- a/anatomy/scapula_calculation_files/importStl.m +++ b/anatomy/scapula_calculation_files/importStl.m @@ -1,266 +1,266 @@ -function varargout=importStl(filename,mode) -% STL_Import is a tool designed to import into MATLAB both binary and ASCII STL files. -% -% This scprit is mainly a collage betwwen file axchange fileid 22409 and 3642, plus -% some other features that can be considered new on FEX. -% -% SYNOPSIS: -% -% -% %mode 1 (default) -% [p,t,tnorm]=STL_Import(filename,mode) -% -% %mode 2 -% [v,tnorm])=STL_Import(filename,mode) -% -% -% INPUT: -% -% filename: string representing the name fo the file -% -% mode: -% -% -% mode=1 (if omitted is automatically set to one) -% -% set the the output to: -% -% output=[p,t,tnorm] -% -% where -% -% p=points (unique) of the model nx3 array -% -% t=triangles indexes of the model -% -% tnorm= normals of triangles -% -% -% mode=2 -% -% set the the output to: -% -% output=[v,tnorm] -% -% where -% -% v= vertex of the model(not unique points) of the model nx3 array. Each -% three points we have a triangle in consecutive order. -% -% tnorm= normals of triangles -% -% EXAMPLES: -% -% [p,t,tnorm]=STL_Import('link1.stl',1); -% [pv,tnorm]=STL_Import('link1.stl',2); -% -% -% Visit: -% -% http://giaccariluigi.altervista.org/blog/ -% -% Author: Giaccari Luigi (giaccariluigi@msn.com) - - - -if nargin<2 - mode=1;%default value -end - - -if ~(mode==1 || mode==2) - error('invalid mode') -end - -if nargout<3 && mode==1 - error('invalid input number /mode setting') -end -if nargout>2 && mode==2 - error('invalid input number /mode setting') -end - - -%open file -fid=fopen(filename, 'r'); %Open the file, assumes STL ASCII format. -if fid == -1 - error('File could not be opened, check name or path.') -end - - -M = fread(fid,inf,'uint8=>uint8'); -fclose(fid); - -if( isbinary(M) ) - [v,tnorm]=ImportSTL_binary(M); - -else - clear M; - [v,tnorm]=ImportSTL_ASCII(filename); - -end - -clear M - -varargout = cell(1,nargout); -switch mode - case 1 - [p,t]=fv2pt(v,length(v)/3);%gets points and triangles - - varargout{1} = p; - varargout{2} = t; - varargout{3} = tnorm; - case 2 - varargout{1} = v; - varargout{2} = tnorm; -end -end - - - -function [v,tnorm]=ImportSTL_ASCII(filename) - -%counting the number of vertex -vnum=0; -fid=fopen(filename, 'r'); %Open the file, assumes STL ASCII format. -while feof(fid) == 0 % test for end of file, if not then do stuff - tline = fgetl(fid); % reads a line of data from file. - fword = sscanf(tline, '%s '); % make the line a character string - if strncmpi(fword, 'v',1) ; % Checking if a "V"ertex line, as "V" is 1st char. - vnum = vnum + 1; % If a V we count the # of V's - end -end -fclose(fid); -numt=ceil(vnum/3);%triangles number equals vertex number/3 - -tnorm=zeros(numt,3);%preallocate for normals -v=zeros(vnum,3);%not unique vertex - -c=0;%vertex counter -fnum=0; -fid=fopen(filename, 'r'); %REOpen the file -while feof(fid) == 0 % test for end of file, if not then do stuff - tline = fgetl(fid); % reads a line of data from file. - fword = sscanf(tline, '%s '); % make the line a character string - - %% Check vertex - if strncmpi(fword, 'v',1) ; % Checking if a "V"ertex line, as "V" is 1st char. - c = c + 1; % If a V we count the # of V's - v(c,:) = sscanf(tline, '%*s %f %f %f'); % & if a V, get the XYZ data of it. - - %% Check facet normal - elseif strncmpi(fword, 'f',1) ; % Checking if a "V"ertex line, as "V" is 1st char. - fnum =fnum + 1; % If a V we count the # of V's - tnorm(fnum,:) = sscanf(tline, '%*s %*s %f %f %f'); % & if a V, get the XYZ data of it. - - % %% Check for color - % elseif strncmpi(fword, 'c',1) ; % Checking if a "C"olor line, as "C" is 1st char. - % VColor = sscanf(tline, '%*s %f %f %f'); % & if a C, get the RGB color data of the face. - % % Keep this color, until the next color is used. - - end - -end -fclose(fid); -end - - -function [p,t]=fv2pt(v,fnum) - -%gets points and triangle indexes given vertex and facet number - -c=size(v,1); - -%triangles with vertex id data -t=zeros(3,fnum); -t(:)=1:c; - - -%now we have to keep unique points fro vertex -[p,i,j]=unique(v,'rows'); %now v=p(j) p(i)=v; -t(:)=j(t(:)); -t=t'; - -end - -% - - -function tf = isbinary(A) -% ISBINARY determines if an STL file is binary or ASCII. - -% Look for the string 'endsolid' near the end of the file -if isempty(A) || length(A) < 16 - error('MATLAB:stlread:incorrectFormat', ... - 'File does not appear to be an ASCII or binary STL file.'); -end - -% Read final 16 characters of M -i2 = length(A); -i1 = i2 - 100;%100 empirical value -str = char( A(i1:i2)' ); - -k = strfind(lower(str), 'endsolid'); -if ~isempty(k) - tf = false; % ASCII -else - tf = true; % Binary -end -end - - -function [V,N]=ImportSTL_binary(M) - - - -if length(M) < 84 - error('MATLAB:stlread:incorrectFormat', ... - 'Incomplete header information in binary STL file.'); -end - -% Bytes 81-84 are an unsigned 32-bit integer specifying the number of faces -% that follow. -numFaces = typecast(M(81:84),'uint32'); -%numFaces = double(numFaces); -if numFaces == 0 - warning('MATLAB:stlread:nodata','No data in STL file.'); - return -end - -T = M(85:end); - -V = NaN(3*numFaces,3); -N = NaN(numFaces,3); - -numRead = 0; -while numRead < numFaces - % Each facet is 50 bytes - % - Three single precision values specifying the face normal vector - % - Three single precision values specifying the first vertex (XYZ) - % - Three single precision values specifying the second vertex (XYZ) - % - Three single precision values specifying the third vertex (XYZ) - % - Two unused bytes - i1 = 50 * numRead + 1; - i2 = i1 + 50 - 1; - facet = T(i1:i2)'; - - n = typecast(facet(1:12),'single'); - v1 = typecast(facet(13:24),'single'); - v2 = typecast(facet(25:36),'single'); - v3 = typecast(facet(37:48),'single'); - - n = double(n); - v = double([v1; v2; v3]); - - % Figure out where to fit these new vertices, and the face, in the - % larger F and V collections. - fInd = numRead + 1; - vInd1 = 3 * (fInd - 1) + 1; - vInd2 = vInd1 + 3 - 1; - - V(vInd1:vInd2,:) = v; - N(fInd,:) = n; - - numRead = numRead + 1; -end - +function varargout=importStl(filename,mode) +% STL_Import is a tool designed to import into MATLAB both binary and ASCII STL files. +% +% This scprit is mainly a collage betwwen file axchange fileid 22409 and 3642, plus +% some other features that can be considered new on FEX. +% +% SYNOPSIS: +% +% +% %mode 1 (default) +% [p,t,tnorm]=STL_Import(filename,mode) +% +% %mode 2 +% [v,tnorm])=STL_Import(filename,mode) +% +% +% INPUT: +% +% filename: string representing the name fo the file +% +% mode: +% +% +% mode=1 (if omitted is automatically set to one) +% +% set the the output to: +% +% output=[p,t,tnorm] +% +% where +% +% p=points (unique) of the model nx3 array +% +% t=triangles indexes of the model +% +% tnorm= normals of triangles +% +% +% mode=2 +% +% set the the output to: +% +% output=[v,tnorm] +% +% where +% +% v= vertex of the model(not unique points) of the model nx3 array. Each +% three points we have a triangle in consecutive order. +% +% tnorm= normals of triangles +% +% EXAMPLES: +% +% [p,t,tnorm]=STL_Import('link1.stl',1); +% [pv,tnorm]=STL_Import('link1.stl',2); +% +% +% Visit: +% +% http://giaccariluigi.altervista.org/blog/ +% +% Author: Giaccari Luigi (giaccariluigi@msn.com) + + + +if nargin<2 + mode=1;%default value +end + + +if ~(mode==1 || mode==2) + error('invalid mode') +end + +if nargout<3 && mode==1 + error('invalid input number /mode setting') +end +if nargout>2 && mode==2 + error('invalid input number /mode setting') +end + + +%open file +fid=fopen(filename, 'r'); %Open the file, assumes STL ASCII format. +if fid == -1 + error('File could not be opened, check name or path.') +end + + +M = fread(fid,inf,'uint8=>uint8'); +fclose(fid); + +if( isbinary(M) ) + [v,tnorm]=ImportSTL_binary(M); + +else + clear M; + [v,tnorm]=ImportSTL_ASCII(filename); + +end + +clear M + +varargout = cell(1,nargout); +switch mode + case 1 + [p,t]=fv2pt(v,length(v)/3);%gets points and triangles + + varargout{1} = p; + varargout{2} = t; + varargout{3} = tnorm; + case 2 + varargout{1} = v; + varargout{2} = tnorm; +end +end + + + +function [v,tnorm]=ImportSTL_ASCII(filename) + +%counting the number of vertex +vnum=0; +fid=fopen(filename, 'r'); %Open the file, assumes STL ASCII format. +while feof(fid) == 0 % test for end of file, if not then do stuff + tline = fgetl(fid); % reads a line of data from file. + fword = sscanf(tline, '%s '); % make the line a character string + if strncmpi(fword, 'v',1) ; % Checking if a "V"ertex line, as "V" is 1st char. + vnum = vnum + 1; % If a V we count the # of V's + end +end +fclose(fid); +numt=ceil(vnum/3);%triangles number equals vertex number/3 + +tnorm=zeros(numt,3);%preallocate for normals +v=zeros(vnum,3);%not unique vertex + +c=0;%vertex counter +fnum=0; +fid=fopen(filename, 'r'); %REOpen the file +while feof(fid) == 0 % test for end of file, if not then do stuff + tline = fgetl(fid); % reads a line of data from file. + fword = sscanf(tline, '%s '); % make the line a character string + + %% Check vertex + if strncmpi(fword, 'v',1) ; % Checking if a "V"ertex line, as "V" is 1st char. + c = c + 1; % If a V we count the # of V's + v(c,:) = sscanf(tline, '%*s %f %f %f'); % & if a V, get the XYZ data of it. + + %% Check facet normal + elseif strncmpi(fword, 'f',1) ; % Checking if a "V"ertex line, as "V" is 1st char. + fnum =fnum + 1; % If a V we count the # of V's + tnorm(fnum,:) = sscanf(tline, '%*s %*s %f %f %f'); % & if a V, get the XYZ data of it. + + % %% Check for color + % elseif strncmpi(fword, 'c',1) ; % Checking if a "C"olor line, as "C" is 1st char. + % VColor = sscanf(tline, '%*s %f %f %f'); % & if a C, get the RGB color data of the face. + % % Keep this color, until the next color is used. + + end + +end +fclose(fid); +end + + +function [p,t]=fv2pt(v,fnum) + +%gets points and triangle indexes given vertex and facet number + +c=size(v,1); + +%triangles with vertex id data +t=zeros(3,fnum); +t(:)=1:c; + + +%now we have to keep unique points fro vertex +[p,i,j]=unique(v,'rows'); %now v=p(j) p(i)=v; +t(:)=j(t(:)); +t=t'; + +end + +% + + +function tf = isbinary(A) +% ISBINARY determines if an STL file is binary or ASCII. + +% Look for the string 'endsolid' near the end of the file +if isempty(A) || length(A) < 16 + error('MATLAB:stlread:incorrectFormat', ... + 'File does not appear to be an ASCII or binary STL file.'); +end + +% Read final 16 characters of M +i2 = length(A); +i1 = i2 - 100;%100 empirical value +str = char( A(i1:i2)' ); + +k = strfind(lower(str), 'endsolid'); +if ~isempty(k) + tf = false; % ASCII +else + tf = true; % Binary +end +end + + +function [V,N]=ImportSTL_binary(M) + + + +if length(M) < 84 + error('MATLAB:stlread:incorrectFormat', ... + 'Incomplete header information in binary STL file.'); +end + +% Bytes 81-84 are an unsigned 32-bit integer specifying the number of faces +% that follow. +numFaces = typecast(M(81:84),'uint32'); +%numFaces = double(numFaces); +if numFaces == 0 + warning('MATLAB:stlread:nodata','No data in STL file.'); + return +end + +T = M(85:end); + +V = NaN(3*numFaces,3); +N = NaN(numFaces,3); + +numRead = 0; +while numRead < numFaces + % Each facet is 50 bytes + % - Three single precision values specifying the face normal vector + % - Three single precision values specifying the first vertex (XYZ) + % - Three single precision values specifying the second vertex (XYZ) + % - Three single precision values specifying the third vertex (XYZ) + % - Two unused bytes + i1 = 50 * numRead + 1; + i2 = i1 + 50 - 1; + facet = T(i1:i2)'; + + n = typecast(facet(1:12),'single'); + v1 = typecast(facet(13:24),'single'); + v2 = typecast(facet(25:36),'single'); + v3 = typecast(facet(37:48),'single'); + + n = double(n); + v = double([v1; v2; v3]); + + % Figure out where to fit these new vertices, and the face, in the + % larger F and V collections. + fInd = numRead + 1; + vInd1 = 3 * (fInd - 1) + 1; + vInd2 = vInd1 + 3 - 1; + + V(vInd1:vInd2,:) = v; + N(fInd,:) = n; + + numRead = numRead + 1; +end + end \ No newline at end of file diff --git a/anatomy/scapula_calculation_files/project2Plane.m b/anatomy/scapula_calculation_files/project2Plane.m index f70b3f9..ac97107 100644 --- a/anatomy/scapula_calculation_files/project2Plane.m +++ b/anatomy/scapula_calculation_files/project2Plane.m @@ -1,18 +1,18 @@ -function [P0] = project2Plane(P,N,Q,m) - -% Let P be the m x 3 array of the 3D points to be projected, let Q be the -% 1 x 3 vector of the given point on the plane, let N be the 1 x 3 vector -% of the normal direction to the plane, and let P0 be the m x 3 array of -% points orthogonally projected from P onto the plane. Then do this: -% -N = N/norm(N); % <-- do this if N is not normalized -% V = P-Q; -% D = dot(V,D); -% P0 = P-D*N - - -N2 = N.'*N; -P0 = P*(eye(3)-N2)+repmat(Q*N2,m,1); -%P0 = P0/norm(P0); - +function [P0] = project2Plane(P,N,Q,m) + +% Let P be the m x 3 array of the 3D points to be projected, let Q be the +% 1 x 3 vector of the given point on the plane, let N be the 1 x 3 vector +% of the normal direction to the plane, and let P0 be the m x 3 array of +% points orthogonally projected from P onto the plane. Then do this: +% +N = N/norm(N); % <-- do this if N is not normalized +% V = P-Q; +% D = dot(V,D); +% P0 = P-D*N + + +N2 = N.'*N; +P0 = P*(eye(3)-N2)+repmat(Q*N2,m,1); +%P0 = P0/norm(P0); + end \ No newline at end of file diff --git a/anatomy/scapula_measure.m b/anatomy/scapula_measure.m index a9cdf67..a0be7f6 100644 --- a/anatomy/scapula_measure.m +++ b/anatomy/scapula_measure.m @@ -1,133 +1,133 @@ -function scapula_measure(caseID) -%%SCAPULA_MEASURE fills the Shoulder database with scapula anatomical data -% -% This function fills the Shoulder database with the scapula anatomical -% data of the case given as input. The anatomical measurements are stored -% simultaneously in a XLS file and in the MySQL database. -% -% USE: scapula_measure(caseID) -% -% INPUT caseID: Case IDs for which you want to compute scapula anatomical -% data. Cell array that stores the case IDs as a char in the form 'P###' or -% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). -% -% REMARKS The anatomical data are saved in the ScapulaAnatomicalData.xls -% file. -% -% created with MATLAB ver.: 8.6.0.267246 (R2015b) on Windows 7 -% Author: EPFL-LBO-VMC -% Date: 20-Apr-2017 -% - CTDatabaseLocation = '../../../data'; % Location of the CT database - XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase - - % Check validity of input argument (cell array) - validateattributes(caseID,{'cell'},{'nonempty'}); - - % Check that imput case IDs are unique - [caseIDNames,~,uniqueCaseID] = unique(caseID); - countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; - freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); - repeatedValues = find([freqCaseIDs.freq] > 1); - - if(~isempty(repeatedValues)) - warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) - caseID = caseIDNames; - end - - % open mySQL connection - conn = openSQL(); - - % Get the list of exisiting cases in XLS database - [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls']); - currDatabaseCaseIDlist = currDatabase(2:end,1); - - % --Get CT directory adress from CT database location and case ID-- - - % Compute scapula anatomical data and fill database - for i = 1:length(caseID) - - % Check validity of input arguments (char and format 'P###' or 'N###') - validateattributes(caseID{i},{'char'},{'nonempty'}); - - if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) - error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); - end - - levelDir1 = caseID{i}(1); - - if (length(caseID{i}(2:end)) < 2) - levelDir2 = '0'; - levelDir3 = '0'; - elseif (length(caseID{i}(2:end)) < 3) - levelDir2 = '0'; - levelDir3 = caseID{i}(2); - else - levelDir2 = caseID{i}(2); - levelDir3 = caseID{i}(3); - end - - FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); - if (isempty(FindCaseCTFolder)) - error(['Missing CT directory for CaseID: ' caseID{i}]); - end - - CaseID_IPP = FindCaseCTFolder.name; - CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; - - % Compute scapula measurements and store in the XLS database - if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/amira'],'dir') == 7 %if a folder "amira" exist, the patient name is added to the "measured" column - - finalDirectory = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3] ; - anatomy = scapula_calculation(CaseID_IPP, finalDirectory, levelDir1); % Function that calculates the anatomical data - - % Write to XLS file - % Replace line if already existing or append - if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls'],struct2cell(anatomy)','Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); - else - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls'],struct2cell(anatomy)','Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); - end - - % Write to MySQL database - % Replace line if already existing or issue warning message - sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; - curs = exec(conn,sqlquery); - if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); - end - - curs=fetch(curs); - sqlresult = curs.Data; - - if isnumeric(sqlresult{1}) - - % Update data in table 'glenoid' - data = {sqlresult{1} anatomy.glenoid_Radius anatomy.glenoid_Radius/anatomy.glenoid_radiusRMSE '' anatomy.glenoid_depth anatomy.glenoid_width anatomy.glenoid_height anatomy.glenoid_version_ampl anatomy.glenoid_version_orient anatomy.glenoid_center_PA anatomy.glenoid_center_IS anatomy.glenoid_center_ML anatomy.glenoid_Version anatomy.glenoid_Inclination 1 ''}; - fieldNames = {'CT_id','radius','sphericity','biconcave','depth','width','height','version_ampl','version_orient','center_PA','center_IS','center_ML','version', 'inclination', 'version_2D', 'walch_class'}; - upsert(conn, 'glenoid', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); - - % Update data in table 'humerus' - data = {sqlresult{1} 0.0 anatomy.humeral_head_radius anatomy.humerus_SHsubluxation_ampl anatomy.humerus_SHsubluxation_orient anatomy.humerus_GHsubluxation_ampl anatomy.humerus_GHsubluxation_orient 0.0 0.0}; - fieldNames = {'CT_id','joint_radius','head_radius','SHsublux_ampl','SHsublux_orient','GHsublux_ampl','GHsublux_orient','SHsublux_2D','GHsublux_2D'}; - upsert(conn, 'humerus', fieldNames, [1 0 0 0 0 0 0 0 0], data); - - % Update data in table 'scapula' - data = {sqlresult{1} anatomy.scapula_CTangle anatomy.scapula_AI}; - fieldNames = {'CT_id','CT_angle','AI'}; - upsert(conn, 'scapula', fieldNames, [1 0 0], data); - - else - error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); - end - - else - error(['Amira files missing for CaseID: ' caseID{i}]); - end - - end - - % Close mySQL database - closeSQL(conn); - -end +function scapula_measure(caseID) +%%SCAPULA_MEASURE fills the Shoulder database with scapula anatomical data +% +% This function fills the Shoulder database with the scapula anatomical +% data of the case given as input. The anatomical measurements are stored +% simultaneously in a XLS file and in the MySQL database. +% +% USE: scapula_measure(caseID) +% +% INPUT caseID: Case IDs for which you want to compute scapula anatomical +% data. Cell array that stores the case IDs as a char in the form 'P###' or +% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). +% +% REMARKS The anatomical data are saved in the ScapulaAnatomicalData.xls +% file. +% +% created with MATLAB ver.: 8.6.0.267246 (R2015b) on Windows 7 +% Author: EPFL-LBO-VMC +% Date: 20-Apr-2017 +% + CTDatabaseLocation = '../../../data'; % Location of the CT database + XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase + + % Check validity of input argument (cell array) + validateattributes(caseID,{'cell'},{'nonempty'}); + + % Check that imput case IDs are unique + [caseIDNames,~,uniqueCaseID] = unique(caseID); + countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; + freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); + repeatedValues = find([freqCaseIDs.freq] > 1); + + if(~isempty(repeatedValues)) + warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) + caseID = caseIDNames; + end + + % open mySQL connection + conn = openSQL(); + + % Get the list of exisiting cases in XLS database + [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls']); + currDatabaseCaseIDlist = currDatabase(2:end,1); + + % --Get CT directory adress from CT database location and case ID-- + + % Compute scapula anatomical data and fill database + for i = 1:length(caseID) + + % Check validity of input arguments (char and format 'P###' or 'N###') + validateattributes(caseID{i},{'char'},{'nonempty'}); + + if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) + error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); + end + + levelDir1 = caseID{i}(1); + + if (length(caseID{i}(2:end)) < 2) + levelDir2 = '0'; + levelDir3 = '0'; + elseif (length(caseID{i}(2:end)) < 3) + levelDir2 = '0'; + levelDir3 = caseID{i}(2); + else + levelDir2 = caseID{i}(2); + levelDir3 = caseID{i}(3); + end + + FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); + if (isempty(FindCaseCTFolder)) + error(['Missing CT directory for CaseID: ' caseID{i}]); + end + + CaseID_IPP = FindCaseCTFolder.name; + CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; + + % Compute scapula measurements and store in the XLS database + if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/amira'],'dir') == 7 %if a folder "amira" exist, the patient name is added to the "measured" column + + finalDirectory = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3] ; + anatomy = scapula_calculation(CaseID_IPP, finalDirectory, levelDir1); % Function that calculates the anatomical data + + % Write to XLS file + % Replace line if already existing or append + if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls'],struct2cell(anatomy)','Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); + else + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/anatomy.xls'],struct2cell(anatomy)','Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); + end + + % Write to MySQL database + % Replace line if already existing or issue warning message + sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; + curs = exec(conn,sqlquery); + if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); + end + + curs=fetch(curs); + sqlresult = curs.Data; + + if isnumeric(sqlresult{1}) + + % Update data in table 'glenoid' + data = {sqlresult{1} anatomy.glenoid_Radius anatomy.glenoid_Radius/anatomy.glenoid_radiusRMSE '' anatomy.glenoid_depth anatomy.glenoid_width anatomy.glenoid_height anatomy.glenoid_version_ampl anatomy.glenoid_version_orient anatomy.glenoid_center_PA anatomy.glenoid_center_IS anatomy.glenoid_center_ML anatomy.glenoid_Version anatomy.glenoid_Inclination 1 ''}; + fieldNames = {'CT_id','radius','sphericity','biconcave','depth','width','height','version_ampl','version_orient','center_PA','center_IS','center_ML','version', 'inclination', 'version_2D', 'walch_class'}; + upsert(conn, 'glenoid', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); + + % Update data in table 'humerus' + data = {sqlresult{1} 0.0 anatomy.humeral_head_radius anatomy.humerus_SHsubluxation_ampl anatomy.humerus_SHsubluxation_orient anatomy.humerus_GHsubluxation_ampl anatomy.humerus_GHsubluxation_orient 0.0 0.0}; + fieldNames = {'CT_id','joint_radius','head_radius','SHsublux_ampl','SHsublux_orient','GHsublux_ampl','GHsublux_orient','SHsublux_2D','GHsublux_2D'}; + upsert(conn, 'humerus', fieldNames, [1 0 0 0 0 0 0 0 0], data); + + % Update data in table 'scapula' + data = {sqlresult{1} anatomy.scapula_CTangle anatomy.scapula_AI}; + fieldNames = {'CT_id','CT_angle','AI'}; + upsert(conn, 'scapula', fieldNames, [1 0 0], data); + + else + error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); + end + + else + error(['Amira files missing for CaseID: ' caseID{i}]); + end + + end + + % Close mySQL database + closeSQL(conn); + +end diff --git a/muscles/degenerationAll.m b/muscles/degenerationAll.m index b8b7f23..3dccfb7 100644 --- a/muscles/degenerationAll.m +++ b/muscles/degenerationAll.m @@ -1,158 +1,158 @@ -function muscles_results = degenerationAll(caseID, musclesList) -%%DEGENERATIONALL returns a structure array containing muscle -% measurements for each case ID entered as input and fills the Shoulder -% database with muscle degeneration measurements -% -% This function fills the Shoulder database with the muscle degeneration -% values of the case given as input. The measurements are stored in a XLS -% file and in the MySQL database. -% -% USE: degenerationAll(caseID, musclesList) -% -% INPUT caseID: Case IDs for which you want to compute muscle degeneration. -% Cell array that stores the case IDs as a char in the form 'P###' or -% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). -% -% musclesList: List of muscles which you want to compute muscle -% degeneration. Cell array that stores each msucle name as a char. -% -% OUTPUT muscles_results: structure array containing fields 'Case_ID', -% and fields "muscle name" with muscle measurements for the -% current patient: 'Stot', 'Satrophy', 'Sinfiltration', -% 'Sosteochondroma' and 'Sdegeneration', 'atrophy', -% 'infiltration', 'osteochondroma' and 'degeneration' -% -% REMARKS The muscle values (ratios) are saved in the muscles.xls file -% and in the shoulder MySQL database. -% -% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 -% Author: EPFL-LBO-VMC -% Date: 27-Jun-2017 -% - - CTDatabaseLocation = '../../../data'; % Location of the CT database - XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase - - HUThresholdMuscle = 0; %#ok - HUThresholdFat = 30; %#ok - HUThresholdOsteochondroma = 166; %#ok - - % Check validity of input argument (cell array) - validateattributes(caseID,{'cell'},{'nonempty'}); - - % Check that imput case IDs are unique - [caseIDNames,~,uniqueCaseID] = unique(caseID); - countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; - freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); - repeatedValues = find([freqCaseIDs.freq] > 1); - - if(~isempty(repeatedValues)) - warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) - caseID = caseIDNames'; - end - - % open mySQL connection - conn = openSQL(); - - % Get the list of exisiting cases in XLS database - [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls']); - currDatabaseCaseIDlist = currDatabase(2:end,1); - - %Initialize structure array for results - muscles_results = []; - muscles_results = setfield(muscles_results,{length(caseID),1},'Case_ID',[]); - - % Compute muscle degeneration values and fill database - for i = 1:length(caseID) - - % Check validity of input arguments (char and format 'P###' or 'N###') - validateattributes(caseID{i},{'char'},{'nonempty'}); - - if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) - error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); - end - - % Find CT folder for the given Case ID - levelDir1 = caseID{i}(1); - - if (length(caseID{i}(2:end)) < 2) - levelDir2 = '0'; - levelDir3 = '0'; - elseif (length(caseID{i}(2:end)) < 3) - levelDir2 = '0'; - levelDir3 = caseID{i}(2); - else - levelDir2 = caseID{i}(2); - levelDir3 = caseID{i}(3); - end - - FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); - if (isempty(FindCaseCTFolder)) - error(['Missing CT directory for CaseID: ' caseID{i}]); - end - - CaseID_IPP = FindCaseCTFolder.name; - CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; - - % Compute musle degeneration values and store in the XLS database - if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the patient name is added to the "measured" column - - muscleDirectory = [CaseCTFolder '/CT-' CaseID_IPP '-1/muscles']; %#ok - - muscles_results(i).Case_ID = caseID{i}; - - for j=1:length(musclesList) - - disp(caseID{i}); - % Compute muscle degeneration values - eval([musclesList{j} '= muscleDegeneration(caseID{i}, musclesList{j},HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory);']); - eval(['muscles_results = setfield(muscles_results,{i,1}, musclesList{j}, ' musclesList{j} ');']); - - end - - muscles = cellstr(muscles_results(i).Case_ID)'; - for j = 1:length(musclesList) - eval(['muscles = horzcat(muscles, transpose(struct2cell(muscles_results(i).' musclesList{j} ')));']); - end - - % Write to XLS file - % Replace line if already existing or append - if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles(:,[1,7:10,16:19,25:28,34:37]),'Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); - else - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles(:,[1,7:10,16:19,25:28,34:37]),'Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); - end - % Write to MySQL database - % Replace line if already existing or issue warning message - sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; - curs = exec(conn,sqlquery); - if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); - end - - curs=fetch(curs); - sqlresult = curs.Data; - - if isnumeric(sqlresult{1}) - - % Update data in table 'muscle' - data = {sqlresult{1} ... - muscles_results(i).SS.atrophy muscles_results(i).SS.infiltration muscles_results(i).SS.osteochondroma muscles_results(i).SS.degeneration ... - muscles_results(i).IS.atrophy muscles_results(i).IS.infiltration muscles_results(i).IS.osteochondroma muscles_results(i).IS.degeneration ... - muscles_results(i).SC.atrophy muscles_results(i).SC.infiltration muscles_results(i).SC.osteochondroma muscles_results(i).SC.degeneration ... - muscles_results(i).TM.atrophy muscles_results(i).TM.infiltration muscles_results(i).TM.osteochondroma muscles_results(i).TM.degeneration}; - fieldNames = {'CT_id','SSA','SSI','SSO','SSD','ISA','ISI','ISO','ISD','SCA','SCI','SCO','SCD', 'TMA', 'TMI', 'TMO', 'TMD'}; - upsert(conn, 'muscle', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); - - else - error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); - end - - else - error(['"muscles" folder missing for CaseID: ' caseID{i}]); - end - - end - - -end +function muscles_results = degenerationAll(caseID, musclesList) +%%DEGENERATIONALL returns a structure array containing muscle +% measurements for each case ID entered as input and fills the Shoulder +% database with muscle degeneration measurements +% +% This function fills the Shoulder database with the muscle degeneration +% values of the case given as input. The measurements are stored in a XLS +% file and in the MySQL database. +% +% USE: degenerationAll(caseID, musclesList) +% +% INPUT caseID: Case IDs for which you want to compute muscle degeneration. +% Cell array that stores the case IDs as a char in the form 'P###' or +% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). +% +% musclesList: List of muscles which you want to compute muscle +% degeneration. Cell array that stores each msucle name as a char. +% +% OUTPUT muscles_results: structure array containing fields 'Case_ID', +% and fields "muscle name" with muscle measurements for the +% current patient: 'Stot', 'Satrophy', 'Sinfiltration', +% 'Sosteochondroma' and 'Sdegeneration', 'atrophy', +% 'infiltration', 'osteochondroma' and 'degeneration' +% +% REMARKS The muscle values (ratios) are saved in the muscles.xls file +% and in the shoulder MySQL database. +% +% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 +% Author: EPFL-LBO-VMC +% Date: 27-Jun-2017 +% + + CTDatabaseLocation = '../../../data'; % Location of the CT database + XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase + + HUThresholdMuscle = 0; %#ok + HUThresholdFat = 30; %#ok + HUThresholdOsteochondroma = 166; %#ok + + % Check validity of input argument (cell array) + validateattributes(caseID,{'cell'},{'nonempty'}); + + % Check that imput case IDs are unique + [caseIDNames,~,uniqueCaseID] = unique(caseID); + countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; + freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); + repeatedValues = find([freqCaseIDs.freq] > 1); + + if(~isempty(repeatedValues)) + warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) + caseID = caseIDNames'; + end + + % open mySQL connection + conn = openSQL(); + + % Get the list of exisiting cases in XLS database + [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls']); + currDatabaseCaseIDlist = currDatabase(2:end,1); + + %Initialize structure array for results + muscles_results = []; + muscles_results = setfield(muscles_results,{length(caseID),1},'Case_ID',[]); + + % Compute muscle degeneration values and fill database + for i = 1:length(caseID) + + % Check validity of input arguments (char and format 'P###' or 'N###') + validateattributes(caseID{i},{'char'},{'nonempty'}); + + if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) + error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); + end + + % Find CT folder for the given Case ID + levelDir1 = caseID{i}(1); + + if (length(caseID{i}(2:end)) < 2) + levelDir2 = '0'; + levelDir3 = '0'; + elseif (length(caseID{i}(2:end)) < 3) + levelDir2 = '0'; + levelDir3 = caseID{i}(2); + else + levelDir2 = caseID{i}(2); + levelDir3 = caseID{i}(3); + end + + FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); + if (isempty(FindCaseCTFolder)) + error(['Missing CT directory for CaseID: ' caseID{i}]); + end + + CaseID_IPP = FindCaseCTFolder.name; + CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; + + % Compute musle degeneration values and store in the XLS database + if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the patient name is added to the "measured" column + + muscleDirectory = [CaseCTFolder '/CT-' CaseID_IPP '-1/muscles']; %#ok + + muscles_results(i).Case_ID = caseID{i}; + + for j=1:length(musclesList) + + disp(caseID{i}); + % Compute muscle degeneration values + eval([musclesList{j} '= muscleDegeneration(caseID{i}, musclesList{j},HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory);']); + eval(['muscles_results = setfield(muscles_results,{i,1}, musclesList{j}, ' musclesList{j} ');']); + + end + + muscles = cellstr(muscles_results(i).Case_ID)'; + for j = 1:length(musclesList) + eval(['muscles = horzcat(muscles, transpose(struct2cell(muscles_results(i).' musclesList{j} ')));']); + end + + % Write to XLS file + % Replace line if already existing or append + if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles(:,[1,7:10,16:19,25:28,34:37]),'Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); + else + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles(:,[1,7:10,16:19,25:28,34:37]),'Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); + end + % Write to MySQL database + % Replace line if already existing or issue warning message + sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; + curs = exec(conn,sqlquery); + if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); + end + + curs=fetch(curs); + sqlresult = curs.Data; + + if isnumeric(sqlresult{1}) + + % Update data in table 'muscle' + data = {sqlresult{1} ... + muscles_results(i).SS.atrophy muscles_results(i).SS.infiltration muscles_results(i).SS.osteochondroma muscles_results(i).SS.degeneration ... + muscles_results(i).IS.atrophy muscles_results(i).IS.infiltration muscles_results(i).IS.osteochondroma muscles_results(i).IS.degeneration ... + muscles_results(i).SC.atrophy muscles_results(i).SC.infiltration muscles_results(i).SC.osteochondroma muscles_results(i).SC.degeneration ... + muscles_results(i).TM.atrophy muscles_results(i).TM.infiltration muscles_results(i).TM.osteochondroma muscles_results(i).TM.degeneration}; + fieldNames = {'CT_id','SSA','SSI','SSO','SSD','ISA','ISI','ISO','ISD','SCA','SCI','SCO','SCD', 'TMA', 'TMI', 'TMO', 'TMD'}; + upsert(conn, 'muscle', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); + + else + error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); + end + + else + error(['"muscles" folder missing for CaseID: ' caseID{i}]); + end + + end + + +end diff --git a/muscles/degenerationAll3D.m b/muscles/degenerationAll3D.m index 3d5faf7..3bc8704 100644 --- a/muscles/degenerationAll3D.m +++ b/muscles/degenerationAll3D.m @@ -1,192 +1,192 @@ -function muscles_results = degenerationAll3D(caseID, musclesList) -%%DEGENERATIONALL3D returns a structure array containing muscle -% measurements for each case ID entered as input and fills the Shoulder -% database with muscle degeneration measurements -% -% This function fills the Shoulder database with the muscle degeneration -% values of the case given as input, for all CT slices available. -% The measurements are stored in a XLS file and in the MySQL database. -% -% USE: degenerationAll3D(caseID, musclesList) -% -% INPUT caseID: Case IDs for which you want to compute muscle degeneration. -% Cell array that stores the case IDs as a char in the form 'P###' or -% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). -% -% musclesList: List of muscles which you want to compute muscle -% degeneration. Cell array that stores each msucle name as a char. -% -% OUTPUT muscles_results: structure array containing fields 'Case_ID', -% and structure array field "muscle name" with muscle -% measurements for each slice for the current patient: 'Stot', -% 'Satrophy', 'Sinfiltration', 'Sosteochondroma' and -% 'Sdegeneration', 'atrophy', 'infiltration', 'osteochondroma' -% and 'degeneration' -% -% REMARKS The muscle values (ratios) for all available CT slices are -% saved in the muscles.xls file and in the shoulder MySQL -% database. -% -% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 -% Author: EPFL-LBO-VMC -% Date: 29-kun-2017 -% - - CTDatabaseLocation = '../../../data'; % Location of the CT database -% XLSShoulderDatabaseLocation = 'C:/Users/vmalfroy/Dropbox/shoulderDatabase/'; % Location of the XLS ShoulderDatabase - XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase - HUThresholdMuscle = 0; %#ok - HUThresholdFat = 30; %#ok - HUThresholdOsteochondroma = 166; %#ok - - % Check validity of input argument (cell array) - validateattributes(caseID,{'cell'},{'nonempty'}); - - % Check that imput case IDs are unique - [caseIDNames,~,uniqueCaseID] = unique(caseID); - countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; - freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); - repeatedValues = find([freqCaseIDs.freq] > 1); - - if(~isempty(repeatedValues)) - warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) - caseID = caseIDNames'; - end - - % open mySQL connection - conn = openSQL(); - - % Get the list of exisiting cases in XLS database - [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls']); - currDatabaseCaseIDlist = currDatabase(2:end,1); - - %Initialize structure array for results - muscles_results = []; - muscles_results = setfield(muscles_results,{length(caseID),1},'Case_ID',[]); - - % Compute muscle degeneration values and fill database - for i = 1:length(caseID) - - % Check validity of input arguments (char and format 'P###' or 'N###') - validateattributes(caseID{i},{'char'},{'nonempty'}); - - if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) - error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); - end - - % Find CT folder for the given Case ID - levelDir1 = caseID{i}(1); - - if (length(caseID{i}(2:end)) < 2) - levelDir2 = '0'; - levelDir3 = '0'; - elseif (length(caseID{i}(2:end)) < 3) - levelDir2 = '0'; - levelDir3 = caseID{i}(2); - else - levelDir2 = caseID{i}(2); - levelDir3 = caseID{i}(3); - end - - FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); - if (isempty(FindCaseCTFolder)) - error(['Missing CT directory for CaseID: ' caseID{i}]); - end - - CaseID_IPP = FindCaseCTFolder.name; - CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; - - % Compute musle degeneration values and store in the XLS database - if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the patient name is added to the "measured" column - - muscleDirectory = [CaseCTFolder '/CT-' CaseID_IPP '-1/muscles']; - - slicesList = dir(muscleDirectory); - slicesList = slicesList(3:end); - - muscles_results(i).Case_ID = caseID{i}; - - field2sort = 'SliceNumber'; %#ok - - for j=1:length(slicesList) - - for k=1:length(musclesList) - - disp(caseID{i}); - - % Compute muscle degeneration values - eval([musclesList{k} '= muscleDegeneration3D(caseID{i}, musclesList{k},HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory);']); - eval(['muscles_results = setfield(muscles_results,{i,1}, musclesList{k}, ' musclesList{k} ');']); - - % Sort muscle field by slice number - eval(['[~,I] = sort(arrayfun (@(x) x.(field2sort), muscles_results(i,1).' musclesList{k} '));']); - eval(['muscles_results(i,1).' musclesList{k} ' = muscles_results(i,1).' musclesList{k} '(I);']); - - end - - end - - muscles = cellstr(muscles_results(i).CT_id)'; - - - for k = 1:length(musclesList) - eval(['muscles = horzcat(muscles, transpose(struct2cell(muscles_results.' musclesList{k} ')));']); - end - - % Write to XLS file - % Replace line if already existing or append - if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles,'Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); - else - xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles,'Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); - end - % Write to MySQL database - % Replace line if already existing or issue warning message - sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; - curs = exec(conn,sqlquery); - if ~isempty(curs.Message) - fprintf('\nMessage: %s\n', curs.Message); - end - - curs=fetch(curs); - sqlresult = curs.Data; - - if isnumeric(sqlresult{1}) - - SS_atrophy = sum([muscles_results(i).SS(:).Satrophy])/sum([muscles_results(i).SS(:).Stot]); - SS_infiltration = sum([muscles_results(i).SS(:).Sinfiltration])/sum([muscles_results(i).SS(:).Stot]); - SS_osteochondroma = sum([muscles_results(i).SS(:).Sosteochondroma])/sum([muscles_results(i).SS(:).Stot]); - SS_degeneration = (sum([muscles_results(i).SS(:).Stot]) - sum([muscles_results(i).SS(:).Sdegeneration]))/sum([muscles_results(i).SS(:).Stot]); - - IS_atrophy = sum([muscles_results(i).IS(:).Satrophy])/sum([muscles_results(i).IS(:).Stot]); - IS_infiltration = sum([muscles_results(i).IS(:).Sinfiltration])/sum([muscles_results(i).IS(:).Stot]); - IS_osteochondroma = sum([muscles_results(i).IS(:).Sosteochondroma])/sum([muscles_results(i).IS(:).Stot]); - IS_degeneration = (sum([muscles_results(i).IS(:).Stot]) - sum([muscles_results(i).IS(:).Sdegeneration]))/sum([muscles_results(i).IS(:).Stot]); - - SC_atrophy = sum([muscles_results(i).SC(:).Satrophy])/sum([muscles_results(i).SC(:).Stot]); - SC_infiltration = sum([muscles_results(i).SC(:).Sinfiltration])/sum([muscles_results(i).SC(:).Stot]); - SC_osteochondroma = sum([muscles_results(i).SC(:).Sosteochondroma])/sum([muscles_results(i).SC(:).Stot]); - SC_degeneration = (sum([muscles_results(i).SC(:).Stot]) - sum([muscles_results(i).SC(:).Sdegeneration]))/sum([muscles_results(i).SC(:).Stot]); - - TM_atrophy = sum([muscles_results(i).TM(:).Satrophy])/sum([muscles_results(i).TM(:).Stot]); - TM_infiltration = sum([muscles_results(i).TM(:).Sinfiltration])/sum([muscles_results(i).TM(:).Stot]); - TM_osteochondroma = sum([muscles_results(i).TM(:).Sosteochondroma])/sum([muscles_results(i).TM(:).Stot]); - TM_degeneration = (sum([muscles_results(i).TM(:).Stot]) - sum([muscles_results(i).TM(:).Sdegeneration]))/sum([muscles_results(i).TM(:).Stot]); - - % Update data in table 'muscle' - data = {sqlresult{1} SS_atrophy SS_infiltration SS_osteochondroma SS_degeneration IS_atrophy IS_infiltration IS_osteochondroma IS_degeneration SC_atrophy SC_infiltration SC_osteochondroma SC_degeneration TM_atrophy TM_infiltration TM_osteochondroma TM_degeneration}; - fieldNames = {'CT_id','SSA','SSI','SSO','SSD','ISA','ISI','ISO','ISD','SCA','SCI','SCO','SCD', 'TMA', 'TMI', 'TMO', 'TMD'}; - upsert(conn, 'muscle', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); - - else - error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); - end - - else - error(['"muscles" folder missing for CaseID: ' caseID{i}]); - end - - end - - -end +function muscles_results = degenerationAll3D(caseID, musclesList) +%%DEGENERATIONALL3D returns a structure array containing muscle +% measurements for each case ID entered as input and fills the Shoulder +% database with muscle degeneration measurements +% +% This function fills the Shoulder database with the muscle degeneration +% values of the case given as input, for all CT slices available. +% The measurements are stored in a XLS file and in the MySQL database. +% +% USE: degenerationAll3D(caseID, musclesList) +% +% INPUT caseID: Case IDs for which you want to compute muscle degeneration. +% Cell array that stores the case IDs as a char in the form 'P###' or +% 'N###' (starts with "P" or "N" followed by 1 to 3 digits). +% +% musclesList: List of muscles which you want to compute muscle +% degeneration. Cell array that stores each msucle name as a char. +% +% OUTPUT muscles_results: structure array containing fields 'Case_ID', +% and structure array field "muscle name" with muscle +% measurements for each slice for the current patient: 'Stot', +% 'Satrophy', 'Sinfiltration', 'Sosteochondroma' and +% 'Sdegeneration', 'atrophy', 'infiltration', 'osteochondroma' +% and 'degeneration' +% +% REMARKS The muscle values (ratios) for all available CT slices are +% saved in the muscles.xls file and in the shoulder MySQL +% database. +% +% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 +% Author: EPFL-LBO-VMC +% Date: 29-kun-2017 +% + + CTDatabaseLocation = '../../../data'; % Location of the CT database +% XLSShoulderDatabaseLocation = 'C:/Users/vmalfroy/Dropbox/shoulderDatabase/'; % Location of the XLS ShoulderDatabase + XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase + HUThresholdMuscle = 0; %#ok + HUThresholdFat = 30; %#ok + HUThresholdOsteochondroma = 166; %#ok + + % Check validity of input argument (cell array) + validateattributes(caseID,{'cell'},{'nonempty'}); + + % Check that imput case IDs are unique + [caseIDNames,~,uniqueCaseID] = unique(caseID); + countOfCaseID = hist(uniqueCaseID,unique(uniqueCaseID))'; + freqCaseIDs = struct('name',caseIDNames,'freq',num2cell(countOfCaseID)); + repeatedValues = find([freqCaseIDs.freq] > 1); + + if(~isempty(repeatedValues)) + warning('Input contains repeated Case ID: %s. Repeated occurences of that Case ID will be omitted. \n', freqCaseIDs(repeatedValues).name) + caseID = caseIDNames'; + end + + % open mySQL connection + conn = openSQL(); + + % Get the list of exisiting cases in XLS database + [~,~,currDatabase] = xlsread([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls']); + currDatabaseCaseIDlist = currDatabase(2:end,1); + + %Initialize structure array for results + muscles_results = []; + muscles_results = setfield(muscles_results,{length(caseID),1},'Case_ID',[]); + + % Compute muscle degeneration values and fill database + for i = 1:length(caseID) + + % Check validity of input arguments (char and format 'P###' or 'N###') + validateattributes(caseID{i},{'char'},{'nonempty'}); + + if (numel(regexp(caseID{i},'^[PN]\d{1,3}$')) == 0) + error(['Invalid format of CaseID: ' caseID{i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); + end + + % Find CT folder for the given Case ID + levelDir1 = caseID{i}(1); + + if (length(caseID{i}(2:end)) < 2) + levelDir2 = '0'; + levelDir3 = '0'; + elseif (length(caseID{i}(2:end)) < 3) + levelDir2 = '0'; + levelDir3 = caseID{i}(2); + else + levelDir2 = caseID{i}(2); + levelDir3 = caseID{i}(3); + end + + FindCaseCTFolder = dir([CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' caseID{i} '*']); + if (isempty(FindCaseCTFolder)) + error(['Missing CT directory for CaseID: ' caseID{i}]); + end + + CaseID_IPP = FindCaseCTFolder.name; + CaseCTFolder = [CTDatabaseLocation '/' levelDir1 '/' levelDir2 '/' levelDir3 '/' CaseID_IPP]; + + % Compute musle degeneration values and store in the XLS database + if exist([CaseCTFolder '/CT-' CaseID_IPP '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the patient name is added to the "measured" column + + muscleDirectory = [CaseCTFolder '/CT-' CaseID_IPP '-1/muscles']; + + slicesList = dir(muscleDirectory); + slicesList = slicesList(3:end); + + muscles_results(i).Case_ID = caseID{i}; + + field2sort = 'SliceNumber'; %#ok + + for j=1:length(slicesList) + + for k=1:length(musclesList) + + disp(caseID{i}); + + % Compute muscle degeneration values + eval([musclesList{k} '= muscleDegeneration3D(caseID{i}, musclesList{k},HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory);']); + eval(['muscles_results = setfield(muscles_results,{i,1}, musclesList{k}, ' musclesList{k} ');']); + + % Sort muscle field by slice number + eval(['[~,I] = sort(arrayfun (@(x) x.(field2sort), muscles_results(i,1).' musclesList{k} '));']); + eval(['muscles_results(i,1).' musclesList{k} ' = muscles_results(i,1).' musclesList{k} '(I);']); + + end + + end + + muscles = cellstr(muscles_results(i).CT_id)'; + + + for k = 1:length(musclesList) + eval(['muscles = horzcat(muscles, transpose(struct2cell(muscles_results.' musclesList{k} ')));']); + end + + % Write to XLS file + % Replace line if already existing or append + if(any(strcmp(currDatabaseCaseIDlist, caseID{i}))) + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles,'Feuil1',['A' int2str(find(strcmp(currDatabaseCaseIDlist, caseID{i}))+1)]); + else + xlswrite([XLSShoulderDatabaseLocation 'xlsFromMatlab/muscles.xls'],muscles,'Feuil1',['A' int2str(length(currDatabaseCaseIDlist)+1+i)]); + end + % Write to MySQL database + % Replace line if already existing or issue warning message + sqlquery = ['SELECT CT_id FROM CT WHERE shoulder_id IN (SELECT shoulder_id FROM sCase WHERE folder_name = "' caseID{i} '")']; + curs = exec(conn,sqlquery); + if ~isempty(curs.Message) + fprintf('\nMessage: %s\n', curs.Message); + end + + curs=fetch(curs); + sqlresult = curs.Data; + + if isnumeric(sqlresult{1}) + + SS_atrophy = sum([muscles_results(i).SS(:).Satrophy])/sum([muscles_results(i).SS(:).Stot]); + SS_infiltration = sum([muscles_results(i).SS(:).Sinfiltration])/sum([muscles_results(i).SS(:).Stot]); + SS_osteochondroma = sum([muscles_results(i).SS(:).Sosteochondroma])/sum([muscles_results(i).SS(:).Stot]); + SS_degeneration = (sum([muscles_results(i).SS(:).Stot]) - sum([muscles_results(i).SS(:).Sdegeneration]))/sum([muscles_results(i).SS(:).Stot]); + + IS_atrophy = sum([muscles_results(i).IS(:).Satrophy])/sum([muscles_results(i).IS(:).Stot]); + IS_infiltration = sum([muscles_results(i).IS(:).Sinfiltration])/sum([muscles_results(i).IS(:).Stot]); + IS_osteochondroma = sum([muscles_results(i).IS(:).Sosteochondroma])/sum([muscles_results(i).IS(:).Stot]); + IS_degeneration = (sum([muscles_results(i).IS(:).Stot]) - sum([muscles_results(i).IS(:).Sdegeneration]))/sum([muscles_results(i).IS(:).Stot]); + + SC_atrophy = sum([muscles_results(i).SC(:).Satrophy])/sum([muscles_results(i).SC(:).Stot]); + SC_infiltration = sum([muscles_results(i).SC(:).Sinfiltration])/sum([muscles_results(i).SC(:).Stot]); + SC_osteochondroma = sum([muscles_results(i).SC(:).Sosteochondroma])/sum([muscles_results(i).SC(:).Stot]); + SC_degeneration = (sum([muscles_results(i).SC(:).Stot]) - sum([muscles_results(i).SC(:).Sdegeneration]))/sum([muscles_results(i).SC(:).Stot]); + + TM_atrophy = sum([muscles_results(i).TM(:).Satrophy])/sum([muscles_results(i).TM(:).Stot]); + TM_infiltration = sum([muscles_results(i).TM(:).Sinfiltration])/sum([muscles_results(i).TM(:).Stot]); + TM_osteochondroma = sum([muscles_results(i).TM(:).Sosteochondroma])/sum([muscles_results(i).TM(:).Stot]); + TM_degeneration = (sum([muscles_results(i).TM(:).Stot]) - sum([muscles_results(i).TM(:).Sdegeneration]))/sum([muscles_results(i).TM(:).Stot]); + + % Update data in table 'muscle' + data = {sqlresult{1} SS_atrophy SS_infiltration SS_osteochondroma SS_degeneration IS_atrophy IS_infiltration IS_osteochondroma IS_degeneration SC_atrophy SC_infiltration SC_osteochondroma SC_degeneration TM_atrophy TM_infiltration TM_osteochondroma TM_degeneration}; + fieldNames = {'CT_id','SSA','SSI','SSO','SSD','ISA','ISI','ISO','ISD','SCA','SCI','SCO','SCD', 'TMA', 'TMI', 'TMO', 'TMD'}; + upsert(conn, 'muscle', fieldNames, [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], data); + + else + error(['No entry for caseID ' caseID{i} ' was found in the ' conn.Instance ' MySQL database.']); + end + + else + error(['"muscles" folder missing for CaseID: ' caseID{i}]); + end + + end + + +end diff --git a/muscles/muscleContours.m b/muscles/muscleContours.m index 4cf7ac4..0e4fdcd 100644 --- a/muscles/muscleContours.m +++ b/muscles/muscleContours.m @@ -1,148 +1,148 @@ -%% muscleContours - -% This script allows to select the contours on Tiff images of the four -% cuff rotator muscles: Supraspinatus (SS), Infraspinatus (IS), Subscapularis (SC) -% and Teres Minor (TM). -% - -% INPUT: -% subject: ### subject number -% type: 'normal' or 'pathologic' - -% Author: EPFL-LBO -% Date: 2016-09-05 -%% - -function [] = muscleContours(subject,type) - -directory = '../../../data'; % Location of the CT database -location = 'P'; -CTn = sprintf('P%03d',subject); - -listDirInCurrDir = dir([directory '/' CTn(1) '/' CTn(2) '/' CTn(3)]); -listDirInCurrDir = listDirInCurrDir(3:end); - -for i=1:length(listDirInCurrDir) - if ~isempty(regexp(listDirInCurrDir(i).name,['^[PN]\d{0,2}[' CTn(4) '][-]\w*'], 'once')) - - listDirInCaseFolder = dir([listDirInCurrDir(i).folder '\' listDirInCurrDir(i).name]); - listDirInCaseFolder = listDirInCaseFolder(3:end); - - % In all CT folders in the current Case Folder, find CT #1 - for j=1:length(listDirInCaseFolder) - if ~isempty(regexp(listDirInCaseFolder(j).name,'^[C][T][-][PN]\d{1,3}[-]\d*[-][1]', 'once')) - outputPath = [listDirInCaseFolder(j).folder '\' listDirInCaseFolder(j).name]; - end - end - end -end - - -pathologic_list = dir(sprintf('%s/%s/P*', directory, location)); %Create the case list -for i = 1:1:numel(pathologic_list) % Loop over the list of patients and extract the complete name of the specific patient - name = pathologic_list(i).name; - t = strfind(name,CTn); - if t == 1; - patient_name = name; - end -end - -finaldirectory = [outputPath '\amira\*tif']; -muscleName = cellstr(['SS';'IS';'SC';'TM']); - -close all; % Close all figure windows except those created by imtool. -imtool close all; % Close all figure windows created by imtool. -workspace; % Make sure the workspace panel is showing. -fontSize = 16; -imageWindow = [-100 200]; - -% Choose file -[inputFile, inputPath] = uigetfile(finaldirectory,'Select the image for muscle atrophy measurement'); -whereisp = strfind(inputFile, 'P'); -if isempty(whereisp) - whereisp = strfind(inputFile, 'N'); - if isempty(whereisp) - return - end -end -subject = inputFile(whereisp:whereisp+3); - -% Read gray scale image. -inputImage = imread(sprintf('%s%s',inputPath,inputFile)); - -if size(inputImage,3) == 3 -% Calculate the monochrome luminance by combining the RGB values according to the NTSC standard - inputImage = 0.2989*inputImage(:,:,1)... - +0.5870*inputImage(:,:,2)... - +0.1140*inputImage(:,:,3); -end - -for i=1:4; - - finished = 'Redo'; - while strcmp('Redo',finished) - msg = 0; % 0 = no message - % clc; % Clear command window. - close all; % Close all figure windows except those created by imtool. - imtool close all; % Close all figure windows created by imtool. - imshow(inputImage,imageWindow); - imagetitle = sprintf('Select %s ideal section', char(muscleName(i))); - title(imagetitle, 'FontSize', fontSize); - set(gcf, 'Position', get(0,'Screensize')); % Maximize figure. - - if msg == 1 - message = sprintf('Select the ideal section of your muscle in the image. \nLeft click to place points and draw a polygon. Click again on the first point to close the shape.\nYou can move the points once the line is closed. Double-click or right-click and "Create Mask" to finish.'); - msg = 0; - uiwait(msgbox(message)); - end - - binaryImage1 = roipoly(); - - if isempty(binaryImage1) - error('Invalid selection or interface closed') - end - - binaryImage1 = bwmorph(binaryImage1,'majority'); - % Get coordinates of the boundary of the polygonal drawn region. - structBoundaries1 = bwboundaries(binaryImage1); - xy1 = structBoundaries1{1}; % Get n by 2 array of x,y coordinates. - x1 = xy1(:, 2); % Columns. - y1 = xy1(:, 1); % Rows. - hold on - plot(x1, y1, 'color','green'); - - % check contours - finished = questdlg('Please verify the contours. Press "Save" if they are satisfactory or "Redo" if you would like to redo the selection. ',... - 'Contour verification','Save','Redo','Save'); - end - - %% Report results - % mkdir('./results',subject); - if exist([outputPath '\muscles'],'dir') == 0 - mkdir(outputPath,'muscles'); - end - - outputPath2 = [outputPath '\muscles\' char(muscleName(i)) '\']; - mkdir(outputPath2); - outputFile = sprintf('ssContours%s',subject); - - % Check for overwriting - overwrite = exist(sprintf('%s%s.mat',outputPath2,outputFile), 'file'); - switch overwrite - case 2 - isoverwrite = questdlg('Overwrite existing file ?', 'Warning', 'No', 'Yes', 'No'); - switch isoverwrite - case 'Yes' - save(sprintf('%s%s.mat',outputPath2,outputFile), 'inputImage','binaryImage1') - saveas(gcf,sprintf('%s%s.tif',outputPath2,outputFile)) - fprintf('%s%s.mat\n%s%s.tif\n',outputPath2,outputFile,outputPath2,outputFile); - end - - case 0 - save(sprintf('%s%s.mat',outputPath2,outputFile), 'inputImage','binaryImage1') - saveas(gcf,sprintf('%s%s.tif',outputPath2,outputFile)) - fprintf('%s%s.mat\n%s%s.tif\n',outputPath2,outputFile,outputPath2,outputFile); - end -end -close all; +%% muscleContours + +% This script allows to select the contours on Tiff images of the four +% cuff rotator muscles: Supraspinatus (SS), Infraspinatus (IS), Subscapularis (SC) +% and Teres Minor (TM). +% + +% INPUT: +% subject: ### subject number +% type: 'normal' or 'pathologic' + +% Author: EPFL-LBO +% Date: 2016-09-05 +%% + +function [] = muscleContours(subject,type) + +directory = '../../../data'; % Location of the CT database +location = 'P'; +CTn = sprintf('P%03d',subject); + +listDirInCurrDir = dir([directory '/' CTn(1) '/' CTn(2) '/' CTn(3)]); +listDirInCurrDir = listDirInCurrDir(3:end); + +for i=1:length(listDirInCurrDir) + if ~isempty(regexp(listDirInCurrDir(i).name,['^[PN]\d{0,2}[' CTn(4) '][-]\w*'], 'once')) + + listDirInCaseFolder = dir([listDirInCurrDir(i).folder '\' listDirInCurrDir(i).name]); + listDirInCaseFolder = listDirInCaseFolder(3:end); + + % In all CT folders in the current Case Folder, find CT #1 + for j=1:length(listDirInCaseFolder) + if ~isempty(regexp(listDirInCaseFolder(j).name,'^[C][T][-][PN]\d{1,3}[-]\d*[-][1]', 'once')) + outputPath = [listDirInCaseFolder(j).folder '\' listDirInCaseFolder(j).name]; + end + end + end +end + + +pathologic_list = dir(sprintf('%s/%s/P*', directory, location)); %Create the case list +for i = 1:1:numel(pathologic_list) % Loop over the list of patients and extract the complete name of the specific patient + name = pathologic_list(i).name; + t = strfind(name,CTn); + if t == 1; + patient_name = name; + end +end + +finaldirectory = [outputPath '\amira\*tif']; +muscleName = cellstr(['SS';'IS';'SC';'TM']); + +close all; % Close all figure windows except those created by imtool. +imtool close all; % Close all figure windows created by imtool. +workspace; % Make sure the workspace panel is showing. +fontSize = 16; +imageWindow = [-100 200]; + +% Choose file +[inputFile, inputPath] = uigetfile(finaldirectory,'Select the image for muscle atrophy measurement'); +whereisp = strfind(inputFile, 'P'); +if isempty(whereisp) + whereisp = strfind(inputFile, 'N'); + if isempty(whereisp) + return + end +end +subject = inputFile(whereisp:whereisp+3); + +% Read gray scale image. +inputImage = imread(sprintf('%s%s',inputPath,inputFile)); + +if size(inputImage,3) == 3 +% Calculate the monochrome luminance by combining the RGB values according to the NTSC standard + inputImage = 0.2989*inputImage(:,:,1)... + +0.5870*inputImage(:,:,2)... + +0.1140*inputImage(:,:,3); +end + +for i=1:4; + + finished = 'Redo'; + while strcmp('Redo',finished) + msg = 0; % 0 = no message + % clc; % Clear command window. + close all; % Close all figure windows except those created by imtool. + imtool close all; % Close all figure windows created by imtool. + imshow(inputImage,imageWindow); + imagetitle = sprintf('Select %s ideal section', char(muscleName(i))); + title(imagetitle, 'FontSize', fontSize); + set(gcf, 'Position', get(0,'Screensize')); % Maximize figure. + + if msg == 1 + message = sprintf('Select the ideal section of your muscle in the image. \nLeft click to place points and draw a polygon. Click again on the first point to close the shape.\nYou can move the points once the line is closed. Double-click or right-click and "Create Mask" to finish.'); + msg = 0; + uiwait(msgbox(message)); + end + + binaryImage1 = roipoly(); + + if isempty(binaryImage1) + error('Invalid selection or interface closed') + end + + binaryImage1 = bwmorph(binaryImage1,'majority'); + % Get coordinates of the boundary of the polygonal drawn region. + structBoundaries1 = bwboundaries(binaryImage1); + xy1 = structBoundaries1{1}; % Get n by 2 array of x,y coordinates. + x1 = xy1(:, 2); % Columns. + y1 = xy1(:, 1); % Rows. + hold on + plot(x1, y1, 'color','green'); + + % check contours + finished = questdlg('Please verify the contours. Press "Save" if they are satisfactory or "Redo" if you would like to redo the selection. ',... + 'Contour verification','Save','Redo','Save'); + end + + %% Report results + % mkdir('./results',subject); + if exist([outputPath '\muscles'],'dir') == 0 + mkdir(outputPath,'muscles'); + end + + outputPath2 = [outputPath '\muscles\' char(muscleName(i)) '\']; + mkdir(outputPath2); + outputFile = sprintf('ssContours%s',subject); + + % Check for overwriting + overwrite = exist(sprintf('%s%s.mat',outputPath2,outputFile), 'file'); + switch overwrite + case 2 + isoverwrite = questdlg('Overwrite existing file ?', 'Warning', 'No', 'Yes', 'No'); + switch isoverwrite + case 'Yes' + save(sprintf('%s%s.mat',outputPath2,outputFile), 'inputImage','binaryImage1') + saveas(gcf,sprintf('%s%s.tif',outputPath2,outputFile)) + fprintf('%s%s.mat\n%s%s.tif\n',outputPath2,outputFile,outputPath2,outputFile); + end + + case 0 + save(sprintf('%s%s.mat',outputPath2,outputFile), 'inputImage','binaryImage1') + saveas(gcf,sprintf('%s%s.tif',outputPath2,outputFile)) + fprintf('%s%s.mat\n%s%s.tif\n',outputPath2,outputFile,outputPath2,outputFile); + end +end +close all; end \ No newline at end of file diff --git a/muscles/muscleDegeneration.m b/muscles/muscleDegeneration.m index e269cc0..0d7aa07 100644 --- a/muscles/muscleDegeneration.m +++ b/muscles/muscleDegeneration.m @@ -1,227 +1,227 @@ -function muscle = muscleDegeneration(caseID,muscleName,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory) -%%MUSCLEDEGENERATION computes degeneration parameters from muscle contours -% -% This function returns muscle atrophy, fat infiltration, osteochondroma -% and degeneration values for a given muscle -% -% USE: muscleDegeneration(caseID,muscleName,HUThresholdMuscle,HUThresholdOsteochondroma,muscleDirectory) -% -% INPUT caseID: patient number -% muscleName: name of the rotator cuff muscle being measured (SS,IS, SC or TM) -% HUThresholdMuscle: HU threshold used for muscle segmentation (atrophied muscle limit) -% HUThresholdFat: HU threshold used for fat infiltration segmentation (fat-muscle limit) -% HUThresholdOsteochondroma:HU threshold used for osteochondroma segmentation (muscle-osteochondroma limit) -% muscleDirectory: Path to 'muscles' directory where contours and output images are stored -% -% OUTPUT muscle: structure containing fields 'Stot', 'Satrophy', -% 'Sinfiltration', 'Sosteochondroma' and 'Sdegeneration', -% 'atrophy', 'infiltration', 'osteochondroma' and 'degeneration' -% -% REMARKS An image of the segmentation of atrophied muscle, fat -% infiltration and osteochondroma is saved in muscles directory, -% in the 'muscleName' folder. -% -% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 -% Author: EPFL-LBO-VMC -% Date: 27-Jun-2017 -% - -inputFile = ['ssContours' caseID '.mat']; -contouredMuscle = load([muscleDirectory '\' muscleName '\' inputFile]); - -% --Plot CT slice with all contours-- - -fontSize = 14; -visualizationWindow = [-100 200]; - -imshow(contouredMuscle.inputImage,visualizationWindow); - -hold on - -% --Atrophied muscle segmentation-- - -% MATLAB does not accept to threshold an image with a negative value, as is -% the case when using HU scale. CT scanners usually have a CT range from -% -1000 HU to 1000 HU or -2000 HU to 2000 HU for modern scanners. In this -% case, we're not using HU values lower than -1000 and higher than +1000 so -% all pixel intensity values of the input image< -1000 HU are set to -1000 -% HU, and values > 1000 HU are set to 1000 HU. Then, the input image is -% mapped to the [0 1] range -1000->0 1000->1, as are the thresholds for -% muscle and osteochondroma. - -% Normalize image and thresholds - -contouredMuscle.inputImage(contouredMuscle.inputImage < -1000) = -1000; -contouredMuscle.inputImage(contouredMuscle.inputImage > 1000) = 1000; - -rangeHU = double([-1000 1000]); - -% Binarization of images works so that pixel value > threshold is kept. So -% if you condier that a msucle is normal for 30 HU and abnormal for 29 HU, -% the threshold should be set at 29 HU. -normalizedThresholdMuscle = ((HUThresholdMuscle - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); -normalizedThresholdFat = ((HUThresholdFat - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); -normalizedThresholdOsteochondroma = ((HUThresholdOsteochondroma - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); - -normalizedInputImage = (double(contouredMuscle.inputImage)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); - -% Mask image using muscle fossa contours -maskedImage = normalizedInputImage; -maskedImage(~contouredMuscle.binaryImage1) = 0; - -% Plot muscle fossa contours. -structBoundaries = bwboundaries(contouredMuscle.binaryImage1); -xy=structBoundaries{1}; % Get n by 2 array of x,y coordinates. -x = xy(:, 2); % Columns. -y = xy(:, 1); % Rows. -p = plot(x, y, 'color','green','DisplayName','Muscle fossa'); -p_count = 1; - -% Segment using muscle threshold -myMuscle = imbinarize(maskedImage,normalizedThresholdMuscle); -myMuscle = imfill(myMuscle, 'holes'); % Fill holes - -% Keep only the biggest connected component (i.e. remove islands) -CC = bwconncomp(myMuscle); -numPixels = cellfun(@numel,CC.PixelIdxList); -[~,idx] = max(numPixels); -myMuscle(:,:) = 0; -myMuscle(CC.PixelIdxList{idx}) = 1; - -% Plot atrophied muscle contours -structBoundaries = bwboundaries(myMuscle); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; -end - -for i = 1:size(structBoundaries,1) - xy = structBoundaries{i}; % Get n by 2 array of x,y coordinates. - x = xy(:, 2); % Columns. - y = xy(:, 1); % Rows. - hold on; % Keep the image. - p(p_count) = plot(x, y, 'color','blue','DisplayName','Atrophied Muscle'); -end - -% --Fat infiltration segmentation-- - -% Mask image using atrophied muscle segmentation -maskedImage = normalizedInputImage; -maskedImage(~myMuscle) = 0; - -% Segment using fat threshold -myMuscleNoFat = imbinarize(maskedImage,normalizedThresholdFat); -myFat = ~myMuscleNoFat; -myFat(~myMuscle) = 0; - -% Plot fat infiltration surfaces -structBoundaries = bwboundaries(myFat); - -% The 'fill' function cannot handle hollow polygons (with holes = 2 -% boundaries). To overcome this difficulty, fat infiltration is plotted as -% a coloured overlay image with transparency zero outside fat regions and -% transparency 0.25 in fat regions. - -myFatAlpha = double(myFat); -myFatAlpha(myFat) = 0.25; -red=zeros(size(myFat,1),size(myFat,2),3); -red(:,:,1)=1; -h=imshow(red); -set(h,'AlphaData',myFatAlpha); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; - p(p_count) = fill(0, 0,'red','EdgeColor','none','FaceAlpha', 0.25,'DisplayName','Fat infiltration'); % plot a filled patch outside the plot region to add to the legend -end - -% --Osteochondroma segmentation-- - -%Segment using osteochondroma threshold -myOsteo = imbinarize(maskedImage,normalizedThresholdOsteochondroma); -myOsteo = imfill(myOsteo, 'holes'); % Fill holes - -% Plot osteochondroma surfaces -structBoundaries = bwboundaries(myOsteo); - -% The 'fill' function cannot handle hollow polygons (with holes = 2 -% boundaries). To overcome this difficulty, osteochondroma are plotted as -% a coloured overlay image with transparency zero outside osteochondroma -% and transparency 0.25 in osteochondroma regions. - -myOsteoAlpha = double(myOsteo); -myOsteoAlpha(myOsteo) = 0.5; -yellow=zeros(size(myOsteo,1),size(myOsteo,2),3); -yellow(:,:,1)=1; -yellow(:,:,2)=1; -h=imshow(yellow); -set(h,'AlphaData',myOsteoAlpha); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; - p(p_count) = fill(0, 0,'yellow','EdgeColor','none','FaceAlpha', 0.5,'DisplayName','Osteochondroma'); % plot a filled patch outside the plot region to add to the legend -end - -% --Measure atrophy and degeneration so that Stot = Sa + Sm + Si + So-- - -muscle.caseID = caseID; -muscle.Stot = bwarea(contouredMuscle.binaryImage1); % Stot: total area of the muscle fossa (manually delimited contours) -muscle.Satrophy = bwarea(contouredMuscle.binaryImage1) - bwarea(myMuscle); % Sa: area of the atrophy (Stot - Sm) -muscle.Sinfiltration = bwarea(myFat); % Si: area of fat infiltration -muscle.Sosteochondroma = bwarea(myOsteo); % So: area of osteochondroma -muscle.Sdegeneration = muscle.Stot-muscle.Satrophy-muscle.Sinfiltration-muscle.Sosteochondroma; % Sm: area of the degenerated muscle, without atrophy, fat infiltration and osteochondroma - -muscle.atrophy = muscle.Satrophy/muscle.Stot; % ratio of muscle atrophy (area atrophy over total area of muscle fossa) -muscle.infiltration = muscle.Sinfiltration/muscle.Stot; % ratio of fat infiltration in the muscle (area fat infiltration over total area of muscle fossa) -muscle.osteochondroma = muscle.Sosteochondroma/muscle.Stot; % ratio of osteochondroma in the muscle (area osteochondroma over total area of muscle fossa) -muscle.degeneration = (muscle.Stot-muscle.Sdegeneration)/muscle.Stot; % the ratio of degeneration of the muscle (sum of atrophy, fat infiltration and osteochondroma over total area of muscle fossa) - - -% --Save images-- - -title({['Case ID: ' caseID]; [muscleName ' Muscle threshold: ' sprintf('%d',HUThresholdMuscle) ' HU']; sprintf('Muscle atrophy: %.2f%%, Muscle infiltration: %.2f%%, Osteochondroma: %.2f%%', ... - muscle.atrophy*100,muscle.infiltration*100,muscle.osteochondroma*100)}, 'FontSize', fontSize); -fig = get(groot,'CurrentFigure'); -fig.OuterPosition = [-1140 -1 871 1028]; -hLegend = legend(p); -set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); -hLegend.EdgeColor = 'none'; - -% % This piece of code can be used to make the rectangle patches in the -% legend have the correct alpha value (transparency) as used in the plot. -% Unfortunately, it doesnt work in MATLAB r2017a because calling the -% 'legend' function with multiple output arguments results in a graphics -% bug and prevent the rectangular patches to display. I keep this piece of -% code here, as this bug might be solved in a future version. -% -% VMC-29-Jun-2017 -% -% % Get handles of all patch objects in current axis: -% hla = findobj(gca,'type','patch'); -% -% % Get alpha properties of the current plotted patches: -% hlaFA = get(hla,'FaceAlpha'); -% hlaFC = get(hla,'FaceColor'); -% -% % Create a legend: -% [hLegend,oLegend,~,~]= legend(p); -% set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); -% hLegend.EdgeColor = 'none'; -% -% % Get handles of patch objects in the legend: -% hlp = findobj(oLegend,'type','patch'); -% -% % Set alpha values: -% for k = 1:length(hlp) -% set(hlp(k),'facec',hlaFC(k,:)); -% set(hlp(k),'facea',hlaFA(k)); -% end - -outputFile = sprintf('%sDegeneration%s_%d',char(muscleName),char(caseID),HUThresholdMuscle); -% saveas(gcf,sprintf('%s%s.png',[muscleDirectory '/' muscleName '/'],outputFile)) - -close all - -end - - - +function muscle = muscleDegeneration(caseID,muscleName,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory) +%%MUSCLEDEGENERATION computes degeneration parameters from muscle contours +% +% This function returns muscle atrophy, fat infiltration, osteochondroma +% and degeneration values for a given muscle +% +% USE: muscleDegeneration(caseID,muscleName,HUThresholdMuscle,HUThresholdOsteochondroma,muscleDirectory) +% +% INPUT caseID: patient number +% muscleName: name of the rotator cuff muscle being measured (SS,IS, SC or TM) +% HUThresholdMuscle: HU threshold used for muscle segmentation (atrophied muscle limit) +% HUThresholdFat: HU threshold used for fat infiltration segmentation (fat-muscle limit) +% HUThresholdOsteochondroma:HU threshold used for osteochondroma segmentation (muscle-osteochondroma limit) +% muscleDirectory: Path to 'muscles' directory where contours and output images are stored +% +% OUTPUT muscle: structure containing fields 'Stot', 'Satrophy', +% 'Sinfiltration', 'Sosteochondroma' and 'Sdegeneration', +% 'atrophy', 'infiltration', 'osteochondroma' and 'degeneration' +% +% REMARKS An image of the segmentation of atrophied muscle, fat +% infiltration and osteochondroma is saved in muscles directory, +% in the 'muscleName' folder. +% +% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 +% Author: EPFL-LBO-VMC +% Date: 27-Jun-2017 +% + +inputFile = ['ssContours' caseID '.mat']; +contouredMuscle = load([muscleDirectory '\' muscleName '\' inputFile]); + +% --Plot CT slice with all contours-- + +fontSize = 14; +visualizationWindow = [-100 200]; + +imshow(contouredMuscle.inputImage,visualizationWindow); + +hold on + +% --Atrophied muscle segmentation-- + +% MATLAB does not accept to threshold an image with a negative value, as is +% the case when using HU scale. CT scanners usually have a CT range from +% -1000 HU to 1000 HU or -2000 HU to 2000 HU for modern scanners. In this +% case, we're not using HU values lower than -1000 and higher than +1000 so +% all pixel intensity values of the input image< -1000 HU are set to -1000 +% HU, and values > 1000 HU are set to 1000 HU. Then, the input image is +% mapped to the [0 1] range -1000->0 1000->1, as are the thresholds for +% muscle and osteochondroma. + +% Normalize image and thresholds + +contouredMuscle.inputImage(contouredMuscle.inputImage < -1000) = -1000; +contouredMuscle.inputImage(contouredMuscle.inputImage > 1000) = 1000; + +rangeHU = double([-1000 1000]); + +% Binarization of images works so that pixel value > threshold is kept. So +% if you condier that a msucle is normal for 30 HU and abnormal for 29 HU, +% the threshold should be set at 29 HU. +normalizedThresholdMuscle = ((HUThresholdMuscle - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); +normalizedThresholdFat = ((HUThresholdFat - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); +normalizedThresholdOsteochondroma = ((HUThresholdOsteochondroma - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); + +normalizedInputImage = (double(contouredMuscle.inputImage)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); + +% Mask image using muscle fossa contours +maskedImage = normalizedInputImage; +maskedImage(~contouredMuscle.binaryImage1) = 0; + +% Plot muscle fossa contours. +structBoundaries = bwboundaries(contouredMuscle.binaryImage1); +xy=structBoundaries{1}; % Get n by 2 array of x,y coordinates. +x = xy(:, 2); % Columns. +y = xy(:, 1); % Rows. +p = plot(x, y, 'color','green','DisplayName','Muscle fossa'); +p_count = 1; + +% Segment using muscle threshold +myMuscle = imbinarize(maskedImage,normalizedThresholdMuscle); +myMuscle = imfill(myMuscle, 'holes'); % Fill holes + +% Keep only the biggest connected component (i.e. remove islands) +CC = bwconncomp(myMuscle); +numPixels = cellfun(@numel,CC.PixelIdxList); +[~,idx] = max(numPixels); +myMuscle(:,:) = 0; +myMuscle(CC.PixelIdxList{idx}) = 1; + +% Plot atrophied muscle contours +structBoundaries = bwboundaries(myMuscle); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; +end + +for i = 1:size(structBoundaries,1) + xy = structBoundaries{i}; % Get n by 2 array of x,y coordinates. + x = xy(:, 2); % Columns. + y = xy(:, 1); % Rows. + hold on; % Keep the image. + p(p_count) = plot(x, y, 'color','blue','DisplayName','Atrophied Muscle'); +end + +% --Fat infiltration segmentation-- + +% Mask image using atrophied muscle segmentation +maskedImage = normalizedInputImage; +maskedImage(~myMuscle) = 0; + +% Segment using fat threshold +myMuscleNoFat = imbinarize(maskedImage,normalizedThresholdFat); +myFat = ~myMuscleNoFat; +myFat(~myMuscle) = 0; + +% Plot fat infiltration surfaces +structBoundaries = bwboundaries(myFat); + +% The 'fill' function cannot handle hollow polygons (with holes = 2 +% boundaries). To overcome this difficulty, fat infiltration is plotted as +% a coloured overlay image with transparency zero outside fat regions and +% transparency 0.25 in fat regions. + +myFatAlpha = double(myFat); +myFatAlpha(myFat) = 0.25; +red=zeros(size(myFat,1),size(myFat,2),3); +red(:,:,1)=1; +h=imshow(red); +set(h,'AlphaData',myFatAlpha); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; + p(p_count) = fill(0, 0,'red','EdgeColor','none','FaceAlpha', 0.25,'DisplayName','Fat infiltration'); % plot a filled patch outside the plot region to add to the legend +end + +% --Osteochondroma segmentation-- + +%Segment using osteochondroma threshold +myOsteo = imbinarize(maskedImage,normalizedThresholdOsteochondroma); +myOsteo = imfill(myOsteo, 'holes'); % Fill holes + +% Plot osteochondroma surfaces +structBoundaries = bwboundaries(myOsteo); + +% The 'fill' function cannot handle hollow polygons (with holes = 2 +% boundaries). To overcome this difficulty, osteochondroma are plotted as +% a coloured overlay image with transparency zero outside osteochondroma +% and transparency 0.25 in osteochondroma regions. + +myOsteoAlpha = double(myOsteo); +myOsteoAlpha(myOsteo) = 0.5; +yellow=zeros(size(myOsteo,1),size(myOsteo,2),3); +yellow(:,:,1)=1; +yellow(:,:,2)=1; +h=imshow(yellow); +set(h,'AlphaData',myOsteoAlpha); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; + p(p_count) = fill(0, 0,'yellow','EdgeColor','none','FaceAlpha', 0.5,'DisplayName','Osteochondroma'); % plot a filled patch outside the plot region to add to the legend +end + +% --Measure atrophy and degeneration so that Stot = Sa + Sm + Si + So-- + +muscle.caseID = caseID; +muscle.Stot = bwarea(contouredMuscle.binaryImage1); % Stot: total area of the muscle fossa (manually delimited contours) +muscle.Satrophy = bwarea(contouredMuscle.binaryImage1) - bwarea(myMuscle); % Sa: area of the atrophy (Stot - Sm) +muscle.Sinfiltration = bwarea(myFat); % Si: area of fat infiltration +muscle.Sosteochondroma = bwarea(myOsteo); % So: area of osteochondroma +muscle.Sdegeneration = muscle.Stot-muscle.Satrophy-muscle.Sinfiltration-muscle.Sosteochondroma; % Sm: area of the degenerated muscle, without atrophy, fat infiltration and osteochondroma + +muscle.atrophy = muscle.Satrophy/muscle.Stot; % ratio of muscle atrophy (area atrophy over total area of muscle fossa) +muscle.infiltration = muscle.Sinfiltration/muscle.Stot; % ratio of fat infiltration in the muscle (area fat infiltration over total area of muscle fossa) +muscle.osteochondroma = muscle.Sosteochondroma/muscle.Stot; % ratio of osteochondroma in the muscle (area osteochondroma over total area of muscle fossa) +muscle.degeneration = (muscle.Stot-muscle.Sdegeneration)/muscle.Stot; % the ratio of degeneration of the muscle (sum of atrophy, fat infiltration and osteochondroma over total area of muscle fossa) + + +% --Save images-- + +title({['Case ID: ' caseID]; [muscleName ' Muscle threshold: ' sprintf('%d',HUThresholdMuscle) ' HU']; sprintf('Muscle atrophy: %.2f%%, Muscle infiltration: %.2f%%, Osteochondroma: %.2f%%', ... + muscle.atrophy*100,muscle.infiltration*100,muscle.osteochondroma*100)}, 'FontSize', fontSize); +fig = get(groot,'CurrentFigure'); +fig.OuterPosition = [-1140 -1 871 1028]; +hLegend = legend(p); +set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); +hLegend.EdgeColor = 'none'; + +% % This piece of code can be used to make the rectangle patches in the +% legend have the correct alpha value (transparency) as used in the plot. +% Unfortunately, it doesnt work in MATLAB r2017a because calling the +% 'legend' function with multiple output arguments results in a graphics +% bug and prevent the rectangular patches to display. I keep this piece of +% code here, as this bug might be solved in a future version. +% +% VMC-29-Jun-2017 +% +% % Get handles of all patch objects in current axis: +% hla = findobj(gca,'type','patch'); +% +% % Get alpha properties of the current plotted patches: +% hlaFA = get(hla,'FaceAlpha'); +% hlaFC = get(hla,'FaceColor'); +% +% % Create a legend: +% [hLegend,oLegend,~,~]= legend(p); +% set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); +% hLegend.EdgeColor = 'none'; +% +% % Get handles of patch objects in the legend: +% hlp = findobj(oLegend,'type','patch'); +% +% % Set alpha values: +% for k = 1:length(hlp) +% set(hlp(k),'facec',hlaFC(k,:)); +% set(hlp(k),'facea',hlaFA(k)); +% end + +outputFile = sprintf('%sDegeneration%s_%d',char(muscleName),char(caseID),HUThresholdMuscle); +% saveas(gcf,sprintf('%s%s.png',[muscleDirectory '/' muscleName '/'],outputFile)) + +close all + +end + + + diff --git a/muscles/muscleDegeneration3D.m b/muscles/muscleDegeneration3D.m index b80f9b6..b874fdb 100644 --- a/muscles/muscleDegeneration3D.m +++ b/muscles/muscleDegeneration3D.m @@ -1,235 +1,235 @@ -function muscle = muscleDegeneration3D(caseID,muscleName,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory) -%%MUSCLEDEGENERATION3D computes degeneration parameters from muscle contours -% -% This function returns muscle atrophy, fat infiltration, osteochondroma -% and degeneration values computed from a CT slice for a given muscle -% -% USE: muscleDegeneration3D(caseID,muscleName,HUThresholdMuscle,HUThresholdOsteochondroma,muscleDirectory) -% -% INPUT caseID: patient number -% muscleName: name of the rotator cuff muscle being measured (SS,IS, SC or TM) -% HUThresholdMuscle: HU threshold used for muscle segmentation (atrophied muscle limit) -% HUThresholdFat: HU threshold used for fat infiltration segmentation (fat-muscle limit) -% HUThresholdOsteochondroma:HU threshold used for osteochondroma segmentation (muscle-osteochondroma limit) -% muscleDirectory: Path to 'muscles' directory where contours and output images are stored -% -% OUTPUT muscle: structure containing fields 'Stot', 'Satrophy', -% 'Sinfiltration', 'Sosteochondroma' and 'Sdegeneration', -% 'atrophy', 'infiltration', 'osteochondroma' and 'degeneration' -% -% REMARKS An image of the segmentation of atrophied muscle, fat -% infiltration and osteochondroma is saved in muscles directory, -% in the 'muscleName' folder. -% -% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 -% Author: EPFL-LBO-VMC -% Date: 29-Jun-2017 -% - -% Get slice number -segments = textscan(muscleDirectory,'%s','Delimiter','\'); -segments = segments{1}; -sliceNum = segments(end); - -% Find contours -inputFile = ['ssContours' caseID '_' sliceNum{1} '.mat']; -contouredMuscle = load([muscleDirectory '\' muscleName '\' inputFile]); - -% --Plot CT slice with all contours-- - -fontSize = 14; -visualizationWindow = [-100 200]; - -imshow(contouredMuscle.inputImage,visualizationWindow); - -hold on - -% --Atrophied muscle segmentation-- - -% MATLAB does not accept to threshold an image with a negative value, as is -% the case when using HU scale. CT scanners usually have a CT range from -% -1000 HU to 1000 HU or -2000 HU to 2000 HU for modern scanners. In this -% case, we're not using HU values lower than -1000 and higher than +1000 so -% all pixel intensity values of the input image< -1000 HU are set to -1000 -% HU, and values > 1000 HU are set to 1000 HU. Then, the input image is -% mapped to the [0 1] range -1000->0 1000->1, as are the thresholds for -% muscle and osteochondroma. - -% Normalize image and thresholds - -contouredMuscle.inputImage(contouredMuscle.inputImage < -1000) = -1000; -contouredMuscle.inputImage(contouredMuscle.inputImage > 1000) = 1000; - -rangeHU = double([-1000 1000]); - -% Binarization of images works so that pixel value > threshold is kept. So -% if you condier that a msucle is normal for 30 HU and abnormal for 29 HU, -% the threshold should be set at 29 HU. -normalizedThresholdMuscle = ((HUThresholdMuscle - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); -normalizedThresholdFat = ((HUThresholdFat - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); -normalizedThresholdOsteochondroma = ((HUThresholdOsteochondroma - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); - -normalizedInputImage = (double(contouredMuscle.inputImage)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); - -% Mask image using muscle fossa contours -maskedImage = normalizedInputImage; -maskedImage(~contouredMuscle.binaryImage1) = 0; - -% Plot muscle fossa contours. -structBoundaries = bwboundaries(contouredMuscle.binaryImage1); -xy=structBoundaries{1}; % Get n by 2 array of x,y coordinates. -x = xy(:, 2); % Columns. -y = xy(:, 1); % Rows. -p = plot(x, y, 'color','green','DisplayName','Muscle fossa'); -p_count = 1; - -% Segment using muscle threshold -myMuscle = imbinarize(maskedImage,normalizedThresholdMuscle); -myMuscle = imfill(myMuscle, 'holes'); % Fill holes - -% Keep only the biggest connected component (i.e. remove islands) -CC = bwconncomp(myMuscle); -numPixels = cellfun(@numel,CC.PixelIdxList); -[~,idx] = max(numPixels); -myMuscle(:,:) = 0; -myMuscle(CC.PixelIdxList{idx}) = 1; - -% Plot atrophied muscle contours -structBoundaries = bwboundaries(myMuscle); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; -end - -for i = 1:size(structBoundaries,1) - xy = structBoundaries{i}; % Get n by 2 array of x,y coordinates. - x = xy(:, 2); % Columns. - y = xy(:, 1); % Rows. - hold on; % Keep the image. - p(p_count) = plot(x, y, 'color','blue','DisplayName','Atrophied Muscle'); -end - -% --Fat infiltration segmentation-- - -% Mask image using atrophied muscle segmentation -maskedImage = normalizedInputImage; -maskedImage(~myMuscle) = 0; - -% Segment using fat threshold -myMuscleNoFat = imbinarize(maskedImage,normalizedThresholdFat); -myFat = ~myMuscleNoFat; -myFat(~myMuscle) = 0; - -% Plot fat infiltration surfaces -structBoundaries = bwboundaries(myFat); - -% The 'fill' function cannot handle hollow polygons (with holes = 2 -% boundaries). To overcome this difficulty, fat infiltration is plotted as -% a coloured overlay image with transparency zero outside fat regions and -% transparency 0.25 in fat regions. - -myFatAlpha = double(myFat); -myFatAlpha(myFat) = 0.25; -red=zeros(size(myFat,1),size(myFat,2),3); -red(:,:,1)=1; -h=imshow(red); -set(h,'AlphaData',myFatAlpha); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; - p(p_count) = fill(0, 0,'red','EdgeColor','none','FaceAlpha', 0.25,'DisplayName','Fat infiltration'); % plot a filled patch outside the plot region to add to the legend -end - -% --Osteochondroma segmentation-- - -%Segment using osteochondroma threshold -myOsteo = imbinarize(maskedImage,normalizedThresholdOsteochondroma); -myOsteo = imfill(myOsteo, 'holes'); % Fill holes - -% Plot osteochondroma surfaces -structBoundaries = bwboundaries(myOsteo); - -% The 'fill' function cannot handle hollow polygons (with holes = 2 -% boundaries). To overcome this difficulty, osteochondroma are plotted as -% a coloured overlay image with transparency zero outside osteochondroma -% and transparency 0.25 in osteochondroma regions. - -myOsteoAlpha = double(myOsteo); -myOsteoAlpha(myOsteo) = 0.5; -yellow=zeros(size(myOsteo,1),size(myOsteo,2),3); -yellow(:,:,1)=1; -yellow(:,:,2)=1; -h=imshow(yellow); -set(h,'AlphaData',myOsteoAlpha); - -if size(structBoundaries,1) > 0 - p_count = p_count+1; - p(p_count) = fill(0, 0,'yellow','EdgeColor','none','FaceAlpha', 0.5,'DisplayName','Osteochondroma'); % plot a filled patch outside the plot region to add to the legend -end - -% --Measure atrophy and degeneration so that Stot = Sa + Sm + Si + So-- - -muscle.SliceNumber = str2double(sliceNum); - -muscle.Stot = bwarea(contouredMuscle.binaryImage1); % Stot: total area of the muscle fossa (manually delimited contours) -muscle.Satrophy = bwarea(contouredMuscle.binaryImage1) - bwarea(myMuscle); % Sa: area of the atrophy (Stot - Sm) -muscle.Sinfiltration = bwarea(myFat); % Si: area of fat infiltration -muscle.Sosteochondroma = bwarea(myOsteo); % So: area of osteochondroma -muscle.Sdegeneration = muscle.Stot-muscle.Satrophy-muscle.Sinfiltration-muscle.Sosteochondroma; % Sm: area of the degenerated muscle, without atrophy, fat infiltration and osteochondroma - -muscle.atrophy = muscle.Satrophy/muscle.Stot; % ratio of muscle atrophy (area atrophy over total area of muscle fossa) -muscle.infiltration = muscle.Sinfiltration/muscle.Stot; % ratio of fat infiltration in the muscle (area fat infiltration over total area of muscle fossa) -muscle.osteochondroma = muscle.Sosteochondroma/muscle.Stot; % ratio of osteochondroma in the muscle (area osteochondroma over total area of muscle fossa) -muscle.degeneration = (muscle.Stot-muscle.Sdegeneration)/muscle.Stot; % the ratio of degeneration of the muscle (sum of atrophy, fat infiltration and osteochondroma over total area of muscle fossa) - - -% --Save images-- - -title({['Case ID: ' caseID ', Slice #: ' sliceNum{1}]; [muscleName ' Muscle threshold: ' sprintf('%d',HUThresholdMuscle) ' HU']; sprintf('Muscle atrophy: %.2f%%, Muscle infiltration: %.2f%%, Osteochondroma: %.2f%%', ... - muscle.atrophy*100,muscle.infiltration*100,muscle.osteochondroma*100)}, 'FontSize', fontSize); -fig = get(groot,'CurrentFigure'); -fig.OuterPosition = [-1140 -1 871 1028]; -hLegend = legend(p); -set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); -hLegend.EdgeColor = 'none'; - -% % This piece of code can be used to make the rectangle patches in the -% legend have the correct alpha value (transparency) as used in the plot. -% Unfortunately, it doesnt work in MATLAB r2017a because calling the -% 'legend' function with multiple output arguments results in a graphics -% bug and prevent the rectangular patches to display. I keep this piece of -% code here, as this bug might be solved in a future version. -% -% VMC-29-Jun-2017 -% -% % Get handles of all patch objects in current axis: -% hla = findobj(gca,'type','patch'); -% -% % Get alpha properties of the current plotted patches: -% hlaFA = get(hla,'FaceAlpha'); -% hlaFC = get(hla,'FaceColor'); -% -% % Create a legend: -% [hLegend,oLegend,~,~]= legend(p); -% set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); -% hLegend.EdgeColor = 'none'; -% -% % Get handles of patch objects in the legend: -% hlp = findobj(oLegend,'type','patch'); -% -% % Set alpha values: -% for k = 1:length(hlp) -% set(hlp(k),'facec',hlaFC(k,:)); -% set(hlp(k),'facea',hlaFA(k)); -% end - -sliceNum = sprintf(num2str(str2double(sliceNum{1}),'%03i')); -outputFile = sprintf('%sDegeneration%s_%s',muscleName,caseID,sliceNum); -saveas(gcf,sprintf('%s%s.png',[muscleDirectory '\'],outputFile)); - -close all - -end - - - +function muscle = muscleDegeneration3D(caseID,muscleName,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma,muscleDirectory) +%%MUSCLEDEGENERATION3D computes degeneration parameters from muscle contours +% +% This function returns muscle atrophy, fat infiltration, osteochondroma +% and degeneration values computed from a CT slice for a given muscle +% +% USE: muscleDegeneration3D(caseID,muscleName,HUThresholdMuscle,HUThresholdOsteochondroma,muscleDirectory) +% +% INPUT caseID: patient number +% muscleName: name of the rotator cuff muscle being measured (SS,IS, SC or TM) +% HUThresholdMuscle: HU threshold used for muscle segmentation (atrophied muscle limit) +% HUThresholdFat: HU threshold used for fat infiltration segmentation (fat-muscle limit) +% HUThresholdOsteochondroma:HU threshold used for osteochondroma segmentation (muscle-osteochondroma limit) +% muscleDirectory: Path to 'muscles' directory where contours and output images are stored +% +% OUTPUT muscle: structure containing fields 'Stot', 'Satrophy', +% 'Sinfiltration', 'Sosteochondroma' and 'Sdegeneration', +% 'atrophy', 'infiltration', 'osteochondroma' and 'degeneration' +% +% REMARKS An image of the segmentation of atrophied muscle, fat +% infiltration and osteochondroma is saved in muscles directory, +% in the 'muscleName' folder. +% +% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 +% Author: EPFL-LBO-VMC +% Date: 29-Jun-2017 +% + +% Get slice number +segments = textscan(muscleDirectory,'%s','Delimiter','\'); +segments = segments{1}; +sliceNum = segments(end); + +% Find contours +inputFile = ['ssContours' caseID '_' sliceNum{1} '.mat']; +contouredMuscle = load([muscleDirectory '\' muscleName '\' inputFile]); + +% --Plot CT slice with all contours-- + +fontSize = 14; +visualizationWindow = [-100 200]; + +imshow(contouredMuscle.inputImage,visualizationWindow); + +hold on + +% --Atrophied muscle segmentation-- + +% MATLAB does not accept to threshold an image with a negative value, as is +% the case when using HU scale. CT scanners usually have a CT range from +% -1000 HU to 1000 HU or -2000 HU to 2000 HU for modern scanners. In this +% case, we're not using HU values lower than -1000 and higher than +1000 so +% all pixel intensity values of the input image< -1000 HU are set to -1000 +% HU, and values > 1000 HU are set to 1000 HU. Then, the input image is +% mapped to the [0 1] range -1000->0 1000->1, as are the thresholds for +% muscle and osteochondroma. + +% Normalize image and thresholds + +contouredMuscle.inputImage(contouredMuscle.inputImage < -1000) = -1000; +contouredMuscle.inputImage(contouredMuscle.inputImage > 1000) = 1000; + +rangeHU = double([-1000 1000]); + +% Binarization of images works so that pixel value > threshold is kept. So +% if you condier that a msucle is normal for 30 HU and abnormal for 29 HU, +% the threshold should be set at 29 HU. +normalizedThresholdMuscle = ((HUThresholdMuscle - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); +normalizedThresholdFat = ((HUThresholdFat - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); +normalizedThresholdOsteochondroma = ((HUThresholdOsteochondroma - 1)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); + +normalizedInputImage = (double(contouredMuscle.inputImage)-rangeHU(1))/(rangeHU(2)-rangeHU(1)); + +% Mask image using muscle fossa contours +maskedImage = normalizedInputImage; +maskedImage(~contouredMuscle.binaryImage1) = 0; + +% Plot muscle fossa contours. +structBoundaries = bwboundaries(contouredMuscle.binaryImage1); +xy=structBoundaries{1}; % Get n by 2 array of x,y coordinates. +x = xy(:, 2); % Columns. +y = xy(:, 1); % Rows. +p = plot(x, y, 'color','green','DisplayName','Muscle fossa'); +p_count = 1; + +% Segment using muscle threshold +myMuscle = imbinarize(maskedImage,normalizedThresholdMuscle); +myMuscle = imfill(myMuscle, 'holes'); % Fill holes + +% Keep only the biggest connected component (i.e. remove islands) +CC = bwconncomp(myMuscle); +numPixels = cellfun(@numel,CC.PixelIdxList); +[~,idx] = max(numPixels); +myMuscle(:,:) = 0; +myMuscle(CC.PixelIdxList{idx}) = 1; + +% Plot atrophied muscle contours +structBoundaries = bwboundaries(myMuscle); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; +end + +for i = 1:size(structBoundaries,1) + xy = structBoundaries{i}; % Get n by 2 array of x,y coordinates. + x = xy(:, 2); % Columns. + y = xy(:, 1); % Rows. + hold on; % Keep the image. + p(p_count) = plot(x, y, 'color','blue','DisplayName','Atrophied Muscle'); +end + +% --Fat infiltration segmentation-- + +% Mask image using atrophied muscle segmentation +maskedImage = normalizedInputImage; +maskedImage(~myMuscle) = 0; + +% Segment using fat threshold +myMuscleNoFat = imbinarize(maskedImage,normalizedThresholdFat); +myFat = ~myMuscleNoFat; +myFat(~myMuscle) = 0; + +% Plot fat infiltration surfaces +structBoundaries = bwboundaries(myFat); + +% The 'fill' function cannot handle hollow polygons (with holes = 2 +% boundaries). To overcome this difficulty, fat infiltration is plotted as +% a coloured overlay image with transparency zero outside fat regions and +% transparency 0.25 in fat regions. + +myFatAlpha = double(myFat); +myFatAlpha(myFat) = 0.25; +red=zeros(size(myFat,1),size(myFat,2),3); +red(:,:,1)=1; +h=imshow(red); +set(h,'AlphaData',myFatAlpha); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; + p(p_count) = fill(0, 0,'red','EdgeColor','none','FaceAlpha', 0.25,'DisplayName','Fat infiltration'); % plot a filled patch outside the plot region to add to the legend +end + +% --Osteochondroma segmentation-- + +%Segment using osteochondroma threshold +myOsteo = imbinarize(maskedImage,normalizedThresholdOsteochondroma); +myOsteo = imfill(myOsteo, 'holes'); % Fill holes + +% Plot osteochondroma surfaces +structBoundaries = bwboundaries(myOsteo); + +% The 'fill' function cannot handle hollow polygons (with holes = 2 +% boundaries). To overcome this difficulty, osteochondroma are plotted as +% a coloured overlay image with transparency zero outside osteochondroma +% and transparency 0.25 in osteochondroma regions. + +myOsteoAlpha = double(myOsteo); +myOsteoAlpha(myOsteo) = 0.5; +yellow=zeros(size(myOsteo,1),size(myOsteo,2),3); +yellow(:,:,1)=1; +yellow(:,:,2)=1; +h=imshow(yellow); +set(h,'AlphaData',myOsteoAlpha); + +if size(structBoundaries,1) > 0 + p_count = p_count+1; + p(p_count) = fill(0, 0,'yellow','EdgeColor','none','FaceAlpha', 0.5,'DisplayName','Osteochondroma'); % plot a filled patch outside the plot region to add to the legend +end + +% --Measure atrophy and degeneration so that Stot = Sa + Sm + Si + So-- + +muscle.SliceNumber = str2double(sliceNum); + +muscle.Stot = bwarea(contouredMuscle.binaryImage1); % Stot: total area of the muscle fossa (manually delimited contours) +muscle.Satrophy = bwarea(contouredMuscle.binaryImage1) - bwarea(myMuscle); % Sa: area of the atrophy (Stot - Sm) +muscle.Sinfiltration = bwarea(myFat); % Si: area of fat infiltration +muscle.Sosteochondroma = bwarea(myOsteo); % So: area of osteochondroma +muscle.Sdegeneration = muscle.Stot-muscle.Satrophy-muscle.Sinfiltration-muscle.Sosteochondroma; % Sm: area of the degenerated muscle, without atrophy, fat infiltration and osteochondroma + +muscle.atrophy = muscle.Satrophy/muscle.Stot; % ratio of muscle atrophy (area atrophy over total area of muscle fossa) +muscle.infiltration = muscle.Sinfiltration/muscle.Stot; % ratio of fat infiltration in the muscle (area fat infiltration over total area of muscle fossa) +muscle.osteochondroma = muscle.Sosteochondroma/muscle.Stot; % ratio of osteochondroma in the muscle (area osteochondroma over total area of muscle fossa) +muscle.degeneration = (muscle.Stot-muscle.Sdegeneration)/muscle.Stot; % the ratio of degeneration of the muscle (sum of atrophy, fat infiltration and osteochondroma over total area of muscle fossa) + + +% --Save images-- + +title({['Case ID: ' caseID ', Slice #: ' sliceNum{1}]; [muscleName ' Muscle threshold: ' sprintf('%d',HUThresholdMuscle) ' HU']; sprintf('Muscle atrophy: %.2f%%, Muscle infiltration: %.2f%%, Osteochondroma: %.2f%%', ... + muscle.atrophy*100,muscle.infiltration*100,muscle.osteochondroma*100)}, 'FontSize', fontSize); +fig = get(groot,'CurrentFigure'); +fig.OuterPosition = [-1140 -1 871 1028]; +hLegend = legend(p); +set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); +hLegend.EdgeColor = 'none'; + +% % This piece of code can be used to make the rectangle patches in the +% legend have the correct alpha value (transparency) as used in the plot. +% Unfortunately, it doesnt work in MATLAB r2017a because calling the +% 'legend' function with multiple output arguments results in a graphics +% bug and prevent the rectangular patches to display. I keep this piece of +% code here, as this bug might be solved in a future version. +% +% VMC-29-Jun-2017 +% +% % Get handles of all patch objects in current axis: +% hla = findobj(gca,'type','patch'); +% +% % Get alpha properties of the current plotted patches: +% hlaFA = get(hla,'FaceAlpha'); +% hlaFC = get(hla,'FaceColor'); +% +% % Create a legend: +% [hLegend,oLegend,~,~]= legend(p); +% set(hLegend.BoxFace, 'ColorType','truecoloralpha', 'ColorData',uint8(255*[.8;.8;.8;.8])); +% hLegend.EdgeColor = 'none'; +% +% % Get handles of patch objects in the legend: +% hlp = findobj(oLegend,'type','patch'); +% +% % Set alpha values: +% for k = 1:length(hlp) +% set(hlp(k),'facec',hlaFC(k,:)); +% set(hlp(k),'facea',hlaFA(k)); +% end + +sliceNum = sprintf(num2str(str2double(sliceNum{1}),'%03i')); +outputFile = sprintf('%sDegeneration%s_%s',muscleName,caseID,sliceNum); +saveas(gcf,sprintf('%s%s.png',[muscleDirectory '\'],outputFile)); + +close all + +end + + + diff --git a/muscles/rebuildDatabaseMuscleMeasurements.m b/muscles/rebuildDatabaseMuscleMeasurements.m index 4d339dc..1006673 100644 --- a/muscles/rebuildDatabaseMuscleMeasurements.m +++ b/muscles/rebuildDatabaseMuscleMeasurements.m @@ -1,25 +1,25 @@ -% This script builds a cell array listing all caseIDs in the CT database -% for which an Amira folder exists and scapula measurements can be -% computed. Then computes scapula anatomical data for these cases. - -CTDatabaseLocation = 'Z://data'; % Location of the CT database -CaseType = ['P';'N']; % Folders to be reconstructed - -CaseIDsList = cell(0,0); - -for i=1:length(CaseType) %Loop - for j=0:9 - for k=0:9 - listDirInCurrDir = dir([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' CaseType(i) '*']); - if (~isempty(listDirInCurrDir)) - for m=1:length(listDirInCurrDir) - if exist([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' listDirInCurrDir(m).name '/CT-' listDirInCurrDir(m).name '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the case ID is added to the list - CaseIDsList{end+1,1} = strtok(listDirInCurrDir(m).name,'-'); - end - end - end - end - end -end - +% This script builds a cell array listing all caseIDs in the CT database +% for which an Amira folder exists and scapula measurements can be +% computed. Then computes scapula anatomical data for these cases. + +CTDatabaseLocation = 'Z://data'; % Location of the CT database +CaseType = ['P';'N']; % Folders to be reconstructed + +CaseIDsList = cell(0,0); + +for i=1:length(CaseType) %Loop + for j=0:9 + for k=0:9 + listDirInCurrDir = dir([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' CaseType(i) '*']); + if (~isempty(listDirInCurrDir)) + for m=1:length(listDirInCurrDir) + if exist([CTDatabaseLocation '/' CaseType(i) '/' int2str(j) '/' int2str(k) '/' listDirInCurrDir(m).name '/CT-' listDirInCurrDir(m).name '-1/muscles'],'dir') == 7 %if a folder "muscles" exist, the case ID is added to the list + CaseIDsList{end+1,1} = strtok(listDirInCurrDir(m).name,'-'); + end + end + end + end + end +end + degenerationAll(CaseIDsList,[{'IS'};{'SC'};{'SS'};{'TM'}]); \ No newline at end of file