diff --git a/ImportSCase/README.md b/ImportSCase/README.md index 3c92272..cb08db9 100644 --- a/ImportSCase/README.md +++ b/ImportSCase/README.md @@ -1,272 +1,14 @@ -# Protocol: import a case into the shoulder database -This document describes the protocol to add a case (set of CT scans) into the shoulder database. After the preliminary notes below, the protocol is described in two phases: 1) Copy CT scans in pending directory, 2) Import CT scans from pending directory. Finally, an alternative protocol is described at the end of the document. - - -## Preliminary notes -* Data - * They are in *lbovenus.epfl.ch:/home/shoulder/data/*. - * This directory should be mounted locally. - * Each entry of the database is a shoulder case (***SCase***) associated to a patient. - * Each SCase is uniquely identified by a SCaseID. - * There are two SCase types or groups: normal (N) and pathologic (P). - * In the pathologic group, each SCase corresponds to the treatment of the pathology of a shoulder. Since patients may have been operated on both shoulders, possibly twice on the same shoulder, the same patient might be associated up to four SCases. Besides, a SCase may be associated with several CT scans, for the same patient and shoulder. - * Patients of the normal group appear only once, but may have both shoulders in the CT scans, and by definition, have no pathology. They usually have a trauma elsewhere. - * The import process consists in importing CT scans into a SCase, which might be created, or updated - * CT scans are coded with DICOM (Digital Imaging and Communications in Medicine) format, which is a worldwide-accepted standard for medical images exchange, managed by the Medical Imaging & Technology Alliance – a division of the National Electrical Manufacturers Association (*https://www.dicomstandard.org/current/*). - * After importation, CT scans are classify as follows: - - Group of patients: - - **Normal (N)**: CTs from patients without shoulder pathology. Those CT scans are often acquired in order to find multiple traumas after an accident, so they might contain a much larger portion of the patient body than the area of interest for this database. - - **Pathological (P)**: CTs from patients with shoulder pathologies. Those CT scans are aimed to analyze the shoulder anatomy and can contain several CT sets with different acquisition parameters. - - Acquisition stage: - - **Preop** CT sets acquired before the shoulder joint replacement surgery either for diagnostic or surgery planning purposes. Every CT set without prostheses in the shoulder of interest must be classified as *preop*. - - **Postop** The CT sets acquired after the surgery aiming to validate the prostheses placement (just after surgery) or checking their state (some time after the surgery). This means that every CT with a shoulder prostheses on the shoulder of interest should be initially considered as *postop* (unless the responsible clinician informs explicitly about the eventual case of a prostheses replacement). - - Body part: - - **Shoulder** is the tag for every CT containing (at least) the shoulder of interest. - - **Elbow** is the tag for CTs containing elbow without shoulder. - - **Contralateral** or *Opposed Shoulder* is the tag for CTs with the opposed shoulder to the shoulder of interest. Previous identification of shoulder of interest is necesary. - - **Thorax** is the tag for the CTs containing the patient thorax without any complete shoulder. - - **Other** is the tag for any other body without any complete shoulder. - - Phantoms (**with** or **without**): - - These are samples of synthetic bone with accurate and homogeneous mineral density and cylindrical shape. In the transversal plane, they can be identified as circles with different intensities corresponding to the different mineral densities. Phantoms are typically placed behind the back of the patient and are necessary to calibrate the measurement of the bone mineral density. At least 2 phantoms are needed for an accurate calibration. - - The following table summarises CT classification adding a **CT series label** as a numerical value for each enumerated class (column "#") - - - | Acquisition | Body | Phantoms | Kernel | # | - | - | - | - | - | - | - | Preop | Shoulder | Without | Sharp | 1 | - | Preop | Shoulder | Without | Smooth | 2 | - | Preop | Shoulder | With | Sharp | 3 | - | Preop | Shoulder | With | Smooth | 4 | - | Preop | Elbow | - | Sharp | 5 | - | Preop | Elbow | - | Smooth | 6 | - | Preop | Thorax | - | Sharp | 7 | - | Preop | Thorax | - | Smooth | 8 | - | Preop | Opposed Shoulder | - | Sharp | 9 | - | Preop | Opposed Shoulder | - | Smooth | 10 | - | Preop | Other | - | | 11 | - | - | - | - | - | - | - | Postop | Shoulder | Without | Sharp | p1 | - | Postop | Shoulder | Without | Smooth | p2 | - | Postop | Shoulder | With | Sharp | p3 | - | Postop | Shoulder | With | Smooth | p4 | - | Postop | Elbow | - | Sharp | p5 | - | Postop | Elbow | - | Smooth | p6 | - | Postop | Thorax | - | Sharp | p7 | - | Postop | Thorax | - | Smooth | p8 | - | Postop | Opposed Shoulder | - | Sharp | p9 | - | Postop | Opposed Shoulder | - | Smooth | p10 | - | Postop | Other | - | | p11 | - - - -* Scripts - * Can be executed locally - * Require the graphical interface (GUI) - * They are in *lbovenus.epfl.ch:/home/shoulder/methods/matlab/database/* - * They can be pulled from remote git repository *https://c4science.ch/diffusion/8218/shoulderdb_importsegmentmeasureanalyze.git* - * The import script is located in the sub-directory *ImportSCase* and should be executed from there. - * Each patient folder pending to be imported should be initially considered as a different SCase. When more than one set of CTs are present for a single SCase, we might find different types of CT images: scout-scans, shoulder CTs, elbow CTs, thorax CTs, opposed shoulder CTs, etc. Not all of them must be imported, however this will be explained later in this document (more information in section *CT types classification*). - * The anonymization is performed during importation (to be modified before 2020). This consists in removing some information from the metadata of the DICOM files that can identify the patient, such as its name or birth date. This can be automatically done by the Matlab script (recommended) or manually with Amira. - * After importation, the script *dicominfoSCase.m* should be executed to update the Excel files CTdicomInfo.csv and CTdicomInfo.xls, located in ***shoulder/method/database/***. File CTdicomInfo.xls is linked to the main Excel file ***ShoulderDataBase.xlsx***. - - This is now executed when bottom **"Add new SCase to Excell Database"** is used in *step15* of the Matlab script protocol (section 2. Import CT scan from pending directory) - - -## Protocol -### 1. Copy CT scans in pending directory -1. Extract the archive (.zip) file sent from the responsible clinician at CHUV. Inside the unzipped folder, each patient folder, is named with the *Patient Permanent Identifier*, **IPP** (*Identifiant Permanent du Patient*), and contains at least one sub-folder with a CT reconstruction set of DICOM images.These DICOM files are CT scans that we will be imported. -2. For normal SCase, save the unzipped folder in the pending directory: - * */data/N/pending/N-update-[YYYY-MM-DD]*, where [YYYY-MM-DD] is the date of zipped archive reception -3. For a pathologic SCase, save the unzipped folder in the pending directory: - */data/P/pending/TSA-update-[YYYY-MM-DD]*, where [YYYY-MM-DD] is the date of zipped archive reception - - Example: */data/P/pending/TSA-update-2018-07-26* - -### 2. Import CT scan from pending directory -In this subsection, we will refer to the numbered paragraphs as *step1*, *step2*, etc. -1. Run the Matlab function ***importSCase.m*** located in *shoulder/methods/matlab/database/ImportSCases* - - -2. When starting the importation of the next pending directory (first CT), click on button ***Select Pending Directory*** and select the patient folder of your interest. Otherwise, if the next CT is from a previously started pending patient directory, you might skip this step. - - IMPORTANT: select the folder that contains the sub-folders with the CTs. For example: - - The pending patient directory *lbovenus.epfl.ch:/shoulder/data/P/pending/TSA-update-2017-08-18/97212* contains: - - *DICOMDIR* (file) - - *LOCKFILE* (file) - - *VERSION* (file) - - *24NZUR5U* **(Folder to be selected in this step)** - - This folder contains the *sub-folders* *CO1ALS1N*, *CO02LW5N*, *FFNFXVOY*, *FMNFXVOY* and *RMEJ0LXY*, each one of which contains a CT series. - - Information: This step... - - ... finds the number of slices present in each of the CTs of the new patient. - - ... adds a prefix to the CTs sub-folders with the number of slices contained so they can be prioritized depending in the CT size. - - ... adds a suffix that help discriminate between potential shoulder CTs, elbow CTs or scout CTs. - - IMPORTANT: This is also necessary to identify the main *data* directory, where the imported CTs will be stored. And at the same time the *Patient Group* (Pathologic or Normal) will be identified as well. Hence, if the user wants to change the data directory (e.g. from *data* to *dataDev*) this *step 2* mast be performed again. - -3. Click on ***Select CT Scan*** to load the next CT set. - - Navigate inside the patient folder until finding the set of sub-folders that contain the CTs. If you followed the *step2*, these sub-folders have now prefixes indicating the number of CT slices inside. Start loading the largest CT (usually *shoulders* are in the largest CTs) and continue in decreasing order. - - Select all DICOM files (not file “VERSION”) in the folder and click on button *Open*. By selecting only one DICOM file, the script will automatically select all of them. - - Two bar-progress windows will be prompted showing the loading progress. Don't close them before the process is finished, because this will stop the loading and you will need to start again this *step3*. - - The CT slices will appear in main viewer (2D) and (if *3D visualization* is activated) a 3D rendering will be shown in the 3D viewer as well. - - The information box above the 2D viewer shows the current data directory and the full-name of the slice shown in the 2D viewer. - - During loading, some types of errors might appear: - - Window prompted with *Error identifier:* ***heterogenousStructAssignment***: - - This means that at least one of the selected slices has a different format, so they cannot be loaded as a single CT set. - - Try to identify the problematic images and then load again the CT set but skipping those files: - - Start by checking in which image the loading progress bar has stopped, because this must be the file producing the error. - - Load the rest of the DICOM images by selecting all of them except the problematic one. - - If the problem is still present it could be caused by several DICOM files with different format. Hence, repeat the process a few times to avoid all the problematic files. - - Alternatively, open the full CT set with another software like Amira, which is able to differentiate the problematic slices form most of the cases. - - Then, saving the set of correct images in DICOM format within a different folder should allow further loading of the new set without the *heterogeneous error*. - - This process might divide the CT set in two or more parts (when the problematic file is neither the first nor the last). - - If Amira shows a similar error (**Non-uniform coordinates not supported**) when trying to save the slices into DICOM files, we cannot import the images. The clinician from CHUV will have to be required to check the original CT scan and send them. - - Do not import unusual CT image formats (atypical orientation, slices from a 3D rendering, images with superposed grid or measurements, etc). - - Some of these unusual formats are treated by the script in such a way that the containing folder will be automatically renamed adding the suffix *wrongOrientation*. The user just needs to avoid these sets and continue loading the next one. - - If the Matlab script cannot read the DICOM file metadata - - The alternative is to import CT with **Amira (described later in this document)**. - - -4. (optional) If needed, activate the 3D viewer. - - The 3D visualization is useful for shoulder side identification of Pathological series, but rarely needed in Normal patient because Normal CTs usually contain both shoulders. - - WARNING: when the CT are very large (over 400 slices) and the computer has a limited memory the loading might slow down or even stop the script. - - When activated it shows a *volume rendering* of the CTs. - - The 3D viewer can be activated later, if necessary. - - -5. Select ***Left***, ***Right*** or ***Both*** in the radio-buttons group *Shoulder Side*. - - The side selected must match with the *shoulder of interest* and not with the *contralateral shoulder* if this is present. (For more information read *Preliminary notes* and *CT types classification* above in the current document) - - For Normal CTs when both shoulders are present (and complete) in normal resting position, select *Both* shoulders. - - **Exception**: sometimes the clinician add **_R** or **_L** to the pending folder name, as an indication of the shoulder side of interest. In those cases, select the *Right* or *Left* side accordingly with this indication: - - *_R* -> select *Right* - - *_L* -> select *Left* - - -6. Select ***Preop*** or ***Postop*** in the radio-buttons group *Acquisition stage*. For more information read the section *Preliminary notes* at the beginning of this document. - - -7. Click in button ***Check Database*** to start the database checking for IPP coincidences. - - A window might be prompted asking you to select the Excel file containing the database, if the main *data* directory was not correctly indicated before (*step2*). This is *ShoulderDataBase.xlsx*, located in *lbovenus.epfl.ch\data\Excel* (or *lbovenus.epfl.ch\dataDev\Excel* for development purposes). - - The checking results will be shown in the *DB check result* information box. - - Depending on the checking results, the correct *SCaseID* will be suggested. But if any problem rises, you might always perform a manual selection of the *SCaseID*, as described in the next *step9*. - - If the patient is already present in the database, the script will detect it and will shows some details of the existing SCases in the *DB check result* information box. In addition, a prompted question dialog box will ask the user to select between the existing SCases and a new one. - - -8. (optional) If the *Suggested SCaseID* does not match with your expectations, you might choose manually the output directory by clicking in button ***Manual SCase***. - - Example: *shoulder/data/P/5/5* - - -9. Verify the *Parent output directory* was updated correctly or modify it manually if needed. - - Example: *lbovenus.epfl.ch:/shoulder/data/P/5/5* - -10. Choose between the different possibilities in the radio-buttons group *Body part*: - - **Shoulder** must be chosen when at least one complete shoulder is present in the CT reconstruction. - - **Elbow** must be chosen when the CT contains at least one elbow without any complete shoulder. - - **Thorax** must be selected only when the main body in the CT is the *thorax* and no shoulder is present or at least not complete. - - **Contralateral** must be selected when the only complete shoulder present in the CT is not the *shoulder of interest*. - - For more information read the section *Preliminary notes* at the beginning of this document. - - -11. In *CT Kernel (image filter)*, select ***Sharp (bone)*** when the 2D viewer shows a *sand like* texture for the CT slices or ***Smooth (soft tissue)*** the image looks soft and smooth. - - -12. Select ***With*** or ***Without*** in the radio-buttons group *Phantoms*. For more information read the section *Preliminary notes* at the beginning of this document. - - -13. (optional) When a SCase contains many CT sets, it might be very useful adding some extra information to the *README* file, so the CTs can be differentiated easily. As examples: - - When both shoulders are present and complete --> add *both shoulders* - - When several CT series can be classified similarly in terms of body part or CT Kernel, it might be useful adding other CT data. - - This is also the place where the operator might add any comments. - - -14. Check the **Suggested CT classification label**: - - It should be coherent with yours selections (radio-buttons) and the table showed in the section *Preliminary notes* at the beginning of this document. If not: - - Click again in all the selected radio-buttons. - - or write manually the correct label numbered - - If you are importing several CTs with similar classification parameters, the suggested label will be the same and consequently wrong. - - choose a number bigger than 10 to avoid the preselected labels and write manually the correct label numbered. - - we suggest to simply add *10*, *20*, *30*, *...* to the suggested label, so at least, the less significant digit will keep the classification code introduced in the table showed in the section *Preliminary notes* at the beginning of this document. - - -14. The text field *Parent output directory* shows the path where importation process will create a folder the current SCase. Each *parent directory* contains 10 folders with 10 different SCases. The parent directory path matches the SCase_ID until its 3rd character in a directory. The 4th and last character of the *SCase:ID* identifies each one of these 10n folders. - - If the imported CT is meant to be the first CT of a new SCase in the database, the importation process will create a new folder in the directory indicated in the text field *Parent output directory* followed by the SCase identification number indicated in *Suggested SCaseID* and the IPP found in the DICOM metadata (shown in the field *IPP*, in the *DICOM Metadata* box). Example: *shoulder/data/P/5/5/P555-918819*. - - Inside the SCase folder, a new sub-folder will be created to store the imported CT images: - - The name of this sub-folder contains the SCaseID, the IPP and the CT set classification numeric label. Example: ***CT-[SCaseID]-[IPP]-#*** --> ***CT-P555-229341-1*** - - A new line will be added to a *README.txt* file per each SCase folder. This line must contains the name of the new folder followed by some information like the *kernel*, the presence of phantoms, the main body in the CT, etc. - - If the CT set label already exists in the SCase folder, then a new folder will be automatically created with *"xxx"* as CT set label. Consequently, the CT series label will need to be updated manually. And the corresponding line in the *README.txt* file will need to be corrected (also manually). - - The anonymized DICOM images will be finally stored inside the directory like ***shoulder/data/P/5/5/P555-918819/CT-P555-229341-1/dicom***. - - Check the *Parent output directory* to verify the desired path. Example: *shoulder/data/P/5/5* - - Check in the *Readme line* below the 2D viewer that all the options selected are correct and click on button **Import to Database**. - - The process might take up to 2 minutes, depending on the number of slices to be imported. The progress is shown at the right of the button and when the importation ends a message will say **CT imported**. - - - -15. If the recently imported CT set was a new SCase, the Excel Database need to be updated. -In that case, click on button ***Add new SCase to the Excel DB***. This automatically updates the files *SCaseImport.xlsx*, *CTdicomInfo.csv* and *CTdicomInfo.xls* (through the script *dicominfoSCase.m*), which are read by the main Excel Database. - - Open the file ***ShoulderDataBase.xlsx***, located in *//lbovenus.epfl.ch/shoulder/data/Excel/* (or *//lbovenus.epfl.ch/shoulder/dataDev/Excel/* for development purposes). - - Check that the new SCase was correctly added as a new line at the bottom. - - Check the following parameters: - - IPP - - Patient Birthday - - Patient Gender - - Patient Initials - - Shoulder side - - CT date - - IMPORTANT: Never modify this file (***ShoulderDataBase.xlsx***). In the contrary, if some correction must be done in any of those parameters: - - Close *ShoulderDataBase.xlsx* without saving. - - Open *SCaseImport.xlsx*, located in *//lbovenus.epfl.ch/shoulder/data/Excel/xlsFromMatlab/* (or *//lbovenus.epfl.ch/shoulder/dataDev/Excel/xlsFromMatlab/* for development purposes). - - Do the corrections needed, save and close. - - Check again the main Excel file (*ShoulderDataBase.xlsx*). - - If the main Excel file looks correct, save and close. - - -16. If the patient pending folder contains more CT sets to import, continue from *step3*. Otherwise, start a new patient folder and continue from *step2*. - - -## Alternative import protocol using AMIRA -### !! Might be simplified by just saving in a correct format, usable by matlab !! --> already included above ? - - When the CT has a non-uniform resolution, the (above) Matlab protocol may not work, but can be replaced by the following Amira protocol. - -1. Start Amira and click on *Open Data…* (Ctrl + O). Navigate to *shoulder/data/[X]/pending/[IPP]*. Inside the CT folder, there might be several reconstruction folders. - - -2. Load the images of the CT folder that you could not import with the Matlab script - - Select all files except the file named *VERSION* (if it exists). - - -3. Check the parameter *series description*, in the prompted window, to know the kernel used for this set in *series description*. Usually it is *OS* for **Sharp** and *STD* for **Smooth**. Click on OK. - - -4. Attach an *Ortho Slice* or *Slice* display object to the newly added green data modules to visualize the content of the image stacks. - - -5. Open Excel shoulder database *shoulder/data/Excel/ShoulderDatabase.xlsx* database (*SCase* tab) and compare the IPP, CT date, shoulder side and DICOM information to determine if the new case is already in the database. - - If the IPP already exits in the database: - - Find the *Readme* file in the SCase folder of the existing SCase and compare the data inside with the metadata of the current CT to decide whether the current CT set already exist in the SCase folder or it have to be imported. - - If it does not exist continue with *step6*. - - -6. Right-click on the first data module and click on *Export Data As…* and choose type *DICOM*. - - -7. Go to the correct folder *shoulder/data/[X]/[#1]/[#2]/[CaseID]-[IPP]* , where *#1* and *#2* are the first two digits following ‘P’ or ‘N’ in the current SCaseID. For example: *shoulder/data/P/0/1* for SCase *P13*. If folder [CaseID]-[IPP] does not exist yet, create it. Then create the folder *CT-[CaseID]-[IPP]–#* and inside this folder, create a folder *dicom*. - - -8. Save the series with the name *[CaseID]-[IPP]_[PatientInitials]-[bone/smooth].0*, e.g. *P123-456789_XY.0*. - - -9. In the next window, replace the patient’s name by *[CaseID]_[IPP]–[PatientInitials]*. - - -10. If the error: *Non-uniform coordinates not supported* appears, you will need to convert your data to uniform coordinates before exporting to DICOM. - - To do the conversion, attach an *Arithmetic* module to the data object. Select *regular* for the result type. - - Specify the resolution (ex: 512x512x267) similar to the original data. - - In the *Expression field* put A (the input data object). - - The result is a new uniform coordinate data object. - - Save the result as described in points 7 to 9. - - (**WHAT?? HOW TO APPLY THE RESULTING COORDINATE DATA OBJECT**) - - -11. Rename the folder *CT-###-IPP-xxx* following the classification presented at the beginning of this document. - - -12. This step is similar to the step16 of the Matlab SOP, described in the previous section *2. Matlab interface for SCase anonymization and importation* - - -13. Continue with the next CT set. +# README.md of directory ImportScase +This document describes the content of the directory where it is located. + +## Directory reason to exists +- This directory contain the files needed to execute a dedicated Matlab interface (GUI) developed by Jorge Solana Muñoz at LBO. +- This interface aims to simplify the task of importing and anonymizing new CT scans to the existant Shoulder Database. + +## Files +- *config.txt*: This text file might contain a preselected path to the main directory of CT data. When the file is void or the content is useless, the software should ask the operator to indicate the correct path with the help of an interactive navigator window. +- *importSCase.m*: This is the main script of this software application +- *importSCase.fig*: This Matlab figure Script contains the graphical description of the graphical interface. +- *README.md*: This is this current file. +- *sortStructByField*: This is a dedicated function used by the main script (*importSCase.m*) to sort the CT's slices depending on the specified DICOM field. This function has been separated to allow it use by other scripts. +- *tempInfo.dat*: This file contains the variables used during the execution of *importSCase.m*. Temporal file created to allow deeper understanding of the function. diff --git a/ImportSCase/config.txt.txt b/ImportSCase/config.txt.txt deleted file mode 100644 index e69de29..0000000 diff --git a/ImportSCase/importSCase.fig b/ImportSCase/importSCase.fig index 5af7cd9..4c18d67 100644 Binary files a/ImportSCase/importSCase.fig and b/ImportSCase/importSCase.fig differ diff --git a/ImportSCase/importSCase.m b/ImportSCase/importSCase.m index 1c03a1d..318cb68 100644 --- a/ImportSCase/importSCase.m +++ b/ImportSCase/importSCase.m @@ -1,2221 +1,2078 @@ function varargout = importSCase(varargin) % IMPORTSCASE MATLAB code for importSCase.fig % IMPORTSCASE, by itself, creates a new IMPORTSCASE or raises the existing % singleton*. % % H = IMPORTSCASE returns the handle to a new IMPORTSCASE or the handle to % the existing singleton*. % % IMPORTSCASE('CALLBACK',hObject,eventData,handles,...) calls the local % function named CALLBACK in IMPORTSCASE.M with the given input arguments. % % IMPORTSCASE('Property','Value',...) creates a new IMPORTSCASE or raises the % existing singleton*. Starting from the left, property value pairs are % applied to the GUI before importSCase_OpeningFcn gets called. An % unrecognized property name or invalid value makes property application % stop. All inputs are passed to importSCase_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 importSCase % Last Modified by GUIDE v2.5 20-Feb-2019 10:59:13 %% Begin initialization code - DO NOT EDIT gui_Singleton = 1; gui_State = struct('gui_Name', mfilename, ... 'gui_Singleton', gui_Singleton, ... 'gui_OpeningFcn', @importSCase_OpeningFcn, ... 'gui_OutputFcn', @importSCase_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 % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% From here this script has been edited by Jorge Solana Mu?oz, working at +% the Laboratory of Biomechanical Orthopedics at EPFL (4.4.2019) + +% This function opens the grafical interfaces using the FIG file with the +% same name of the current one function importSCase_OpeningFcn(hObject, eventdata, handles, varargin) %% --- Executes just before importSCase is made visible. % 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 importSCase (see VARARGIN) %-------------------------------------------------------------------------- % Choose default command line output for importSCase handles.output = hObject; % Update handles structure guidata(hObject, handles); % UIWAIT makes importSCase wait for user response (see UIRESUME) % uiwait(handles.figure1); movegui('center') %-------------------------------------------------------------------------- -%% Customized by JSM 2018/10/26 +%% Customized by JSM 2019/04/04 % Setting default values for some global variables % Default directory = the current directory handles.script_dir = pwd; -% cd([pwd '/../../../../']); -% handles.home_path = pwd; -% handles.home_path = uigetdir(handles.script_dir,"Please select the home directory containing folder 'data'"); -% cd([pwd '/data/']); -% handles.data_dir = pwd; -% handles.data_dir = uigetdir(handles.script_dir,"Please select the main directory 'data or dataDev'"); -% cd([pwd '/../']); -% handles.home_path = pwd; -% cd(handles.script_dir); - -% Radiobuttons -% *handles.acquisition_stage* => might be 'Normal' or 'Pathologic' -% set(handles.radiobutton_Pathologic,'Value',1); -% handles.patient_group="P"; -% *handles.shoulder_side* => might be 'Right' or 'Left' or 'Both' + +% Radiobuttons default values set(handles.radiobutton_right,'Value',1); % *handles.acquisition_stage* => might be 'Preop' or 'Postop' set(handles.radiobutton_preop,'Value',1); % *handles.output_dir* => importation folder. By default the current dir. set(handles.output_dir,'String',pwd); % *handles.body* => might be 'Shoulder', 'Elbow' or 'Other[]' set(handles.radiobutton_shoulder,'Value',1); % *handles.CTlabel_text* => might be '-1', '-2', ..., '-1p', '-2p', ... set(handles.CTlabel_text,'String','-xxx'); % Information extra added to the output README file handles.extra_info='.'; - +% Updates global variables guidata(hObject,handles); +% Function definition needed for the interface. Don't edit it. function varargout = importSCase_OutputFcn(hObject, eventdata, handles) %% --- Outputs from this function are returned to the command line. % 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; %% ====================================================================== %% "Create Functions" execute during object creation, after setting all properties. +% Those functions define graphical parameters -function patient_num_CreateFcn(hObject, eventdata, handles) %% --- 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 during object creation, after setting all properties. function slider_2Dslice_CreateFcn(hObject, eventdata, handles) % hObject handle to slider_2Dslice (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 +% Updates global variables guidata(hObject,handles); -function edit_otherBody_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. +function edit_otherBody_CreateFcn(hObject, eventdata, handles) % hObject handle to edit_otherBody (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 +% Updates global variables guidata(hObject,handles); -function edit_extra_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. +function edit_extra_CreateFcn(hObject, eventdata, handles) % hObject handle to edit_extra (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 set(hObject,'String','Write here every extra info...'); -% update_infoFields(handles); +% Updates global variables guidata(hObject,handles); - %% ====================================================================== -%% "Callback Functions" execute on hObject's action events - -%% ************** Patient Type (group) ************** -% function radiobutton_Pathologic_Callback(hObject, eventdata, handles) -% %% --- Executes on button press in radiobutton_Pathologic. -% % hObject handle to radiobutton_Pathologic (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 radiobutton_Pathologic -% handles.patient_group="P"; -% set(handles.act3Dview,'value',1) -% act3Dview_Callback(handles.act3Dview,eventdata,handles) -% patient_num = get(handles.patient_num,'String'); -% set(handles.patient_num,'String',['P' patient_num(2:end)]); -% -% -% function radiobutton_Normal_Callback(hObject, eventdata, handles) -% %% --- Executes on button press in radiobutton_Normal. -% % hObject handle to radiobutton_Normal (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 radiobutton_Normal -% handles.patient_group="N"; -% set(handles.act3Dview,'value',0) -% act3Dview_Callback(handles.act3Dview,eventdata,handles) -% patient_num = get(handles.patient_num,'String'); -% set(handles.patient_num,'String',['N' patient_num(2:end)]); -% -% -% % --- Executes on button press in select_data_dir. -% function select_data_dir_Callback(hObject, eventdata, handles) -% % hObject handle to select_data_dir (see GCBO) -% % eventdata reserved - to be defined in a future version of MATLAB -% % handles structure with handles and user data (see GUIDATA) -% -% handles.data_dir = uigetdir(handles.script_dir,"Please select the main directory 'data or dataDev'"); -% cd([pwd '/../']); -% handles.home_path = pwd; -% cd(handles.script_dir); -% -% guidata(hObject,handles); - - -%% ************** Start new Patient / sCase ************** -function Select_Pending_Directory_Callback(hObject, eventdata, handles) +%% ********************************************************************** +%% "CALLBACK Functions" execute on hObject's action events + +%% ********** Start Pending Directory / potentialy new SCase ************** %% --- Executes on button press in Select_Pending_Directory. +function Select_Pending_Directory_Callback(hObject, eventdata, handles) % hObject handle to Select_Pending_Directory (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) -% This button lunch the initial check of a folder containing a group of -% sets of CTs. It counts the files per subfolder to clasify them - +% This button lunch the initial check of a pending folder containing a +% group of CTs. It counts the files per subfolder to clasify them %% Choose the main folder for the desired case +% This open a navigation window allowing the user to choose a new directory if isfield(handles,'home_dir') + % After the first use of this button, the software remembers the home + % directory, saving time to the user newPath = uigetdir(handles.home_dir,'Chose the folder containing the all the CT sets'); else + % If the home directory saved is wrong or is the first CT load the + % current working directory is temporaly used as home directory newPath = uigetdir(pwd,'Chose the folder containing all the CT sets'); end -% handles.patient_dir = newPath; + +% The new directory is saved as "patient directory" (handles.patien_dir) current_dir = pwd; cd([newPath '/..']); handles.patient_dir = pwd; cd(current_dir); -% Finding "data" directory from patient directory +% Finding "data" directory from "patient directory" and defining "home" and +% "data" directories +% All those paths will be needed further in this script handles.home_dir = extractBefore(handles.patient_dir,'data'); data_dir_name = extractAfter(handles.patient_dir,handles.home_dir); data_dir_name = extractBefore(data_dir_name,'\'); handles.data_dir = sprintf('%s%s',handles.home_dir,data_dir_name); handles.home_path = handles.home_dir; %% Set the patient group from directory +% Since the patient group is coded in the directory path, the folowing +% lines extract the patient group information from it and define the +% correspondign global variable (handles.patient_group) patient_group = newPath; patient_group = extractAfter(patient_group,'\data'); patient_group = extractAfter(patient_group,'\'); patient_group = extractBefore(patient_group,'\'); handles.patient_group = patient_group; +% Call to another function which ask the user whether the 3D visualization +% should be activated or not act3Dview_Callback(handles.act3Dview, eventdata, handles); + +% Updates the field "patient_num" with the "patient group" information patient_num = get(handles.patient_num,'String'); set(handles.patient_num,'String',[handles.patient_group patient_num(2:end)]); %% Select a limit of minimun dicom files to discard scout scans & other % small scans as elbows +% every CT with <30 DICOM files will be considered a scout scan scoutScanLimit = 30; +% every CT with >29 and <90 DICOM files will be considered a potential +% elbow scan smallScanLimit = 90; -%% Check the given path and extract data from every folder and file inside + +%% Check the chosen path and extract data from every folder and file inside dirList = dir(newPath); -% Select only the folders from the current path --> folderList +% Select only the folders from the current path and create a list folderN = 0; for k=3:length(dirList) folderN = folderN + dirList(k).isdir if folderN * dirList(k).isdir >0 folderList(folderN) = dirList(k); folderN,folderList(folderN).name end end -% Check how many dicom files are in each folder (to discard ScoutScans) +% Check how many dicom files are in each folder +% A "waitbar" is prompted showing the progres to the user wb = waitbar(0,'Checking folders size'); for k=1:folderN - % will contain a struct with data of files in the desired + % *files* will contain a struct with data of files in the desired % folder + % updating the "waitbar" waitbar(k/folderN,wb,sprintf('%s%s%s%i%s%i',"Checking directory ",... string(folderList(k).name),": ",k,"/",folderN)); + % Creates a list of files present in the folder files=dir([folderList(k).folder '/' folderList(k).name]); filesDicom = 0; % Initialitation of dicom files counter for n=1:length(files) - % if is a file and this file is a dicom file, note it - % (files.isdicom) and count it + % if there is a file and this file is a DICOM file, counts it if isfile([files(n).folder '/' files(n).name]) & ... isdicom([files(n).folder '/' files(n).name]) files(n).isdicom = 1; % noting down that it is a dicom file filesDicom = filesDicom +1; % counting the dicom files % Next line builds a list of dicom file names in cell mode folderList(k).dicomList(filesDicom) = cellstr(files(n).name); else - % Otherwise, is not a dicom file and we note it like that + % Otherwise, is not a dicom file and don't count it files(n).isdicom = 0; end end - % Each folder has it own counter of dicom files + % Each folder has its own counter of dicom files folderList(k).dicomN = filesDicom; - % We add a prefix with the number of files per CT, helping to priorize + % Adds a prefix with the number of files per CT, helping to prioritize % the importation order if ~contains(folderList(k).name,['s' char(string(folderList(k).dicomN))]) movefile([folderList(k).folder '/' folderList(k).name],... [folderList(k).folder '/' 's'... char(string(folderList(k).dicomN)) '-' folderList(k).name]); folderList(k).name = ['s' char(string(folderList(k).dicomN))... '-' folderList(k).name]; end - %% From here it could be commented and eliminated (JSM 25102018) - % When a folder has few dicom files we can consider that it was a scan + + %% This part could be removed since with the count of DICOM files per + % folder should be enough information for the user + + % When a folder has few DICOM files we can consider that it was a scan % scout instead of an interesting CT if folderList(k).dicomN < scoutScanLimit folderList(k).scanMode = 'scoutScan'; % Changing the folder name to mark it as scoutScan (if not marked % before) if ~contains(folderList(k).name,'scout') movefile([folderList(k).folder '/' folderList(k).name],... [folderList(k).folder '/' folderList(k).name '_scoutScan']); end elseif folderList(k).dicomN < smallScanLimit folderList(k).scanMode = 'smallScan'; % Changing the folder name to mark it as scoutScan (if not marked % before) if ~contains(folderList(k).name,'small') movefile([folderList(k).folder '/' folderList(k).name],... [folderList(k).folder '/' folderList(k).name '_smallScan']); end else folderList(k).scanMode = 'CT'; end - % Until here %% + % Until here, the removable part%% end -% Makes handles variable visible to other functions in this script -% hanldes.dicomdir = newPath; - +% This shows the path to the database used, in an info-box set(handles.text_data_dir,'String',... sprintf('%s%s','Database directory ',string(handles.data_dir))); +% Updates global variables guidata(hObject,handles); -%% ************** Load next CT set ************** -function Select_CT_Scan_Callback(hObject, eventdata, handles) +%% ************** Select CT scan ************** %% --- Executes on button press in Select_CT_Scan. +function Select_CT_Scan_Callback(hObject, eventdata, handles) % hObject handle to Select_CT_Scan (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) % Before loading a new set of CTs we should remove the last one clear handles.data -% Output Directory update -% if isfield(handles,'patient_dir') -% handles.data_dir = handles.patient_dir; -% else -% handles.data_dir = handles.home_path; -% end - -% Also remove the fields 'myinfo' and 'mydicom' (they could be just -% cleared?) +% Also remove the fields 'myinfo' and 'mydicom' if isfield(handles, 'myinfo'); handles = rmfield(handles, 'myinfo'); % Removing 'myinfo' field from structure in handles else if isfield(handles, 'mydicom'); handles = rmfield(handles, 'mydicom');% Removing 'mydicom' field from structure in handles end end if isfield(handles,'dicomSet') handles = rmfield(handles,'dicomSet'); end if isfield(handles,'dicomSetSorted') handles = rmfield(handles,'dicomSetSorted'); end %% Loading CT files -% Using last directory +% A navigator windows askes the user to select the DICOM files to be loaded +% Using last patient directory if isfield(handles,'patient_dir') [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',handles.patient_dir,'MultiSelect', 'on'); + % When no "patient directory" is present, this uses the "database" path elseif isfield(handles,'data_dir') [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',handles.data_dir,'MultiSelect', 'on'); % Finding patient_dir current_dir = pwd; cd([handles.dicomdir '/../..']); handles.patient_dir = pwd; cd(current_dir); + % If neither the "database" path is recorded, this uses the "home + % directory" elseif isfield(handles,'home_dir') [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',handles.home_dir,'MultiSelect', 'on'); % Finding patient_dir current_dir = pwd; cd([handles.dicomdir '/../..']); handles.patient_dir = pwd; cd(current_dir); % Finding "data" directory from patient directory data_dir_name = extractAfter(handles.patient_dir,handleshome_dir); data_dir_name = extractBefore(data_dir_name,'\'); handles.data_dir = sprintf('%s%s',handles.home_dir,data_dir_name); handles.home_path = handles.home_dir; else + % When even the "home directory" is missing, this uses the current + % working directory [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',pwd,'MultiSelect', 'on'); % Finding patient_dir current_dir = pwd; cd([handles.dicomdir '/../..']); handles.patient_dir = pwd; cd(current_dir); % Finding "data" directory from patient directory handles.home_dir = extractBefore(handles.patient_dir,'data'); data_dir_name = extractAfter(handles.patient_dir,handles.home_dir); data_dir_name = extractBefore(data_dir_name,'\'); handles.data_dir = sprintf('%s%s',handles.home_dir,data_dir_name); handles.home_path = handles.home_dir; end +% If the "patient group" has not been obtained yet, this gets it from the +% chosen path if ~isfield(handles,'patient_group') %% Set the patient group from directory patient_group = handles.dicomdir; patient_group = extractAfter(patient_group,'\data'); patient_group = extractAfter(patient_group,'\'); patient_group = extractBefore(patient_group,'\'); handles.patient_group = patient_group; + % Call to another function which ask the user whether the 3D visualization + % should be activated or not act3Dview_Callback(handles.act3Dview, eventdata, handles); patient_num = get(handles.patient_num,'String'); set(handles.patient_num,'String',[handles.patient_group patient_num(2:end)]); end -% When only one dicom files is selected this select all of them -if length(handles.dicomlist)==1 +% When only one DICOM files is selected this selects all DICOMs present +% This is a quick way to select the whole CT scan +if length(string(handles.dicomlist))==1 files=dir(handles.dicomdir); filesDicom = 0; % Initialitation of dicom files counter handles.dicomlist=[]; for n=1:length(files) % if is a file and this file is a dicom file, note it % (files.isdicom) and count it if isfile([files(n).folder '/' files(n).name]) & ... isdicom([files(n).folder '/' files(n).name]) handles.dicomlist = [handles.dicomlist {files(n).name}]; end end end % Showing the dicom name in the interface set(handles.text_dicom_name,'String',... sprintf('%s%s',string(handles.dicomdir),string(handles.dicomlist(1)))); % Number of dicom files handles.N = length(handles.dicomlist); % Set parametres for 2D visualization set(handles.slider_2Dslice, 'Max', handles.N); set(handles.slider_2Dslice, 'Value', int32(handles.N/2)); set(handles.slider_2Dslice, 'SliderStep',[1/handles.N 10/handles.N]); set(handles.slider_2Dslice, 'Min', 1); set(handles.info_text, 'ForegroundColor', 'black', 'FontWeight', 'normal'); % Read dicom data try - % This loop reads all the dicom files and builds a new struct with the + % This loop reads all the DICOM files and builds a new struct with the % information from each row and the data (the images) - wb = waitbar(0,'Loading DICOM files'); %This creates a waitbar or progressbar - L = length(handles.dicomlist); % number of dicom files to load + wb = waitbar(0,'Loading DICOM files'); %This creates a waitbar + L = length(handles.dicomlist); % number of DICOM files to load for n=1:L waitbar(n/L,wb,sprintf('%s%i%s%i',"Loading DICOM metadata ",n,"/",L)); % Reads the info from each dicomfile handles.dicomSet(n) = dicominfo(sprintf('%s%s',... handles.dicomdir, char(handles.dicomlist(n))),'UseDictionaryVR',true); end for n=1:L waitbar(n/L,wb,sprintf('%s%i%s%i',"Loading DICOM images ",n,"/",L)); handles.dicomSet(n).data = dicomread(sprintf('%s%s', ... handles.dicomdir, char(handles.dicomlist(int32(n))))); end handles.myinfo = handles.dicomSet(1); % To be removed (JSM 25102018) -% % 'handles.myinfo' will keep the metadata of the dicom files loaded -% handles.myinfo(1) = dicominfo(sprintf('%s%s', handles.dicomdir, ... -% char(handles.dicomlist(1))),'UseDictionaryVR',true); % Temporal storing of metadata tempInfo=handles.dicomSet(1); save('tempInfo.mat','tempInfo'); % Shows few metadata in the interface: % - IPP identification number and Patient Initials set(handles.IPP_text, 'String', handles.dicomSet(1).PatientID) set(handles.initials_text,'String',sprintf('(%s%s)',... handles.dicomSet(1).PatientName.FamilyName(1),... handles.dicomSet(1).PatientName.GivenName(1))); set(handles.birthdate_text,'String',handles.dicomSet(1).PatientBirthDate); set(handles.gender_text,'String',handles.dicomSet(1).PatientSex); % - CT resolution in mm set(handles.Resolution_text, 'String', sprintf('%f mm', handles.dicomSet(1).PixelSpacing(1))); % - CT date set(handles.CTdate_text, 'String', sprintf('%s', handles.dicomSet(1).AcquisitionDate)); % The following function sort the dicomFiles by their slice location -% handles.dicomSetSorted=sortStructByField(handles.dicomSet,{'SliceLocation'}); handles.dicomSetSorted=sortStructByField(handles.dicomSet,{'InstanceNumber'},handles); handles.dicomSetOld=handles.dicomSet; handles.dicomSet=handles.dicomSetSorted; 'load finished' catch except except except.identifier if strcmp(except.identifier,'Error identifier: MATLAB:nonExistentField') errordlg(except.message,'Loading Error') -% return -% elseif or(strcmp(except.identifier,'MATLAB:heterogeneousStrucAssignment'),... -% strcmp(except.identifier,'MATLAB:heterogeneousStrucAssignment')) -% f = errordlg(sprintf('%s\n\n%s\n%s', ' CT SCAN WITH UNEXPECTED ORIENTATION.',... -% 'Suggestion: mark the current folder as "Wrong_Orientation"',... -% 'and try with another set of CTs'),'Loading Error'); -% return else errordlg(sprintf('%s%s','Error identifier: ',except.identifier)) end end %% Updating the radioButtons "NONE", "BONE"('BONE'), "SOFT"('STANDARD') if isfield(handles.dicomSet(1), 'ConvolutionKernel') if strfind(handles.dicomSet(1).ConvolutionKernel, 'BONE') set(handles.radiobutton_none, 'Value', 0); set(handles.radiobutton_sharp, 'Value', 1); set(handles.radiobutton_smooth, 'Value', 0); elseif strfind(handles.dicomSet(1).ConvolutionKernel, 'STANDARD') set(handles.radiobutton_none, 'Value', 0); set(handles.radiobutton_sharp, 'Value', 0); set(handles.radiobutton_smooth, 'Value', 1); else set(handles.radiobutton_none, 'Value', 1); set(handles.radiobutton_sharp, 'Value', 0); set(handles.radiobutton_smooth, 'Value', 0); end else set(handles.radiobutton_none, 'Value', 1); set(handles.radiobutton_sharp, 'Value', 0); set(handles.radiobutton_smooth, 'Value', 0); end %% Extracting data for interface representation handles.slice = int32(handles.N / 2); % Chooses the central slice -% handles.mydicom(:,:,handles.slice) = dicomread(sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(handles.slice)))); +% removes previous object "mydicom" if existed if isfield(handles,'mydicom') handles=rmfield(handles,'mydicom'); end +% load *handles.mydicom* with the current slice image handles.mydicom(:,:,handles.slice) = handles.dicomSet(handles.slice).data; +% Setting some visualization parammeters handles.CTmin = -200; handles.CTmax = 2000; - handles.bigmax = 3000; handles.bigmin = -2000; set(handles.slider_2Dcontrast, 'Max', handles.bigmax - handles.bigmin - 1); set(handles.slider_2Dcontrast, 'Min', 0); set(handles.slider_2Dcontrast, 'Value', 900); set(handles.slider_2Dbrightness, 'Max', handles.bigmax); set(handles.slider_2Dbrightness, 'Min', handles.bigmin); set(handles.slider_2Dbrightness, 'Value', 1100); set(handles.slice_num, 'String', handles.slice); axes(handles.axes_2Dviewer); % Set current axes -% imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]); -% imshow(handles.dicomSet(handles.slice).data, [handles.CTmin handles.CTmax]); -% trying dicomreadVolume -% [V, SPATIAL, DIM] = dicomreadVolume(handles.dicomdir); -% imshow(V(:,:,1,handles.slice), [handles.CTmin handles.CTmax]); +% finaly shows the current CT slice image imshow(handles.dicomSet(handles.slice).data, [handles.CTmin handles.CTmax]); -%% ISOSURFACE section ==================================================== +%% ISOSURFACE section ==== 3D ======================================== +% Call to another function which ask the user whether the 3D visualization +% should be activated or not act3Dview_Callback(handles.act3Dview, eventdata, handles); axes(handles.axes_2Dviewer); % Set current axes +% Updates global variables guidata(hObject,handles); -% handles -if and(get(handles.patient_num,'String') ~="P###",... - exist([char(get(handles.output_dir,'String')) '/readme'])==2) - handles.text_readme.String="listo para leer"; -end -% update_infoFields(handles); -% Makes handles variable visible to other functions in this script %% Reset the DB chech result info box set(handles.text_coincidences_info,'String',"Check DB for coincidences..."); set(handles.info_text, 'String', 'CT loaded'); set(handles.info_add_SCase, 'String', ''); set(handles.text_data_dir,'String',... sprintf('%s%s','Database directory ',string(handles.data_dir))); guidata(hObject,handles); function act3Dview_Callback(hObject, eventdata, handles) %% --- Executes on button press in act3Dview. % hObject handle to act3Dview (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 act3Dview % handles.enable_3Dview = get(hObject,'Value') if get(hObject,'Value') C=questdlg(['The 3D viewer might create memory problems when the CT'... ' contains more than 300 slices. Do you want to activate it?'],... '3D viewer activation','Yes, Display 3D view','Abort','Abort') switch C case 'Yes, Display 3D view' handles.enable_3Dview = 1 otherwise handles.enable_3Dview = 0 set(hObject,'Value',0) end else handles.enable_3Dview = 0 end % clear handles.data axes(handles.isosurface); % Set current axes cla if isfield(handles,'N') & handles.enable_3Dview if handles.N>1 directory=handles.dicomdir; % Preparing list of dicom full names (including address) for k=1:handles.N dicomListFullName(k)=""; dicomListFullName(k)=[directory char(handles.dicomlist(k))]; end try [V,spatial,dim] = dicomreadVolume(dicomListFullName); v = squeeze(V); v = double(v); v2= imresize3(v,0.2); handles.v2 = v2; handles.isoValue = 1400; % default limit for isosurface rendering p = patch(isosurface(v2,handles.isoValue)); Sv2 = size(v2); M = max(max(max(v2))); m = min(min(min(v2))); handles.bigmax = M; handles.bigmin = m; set(handles.slider_3Dviewer, 'Max', handles.bigmax); set(handles.slider_3Dviewer, 'Min', handles.bigmin); set(handles.slider_3Dviewer, 'Value', handles.isoValue); d1=(M-m)/Sv2(1); d2=(M-m)/Sv2(2); d3=(M-m)/Sv2(3); [x,y,z]=meshgrid(m:d1:M,m:d2:M,m:d3:M); handles.x=x(1:Sv2(1),1:Sv2(2),1:Sv2(3)); handles.y=y(1:Sv2(1),1:Sv2(2),1:Sv2(3)); handles.z=z(1:Sv2(1),1:Sv2(2),1:Sv2(3)); isocolors(handles.x,handles.y,handles.z,v2,p); p.FaceColor='interp'; p.EdgeColor='none'; view(-20, 10); % isosurface(v2,1400); rotate3d on; catch except if strcmp(except.identifier,'images:dicomread:differentPatientOrientations') f = errordlg(sprintf('%s\n\n%s\n%s', ' CT SCAN WITH UNEXPECTED ORIENTATION.',... 'Suggestion: mark the current folder as "Wrong_Orientation"',... 'and try with another set of CTs'),'Loading Error'); % We change the folder name to mark it as scoutScan (if not marked before) if ~contains(handles.dicomdir,'wrong') movefile([handles.dicomdir(1:end-1)],... [handles.dicomdir(1:end-1) '_wrongOrientatedScan']); end return else errordlg(sprintf('%s%s','Error identifier: ',except.identifier)); - % rethrow(except); end end end end guidata(hObject,handles); function slider_3Dviewer_Callback(hObject, eventdata, handles) %% --- Executes on slider movement. % hObject handle to slider_3Dviewer (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 guidata(hObject,handles); function radiobutton_left_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_left. % hObject handle to radiobutton_left (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 radiobutton_left handles.shoulder_side = 'Left'; update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); % --- Executes on button press in radiobutton_both. function radiobutton_both_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_both (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 radiobutton_both handles.shoulder_side = 'Both'; update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); function radiobutton_right_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_right. % hObject handle to radiobutton_right (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 radiobutton_right handles.shoulder_side = 'Right'; update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); %% --- Executes on button press in radiobutton_preop. function radiobutton_preop_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_preop (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 radiobutton_preop handles.acquisition_stage = 'Preop'; update_readme_Callback(handles.update_readme,eventdata,handles); % update_infoFields(handles); guidata(hObject,handles); function radiobutton_postop_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_postop. % hObject handle to radiobutton_postop (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 radiobutton_postop handles.acquisition_stage = 'Postop'; update_readme_Callback(handles.update_readme,eventdata,handles); % update_infoFields(handles); guidata(hObject,handles); function Check_Database_Callback(hObject, eventdata, handles) %% --- Executes on button press in Check_Database. % hObject handle to Check_Database (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) -% Checking Database ----------------------------------- -% Importing database from excel file depending on computer system -% if computer('arch')=="win64" -% handles.data=importDataBaseTab('../../../../data/Excel/ShoulderDataBase.xlsx','sCase',2,2000); -% excelDB = '../../../../data/Excel/ShoulderDataBase.xlsx'; -% else -% % path2DB=[ '/home/shoulder/data/Excel/ShoulderDataBase.xlsx']; -% system('ls /home/shoulder/data/Excel/'); -% path2DB=['/home/shoulder/data/Excel/ShoulderDataBase.xlsx']; -% excelDB = '/home/shoulder/data/Excel/ShoulderDataBase.xlsx'; -% % ls('/home/shoulder/data/Excel/') -% % f=importDataBaseTab_script; -% handles.data=importDataBaseTab(path2DB,'sCase',2,2000); -% -% end % New way of reading the Excel Database: try -% DBpath = sprintf('%s%s',handles.data_dir,'\Excel\'); -% excelDB= "ShoulderDataBase.xlsx"; DBpath = sprintf('%s%s',handles.data_dir,'\Excel\xlsFromMatlab\'); excelDB= "SCaseImport.xlsx"; catch -% [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',pwd);%handles.data_dir); - [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',pwd);%handles.data_dir); + [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',pwd); end wb = waitbar(0,'Checking Excel DataBase'); "Openning Excel Database" -tic + E = exist(sprintf('%s%s',DBpath,excelDB)); % We store the Excel file adress and name for further use handles.ExcelDB_path = DBpath; handles.ExcelDB_fullname = sprintf('%s%s',DBpath,excelDB); if E == 2 % Only if the file exist as a known format (XLS) -% [~,txt,raw,dates]=xlsread(handles.ExcelDB_fullname,'sCase','','',@convertSpreadsheetExcelDates); [~,txt,raw,dates]=xlsread(handles.ExcelDB_fullname,'SCaseImport','','',@convertSpreadsheetExcelDates); % The dates cell array will have NaN in every cell not containing a "date" dates(~cellfun(@(x) isnumeric(x) || islogical(x), dates)) = {NaN}; handles.data.headers = string(txt(1,:)); % Conversion of dates's cell array to numeric matrix dates=cell2mat(dates); % Finding columns with dates dates_CperR=find(~isnan(dates(1,:))); date_Cells{1}= dates_CperR; txt_Cells{1} = []; for p=2:size(txt,1) dates_CperR=find(~isnan(dates(p,:))); date_Cells{p}= dates_CperR; txt_CperR = find(~ismissing(txt(p,:))); txt_Cells{p} = txt_CperR; end date_columns = []; txt_columns = []; for p=1:length(date_Cells) date_columns= union(date_columns,date_Cells{p}); txt_columns = union(txt_columns,txt_Cells{p}); end num_columns = setdiff(1:size(raw,2),txt_columns); txt_columns = setdiff(txt_columns,date_columns); % Inverting the "date to number" convertion done during the importation dates_only = datetime(dates(2:end,[date_columns]),'ConvertFrom','Excel','Format','dd-MM-yyy'); -% num_only = cell2mat(raw(2:end,num_columns)); for n=1:size(txt,2) % for each text headers (column) [data.(raw{1,n})]=raw(2:end,n); end k=1; for n=date_columns' % for the date columns [data.(raw{1,n})]=dates_only(:,k); k=k+1; end elseif E == 0 warndlg("Excel file not found!") else warndlg("Database file has unrecognized format. It should be an Excel file") end toc handles.data=data; % ***Obtain the first row available for a new case*** % Get the colum of sCase.id, including those from rows available sCase_list = handles.data.SCase_ID; % Cut the list from the end untill found an existing sCases [s1,s2]=size(sCase_list); s0=0; while string(sCase_list(end,:))=="" | ismissing(string(sCase_list(end,:)))% Last position empty... sCase_list = sCase_list(1:end-1,:); s0=s0+1; -% size(sCase_list) % Just for debugging waitbar(s0/s1,wb,'Checking Excel Database'); end handles.DB_length=length(sCase_list); sCase_N=[]; sCase_P=[]; -% sCase_list_sorted=sort(sCase_list); % This helps to avoid errors if a row is eventually void sCase_list_sorted=string(sCase_list); % This have to be cleaned for n=1:s1-s0 if sCase_list_sorted(n,:)~="" & ~ismissing(sCase_list_sorted(n,:)) -% n,sCase_list_sorted(n,:) % Just for debugging L = length(char(extractAfter(sCase_list_sorted(n,:),1))); while L<3 sCase_list_sorted(n,:)=sprintf('%s%s%s',... extractBefore(sCase_list_sorted(n,:),2),... '0',extractAfter(sCase_list_sorted(n,:),1)); L = length(char(extractAfter(sCase_list_sorted(n,:),1))); end if extractBefore(sCase_list_sorted(n,:),2)=="N" sCase_N=[sCase_N; sCase_list_sorted(n,:)]; elseif extractBefore(sCase_list_sorted(n,:),2)=="P" sCase_P=[sCase_P; sCase_list_sorted(n,:)]; else "there is a row without identification (N or P) in the DataBase" end waitbar(s1-s0+n/s1,wb,'Checking Excel Database'); end end sCase_N=sort(sCase_N); sCase_P=sort(sCase_P); % Build the next sCase.id for a potential new sCase % by extracting the 'id' number of the last sCase and adding '1' if handles.patient_group=="N" new_sCase = str2num(extractAfter(char(sCase_N(end)),1))+1; % Finaly convert to string and add a 'P' for 'Pathological' new_sCase = string(['N' num2str(new_sCase)]); elseif handles.patient_group=="P" new_sCase = str2num(extractAfter(char(sCase_P(end)),1))+1; % Finaly convert to string and add a 'N' for 'Normal' new_sCase = string(['P' num2str(new_sCase)]); end handles.data.anonymity_IPP=cell2mat(handles.data.anonymity_IPP(1:handles.DB_length)); handles.data.shoulder_side=cell2mat(handles.data.shoulder_side(1:handles.DB_length)); % *** Q1: Does the new IPP exist in the Database yet? *** % Extract the list of patient IDs from Excel database IPP_list_DB = handles.data.anonymity_IPP; % Extracting patient ID from dicom-set info IPP_sCase = str2num(handles.myinfo(1).PatientID) % If the IPP of the current case already exist in the database, % it finds the position of the current patient ID in the list. % And for this position it extracts the case name "sCase", otherwise it % leave the variables empty % handles.patientIndex=find(handles.data.anonymityIPP==str2num(handles.myinfo(1).PatientID)) handles.patientIndex=find(IPP_list_DB==IPP_sCase); handles.sCase = handles.data.SCase_ID(handles.patientIndex); % Determine how many sCases exits with the same IPP as the current CT Ncoincidences=length(handles.sCase); % *** We start the decision tree *** if Ncoincidences==0 % This is a new sCase!! So we write a short message and set the sCase set(handles.dataCheck_text,'String', sprintf('This is a new case')); set(handles.dataCheck_panel,'BackgroundColor','g'); handles.current_sCase=new_sCase; set(handles.patient_num,'String',sprintf('%s',handles.current_sCase)); set(handles.text_coincidences_info,'String',"Current IPP not found in DB"); % If the IPP exists in the Excel Database in more than one sCase we first % can discriminate by the shoulder side (rigth or left) elseif Ncoincidences>1 % Launch a warning message set(handles.dataCheck_text,'String',... ['Patient found in ' num2str(Ncoincidences) ' sCase(s)']); set(handles.dataCheck_panel,'BackgroundColor','y'); % Shows information of the 2 first coincidences coincidences = sprintf("%s \t%s \t%s \t%s \n%s \t%s \t%s \t\t%s \n%s \t%s \t%s \t\t%s",... "SCase"," IPP "," CT_date ","Side",... string(handles.sCase(1)),... string(IPP_list_DB(handles.patientIndex(1))),... handles.data.CT_date(handles.patientIndex(1)),... handles.data.shoulder_side(handles.patientIndex(1)),... string(handles.sCase(2)),... string(IPP_list_DB(handles.patientIndex(2))),... handles.data.CT_date(handles.patientIndex(2)),... handles.data.shoulder_side(handles.patientIndex(2))); set(handles.text_coincidences_info,'String',coincidences); % *** Q2: is the current CT from the same side of one of the existing? % Get the side of the coincident sCases side_sCase = handles.data.shoulder_side(handles.patientIndex); % Launch 'question dialog' to let the user choose if one the coincident % sCases matches with the side (rigth or left) of the current CT question = sprintf("%s %s %s \n%s\n%s",... "The SCase",string(IPP_sCase),... "was found in the more than one case in the database.",... "Please, check the shoulder side of the currently loaded CT",... "and choose between the following options"); title = "SCase found in database"; btn1 = ['Existing case:' char(handles.sCase(1)) ' side:' char(side_sCase(1))]; btn2 = ['Existing case:' char(handles.sCase(2)) ' side:' char(side_sCase(2))]; btn3 = ['New case:' char(new_sCase) ' side: ?']; dfltbtn = btn3; answer = questdlg(question,title,btn1,btn2,btn3,dfltbtn); switch answer case btn1 handles.current_sCase=handles.sCase(1); answerN = 1; case btn2 handles.current_sCase=handles.sCase(2); answerN = 2; case btn3 handles.current_sCase=string(new_sCase); answerN = 3; otherwise disp("Auto Check DB -ABORTED-"); return; end disp(sprintf("%s %s",string(handles.current_sCase),"chosen")); % Update the list of sCases after selection handles.sCase=handles.current_sCase; Ncoincidences=1; % *** Q3: is the current CT date similar to the CT_date from any of the % concident sCases in the Database? *** % Get the CT's date from the coincident sCase from the Database try CTdate_DB = datestr(handles.data.CT_date(handles.patientIndex),'dd-mmm-yyyy'); catch error message = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s",... "Suggested solution:",... "Opt1. Update InfoCT.xlsx and repeat the 'Auto Check DB'",... "Opt2. Check the CT_date yourself and use 'Manual sCase'",... "Error details:",... error.identifier,error.message); errordlg(message,"Not valid CT_date found in Database"); CTdate_DB = NaT; end % Get the CT's date from the new CT set current_CTdate = handles.myinfo.AcquisitionDate % Adapt date format to make it comparable current_CTdate=datestr([current_CTdate(1:4),'-',... current_CTdate(5:6),'-',current_CTdate(7:8)],'dd-mmm-yyyy') % Compare CT's dates if isequal(current_CTdate,CTdate_DB) % The sCase already exits so the user will have to decide if the CT % must be added to the case of it is already added. set(handles.dataCheck_text,'String',... ['Current CT matches in IPP, side and CTdate with others in the database']); set(handles.dataCheck_panel,'BackgroundColor','r'); set(handles.patient_num,'String',string(handles.current_sCase)); else % The user must check if the current CT set corresponds to a % post-op shoulder set(handles.dataCheck_text,'String',... ['Check if the CT is from a post-op. Current CT matches in IPP, side']); set(handles.dataCheck_panel,'BackgroundColor','r'); set(handles.patient_num,'String',string(handles.current_sCase)); end % If only one sCase was found in the Database elseif Ncoincidences==1 % Launch a warning message set(handles.dataCheck_text,'String',... ['Patient found in one sCase(s)']); set(handles.dataCheck_panel,'BackgroundColor','r'); handles.current_sCase=handles.sCase; coincidences = sprintf("%s \t%s \t%s \t%s \n%s \t%s \t%s \t\t%s",... "SCase"," IPP "," CT_date ","Side",... string(handles.sCase(1)),... string(IPP_list_DB(handles.patientIndex(1))),... handles.data.CT_date(handles.patientIndex(1)),... handles.data.shoulder_side(handles.patientIndex(1))); set(handles.text_coincidences_info,'String',coincidences); side_sCase=handles.data.shoulder_side(handles.patientIndex); % Creating question dialog to let the user choose between the found % SCase and a new SCase label question = sprintf("%s %s %s \n%s\n%s",... "The loaded SCase",string(IPP_sCase),... "was found in the SCase list in the database.",... "Please, check the shoulder side of the currently loaded CT",... "and choose between the following options"); title = "SCase found in database"; btn1 = ['Existing case:' char(handles.sCase(1)) ' side:' char(side_sCase(1))]; btn2 = ['New case:' char(new_sCase) ' side: ?']; dfltbtn = btn2; answer= questdlg(question,title,btn1,btn2,dfltbtn); switch answer case btn1 handles.current_sCase=handles.sCase(1) answerN=1; case btn2 handles.current_sCase=string(new_sCase); answerN=2; otherwise disp("Auto Check DB -ABORTED-"); return; end disp(sprintf("%s %s",string(handles.current_sCase),"chosen")); if answerN==1 % *** Q3: is the current CT date similar to the CT_date from any of the % concident sCases in the Database? *** % Get the CT's date from the coincident sCase from the Database try CTdate_DB = datestr(handles.data.CT_date(handles.patientIndex),'dd-mmm-yyyy'); catch error message = sprintf("%s\n%s\n%s\n\n%s\n%s\n%s",... "Suggested solution:",... "Opt1. Update InfoCT.xlsx and repeat the 'Auto Check DB'",... "Opt2. Check the CT_date yourself and use 'Manual sCase'",... "Error details:",... error.identifier,error.message); errordlg(message,"Not valid CT_date found in Database"); CTdate_DB = NaT; end % Get the CT's date from the new CT set current_CTdate = handles.myinfo.AcquisitionDate % Adapt date format to make it comparable current_CTdate=datestr([current_CTdate(1:4),'-',... current_CTdate(5:6),'-',current_CTdate(7:8)],'dd-mmm-yyyy') % Compare CT's dates if isequal(current_CTdate,CTdate_DB) % The sCase already exits so the user will have to decide if the CT % must be added to the case of it is already added. set(handles.dataCheck_text,'String',... ['Current CT matches in IPP, side and CTdate with others in the database']); set(handles.dataCheck_panel,'BackgroundColor','r'); set(handles.patient_num,'String',sprintf('%s',string(handles.current_sCase))); else % The user must check if the current CT set corresponds to a % post-op shoulder set(handles.dataCheck_text,'String',... ['Check if the CT is from a post-op. Current CT matches in IPP, side']); set(handles.dataCheck_panel,'BackgroundColor','r'); set(handles.patient_num,'String',sprintf('%s',string(handles.current_sCase))); end else % This is a new sCase!! So we write a short message and set the sCase set(handles.dataCheck_text,'String', sprintf('This is a new case')); set(handles.dataCheck_panel,'BackgroundColor','g'); handles.current_sCase=new_sCase; set(handles.patient_num,'String',sprintf('%s',string(handles.current_sCase))); end end F1=extractBefore(char(handles.current_sCase),2); F2=extractBetween(char(handles.current_sCase),2,2); F3=extractBetween(char(handles.current_sCase),3,3); current_dir = cd(char(strcat(handles.data_dir,'/',F1,'/',F2,'/',F3))); % saving current directory and changing to the output dir output_dir = pwd; % needed to update the output directory cd(current_dir); % back to the current directory set(handles.output_dir,'String',output_dir); % set(handles.output_dir,'String',strcat(handles.data_dir,F1,'/',F2,'/',F3));%sprintf('%s%s\%s\%s',data_dir,F1,F2,F3)); waitbar(1,wb,'Checking Excel Database'); guidata(hObject,handles); -function Manual_SCase_Callback(hObject, eventdata, handles) %% --- Executes on button press in Manual_SCase. +function Manual_SCase_Callback(hObject, eventdata, handles) % hObject handle to Manual_SCase (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) sCase_dir = uigetdir(handles.data_dir, "Select the output directory",handles.data_dir); set(handles.output_dir,'String',sCase_dir); update_readme_Callback(handles.update_readme,eventdata,handles); warndlg('Please uptade the sCase.id accordingly with the chosen output directory') guidata(hObject,handles); -function patient_num_Callback(hObject, eventdata, handles) %% Executes with 'Enter' in patient_num +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 % The output directory is stablish by the automatic Check of the DB or the % manual selection. Hence, here we only check whether the sCase number % matches with the selected output directory % From "patient_num" which corresponds to sCase number patient_num = get(handles.patient_num,'String'); % Get the sCase number F0=char(extractBefore(char(patient_num),1)) F0=char(extractBefore(char(patient_num),2)) % Extract the 'N' or 'P' F1=char(extractBetween(char(patient_num),2,2)) % Extract the first digit (1 to 9) F2=char(extractBetween(char(patient_num),3,3)) % Extract the second digit (0to9) % From "output_dir" which corresponds to output directory output_dir = get(handles.output_dir,'String'); % Get the ourput directory L = length(output_dir); % lenght of the string that contains the directory D0=char(extractBetween(char(output_dir),L-4,L-4)) % Extact the 'N' or 'P' D1=char(extractBetween(char(output_dir),L-2,L-2)) % Extract the first digit (1 to 9) D2=char(extractAfter(char(output_dir),L-1)) % Extract the second digit (0 to 9) if F0==D0 & F1==D1 & F2==D2 handles.enable_importation = 1 else handles.enable_importation = 0 warndlg('Importation cancelled. The sCase (patient number) must be consistant with the Output Directory') end guidata(hObject,handles); %% --- Executes on slider movement. function slider_2Dslice_Callback(hObject, eventdata, handles) % hObject handle to slider_2Dslice (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) = handles.dicomSet(handles.slice).data; axes(handles.axes_2Dviewer) % Set current axes imshow(handles.mydicom(:,:,handles.slice), [handles.CTmin handles.CTmax]) % update slice name info set(handles.text_dicom_name,'String',... sprintf('%s%s%s','DICOM File Full-name: ',string(handles.dicomdir),... string(handles.dicomlist(str2num(handles.slice_num.String))))); guidata(hObject,handles); -function Anonymise_Import_Callback(hObject, eventdata, handles) %% --- Executes on button press in Anonymise_Import. +function Anonymise_Import_Callback(hObject, eventdata, handles) % hObject handle to Anonymise_Import (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) sortPictures = 1; set(handles.info_text, 'String', 'Preparing DICOM images to be imported...'); drawnow % Warning message in info panel when no patient's number is found 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 -% W +% Warning message in info panel when no output dir is found if isempty(get(handles.output_dir,'String')) outputdir = uigetdir(handles.home_path,"Select the output directory where the CT must be imported"); if outputdir set(handles.output_dir,'String', outputdir); end else outputdir = get(handles.output_dir,'String'); -% outputdir = outputdir{1}; 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.dicomSet(1).PatientID, '_',... handles.dicomSet(1).PatientName.FamilyName(1),... handles.dicomSet(1).PatientName.GivenName(1)]; if get(handles.radiobutton_sharp,'Value') newname = [newname, '-bone.']; elseif get(handles.radiobutton_smooth,'Value') newname = [newname, '-soft.']; else newname = [newname, '.']; end -% Create output directory -% outputdir = sprintf('%s%s%s%s%s%s%s%s%s%s%s',outputdir,'/', patient,'-',... -% handles.dicomSet(1).PatientID,'/','CT-',patient,'-',... -% handles.dicomSet(1).PatientID,handles.CTlabel_text.String,'/dicom/'); % Checking the existance of a folder with similar SCase_ID and different % IPP if exist(outputdir)==7 f=dir(outputdir); if length(f)>2 % When a subfolder is present this length is >=3 for n=3:length(f) SC{n-2}=extractBefore(f(n).name,'-'); IPP{n-2}=extractAfter(f(n).name,'-'); end SC=string(SC); IPP=string(IPP); SC_dx = find(SC==string(patient)); if ~isempty(SC_dx) if IPP(SC_dx(1))~=string(handles.dicomSet(1).PatientID) errordlg(sprintf("%s %s %s %s", "SCase =",string(patient),... "present with a IPP = ", IPP(SC_dx)),... 'Current SCase_ID already in the directories database'); set(handles.info_text, 'String', 'Preparing DICOM images to be imported...'); drawnow return end end end end suggested_outputdir = sprintf('%s%s%s%s%s%s%s%s%s%s%s',outputdir,'/', patient,'-',... handles.dicomSet(1).PatientID,'/','CT-',patient,'-',... handles.dicomSet(1).PatientID,handles.CTlabel_text.String,'/dicom/'); if exist(suggested_outputdir)==7 outputdir = sprintf('%s%s%s%s%s%s%s%s%s%s%s',outputdir,'/', patient,'-',... handles.dicomSet(1).PatientID,'/','CT-',patient,'-',... handles.dicomSet(1).PatientID,'xxx','/dicom/'); else outputdir = suggested_outputdir; end +% Creating the output directory 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))),'UseDictionaryVR',true); SliceLoc(h,1) = h; -% sprintf('%s%s', handles.dicomdir, char(handles.dicomlist(h))); SliceLoc(h,2) = infoTemp.SliceLocation; end SliceSorted = sortrows(SliceLoc,2); end guidata(hObject,handles); % Read all data % at the importation we set the dicomSet as definitively sorted handles.dicomSet=handles.dicomSetSorted; % with this sentence we could avoid the very previous sorting... for i = 1:handles.N str = sprintf('Copying dicom...\n%d / %d\n', i, handles.N); set(handles.info_text, 'String', str); drawnow % This replace the Given Name by the patient inicials and kernel info if get(handles.radiobutton_sharp,'Value') handles.dicomSet(i).PatientName.GivenName = ... [handles.dicomSet(i).PatientName.FamilyName(1),... handles.dicomSet(i).PatientName.GivenName(1), '_bone']; elseif get(handles.radiobutton_smooth,'Value') handles.dicomSet(i).PatientName.GivenName = ... [handles.dicomSet(i).PatientName.FamilyName(1),... handles.dicomSet(i).PatientName.GivenName(1), '_soft']; else handles.dicomSet(i).PatientName.GivenName = ... [handles.dicomSet(i).PatientName.FamilyName(1),... handles.dicomSet(i).PatientName.GivenName(1)]; end % This replace the Given Name by the IPP handles.dicomSet(i).PatientName.FamilyName = [patient, '_',... handles.dicomSet(1).PatientID]; % Importation dicomwrite(handles.dicomSet(i).data,[outputdir, newname,... sprintf('%04d',i), '.dcm'], handles.dicomSet(i)); end guidata(hObject,handles); % Create readme.txt file if it does not exist already localDir = string(get(handles.output_dir,'String')); localDir = sprintf('%s%s%s%s%s%s',localDir,'/', patient,'-', handles.dicomSet(1).PatientID,'/'); if ~exist(sprintf('%s%s',localDir,'readme.txt'), 'file') readme_file=fopen(sprintf('%s%s',localDir,'readme.txt'), 'wt'); fprintf(readme_file,sprintf('%s%s%s %s %s\r\n',patient,'-',... handles.dicomSet(1).PatientID,handles.initials_text.String,... handles.shoulder_side)); fprintf(readme_file,sprintf('\r\n%s',get(handles.text_readme,'String'))); fclose(readme_file); else readme_file=fopen(sprintf('%s%s',localDir,'readme.txt'), 'at'); % opening to append text fprintf(readme_file,sprintf('\r\n%s',get(handles.text_readme,'String'))); fclose(readme_file); end set(handles.info_text, 'String', 'CT Imported'); guidata(hObject,handles); 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 guidata(hObject,handles); function CTlabel_text_Callback(hObject, eventdata, handles) % hObject handle to CTlabel_text (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 CTlabel_text as text % str2double(get(hObject,'String')) returns contents of CTlabel_text as a double update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); function output_dir_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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 guidata(hObject,handles); %% --- Executes during object creation, after setting all properties. function info_text_CreateFcn(hObject, eventdata, handles) % hObject handle to info_text (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles empty - handles not created until after all CreateFcns called guidata(hObject,handles); %% --- Executes during object creation, after setting all properties. function DicomInfoBox_CreateFcn(hObject, eventdata, handles) % hObject handle to info_text (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles empty - handles not created until after all CreateFcns called guidata(hObject,handles); %% --- Executes during object creation, after setting all properties. function slider_3Dviewer_CreateFcn(hObject, eventdata, handles) % hObject handle to slider_3Dviewer (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 guidata(hObject,handles); %% --- Executes on button press in DicomDetails. function DicomDetails_Callback(hObject, eventdata, handles) % hObject handle to DicomDetails (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) try load tempInfo.mat tempInfo handles.DicomInfoBox.String = ['Dicom Info: ',... 'Patient Sex: ', handles.dicomSet(1).PatientSex,... '. Birthday: ', handles.dicomSet(1).PatientBirthDate]; catch exception "Dicom info not available" end guidata(hObject,handles); -% function checkbox_IPP_Callback(hObject, eventdata, handles) -% %% --- Executes on button press in checkbox_IPP. -% % hObject handle to checkbox_IPP (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 checkbox_IPP -% if get(hObject,'Value') %the IPP must be added to the readme line -% try -% handles.readme.IPP = string(handles.sCase); -% ['IPP info to be added to the Readme line := ' handles.readme.IPP] -% catch except -% sprintf('%s%s','Error identifier: ',except.identifier) -% ['IPP info failed to be added to the Readme line.'] -% handles.readme.IPP = ''; -% end -% else -% handles.readme.IPP = ''; -% ['IPP info not present in Readme line'] -% end -% % update_infoFields(handles); -% update_readme_Callback(handles.update_readme,eventdata,handles); -% guidata(hObject,handles); - - -function checkbox_CTdate_Callback(hObject, eventdata, handles) %% --- Executes on button press in checkbox_CTdate. +function checkbox_CTdate_Callback(hObject, eventdata, handles) % hObject handle to checkbox_CTdate (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 checkbox_CTdate if get(hObject,'Value') %the IPP must be added to the readme line try handles.readme.CTdate = string(handles.myinfo(1).AcquisitionDate); ['CTdate info to be added to the Readme line := ' handles.readme.CTdate] catch except sprintf('%s%s','Error identifier: ',except.identifier) ['CTdate info failed to be added to the Readme line.'] handles.readme.CTdate = ''; end else handles.readme.CTdate = ''; ['CTdate info not present in Readme line'] end -% update_infoFields(handles); update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); -function checkbox_resolution_Callback(hObject, eventdata, handles) %% --- Executes on button press in checkbox_resolution. +function checkbox_resolution_Callback(hObject, eventdata, handles) % hObject handle to checkbox_resolution (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 checkbox_resolution if get(hObject,'Value') %the IPP must be added to the readme line try handles.readme.resolution = string(handles.myinfo(1).PixelSpacing(1)); ['Resolution info to be added to the Readme line := ' handles.readme.resolution] catch except sprintf('%s%s','Error identifier: ',except.identifier) ['Resolution info failed to be added to the Readme line.'] handles.readme.resolution = ''; end else handles.readme.resolution = ''; ['Resolution info not present in Readme line'] end % update_readme_line(handles) % update_infoFields(handles); update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); -function radiobutton_shoulder_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_shoulder. +function radiobutton_shoulder_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_shoulder (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 radiobutton_shoulder handles.body = 'Shoulder'; set(handles.checkbox_contralateral,'Value',0); handles.contralateral=''; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function radiobutton_elbow_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_elbow. +function radiobutton_elbow_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_elbow (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 radiobutton_elbow handles.body = 'Elbow'; set(handles.checkbox_contralateral,'Value',0); handles.contralateral=''; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function radiobutton_thorax_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_thorax. +function radiobutton_thorax_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_thorax (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 radiobutton_thorax handles.body = 'Thorax'; set(handles.checkbox_contralateral,'Value',0); handles.contralateral=''; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function checkbox_contralateral_Callback(hObject, eventdata, handles) %% --- Executes on button press in checkbox_contralateral. +function checkbox_contralateral_Callback(hObject, eventdata, handles) % hObject handle to checkbox_contralateral (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 checkbox_contralateral if get(handles.checkbox_contralateral,'Value') handles.contralateral = ['Contralateral ']; else handles.contralateral = ''; end update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function radiobutton_otherBody_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_otherBody. +function radiobutton_otherBody_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_otherBody (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 radiobutton_otherBody set(handles.checkbox_contralateral,'Value',0); handles.contralateral=''; set(handles.edit_otherBody,'Enable','On'); handles.body = get(handles.edit_otherBody,'String'); update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); function edit_otherBody_Callback(hObject, eventdata, handles) %% % hObject handle to edit_otherBody (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 edit_otherBody as text % str2double(get(hObject,'String')) returns contents of edit_otherBody as a double handles.body = get(handles.edit_otherBody,'String'); guidata(hObject,handles); -function radiobutton_sharp_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_sharp. +function radiobutton_sharp_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_sharp (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 radiobutton_sharp -% -% if get(hObject,'Value') -% set(handles.radiobutton_none,'Value',0); -% set(handles.radiobutton_smooth,'Value',0); -% else -% set(hObject,'Value',1); -% end + handles.kernel='Sharp'; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function radiobutton_smooth_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_smooth. +function radiobutton_smooth_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_smooth (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 radiobutton_smooth -% -% if get(hObject,'Value') -% set(handles.radiobutton_none,'Value',0); -% set(handles.radiobutton_sharp,'Value',0); -% else -% set(hObject,'Value',1); -% end handles.kernel='Smooth'; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); -function radiobutton_none_Callback(hObject, eventdata, handles) %% --- Executes on button press in radiobutton_none. +function radiobutton_none_Callback(hObject, eventdata, handles) % hObject handle to radiobutton_none (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 radiobutton_none handles.kernel='none'; warndlg(char("IMPORTANT: The operator must choose between 'Sharp (Bone)' or 'Smooth (Soft Tissue)'")); update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); function checkbox_with_phantoms_Callback(hObject, eventdata, handles) %% --- Executes on button press in checkbox_with_phantoms. % hObject handle to checkbox_with_phantoms (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 checkbox_with_phantoms handles.phantoms = 'with'; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); function radiobutton_without_phantoms_Callback(hObject, eventdata, handles) % --- Executes on button press in radiobutton_without_phantoms. % hObject handle to radiobutton_without_phantoms (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 radiobutton_without_phantoms handles.phantoms = 'without'; update_readme_Callback(handles.update_readme, eventdata, handles); guidata(hObject,handles); function edit_extra_Callback(hObject, eventdata, handles) %% % hObject handle to edit_extra (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 edit_extra as text % str2double(get(hObject,'String')) returns contents of edit_extra as a double handles.extra_info=handles.edit_extra.String; update_readme_Callback(handles.update_readme,eventdata,handles); guidata(hObject,handles); -function update_readme_Callback(hObject, eventdata, handles) %% --- Executes on button press in update_readme. +function update_readme_Callback(hObject, eventdata, handles) % hObject handle to update_readme (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) if ~isfield(handles,'shoulder_side') handles.shoulder_side = '*Sh_Side*'; end if ~isfield(handles,'acquisition_stage') handles.acquisition_stage = '*Pre-o-Post_op*'; end -% handles.patient_num always exists -% handles.IPP_text -% handles.CTdate_text -% handles.Resolution_text -% handles.CTlabel_text + if ~isfield(handles,'body') handles.body = '*body*'; end if ~isfield(handles,'kernel') handles.kernel = '*kernel*'; end if ~isfield(handles,'phantoms') handles.phantoms = '*phantoms*'; end if ~isfield(handles,'contralateral') handles.contralateral = ''; end if ~isfield(handles,'N') handles.N = 0; end -% choosing handles.CTlabel_text +% choosing handles.CTlabel_text from radiobutton option chosen by user switch handles.acquisition_stage case 'Preop' if ~get(handles.checkbox_contralateral,'Value') switch handles.body case 'Shoulder' switch handles.phantoms case 'without' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-1'; case 'Smooth' handles.CTlabel_text.String = '-2'; otherwise handles.CTlabel_text.String = '-xxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end case 'with' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-3'; case 'Smooth' handles.CTlabel_text.String = '-4'; otherwise handles.CTlabel_text.String = '-xxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end otherwise handles.CTlabel_text.String = '-xxx'; end case 'Elbow' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-5'; case 'Smooth' handles.CTlabel_text.String = '-6'; otherwise handles.CTlabel_text.String = '-xxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end case 'Thorax' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-7'; case 'Smooth' handles.CTlabel_text.String = '-8'; otherwise handles.CTlabel_text.String = '-xxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end otherwise handles.CTlabel_text.String = '-x11'; end else switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-9'; case 'Smooth' handles.CTlabel_text.String = '-10'; otherwise handles.CTlabel_text.String = '-xxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end end case 'Postop' if ~get(handles.checkbox_contralateral,'Value') switch handles.body case 'Shoulder' switch handles.phantoms case 'without' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-p1'; case 'Smooth' handles.CTlabel_text.String = '-p2'; otherwise handles.CTlabel_text.String = '-pxxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end case 'with' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-p3'; case 'Smooth' handles.CTlabel_text.String = '-p4'; otherwise handles.CTlabel_text.String = '-pxxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end otherwise handles.CTlabel_text.String = '-pxxx'; end case 'Elbow' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-p5'; case 'Smooth' handles.CTlabel_text.String = '-p6'; otherwise handles.CTlabel_text.String = '-pxxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end case 'Thorax' switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-p7'; case 'Smooth' handles.CTlabel_text.String = '-p8'; otherwise handles.CTlabel_text.String = '-pxxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end otherwise handles.CTlabel_text.String = '-px11'; end else switch handles.kernel case 'Sharp' handles.CTlabel_text.String = '-p9'; case 'Smooth' handles.CTlabel_text.String = '-p10'; otherwise handles.CTlabel_text.String = '-pxxx'; warndlg('Operator must choose between Sharp or Smooth kernel'); end end otherwise handles.CTlabel_text.String = '-xxx'; end RL = sprintf('%s%s%s%s%s %s %s %s %s %s %s %s %s ','CT-',... handles.patient_num.String,'-',... handles.IPP_text.String,... handles.CTlabel_text.String,... handles.contralateral,... handles.body,... handles.kernel,... handles.phantoms,'phantoms',... handles.Resolution_text.String,... string(handles.N),'slices',... handles.extra_info); set(handles.text_readme,'String',RL); guidata(hObject,handles); -function add_SCase_toDB_Callback(hObject, eventdata, handles) %% --- Executes on button press in add_SCase_toDB. +function add_SCase_toDB_Callback(hObject, eventdata, handles) % hObject handle to add_SCase_toDB (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) % This function writes a new line inside the file "SCaseImport.xlsx" (F1) % and launches the script "dicominfoSCase.m" (F2) which read one CT from % SCase present in the Database to fill the corresponding DICOM parameters % in the main Excel File "ShoulderDataBase.xlsx" %% F1 - writting the new line inside the file "SCaseImport.xlsx" % Collecting new row parameters and translation to Excel compatible format try new_SCase_ID = {handles.patient_num.String}; IPP_number = str2num(handles.IPP_text.String); Initials={extractBefore(extractAfter(handles.initials_text.String,'('),')')}; BirthDate = {[handles.birthdate_text.String(1:4) ... '-' handles.birthdate_text.String(5:6) ... '-' handles.birthdate_text.String(7:8)]}; Patient_Gender = handles.gender_text.String; Shoulder_Side = handles.shoulder_side(1); CT_date = {[handles.CTdate_text.String(1:4) ... '-' handles.CTdate_text.String(5:6) ... '-' handles.CTdate_text.String(7:8)]}; %handles.dicomSet(1).AcquisitionDate; - % Question dialogo box to allow the user to validate the data or abort + % Question dialog box to allow the user to validate the data or abort quest = sprintf('%s:\n%s, %s, %s, %s,\n%s, %s, %s, %s.\n %s',... "Please confirm if you want to add the followin line to the Excel Database",... string(new_SCase_ID),'SCase_idx*',string(IPP_number),... string(Initials),string(BirthDate),string(Patient_Gender),... string(Shoulder_Side),string(CT_date),'* to be determined while updating'); btn1 = 'Confirm'; btn2 = 'Abort'; defbtn = 'Abort'; catch error % A comun error arrives when the function is launched before selecting % every option in the script graphical interface warndlg("Some parameters are maybe missing. Action Aborted") error return end % The file SCaseImport.xlsx must be located inside the subfolder % xlsFromMatlab/ which is in the same directory of the main Database Excel try % If the Excel directory has been correctly stored before, we can % find the excel file without problems and continue if isfield(handles,'ExcelDB_path') ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"SCaseImport.xlsx"); sprintf("%s%s", "Writting in file ", ExcelImport_fullname) title = sprintf('%s %s','Adding new SCase row to Excel file:',ExcelImport_fullname); % A dialog box opens to let the operator validate the data answer = questdlg(quest,title,btn1,btn2,defbtn); % Else, if at least the "data" or "dataDev" directory has been stored, % we can still find the excel file and continue elseif isfield(handles,'data_dir') title = sprintf('%s %s','Adding new SCase row to Excel file:','FILE NOT FOUND'); % A dialog box opens to let the operator validate the data answer = questdlg(quest,title,btn1,btn2,defbtn); [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',handles.data_dir); handles.ExcelDB_path = DBpath; -% ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"xlsFromMatlab/SCaseImport.xlsx"); ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"SCaseImport.xlsx"); sprintf("%s%s", "Writting in file ", ExcelImport_fullname) else title = sprintf('%s %s','Adding new SCase row to Excel file:','FILE NOT FOUND'); quest = sprintf('%s \n%s','WARNING abort action recommended. Any DICOM info loaded.',quest); answer = questdlg(quest,title,btn1,btn2,defbtn); [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',pwd); handles.ExcelDB_path = DBpath; -% ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"xlsFromMatlab/SCaseImport.xlsx"); ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"SCaseImport.xlsx"); sprintf("%s%s", "Writting in file ", ExcelImport_fullname) end catch error errordlg("database directory not found"); end switch answer case 'Abort' warningdlg("Action aborted"); case 'Confirm' % Text info to the command window info=sprintf('%s:\n%s, %s, %s, %s,\n%s, %s, %s, %s %s.',... "The parameters to write in the Excel Database are",... "SCase_ID","SCase_idx","anonymity_IPP","anonymity_initials",... "anonymity_bithDate","patient_gender","shoulder_side",'and',"CT_date") set(handles.info_add_SCase,'String',info) % In order to avoid the repetition of any SCase we have to read and check % the SCaseImport.xlsx file: [~,txt,raw,dates]=xlsread(ExcelImport_fullname,'SCaseImport','','',@convertSpreadsheetExcelDates); % Looking for the "desired new SCase" in the existing list of SCases of % SCaseImport.xlsx % Removing void rows raw=raw(find(string(raw(:,1))~=''),:); % If there is no coincidence we set the desired SCase as a new one and can % allow to write the new line of Importation Parameters. Otherwise we will ask % to overwrite the existing line of parameters or doing nothing. found_SCase_Index = find(string(raw(:,1))==handles.patient_num.String); new_SCase_Index = handles.DB_length +2; %length(string(raw(:,1)))+1; if isempty(found_SCase_Index) SCase_Index = new_SCase_Index; else question = "The chosen SCase already exist. Do you want to overwrite it?"; title = "WARNING: SCase repeated"; btn1 = "Cancel"; btn2 = "Overwrite"; dfltbtn = "Cancel"; answer = questdlg(question,title,btn1,btn2,dfltbtn); % Handle response switch answer case "Overwrite" SCase_Index = found_SCase_Index case "Cancel" disp("new SCase parameter line aborted") return; otherwise disp("new SCase parameter line aborted") return; end end rowNumber=SCase_Index; -% % Wrtitting SCase_ID -% SCaseCell=sprintf('%s%i','A',rowNumber); -% % Security Check: avoiding doubling SCases -% if isempty(find(string(handles.data.SCase_ID)==string(new_SCase_ID))) -% "SCase_ID written" -% xlswrite(ExcelImport_fullname,new_SCase_ID,'SCaseImport',SCaseCell) -% else -% -% "The selected SCase_ID is already present in the Database" -% return -% end + % Writting SCase_ID "Writting SCase_ID" SCaseCell = sprintf('%s%i','A',rowNumber); xlswrite(ExcelImport_fullname,new_SCase_ID,'SCaseImport',SCaseCell) % Writting SCase_idx "Writting SCase_idx" IdxCell = sprintf('%s%i','B',rowNumber); xlswrite(ExcelImport_fullname,rowNumber,'SCaseImport',IdxCell) % Writting anonymity_IPP IPPCell=sprintf('%s%i','C',rowNumber); xlswrite(ExcelImport_fullname,IPP_number,'SCaseImport',IPPCell) % Writting anonymity_initials InitialsCell=sprintf('%s%i','D',rowNumber); xlswrite(ExcelImport_fullname,Initials,'SCaseImport',InitialsCell) % Writting anonymity_birthDate BirthDateCell=sprintf('%s%i','E',rowNumber); xlswrite(ExcelImport_fullname,BirthDate,'SCaseImport',BirthDateCell) % Writting patient_gender GenderCell = sprintf('%s%i','F',rowNumber); xlswrite(ExcelImport_fullname,Patient_Gender,'SCaseImport',GenderCell) % Writting shoulder_side ShSideCell = sprintf('%s%i','G',rowNumber); xlswrite(ExcelImport_fullname,Shoulder_Side,'SCaseImport',ShSideCell) % Writting CT_date CTdateCell = sprintf('%s%i','H',rowNumber); xlswrite(ExcelImport_fullname,CT_date,'SCaseImport',CTdateCell) %% launch 'dicominfoSCase.m' to update CTdicomInfo CSV and XLS - % currentFolder=pwd; cd([handles.script_dir '/..']) dicominfoSCase % Lauched without arguments to avoid current error (1'20") cd(handles.script_dir) msgbox("Operation completed!") otherwise warndlg("Action aborted"); end - - - - +%%*********************************************** %% Internal functions function update_readme_line(handles) %% -- Updates readme line % Complete the line to be writen in the readme file as: % CT-P454-513554-1 sharp shoulder without phantoms 0.351562 mm 137 slices try handles.readme.IPP = handle.myinfo.PatientID; handles.readme.CTdate = handles.myinfo.AcquisitionDate; handles.readme.CT_label = handles.CTlabel_text; if handles.radiobutton_sharp handles.readme.kernel = 'Sharp (BONE)'; elseif handles.radiobutton_smooth handles.readme.kernel = 'Smooth (SOFT)'; else handles.readme.kernel = 'None'; end handles.readme.line = ['CT-' handles.readme.IPP '-' handles.readme.CT_label... ' ' handles.readme.kernel ' ' handles.readme.body ' '... handles.readme.phantoms ' ' handles.readme.resolution ' ' ... handles.readme.slices ' ' handles.readme.stage ' ' handles.readme.extra... ' '] catch except sprintf('%s%s','Error identifier: ',except.identifier) 'fields lacking' end guidata(hObject,handles); function sortedST = sortStructByField(ST,field,handles) try "internal fuction activated" STfields = fieldnames(ST); STcell = struct2cell(ST); sz = size(STcell); % Notice that this is a 3 dimensional array. % For MxN structure array with P fields, the size % of the converted cell array is PxMxN % Once it's a cell array, you can sort using sortrows: % Convert to a matrix STcell = reshape(STcell, sz(1), []); % Px(MxN) % Make each field a column STcell = STcell'; % (MxN)xP column=0 + % Sort by field for k=1:length(STfields) if isequal(STfields(k),field) column = k; end end - % fieldName=char(STfields(1)) - % field=char(field) - % fieldColum = find(fieldName(1:length(field))==field) + if column == 0 ['Warning: field ' char(field) ' not found in dicom metadata'] sortedST = ST; return else fieldColumn = column; STcell = sortrows(STcell, fieldColumn); - - %And convert it back to a structure array: - + % And convert it back to a structure array: % Put back into original cell array format STcell = reshape(STcell', sz); % Convert to Struct STsorted = cell2struct(STcell, STfields, 1); sortedST = STsorted; end catch except except.identifier except.message ['exception arised during dicom sorting by field'] end - -% function update_infoFields(hObject, handles) -% %% Updating CT labelling -% % if and(get(handles.patient_num,'String') ~='P###',exist([handles.output_dir '/readme'])==2) -% % set(handles.text_readme, "listo para leer") -% % end -% guidata(hObject,handles); - - -%% Functions to be removed - +%% Functions to be removed or reused function slider_2Dcontrast_Callback(hObject, eventdata, handles) %% --- Executes on slider movement. % hObject handle to slider_2Dcontrast (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_2Dbrightness,'Max') + get(handles.slider_2Dbrightness,'Min') - get(handles.slider_2Dbrightness,'Value')) + (get(hObject,'Max') - get(hObject,'Value'))/2; handles.CTmin = (get(handles.slider_2Dbrightness,'Max') + get(handles.slider_2Dbrightness,'Min') - get(handles.slider_2Dbrightness,'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) function slider_2Dcontrast_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % hObject handle to slider_2Dcontrast (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 guidata(hObject,handles); function slider_2Dbrightness_Callback(hObject, eventdata, handles) %% --- Executes on slider movement. % hObject handle to slider_2Dbrightness (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_2Dcontrast,'Max')... - get(handles.slider_2Dcontrast,'Value'))/2; handles.CTmin = (get(hObject,'Max') + get(hObject,'Min')... - get(hObject,'Value')) - (get(handles.slider_2Dcontrast,'Max')... - get(handles.slider_2Dcontrast,'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); function slider_2Dbrightness_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % hObject handle to slider_2Dbrightness (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 guidata(hObject,handles);