diff --git a/Import/Shoulder_Database_Importation_Protocol_SOP.md b/ImportSCase/README.md similarity index 67% rename from Import/Shoulder_Database_Importation_Protocol_SOP.md rename to ImportSCase/README.md index eb48950..3c92272 100644 --- a/Import/Shoulder_Database_Importation_Protocol_SOP.md +++ b/ImportSCase/README.md @@ -1,267 +1,272 @@ # 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 to four SCases. Besides, a SCase may be associated with several CT scans, for the same patient and shoulder. + * 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 of this database. - - **Pathological (P)**: CTs from patient with shoulder pathology. Those CT scans are aimed to analyze the shoulder anatomy and can contain several CT sets with different acquisition parameters. + - **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. + - **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. + - **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. Phantoms are typically placed behind the back of the patient and are used to calibrate the measurement of the bone mineral density. At least 2 phantoms are needed for an accurate calibration. + - 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 + * 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 *Import* (to be renamed ImportSCase), and should be executed from there. + * 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 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 pushed) + * 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 by 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. +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 you received the archive + * */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 you received the archive + */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/Import* (**this directory will change to** ***shoulder/method/database/ImportSCases***) +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, you might skip this step. +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. + - 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”*. Select only one DICOM file to automatically select all of them. - - A bar-progress window will be prompted showing the loading progress. Don't close it before the process is finished, because this will stop the loading and you will need to start again this *step3*. + - 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 full-name of the current slice. + - 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 happens again it means that there are 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. 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*. + - 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 the error is still present it might be necessary to use Amira to import this CT set, but it could be also due to an error made in the reconstruction step. - - Check if it opens correctly in Amira but if it creates several series of images and you cannot identify any of them as relevant it might be better to avoid the CT set. Eventually, shall the CT provider be informed. + - 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, (if the importation software doesn't have this option choose *Right* shoulder and update manually the Excel file *ShoulderDataBase.xlsx*). - - **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* + - 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 launch the database auto-check for IPP coincidences. +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 results will be shown in the *DB check result* information box. - - Depending on the auto-check results, a *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*. + - 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. Check in the readme line below the 2D viewer that all the options selected are correct and click on button **Import to Database**. - - If the imported CT is meant to be the first CT of a new SCase in the database, this will create a new folder in the directory indicated in the text field *Parent output directory* with 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*. +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 the *README* line with 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* file will need to be corrected. - - The anonymized DICOM images will be finally stored inside the directory like ***shoulder/data/P/5/5/P555-918819/CT-P555-229341-1/dicom***. + - 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 Excel Database with almost all the DICOM information from the imported SCase. However, this parameter (SCaseID) have to be manually added to the Excel Database: +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). - - the SCaseID of the last entry in the database for your case type (N or P) and increment by 1 to get the current SCaseID (g.e. if the last entry in database for *P* SCases is *P284*, current SCaseID will be *P285*”). - - Scroll down to find the last SCaseID (last row) and add a new row by taping the new SCaseID in the cell below the last SCaseID (first column). This automatically add a new row to the table, and copy formulas. Then: - - In the second column introduce the row number by simply increasing the number from the immediately superior row. - - From the *DICOM Metadata box* check that the following data are correctly in their corresponding columns of the excel file ***ShoulderDataBase.xlsx***: + - 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 - - Check also the shoulder side column *shoulder_side*, that should contain *R*, *L* or *B*, accordingly with your selection in *step5*. - - Check that the rest of the columns are filled with data as in the precedents rows. But if you find some columns with *NaN* values: - - close the Excel File without saving, - - try to run manually the script *dicominfoSCase.m* without arguments, which is located in *shoulder/methods/matlab/database*. This script updates the new row in the Database with metadata from the imported CT. - - try to solve the errors if some others appear. - - open the Excel file and start this *step16* again. - - Save and close the Excel file. + - 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. + - 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*. -8. 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*. +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*. -9. Save the series with the name *[CaseID]-[IPP]_[PatientInitials]-[bone/smooth].0*, e.g. *P123-456789_XY.0*. +8. Save the series with the name *[CaseID]-[IPP]_[PatientInitials]-[bone/smooth].0*, e.g. *P123-456789_XY.0*. -10. In the next window, replace the patient’s name by *[CaseID]_[IPP]–[PatientInitials]*. +9. In the next window, replace the patient’s name by *[CaseID]_[IPP]–[PatientInitials]*. -11. If the error: *Non-uniform coordinates not supported* appears, you will need to convert your data to uniform coordinates before exporting to DICOM. +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). - - Click on *Apply*. - The result is a new uniform coordinate data object. - - Save the result as described in points 7 to 10. + - Save the result as described in points 7 to 9. + - (**WHAT?? HOW TO APPLY THE RESULTING COORDINATE DATA OBJECT**) -12. Rename the folder *CT-###-IPP-xxx* following the classification presented at the beginning of this document. +11. Rename the folder *CT-###-IPP-xxx* following the classification presented at the beginning of this document. -13. This step is similar to the step16 of the Matlab SOP, described in the previous section *2. Matlab interface for SCase anonymization and importation* +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* -14. Continue with the next CT set. +13. Continue with the next CT set. diff --git a/ImportSCase/README.pdf b/ImportSCase/README.pdf new file mode 100644 index 0000000..8dc87c0 Binary files /dev/null and b/ImportSCase/README.pdf differ diff --git a/ImportSCase/Reporting_student_project_work_PC.md b/ImportSCase/Reporting_student_project_work_PC.md new file mode 100644 index 0000000..fc3ace8 --- /dev/null +++ b/ImportSCase/Reporting_student_project_work_PC.md @@ -0,0 +1,27 @@ +# Reporting Paul Cimadomo (PC) work + +## 2019-02-19, by JSM +- JSM introduced the ImportSCases.m script to PC, starting at 10:30 +- The Matlab version found in student computer was 2017b, older than the 2018b in the computer used for development. This could be the reason of an error that raised when running the last version of script *dicominfoSCase.m*. The same script works in Matlab2018b, while in Matlab2017b it produces the following error: + + Error using dialogCellstrHelper + Expected input to be one of these types: char, cell + Instead its type was string. + Error in dialogCellstrHelper (line 10) + validateattributes(inputStr, {'char','cell'}, {'2d'},mfilename); + Error in errordlg (line 45) + ErrorStringCell = dialogCellstrHelper(ErrorStringIn); + Error in dicominfoSCase (line 89) + errordlg(sprintf("%s\n%s",... + - After PC left the office I will install the last version of Matlab2018b + + +- PC worked with a previous version of *ImportScase.m*, from 2019-2-14 and the SOP validated by JSM and AT at 2019-02-18. + - In this version the operator (PC) need to write some parameters manually in the Excel Database and run the script *dicominfoSCase.m* for each new SCase. +- PC imported 11 Normal SCases in its first working day. Leaving the office at 16:00. +- JSM run *dicominfoSCase.m* with Matlab2018b at the end of the day to update the Excel Database. +- 19 + 17 = 36 normal SCases are still waiting to be imported in 2 pending folders: + - *lbovenus.epfl.ch\data\N\pending\N_update-2019-01-30\Normal 2016 - Partie 1\* + - *lbovenus.epfl.ch\data\N\pending\N_update-2019-01-30\Normal 2016 - Partie 2\* +- PC proposed to continue working the friday of the current week 2019-02-22 to finish the normal SCases. +- JSM will provide the last version of *ImportSCase.m* and *dicominfoSCase.m*, after installation of Matlab2018b and testing in the student computer. diff --git a/ImportSCase/config.txt.txt b/ImportSCase/config.txt.txt new file mode 100644 index 0000000..e69de29 diff --git a/ImportSCase/importSCase.fig b/ImportSCase/importSCase.fig new file mode 100644 index 0000000..5af7cd9 Binary files /dev/null and b/ImportSCase/importSCase.fig differ diff --git a/Import/importSCase.m b/ImportSCase/importSCase.m similarity index 91% copy from Import/importSCase.m copy to ImportSCase/importSCase.m index 5dda0f8..46c2a46 100644 --- a/Import/importSCase.m +++ b/ImportSCase/importSCase.m @@ -1,2148 +1,2218 @@ 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 % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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 % 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' 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='.'; guidata(hObject,handles); 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. function patient_num_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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 guidata(hObject,handles); function edit_otherBody_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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 guidata(hObject,handles); function edit_extra_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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); 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) %% --- Executes on button press in Select_Pending_Directory. % 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 %% Choose the main folder for the desired case if isfield(handles,'home_dir') newPath = uigetdir(handles.home_dir,'Chose the folder containing the all the CT sets'); else newPath = uigetdir(pwd,'Chose the folder containing all the CT sets'); end % handles.patient_dir = newPath; current_dir = pwd; cd([newPath '/..']); 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; %% Set the patient group from directory patient_group = newPath; patient_group = extractAfter(patient_group,'\data'); patient_group = extractAfter(patient_group,'\'); patient_group = extractBefore(patient_group,'\'); handles.patient_group = patient_group; 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)]); %% Select a limit of minimun dicom files to discard scout scans & other % small scans as elbows scoutScanLimit = 30; smallScanLimit = 90; %% Check the given path and extract data from every folder and file inside dirList = dir(newPath); % Select only the folders from the current path --> folderList 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) wb = waitbar(0,'Checking folders size'); for k=1:folderN % will contain a struct with data of files in the desired % folder waitbar(k/folderN,wb,sprintf('%s%s%s%i%s%i',"Checking directory ",... string(folderList(k).name),": ",k,"/",folderN)); 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 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 files(n).isdicom = 0; end end % Each folder has it own counter of dicom files folderList(k).dicomN = filesDicom; % We add a prefix with the number of files per CT, helping to priorize % 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 % 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 %% end % Makes handles variable visible to other functions in this script % hanldes.dicomdir = newPath; + +set(handles.text_data_dir,'String',... + sprintf('%s%s','Database directory ',string(handles.data_dir))); guidata(hObject,handles); %% ************** Load next CT set ************** function Select_CT_Scan_Callback(hObject, eventdata, handles) %% --- Executes on button press in Select_CT_Scan. % 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?) 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 if isfield(handles,'patient_dir') [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',handles.patient_dir,'MultiSelect', 'on'); 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); 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 [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 ~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; 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 if length(handles.dicomlist) 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',strcat(string(handles.dicomdir), string(handles.dicomlist(1)))); +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 % 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 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)))); if isfield(handles,'mydicom') handles=rmfield(handles,'mydicom'); end handles.mydicom(:,:,handles.slice) = handles.dicomSet(handles.slice).data; 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]); imshow(handles.dicomSet(handles.slice).data, [handles.CTmin handles.CTmax]); %% ISOSURFACE section ==================================================== act3Dview_Callback(handles.act3Dview, eventdata, handles); axes(handles.axes_2Dviewer); % Set current axes 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\'); +% 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); 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,'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 sCase_list(end,:)=="" % Last position empty... +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,:)~="" + 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); -handles.data.shoulder_side=cell2mat(handles.data.shoulder_side); +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. % 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 % 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',string(handles.dicomdir),string(handles.dicomlist(handles.slice_num)))); +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. % 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 any patient number is found +% 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 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); + 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 + 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 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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 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. % 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) -% colecting new row parameters +% 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; - quest = sprintf('%s:\n%s, %s, %s, %s,\n%s, %s, %s.\n %s',... + % Question dialogo 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),'* to be determined while updating'); + string(Shoulder_Side),string(CT_date),'* to be determined while updating'); btn1 = 'Confirm'; btn2 = 'Abort'; defbtn = 'Abort'; -catch - warndlg("Some parameters are missing. Action Aborted") + +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 isfield(handles,'ExcelDB_path') - ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"xlsFromMatlab/SCaseImport.xlsx"); + % 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,"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,"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.',"The parameters to write in the Excel Database are",... + 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",'and',"shoulder_side") + "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 = length(string(raw(:,1)))+1; + 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; + SCase_Index = found_SCase_Index case "Cancel" disp("new SCase parameter line aborted") return; otherwise disp("new SCase parameter line aborted") return; end end - % I don't know why this writting is taking a wrong ROW + 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 +% % 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!. Remember to add manually SCase_ID and SCase_idx") + msgbox("Operation completed!") otherwise - warningdlg("Action aborted"); + 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: % 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 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); diff --git a/Import/importSCase.m b/ImportSCase/importSCaseDev.m similarity index 80% rename from Import/importSCase.m rename to ImportSCase/importSCaseDev.m index 5dda0f8..7cb93eb 100644 --- a/Import/importSCase.m +++ b/ImportSCase/importSCaseDev.m @@ -1,2148 +1,1970 @@ -function varargout = importSCase(varargin) -% IMPORTSCASE MATLAB code for importSCase.fig -% IMPORTSCASE, by itself, creates a new IMPORTSCASE or raises the existing +function varargout = importSCaseDev(varargin) +% IMPORTSCASEDEV MATLAB code for importSCaseDev.fig +% IMPORTSCASEDEV, by itself, creates a new IMPORTSCASEDEV or raises the existing % singleton*. % -% H = IMPORTSCASE returns the handle to a new IMPORTSCASE or the handle to +% H = IMPORTSCASEDEV returns the handle to a new IMPORTSCASEDEV 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. +% IMPORTSCASEDEV('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in IMPORTSCASEDEV.M with the given input arguments. % -% IMPORTSCASE('Property','Value',...) creates a new IMPORTSCASE or raises the +% IMPORTSCASEDEV('Property','Value',...) creates a new IMPORTSCASEDEV or raises the % existing singleton*. Starting from the left, property value pairs are -% applied to the GUI before importSCase_OpeningFcn gets called. An +% applied to the GUI before importSCaseDev_OpeningFcn gets called. An % unrecognized property name or invalid value makes property application -% stop. All inputs are passed to importSCase_OpeningFcn via varargin. +% stop. All inputs are passed to importSCaseDev_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 +% Edit the above text to modify the response to help importSCaseDev -% Last Modified by GUIDE v2.5 20-Feb-2019 10:59:13 +% Last Modified by GUIDE v2.5 15-Jan-2019 16:51:28 %% 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_OpeningFcn', @importSCaseDev_OpeningFcn, ... + 'gui_OutputFcn', @importSCaseDev_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 % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -function importSCase_OpeningFcn(hObject, eventdata, handles, varargin) -%% --- Executes just before importSCase is made visible. +function importSCaseDev_OpeningFcn(hObject, eventdata, handles, varargin) +%% --- Executes just before importSCaseDev 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) +% varargin command line arguments to importSCaseDev (see VARARGIN) %-------------------------------------------------------------------------- -% Choose default command line output for importSCase +% Choose default command line output for importSCaseDev handles.output = hObject; % Update handles structure guidata(hObject, handles); -% UIWAIT makes importSCase wait for user response (see UIRESUME) +% UIWAIT makes importSCaseDev wait for user response (see UIRESUME) % uiwait(handles.figure1); movegui('center') %-------------------------------------------------------------------------- %% Customized by JSM 2018/10/26 % 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); +cd([pwd '/../../../../']); +handles.home_path = pwd; +cd([pwd '/data/']); +handles.base_dir = 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' +set(handles.radiobutton_Pathologic,'Value',1); +handles.patient_group="P"; +% *handles.shoulder_side* => might be 'Right' or 'Left' 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='.'; guidata(hObject,handles); -function varargout = importSCase_OutputFcn(hObject, eventdata, handles) +function varargout = importSCaseDev_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. function patient_num_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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 guidata(hObject,handles); function edit_otherBody_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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 guidata(hObject,handles); function edit_extra_CreateFcn(hObject, eventdata, handles) %% --- Executes during object creation, after setting all properties. % 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); 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); +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)]); + %% ************** Start new Patient / sCase ************** -function Select_Pending_Directory_Callback(hObject, eventdata, handles) -%% --- Executes on button press in Select_Pending_Directory. -% hObject handle to Select_Pending_Directory (see GCBO) +function newPatient_Callback(hObject, eventdata, handles) +%% --- Executes on button press in newPatient. +% hObject handle to newPatient (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 +% Select a limit of minimun dicom files to discard scout scans & other +% small scans as elbows +scoutScanLimit = 30; +smallScanLimit = 90; -%% Choose the main folder for the desired case -if isfield(handles,'home_dir') - newPath = uigetdir(handles.home_dir,'Chose the folder containing the all the CT sets'); -else - newPath = uigetdir(pwd,'Chose the folder containing all the CT sets'); -end +% Choose the main folder for the desired case +newPath = uigetdir(handles.base_dir); % handles.patient_dir = newPath; current_dir = pwd; cd([newPath '/..']); 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; - - -%% Set the patient group from directory -patient_group = newPath; -patient_group = extractAfter(patient_group,'\data'); -patient_group = extractAfter(patient_group,'\'); -patient_group = extractBefore(patient_group,'\'); -handles.patient_group = patient_group; - -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)]); - - -%% Select a limit of minimun dicom files to discard scout scans & other -% small scans as elbows -scoutScanLimit = 30; -smallScanLimit = 90; -%% Check the given path and extract data from every folder and file inside +% Check the given path and extract data from every folder and file inside dirList = dir(newPath); % Select only the folders from the current path --> folderList 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) wb = waitbar(0,'Checking folders size'); for k=1:folderN % will contain a struct with data of files in the desired % folder waitbar(k/folderN,wb,sprintf('%s%s%s%i%s%i',"Checking directory ",... string(folderList(k).name),": ",k,"/",folderN)); 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 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 files(n).isdicom = 0; end end % Each folder has it own counter of dicom files folderList(k).dicomN = filesDicom; % We add a prefix with the number of files per CT, helping to priorize % 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 % 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 end % Makes handles variable visible to other functions in this script % hanldes.dicomdir = newPath; guidata(hObject,handles); %% ************** Load next CT set ************** -function Select_CT_Scan_Callback(hObject, eventdata, handles) -%% --- Executes on button press in Select_CT_Scan. -% hObject handle to Select_CT_Scan (see GCBO) +function Load_Next_CT_Callback(hObject, eventdata, handles) +%% --- Executes on button press in Load_Next_CT. +% hObject handle to Load_Next_CT (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; +% handles.base_dir = handles.patient_dir; % else -% handles.data_dir = handles.home_path; +% handles.base_dir = handles.home_path; % end % Also remove the fields 'myinfo' and 'mydicom' (they could be just % cleared?) 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 if isfield(handles,'patient_dir') [handles.dicomlist, handles.dicomdir] = uigetfile('*.*',... 'Select the images to load',handles.patient_dir,'MultiSelect', 'on'); -elseif isfield(handles,'data_dir') +elseif isfield(handles,'base_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); -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; + 'Select the images to load',handles.base_dir,'MultiSelect', 'on'); else [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 ~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; - - 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 - -if length(handles.dicomlist) - 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 + 'Select the images to load',home_path,'MultiSelect', 'on'); end % Showing the dicom name in the interface set(handles.text_dicom_name,'String',strcat(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 % 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 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)))); if isfield(handles,'mydicom') handles=rmfield(handles,'mydicom'); end handles.mydicom(:,:,handles.slice) = handles.dicomSet(handles.slice).data; 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]); imshow(handles.dicomSet(handles.slice).data, [handles.CTmin handles.CTmax]); %% ISOSURFACE section ==================================================== act3Dview_Callback(handles.act3Dview, eventdata, handles); axes(handles.axes_2Dviewer); % Set current axes 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', ''); 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) - +wb = waitbar(0,'Checking Excel Database'); +"Openning Excel Database" % 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"; -catch - [excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File',pwd);%handles.data_dir); -end - -wb = waitbar(0,'Checking Excel DataBase'); -"Openning Excel Database" +[excelDB, DBpath, ~] = uigetfile('*.xlsx','Select the Excel Database File'); 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); % 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 sCase_list(end,:)=="" % Last position empty... sCase_list = sCase_list(1:end-1,:); s0=s0+1; waitbar(s0/s1,wb,'Checking Excel Database'); end 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,:)~="" 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); handles.data.shoulder_side=cell2mat(handles.data.shoulder_side); % *** 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 +current_dir = cd(char(strcat(handles.base_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)); +% set(handles.output_dir,'String',strcat(handles.base_dir,F1,'/',F2,'/',F3));%sprintf('%s%s\%s\%s',base_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. -% hObject handle to Manual_SCase (see GCBO) +function manual_case_selection_Callback(hObject, eventdata, handles) +%% --- Executes on button press in manual_case_selection. +% hObject handle to manual_case_selection (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); +sCase_dir = uigetdir(handles.base_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 % 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',string(handles.dicomdir),string(handles.dicomlist(handles.slice_num)))); guidata(hObject,handles); -function Anonymise_Import_Callback(hObject, eventdata, handles) -%% --- Executes on button press in Anonymise_Import. -% hObject handle to Anonymise_Import (see GCBO) +function Import2database_Callback(hObject, eventdata, handles) +%% --- Executes on button press in Import2database. +% hObject handle to Import2database (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 any patient 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 if isempty(get(handles.output_dir,'String')) - outputdir = uigetdir(handles.home_path,"Select the output directory where the CT must be imported"); + outputdir = uigetdir; 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/'); 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 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'); +set(handles.info_text, 'String', 'Copied'); 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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. % 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 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); + string(handles.N),'slices'),... + get(handles.edit_extra,'String'); 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. -% hObject handle to add_SCase_toDB (see GCBO) +function add_newSCase_excelDB_Callback(hObject, eventdata, handles) +%% --- Executes on button press in add_newSCase_excelDB. +% hObject handle to add_newSCase_excelDB (see GCBO) % eventdata reserved - to be defined in a future version of MATLAB % handles structure with handles and user data (see GUIDATA) -% colecting new row parameters -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); - - quest = sprintf('%s:\n%s, %s, %s, %s,\n%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),'* to be determined while updating'); - btn1 = 'Confirm'; - btn2 = 'Abort'; - defbtn = 'Abort'; -catch - warndlg("Some parameters are missing. Action Aborted") - return -end +% Text info to the command window +sprintf('%s:\n%s, %s, %s, %s,\n%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") + % The file SCaseImport.xlsx must be located inside the subfolder % xlsFromMatlab/ which is in the same directory of the main Database Excel -try - if isfield(handles,'ExcelDB_path') - ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"xlsFromMatlab/SCaseImport.xlsx"); - sprintf("%s%s", "Writting in file ", ExcelImport_fullname) - title = sprintf('%s %s','Adding new SCase row to Excel file:',ExcelImport_fullname); - answer = questdlg(quest,title,btn1,btn2,defbtn); - elseif isfield(handles,'data_dir') - title = sprintf('%s %s','Adding new SCase row to Excel file:','FILE NOT FOUND'); - 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"); - 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"); - sprintf("%s%s", "Writting in file ", ExcelImport_fullname) +ExcelImport_fullname = sprintf('%s%s',handles.ExcelDB_path,"xlsFromMatlab/SCaseImport.xlsx"); +sprintf("%s%s", "Writting in file ", ExcelImport_fullname) + +% 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))~=''),:); +found_SCase_Index = find(string(raw(:,1))==handles.patient_num.String); +new_SCase_Index = length(string(raw(:,1)))+1; + +% If there is no coincidence we set the desired SCase as a new one and can +% allow to write the new line of Import Parameters. Otherwise we will ask +% to overwrite the existing line of parameters or doing nothing. +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 -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.',"The parameters to write in the Excel Database are",... - "SCase_ID","SCase_idx","anonymity_IPP","anonymity_initials",... - "anonymity_bithDate","patient_gender",'and',"shoulder_side") - 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 = 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 - % I don't know why this writting is taking a wrong ROW - 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_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) - - %% 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!. Remember to add manually SCase_ID and SCase_idx") - otherwise - warningdlg("Action aborted"); +% I don't know why this writting is taking a wrong ROW +rowNumber=SCase_Index; +new_SCase_ID = {handles.patient_num.String}; +% 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 - - - +% Wrtitting SCase_idx +IdxCell=sprintf('%s%i','B',rowNumber); +xlswrite(ExcelImport_fullname,rowNumber,'SCaseImport',IdxCell) +% Wrtitting anonymity_IPP +IPP_number = str2num(handles.IPP_text.String); +IPPCell=sprintf('%s%i','C',rowNumber); +xlswrite(ExcelImport_fullname,IPP_number,'SCaseImport',IPPCell) +% Wrtitting anonymity_initials +Initials={extractBefore(extractAfter(handles.initials_text.String,'('),')')}; +InitialsCell=sprintf('%s%i','D',rowNumber); +xlswrite(ExcelImport_fullname,Initials,'SCaseImport',InitialsCell) +% Wrtitting anonymity_birthDate +BirthDate ={[handles.birthdate_text.String(1:4) ... + '-' handles.birthdate_text.String(5:6) ... + '-' handles.birthdate_text.String(7:8)]}; +BirthDateCell=sprintf('%s%i','E',rowNumber); +xlswrite(ExcelImport_fullname,BirthDate,'SCaseImport',BirthDateCell) +% Wrtitting patient_gender +GenderCell=sprintf('%s%i','F',rowNumber); +Patient_Gender = handles.gender_text.String; +xlswrite(ExcelImport_fullname,Patient_Gender,'SCaseImport',GenderCell) +% Wrtitting shoulder_side +ShSideCell=sprintf('%s%i','G',rowNumber); +Shoulder_Side = handles.shoulder_side(1); +xlswrite(ExcelImport_fullname,Shoulder_Side,'SCaseImport',ShSideCell) + +%launch 'dicominfoSCase.m' to update CTdicomInfo CSV and XLS +currentFolder=pwd; +cd([currentFolder '/..']) +dicominfoSCase % Lauched without arguments a avoid current error (1'20") +cd(currentFolder) +msgbox("Operation completed!. Remember to add manually SCase_ID and SCase_idx") %% 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: % 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 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); - diff --git a/Import/importSCase.fig b/ImportSCase/importSCaseb.fig similarity index 100% rename from Import/importSCase.fig rename to ImportSCase/importSCaseb.fig diff --git a/Import/sortStructByField.m b/ImportSCase/sortStructByField.m similarity index 100% rename from Import/sortStructByField.m rename to ImportSCase/sortStructByField.m diff --git a/ImportSCase/tempInfo.mat b/ImportSCase/tempInfo.mat new file mode 100644 index 0000000..f57e69f Binary files /dev/null and b/ImportSCase/tempInfo.mat differ diff --git a/LBODicomViewer.fig b/LBODicomViewer.fig new file mode 100644 index 0000000..300ce53 Binary files /dev/null and b/LBODicomViewer.fig differ diff --git a/LBODicomViewer.m b/LBODicomViewer.m new file mode 100644 index 0000000..0fd01d4 --- /dev/null +++ b/LBODicomViewer.m @@ -0,0 +1,104 @@ +function varargout = LBODicomViewer(varargin) +% LBODICOMVIEWER MATLAB code for LBODicomViewer.fig +% LBODICOMVIEWER, by itself, creates a new LBODICOMVIEWER or raises the existing +% singleton*. +% +% H = LBODICOMVIEWER returns the handle to a new LBODICOMVIEWER or the handle to +% the existing singleton*. +% +% LBODICOMVIEWER('CALLBACK',hObject,eventData,handles,...) calls the local +% function named CALLBACK in LBODICOMVIEWER.M with the given input arguments. +% +% LBODICOMVIEWER('Property','Value',...) creates a new LBODICOMVIEWER or raises the +% existing singleton*. Starting from the left, property value pairs are +% applied to the GUI before LBODicomViewer_OpeningFcn gets called. An +% unrecognized property name or invalid value makes property application +% stop. All inputs are passed to LBODicomViewer_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 LBODicomViewer + +% Last Modified by GUIDE v2.5 26-Mar-2018 14:01:57 + +% Begin initialization code - DO NOT EDIT +gui_Singleton = 1; +gui_State = struct('gui_Name', mfilename, ... + 'gui_Singleton', gui_Singleton, ... + 'gui_OpeningFcn', @LBODicomViewer_OpeningFcn, ... + 'gui_OutputFcn', @LBODicomViewer_OutputFcn, ... + 'gui_LayoutFcn', [] , ... + 'gui_Callback', []); +if nargin && ischar(varargin{1}) + gui_State.gui_Callback = str2func(varargin{1}); +end + +if nargout + [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); +else + gui_mainfcn(gui_State, varargin{:}); +end +% End initialization code - DO NOT EDIT + + +% --- Executes just before LBODicomViewer is made visible. +function LBODicomViewer_OpeningFcn(hObject, eventdata, handles, varargin) +% This function has no output args, see OutputFcn. +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) +% varargin command line arguments to LBODicomViewer (see VARARGIN) + +% Choose default command line output for LBODicomViewer +handles.output = hObject; + +% Update handles structure +guidata(hObject, handles); + +% UIWAIT makes LBODicomViewer wait for user response (see UIRESUME) +% uiwait(handles.figure1); + + +% --- Outputs from this function are returned to the command line. +function varargout = LBODicomViewer_OutputFcn(hObject, eventdata, handles) +% varargout cell array for returning output args (see VARARGOUT); +% hObject handle to figure +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Get default command line output from handles structure +varargout{1} = handles.output; + + +% --- Executes on button press in sourceFolder. +function sourceFolder_Callback(hObject, eventdata, handles) +% hObject handle to sourceFolder (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + "Button 'load' pushed" + sourceF = uigetdir('C:\') + [V,spatial,dim] = dicomreadVolume(sourceF); + v=squeeze(V); + volumeViewer(v) + + + +% --- Executes on button press in outputFolder. +function outputFolder_Callback(hObject, eventdata, handles) +% hObject handle to outputFolder (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + "Button 'Select Output directory' pushed" + outputF = uigetdir('C:\') + + +% --- Executes on button press in copy. +function copy_Callback(hObject, eventdata, handles) +% hObject handle to copy (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + get(handles.outputFolder) + diff --git a/README_importSCaseDev_fServer.md b/README_importSCaseDev_fServer.md new file mode 100644 index 0000000..77bbaac --- /dev/null +++ b/README_importSCaseDev_fServer.md @@ -0,0 +1,8 @@ +# Description of imporsCaseDev_fServer + +## Global Variables + - **handles.baseDir** = *.../shoulder/data/* + - **handles.patient_dir** = this is the new patient directory that contains the set of CTs to be imported + - **handles.dicomdir** -> directory of the last set of CTs loaded + - **handles.dicomSet** -> + - **handles._3Dview** -> logic variable to allow or deactivate the 3D Viewer diff --git a/ShoulderCase/@ShoulderCase/ShoulderCase.m~ b/ShoulderCase/@ShoulderCase/ShoulderCase.m~ new file mode 100644 index 0000000..6294f1d --- /dev/null +++ b/ShoulderCase/@ShoulderCase/ShoulderCase.m~ @@ -0,0 +1,467 @@ +classdef ShoulderCase < handle + % Properties and methods associated to the SoulderCase object. + + % Author: Alexandre Terrier, EPFL-LBO + % Creation date: 2018-07-01 + % Revision date: 2018-08-11 + + % TODO: + % Need method to load properties (from amira, matlab, excel, SQL) + % Need method to save properties (in matlab, excel, SQL) + % Remove datapath from constants + % + properties + id % id of the shoulder case, as it appears in the database (Pnnn) + diagnosis + treatment + outcome + patient + shoulder + study + comment + end + + properties (Constant, Hidden = true) + dataPath = '../../../data'; % path to data folder from package folder + % This should be done differently. Check where we are, and where we + % should go. + end + + properties (Hidden = true) + dataCTPath + dataAmiraPath + dataMatlabPath + id4C % SCaseId with P/N followed by 3 digits --> 4 char + end + + methods + + function obj = ShoulderCase(SCaseId) + %SHOULDERCASE Construct an instance of this class + % If called with argument SCaseId, set id and path of CT + if nargin > 0 + obj.id = SCaseId; + %{ + + % SCaseId must be P or N followed by 1-3 digit. + + if ischar(SCaseId) + if strlength(SCaseId) == 4 + obj.id = SCaseId; + else + error('id must have 4 char'); + end + else + % error('id must be a string'); + end + %} + else + % error('id is required'); + end + + % Initialsation + obj.diagnosis = ''; + obj.treatment = ''; + obj.outcome = ''; + obj.patient = Patient(obj); + obj.shoulder = Shoulder(obj); + obj.study = ''; + obj.comment = ''; + + obj.dataCTPath = ''; + obj.dataAmiraPath = ''; + obj.dataMatlabPath = ''; + obj.id4C = ''; + + % Set path of the SCase + % obj.path; To be set only if varargin ~isempty + end + + function outputArg = path(obj) % Should be private + % PATH Summary of this method goes here + % Detailed explanation goes here + + SCase = obj; + SCaseId = SCase.id; + + % The validity of the format should be checked Pnnn or Nnnn. + if (numel(regexp(SCaseId,'^[PN]\d{1,3}$')) == 0) + error(['Invalid format of SCaseId: ' SCaseId{1i} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); + end + + % Set the folder of the SCaseId + SCaseDirLevelPN = SCaseId(1); % Either 'P' or 'N' + strLengthSCaseId = strlength(SCaseId(2:end)); + if (strLengthSCaseId < 2) + SCaseDirLevel2 = '0'; % Hunderets + SCaseDirLevel1 = '0'; % Dozent + elseif (strLengthSCaseId < 3) + SCaseDirLevel2 = '0'; % Hunderets + SCaseDirLevel1 = SCaseId(2); % Dozent + else + SCaseDirLevel2 = SCaseId(2); % Hunderets + SCaseDirLevel1 = SCaseId(3); % Dozent + end + % Set SCaseId with fixed 3 digits after P/N (needed when id < + % 10 inloading of data in amira directory. + SCaseId4C = [SCaseDirLevelPN SCaseDirLevel2 SCaseDirLevel1 SCaseId(end)]; + obj.id4C = SCaseId4C; + + % Check if a (!unique! to be done) directory exists for this SCaseId + dataDir = obj.dataPath; + FindSCaseIdFolder = dir([dataDir '/' SCaseDirLevelPN '/' SCaseDirLevel2 '/' SCaseDirLevel1 '/' SCaseId '*']); + if (isempty(FindSCaseIdFolder)) % No directory for this SCaseId + error(['Missing directory for SCaseId: ' SCaseId]); + end + SCaseDirLevel0 = FindSCaseIdFolder.name; + SCaseDir = [dataDir '/' SCaseDirLevelPN '/' SCaseDirLevel2 '/' SCaseDirLevel1 '/' SCaseDirLevel0]; + + +%{ 8 lines below to remove when -1 consrain avoided + % Check if this SCaseId has a CT directory with '-1' postfix (preoperative + % sharp CT + FindCTFolder = dir([SCaseDir '/' 'CT*-1']); + if (isempty(FindCTFolder)) % No CT directory for this SCaseId + error(['Missing directory for SCaseId: ' SCaseId]); + % ! should exist script here ! + end + CTDir = [SCaseDir '/' FindCTFolder.name]; +%} + + CTdirList=dir([SCaseDir '/CT-*']); % List directories with CT + iCTdirAmira = 0; + iCTdir2use = 0; + for iCTdirList = length(CTdirList):-1:1 % Loop from last to first (4 to 1) + CTdir = CTdirList(iCTdirList); + % Check that dir name ends with -1,-2,-3,-4 + dirName = CTdir.name; + if strcmp(dirName(end-1),'-') % Exclude postoperative 'p' CT + CTnum = str2num(dirName(end)); + if CTnum <= 4 % Exlude non shoulder (elbow) CT + % Check that the dir contains a dicom dir + CTdir = [CTdir.folder '/' CTdir.name]; + dicomDir = [CTdir '/dicom']; + if exist(dicomDir, 'dir') + % Chech if amira dir exist + amiraDir = [CTdir '/amira']; + if exist(amiraDir, 'dir') + % This is the CT folder to use + iCTdirAmira = iCTdirList; + end + iCTdir2use = iCTdirList; + end + end + end + end + if iCTdir2use == 0 + error(['\n', SCaseID, ' has no valid dicom directory']); + else + if iCTdirAmira % If amira dir exist, use it + iCTdir2use = iCTdirAmira; + end + CTdir = CTdirList(iCTdir2use); + SCaseListIdx = SCaseListIdx + 1; + SCaseList(SCaseListIdx).id = SCaseID; + CTdirPath = [CTdir.folder '/' CTdir.name]; + SCaseList(SCaseListIdx).dir = CTdirPath; + end + + + + + amiraDir = [CTDir '/amira']; % we should check for existance + matlabDir = [CTDir '/matlab']; % we should check for existance + + obj.dataCTPath = CTDir; + obj.dataAmiraPath = amiraDir; + obj.dataMatlabPath = matlabDir; + + outputArg = 1; % Should report on directories existence + if exist(amiraDir, 'dir') ~= 7 + outputArg = 0; + end + + end + + function outputArg = output(obj, varargin) + % Below is copy/past from previous version + % This function is used to output the variable Results, used + % by scapula_measurePKG the modified funcion scapula_measure. + % inputArg could be as in scapula_calculation + % 'density', 'References', 'obliqueSlice', 'display' + + Result.SCase_id = obj.id; % 1 + Result.glenoid_Radius = obj.shoulder.scapula.glenoid.radius; % 2 + Result.glenoid_radiusRMSE = obj.shoulder.scapula.glenoid.sphereRMSE; % 3 % 3 + %Result.glenoidSphericity = ' '; + %Result.glenoid_biconcave = ' '; + Result.glenoid_depth = obj.shoulder.scapula.glenoid.depth; % 4 + Result.glenoid_width = obj.shoulder.scapula.glenoid.width; % 5 + Result.glenoid_height = obj.shoulder.scapula.glenoid.height; % 6 + Result.glenoid_center_PA = obj.shoulder.scapula.glenoid.centerLocal(1); % 7 + Result.glenoid_center_IS = obj.shoulder.scapula.glenoid.centerLocal(2); % 8 + Result.glenoid_center_ML = obj.shoulder.scapula.glenoid.centerLocal(3); % 9 + Result.glenoid_version_ampl = obj.shoulder.scapula.glenoid.versionAmpl; % 10 + Result.glenoid_version_orient = obj.shoulder.scapula.glenoid.versionOrient; % 11 + Result.glenoid_Version = obj.shoulder.scapula.glenoid.version; % 12 + Result.glenoid_Inclination = obj.shoulder.scapula.glenoid.inclination; % 13 + Result.humerus_joint_radius = ' '; % 14 + Result.humeral_head_radius = obj.shoulder.humerus.radius; % 15 + % Result.humerus_GHsublux_2D = ' '; + % Result.humerus_SHsublux_2D = ' '; + Result.humerus_GHsubluxation_ampl = obj.shoulder.humerus.GHSAmpl; % 16 + Result.humerus_GHsubluxation_orient = obj.shoulder.humerus.GHSOrient; % 17 + Result.humerus_SHsubluxation_ampl = obj.shoulder.humerus.SHSAmpl; % 18 + Result.humerus_SHsubluxation_orient = obj.shoulder.humerus.SHSOrient; % 19 + Result.scapula_CSA = obj.shoulder.scapula.acromion.criticalShoulderAngle; % radCSA; + % 5 Lines below should be updated + Result.scapula_CTangle = 0; %CTorientation; %20 + Result.scapula_CTangleVersion = 0; % WearPlaneAngle; %21 + Result.scapula_CTangleSHS = 0; % SHSPlaneAngle; % 22 + Result.scapula_CTangleGHS = 0; % GHSPlaneAngle; % 23 + Result.scapula_PlaneRMSE = 0; %PlaneRMSE;%24 + + Result.scapula_AI = obj.shoulder.scapula.acromion.acromionIndex; + + outputArg = Result; + end + + function outputArg = saveMatlab(obj) + % Save SCase to matlab file + dir = obj.dataMatlabPath; + % Create dir if not exist + try + if ~exist(dir, 'dir') % create directory if it does not exist + mkdir(dir); + end + catch + fprintf('Error creating the matlab directory \n'); % Should be in log + end + + % Save SCase in matlab directoty, in a file named SCaseCNNN.m + filename = 'SCase'; + filename = [dir '/' filename '.mat']; + try + SCase = obj; + save(filename, 'SCase'); + outputArg = 1; + catch + fprintf('Error creating SCase matlab file \n'); % Should be in log + outputArg = -1; + end + end + + function outputArg = saveCSV(~) + % Save SCase to csv file + + + outputArg = 1; + + end + + function outputArg = saveExcel(~) + % Save SCase to Excel file + + + outputArg = 1; + + end + + function outputArg = saveSQL(~) + % Save SCase to MySQL database + + + outputArg = 1; + + end + + %{ + function outputArg = plot(obj) + % code moved to file plot.m + + % Plot SCase + figure; % Create figure + hold on; % Superpose objects on the same figure + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Scapula + + % Plot the scapula surface as points + % If exists, plot the segemented surface (manual) + if strcmp(obj.shoulder.scapula.segmentation, 'M') + points = obj.shoulder.scapula.surface.points; + faces = obj.shoulder.scapula.surface.faces; + x = points(:,1); + y = points(:,2); + z = points(:,3); + trisurf(faces,x,y,z,'Facecolor','yellow','FaceAlpha',1,'EdgeColor','none','FaceLighting','gouraud'); + %scatter3(x,y,z,1,'black'); % Point points + end + % If exists, plot the segemented surface (auto) + if strcmp(obj.shoulder.scapulaAuto.segmentation, 'A') + points = obj.shoulder.scapulaAuto.surface.points; + faces = obj.shoulder.scapulaAuto.surface.faces; + x = points(:,1); + y = points(:,2); + z = points(:,3); + trisurf(faces,x,y,z,'Facecolor','red','FaceAlpha',0.25,'EdgeColor','none','FaceLighting','gouraud'); + end + + % Plot scapula groove (manual) + points = obj.shoulder.scapula.groove; + x = points(:,1); + y = points(:,2); + z = points(:,3); + scatter3(x,y,z,100,'blue'); + % If exists, plot segments between manual and auto + + % Plot scapula markers + points = [... + obj.shoulder.scapula.angulusInferior; + obj.shoulder.scapula.trigonumSpinae; + obj.shoulder.scapula.processusCoracoideus; + obj.shoulder.scapula.acromioClavicular; + obj.shoulder.scapula.angulusAcromialis; + obj.shoulder.scapula.spinoGlenoidNotch... + ]; + x = points(:,1); + y = points(:,2); + z = points(:,3); + scatter3(x,y,z,100,'blue','LineWidth',2); + + % Plot scapular plane + axisLength = 50; + pt0 = obj.shoulder.scapula.coordSys.origin; + pt1 = pt0 + (obj.shoulder.scapula.coordSys.ML + obj.shoulder.scapula.coordSys.IS) * axisLength ; + pt2 = pt1 - obj.shoulder.scapula.coordSys.IS * pdist([pt1 ; obj.shoulder.scapula.angulusInferior]); + pt3 = pt2 - obj.shoulder.scapula.coordSys.ML * pdist([pt1 ; obj.shoulder.scapula.trigonumSpinae] ); + pt4 = pt3 + obj.shoulder.scapula.coordSys.IS * pdist([pt1 ; obj.shoulder.scapula.angulusInferior]); + X = [pt1(1) pt2(1) pt3(1) pt4(1)]; + Y = [pt1(2) pt2(2) pt3(2) pt4(2)]; + Z = [pt1(3) pt2(3) pt3(3) pt4(3)]; + patch(X,Y,Z,'black','FaceAlpha',0.3); % Transparent plane + + + % Plot scapular axis + pt1 = obj.shoulder.scapula.coordSys.origin; + axisLength = 10 + pdist([pt1 ; obj.shoulder.scapula.trigonumSpinae]); + pt2 = pt1 - obj.shoulder.scapula.coordSys.ML * axisLength; + x = [pt1(1), pt2(1)]; + y = [pt1(2), pt2(2)]; + z = [pt1(3), pt2(3)]; + plot3(x,y,z,'Color','blue','LineWidth', 5); + + % Plot scapular coordinate system + axisLength = 50; % Length of the cood. sys. axes + pt0 = obj.shoulder.scapula.coordSys.origin; + pt1 = pt0 + obj.shoulder.scapula.coordSys.PA*axisLength; % X + pt2 = pt0 + obj.shoulder.scapula.coordSys.IS*axisLength; % Y + pt3 = pt0 + obj.shoulder.scapula.coordSys.ML*axisLength; % Z + x = [pt0(1), pt1(1)]; + y = [pt0(2), pt1(2)]; + z = [pt0(3), pt1(3)]; + plot3(x,y,z,'Color','red','LineWidth', 5); + x = [pt0(1), pt2(1)]; + y = [pt0(2), pt2(2)]; + z = [pt0(3), pt2(3)]; + plot3(x,y,z,'Color','green','LineWidth', 5); + % Not plotting ML axis to avoid its superposition with scapular axis on glenoid center +% x = [pt0(1), pt3(1)]; +% y = [pt0(2), pt3(2)]; +% z = [pt0(3), pt3(3)]; +% plot3(x,y,z,'Color','blue','LineWidth', 5); + + % plot scapular axis on glenoid center + axisLength = 50; + pt1 = obj.shoulder.scapula.glenoid.center; + pt2 = pt1 + obj.shoulder.scapula.coordSys.ML*axisLength; + x = [pt1(1), pt2(1)]; + y = [pt1(2), pt2(2)]; + z = [pt1(3), pt2(3)]; + plot3(x,y,z,'Color','blue','LineWidth', 5); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Glenoid + + % Plot the glenoid surface + % We might move them in the direction of th glenoid center + % instead of the ML axis + points = obj.shoulder.scapula.glenoid.surface.points +... + obj.shoulder.scapula.coordSys.ML * 0.2; + faces = obj.shoulder.scapula.glenoid.surface.faces; + x = points(:,1); + y = points(:,2); + z = points(:,3); + %scatter3(x,y,z,1,'blach'); + trisurf(faces,x,y,z,'Facecolor','none','FaceAlpha',1,'EdgeColor','yellow','FaceLighting','none'); + + % plot glenoid centerline + pt1 = obj.shoulder.scapula.glenoid.center; + pt2 = pt1 + obj.shoulder.scapula.glenoid.centerLine; + x = [pt1(1), pt2(1)]; + y = [pt1(2), pt2(2)]; + z = [pt1(3), pt2(3)]; + plot3(x,y,z,'Color','yellow','LineWidth', 5); + + % We might add spherical cap + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Humerus + + % plot humeral head sphere + n = 20; % number of lines + [X,Y,Z] = sphere(n); + X = X*obj.shoulder.humerus.radius; + Y = Y*obj.shoulder.humerus.radius; + Z = Z*obj.shoulder.humerus.radius; + Xc = obj.shoulder.humerus.center(1); + Yc = obj.shoulder.humerus.center(2); + Zc = obj.shoulder.humerus.center(3); + mesh(X+Xc,Y+Yc,Z+Zc,'LineStyle','none','FaceColor','magenta','FaceAlpha',0.3,'FaceLighting','gouraud'); + % hidden off; % Transparent sphere + % check 'FaceLighting','gouraud', + + % plot humeral head centerline + pt1 = obj.shoulder.scapula.glenoid.center; + pt2 = obj.shoulder.humerus.center; + x = [pt1(1), pt2(1)]; + y = [pt1(2), pt2(2)]; + z = [pt1(3), pt2(3)]; + plot3(x,y,z,'Color','magenta','LineWidth', 5); + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Graph properties + + % axis off; % remove axis + lightPosition = obj.shoulder.scapula.glenoid.center + ... + (... + obj.shoulder.scapula.coordSys.ML + ... + obj.shoulder.scapula.coordSys.IS + ... + obj.shoulder.scapula.coordSys.PA ... + ) * 100; + light('Position',lightPosition,'Style','local'); + grid on; + xlabel('X (CT)'); + ylabel('Y (CT)'); + zlabel('Z (CT)'); + axis square; + axis vis3d; % fixed limits and scaling + view(obj.shoulder.scapula.coordSys.IS); % view from top + % Align ML axis with window horizontal axis !to do + % obj.shoulder.scapula.coordSys.ML + %angleDegrees = rad2deg(obj.shoulder.scapula.coordSys.ML + % camroll(angleDegrees); + + figureHandle = gca; + figureHandle.DataAspectRatio = [1 1 1]; % Same relative length of data units along each axis + figureHandle.Box = 'On'; + + outputArg = 1; + + end + %} + end +end + diff --git a/XLS_MySQL_DB/DB_Drivers/InstallJDBCDriversMatlab.txt b/XLS_MySQL_DB/DB_Drivers/InstallJDBCDriversMatlab.txt new file mode 100644 index 0000000..274dd82 --- /dev/null +++ b/XLS_MySQL_DB/DB_Drivers/InstallJDBCDriversMatlab.txt @@ -0,0 +1,8 @@ +1. Go to https://ch.mathworks.com/fr/products/database/driver-installation.html and follow the link to download the MySQL JDBC driver +2. Download a platform-independent ZIP archive oft he MySQL Connector +3. Extract the Zip archive +4. Create a folder „DB_Drivers“ on the „C:/“ disk on Windows or in /home/shoulder/methods/matlab/database/XLS_MySQL_DB/ on lbovenus (CentOS) +5. Copy the extracted archive to DB_Drivers +6. Open Matlab and run the « prefdir command. +7. Navigate to the the resulting path and create a file called javaclasspath.txt in the folder. +8. Open javaclasspath.txt. Add the full path to the database driver JAR file in javaclasspath.txt. The full path includes the path to the folder where you downloaded the JAR file from the database provider and the JAR file name. For example, C:\DB_Drivers\sqljdbc_4.0\enu\sqljdbc4.jar. Save and close javaclasspath.txt. Save javaclasspath.txt and restart matlab. diff --git a/config.txt b/config.txt new file mode 100644 index 0000000..e69de29 diff --git a/dicominfoSCase.m b/dicominfoSCase.m index d8c16b1..23ff859 100755 --- a/dicominfoSCase.m +++ b/dicominfoSCase.m @@ -1,545 +1,546 @@ function outputArg = dicominfoSCase(varargin) %DICOMINFOSCASE Get info from dicom fields and save in csv, xls, mat, and SQL % This function should be started from /shoulder/methods/matlab/database % A logfile is written during execution in % /shoulder/methods/matlab/database/log/dicominfoSCase.log % If the function is called without arguments the csv file is overwritten, % otherwise it is updated. The same applies for xls, mat and SQL. % The fonction can be run from server (lbovenus) with % cd /home/shoulder/methods/matlab/database; matlab -nojvm -nodisplay -nosplash -r "dicominfoSCase;quit" % For 700 cases, it takes less than 60 seconds (when executed from server) % Progress can be be checked throuhth log file with following shell command % cd /home/shoulder/methods/matlab/database;tail -f log/dicominfoSCase.log % This funtion replaces % /home/shoulder/methods/matlab/database/CTinfoCT.m % which only works matlab R2015a on a Windows system % Syntax: outputArg = dicominfoSCase(varargin) % % Input: % varargin: can be nothing (all cases), 'N', 'P', or SCase(s) % % Output: % outputArg: 1 if ok and 0 otherwise % % Example: CTdicomInfo, CTdicominfo('P'), CTdicominfo('P315') % Other M-files required: listSCase % Subfunctions: % See also: measureSCase % % Author: Alexandre Terrier % EPFL-LBO % Creation date: 2018-07-01 % Revision date: 2018-12-30 % % TO DO: % When list of SCases as arguments it doesn't work properly because it % overwrites the CSV with only the listed SCased (by JSM 2019-02-13) % Re-check for double slash in paths % We should implement the ShoulderCase class % We should save data in SCase/matlab directory % We might add family name, given name (to check anonymity) % We should first read csv and then update % We should update MySQL % Check 'Index exceeds matrix dimensions' when 'using dicomdisp' % Structure SCaseDicom should match csv (and xlx aand sql) firld names % rather than dicom %% Open log file % Check if log dir exists in working directory logDir = 'log'; if ~exist('log', 'dir') % If not create dir log mkdir('log') end logFile = [logDir '/dicominfoSCase.log']; logFileID = fopen(logFile, 'w'); fprintf(logFileID, 'dicominfoSCase log file'); fprintf(logFileID, '\nDate: '); fprintf(logFileID, datestr(datetime)); %% Set the data directory from the configuration file config.txt configFile = 'config.txt'; if ~exist(configFile, 'file') error([configFile, ' required']); end fileID = fopen(configFile,'r'); dataDir = fscanf(fileID, '%s'); % Read config file without spaces fclose(fileID); k = strfind(dataDir, 'dataDir='); % Find definition of dataDir k = k + 8; % Remove 'dataDir=' dataDir = dataDir(k:end); % Assume that config file ends with dataDir content % Check if dataDir exists if ~exist(dataDir, 'dir') fprintf(logFileID, ['Data directory not found, check ' configFile]); %error(['Data directory not found, check ' configFile]); % Select the \data directory try dataDir = uigetdir(pwd,"Please select the directory 'data or dataDev'"); if ~exist(dataDir, 'dir') errordlg(sprintf("%s",... "Run 'dicominfoSCase.m' manually and select the data directory"),... "Error: data directory not found.") end catch mistake errordlg(sprintf("%s\n%s",... "Run 'dicominfoSCase.m' manually and select the data directory",... mistake.message),"Error: data directory not found.") end end % Location of the XLS ShoulderDatabase xlsDataDir = strcat(dataDir, '/Excel/xlsFromMatlab'); %% Get list of SCases if nargin == 0 % dicominfoSCase called without argument SCaseList = listSCase(dataDir); % All SCases in data dir with valid dicom CT dir update = 0; elseif nargin == 1 update = 1; inputArg = varargin{1}; SCaseList = listSCase(dataDir,inputArg); % SCase related to inputArg ('N', 'P', or list) end %% Read dicom info tic; % Start stopwatch timer fprintf(logFileID, '\n\nRead dicom meta data\n'); % log file message % Struct below correponds to regular dicom field names, except SCase_ID SCaseDicom = struct(... 'SCase_id' ,[],... 'PatientID' ,[],... 'PatientSex' ,[],... 'PatientAge' ,[],... 'PatientBirthDate' ,[],... 'PatientSize' ,[],... 'PatientWeight' ,[],... 'PatientFamilyName' ,[],... 'PatientGivenName' ,[],... 'AcquisitionDate' ,[],... 'ConvolutionKernel' ,[],... 'PixelSpacing' ,[],... 'SpacingBetweenSlices' ,[],... 'SliceThickness' ,[],... 'KVP' ,[],... 'XrayTubeCurrent' ,[],... 'InstitutionName' ,[],... 'StudyDescription' ,[],... 'SeriesDescription' ,[],... % Might describe shoulder side 'Manufacturer' ,[],... 'ManufacturerModelName' ,[],... 'ProtocolName' ,[]... ); % Loop over all SCases collected above for SCase_idx=1:1:numel(SCaseList) SCase_id = SCaseList(SCase_idx).id; dicomDir = [SCaseList(SCase_idx).dir '/dicom']; dicomFiles = dir([dicomDir, '/*.dcm']); +% SCase_idx dicomFile1 = [dicomDir, '/', dicomFiles(10).name]; % seems better to check 10th than 1st ! dicomInfoError = false; fprintf(logFileID, '\n'); fprintf(logFileID, SCaseList(SCase_idx).id); fprintf(logFileID, ': '); % Try first with matlab function dicominfo try dicomInfo1 = dicominfo(dicomFile1, 'UseVRHeuristic', true); fprintf(logFileID, 'dicominfo ok'); % If dicominfo didn't worked, try with matlab function dicomdisp catch ME dicomInfoError = true; fprintf(logFileID, '\n'); fprintf(logFileID, 'dicominfo error --> using dicomdisp'); fprintf(logFileID, ['\n' ME.message]); dicomdispStr = evalc('dicomdisp(dicomFile1)'); end % If dicominfo didn't worked, continue with output of dicomdisp if dicomInfoError % get dicom info from dicomdisp() PatientID = extractAfter(dicomdispStr,'PatientID'); PatientID = extractAfter(PatientID,'['); PatientID = extractBefore(PatientID,']'); PatientSex = extractAfter(dicomdispStr,'PatientSex'); PatientSex = extractAfter(PatientSex,'['); PatientSex = extractBefore(PatientSex,']'); PatientAge = extractAfter(dicomdispStr,'PatientAge'); PatientAge = extractAfter(PatientAge,'['); PatientAge = extractBefore(PatientAge,']'); PatientBirthDate = extractAfter(dicomdispStr,'PatientBirthDate'); PatientBirthDate = extractAfter(PatientBirthDate,'['); PatientBirthDate = extractBefore(PatientBirthDate,']'); PatientSize = extractAfter(dicomdispStr,'PatientSize'); PatientSize = extractAfter(PatientSize,'['); PatientSize = extractBefore(PatientSize,']'); PatientSize = str2double(PatientSize); PatientWeight = extractAfter(dicomdispStr,'PatientWeight'); PatientWeight = extractAfter(PatientWeight,'['); PatientWeight = extractBefore(PatientWeight,']'); PatientWeight = str2double(PatientWeight); AcquisitionDate = extractAfter(dicomdispStr,'AcquisitionDate'); AcquisitionDate = extractAfter(AcquisitionDate,'['); AcquisitionDate = extractBefore(AcquisitionDate,']'); ConvolutionKernel = extractAfter(dicomdispStr,'ConvolutionKernel'); ConvolutionKernel = extractAfter(ConvolutionKernel,'['); ConvolutionKernel = extractBefore(ConvolutionKernel,']'); PixelSpacing = extractAfter(dicomdispStr,'PixelSpacing'); PixelSpacing = extractAfter(PixelSpacing,'['); PixelSpacing = extractBefore(PixelSpacing,'\'); SpacingBetweenSlices = extractAfter(dicomdispStr,'SpacingBetweenSlices'); SpacingBetweenSlices = extractAfter(SpacingBetweenSlices,'['); SpacingBetweenSlices = extractBefore(SpacingBetweenSlices,']'); SpacingBetweenSlices = str2double(SpacingBetweenSlices); SliceThickness = extractAfter(dicomdispStr,'SliceThickness'); SliceThickness = extractAfter(SliceThickness,'['); SliceThickness = extractBefore(SliceThickness,']'); SliceThickness = str2double(SliceThickness); KVP = extractAfter(dicomdispStr,'KVP'); KVP = extractAfter(KVP,'['); KVP = extractBefore(KVP,']'); KVP = str2double(KVP); XrayTubeCurrent = extractAfter(dicomdispStr,'XrayTubeCurrent'); XrayTubeCurrent = extractAfter(XrayTubeCurrent,'['); XrayTubeCurrent = extractBefore(XrayTubeCurrent,']'); XrayTubeCurrent = str2double(XrayTubeCurrent); InstitutionName = extractAfter(dicomdispStr,'InstitutionName'); InstitutionName = extractAfter(InstitutionName,'['); InstitutionName = extractBefore(InstitutionName,']'); Manufacturer = extractAfter(dicomdispStr,'Manufacturer'); Manufacturer = extractAfter(Manufacturer,'['); Manufacturer = extractBefore(Manufacturer,']'); ManufacturerModelName = extractAfter(dicomdispStr,'ManufacturerModelName'); ManufacturerModelName = extractAfter(ManufacturerModelName,'['); ManufacturerModelName = extractBefore(ManufacturerModelName,']'); ProtocolName = extractAfter(dicomdispStr,'ProtocolName'); ProtocolName = extractAfter(ProtocolName,'['); ProtocolName = extractBefore(ProtocolName,']'); SeriesDescription = extractAfter(dicomdispStr,'SeriesDescription'); SeriesDescription = extractAfter(SeriesDescription,'['); SeriesDescription = extractBefore(SeriesDescription,']'); else % dicomInfo exists if isfield(dicomInfo1,'PatientID') PatientID = dicomInfo1.PatientID; else PatientID = ''; end if isfield(dicomInfo1,'PatientSex') PatientSex = dicomInfo1.PatientSex; else PatientSex = ''; end if isfield(dicomInfo1,'PatientBirthDate') PatientBirthDate = dicomInfo1.PatientBirthDate; else PatientBirthDate = ''; end if isfield(dicomInfo1,'PatientAge') PatientAge = dicomInfo1.PatientAge; else PatientAge = ''; end if isfield(dicomInfo1,'PatientSize') PatientSize = dicomInfo1.PatientSize; else PatientSize = ''; end if isfield(dicomInfo1,'PatientWeight') PatientWeight = dicomInfo1.PatientWeight; else PatientWeight=''; end if isfield(dicomInfo1,'AcquisitionDate') AcquisitionDate = dicomInfo1.AcquisitionDate; else AcquisitionDate = ''; end if isfield(dicomInfo1,'ConvolutionKernel') ConvolutionKernel = dicomInfo1.ConvolutionKernel; else ConvolutionKernel = ''; end if isfield(dicomInfo1,'PixelSpacing') PixelSpacing = dicomInfo1.PixelSpacing(1); PixelSpacing = num2str(PixelSpacing); % PixelSpacing = [,PixelSpacing,]; % Commented because I (AT) don't % understand the effect else PixelSpacing=''; end if isfield(dicomInfo1,'SpacingBetweenSlices') SpacingBetweenSlices = dicomInfo1.SpacingBetweenSlices; else % SpacingBetweenSlices estimated from 2 slices % fprintf(logFileID, '\n'); fprintf(logFileID, ', SpacingBetweenSlices estimated from 2 slices'); dicomFile2 = [dicomDir, '/', dicomFiles(11).name]; dicomInfo2 = dicominfo(dicomFile2, 'UseVRHeuristic', true); ImagePositionPatient1 = dicomInfo1.ImagePositionPatient(3); ImagePositionPatient2 = dicomInfo2.ImagePositionPatient(3); posdDiff = abs(ImagePositionPatient2 - ImagePositionPatient1); InstanceNumber1 = dicomInfo1.InstanceNumber; InstanceNumber2 = dicomInfo2.InstanceNumber; InstanceNumberDiff = abs(InstanceNumber2 - InstanceNumber1); if InstanceNumberDiff == 0 InstanceNumberDiff=1; end SpacingBetweenSlices = posdDiff/InstanceNumberDiff; end if isfield(dicomInfo1,'SliceThickness') SliceThickness = dicomInfo1.SliceThickness; else SliceThickness=''; end if isfield(dicomInfo1,'KVP') KVP = dicomInfo1.KVP; else KVP= ''; end if isfield(dicomInfo1,'XrayTubeCurrent') XrayTubeCurrent = dicomInfo1.XrayTubeCurrent; else XrayTubeCurrent = ''; end if isfield(dicomInfo1,'InstitutionName') InstitutionName = dicomInfo1.InstitutionName; else InstitutionName = ''; end if isfield(dicomInfo1,'Manufacturer') Manufacturer = dicomInfo1.Manufacturer; else Manufacturer =''; end if isfield(dicomInfo1,'ManufacturerModelName') ManufacturerModelName = dicomInfo1.ManufacturerModelName; else ManufacturerModelName = ''; end if isfield(dicomInfo1,'ProtocolName') ProtocolName = dicomInfo1.ProtocolName; else ProtocolName = ''; end if isfield(dicomInfo1,'SeriesDescription') SeriesDescription = dicomInfo1.SeriesDescription; else SeriesDescription = ''; end % End of reading dimcom info end %% Format data PatientBirthDate = datetime(PatientBirthDate,'InputFormat','yyyyMMdd'); PatientBirthDate = datestr(PatientBirthDate, 'yyyy-mm-dd'); PatientAge = str2double(strtok(PatientAge,'Y')); PatientSize = round(100 * PatientSize); if PatientSize == 0 PatientSize = ''; end PatientWeight = round(PatientWeight); if PatientWeight == 0 PatientWeight = ''; end AcquisitionDate = datetime(AcquisitionDate,'InputFormat','yyyyMMdd'); AcquisitionDate = datestr(AcquisitionDate, 'yyyy-mm-dd'); PixelSpacing = str2double(PixelSpacing); PixelSpacing = round(PixelSpacing, 3); SpacingBetweenSlices = round(SpacingBetweenSlices, 3); SliceThickness = round(SliceThickness, 3); KVP = round(KVP); XrayTubeCurrent = round(XrayTubeCurrent); % Replace any ',' by ' ' to avoid problem in csv InstitutionName = strrep(InstitutionName,',',' '); Manufacturer = strrep(Manufacturer,',',' '); ManufacturerModelName = strrep(ManufacturerModelName,',',' '); ProtocolName = strrep(ProtocolName,',',' '); SeriesDescription = strrep(SeriesDescription,',',' '); %% Add data in SCaseDicom structure % This could be directly in SCase.CT object SCaseDicom(SCase_idx).id = SCase_id; SCaseDicom(SCase_idx).PatientID = PatientID; SCaseDicom(SCase_idx).PatientSex = PatientSex; SCaseDicom(SCase_idx).PatientBirthDate = PatientBirthDate; SCaseDicom(SCase_idx).PatientAge = PatientAge; SCaseDicom(SCase_idx).PatientSize = PatientSize; SCaseDicom(SCase_idx).PatientWeight = PatientWeight; SCaseDicom(SCase_idx).AcquisitionDate = AcquisitionDate; SCaseDicom(SCase_idx).ConvolutionKernel = ConvolutionKernel; SCaseDicom(SCase_idx).PixelSpacing = PixelSpacing; SCaseDicom(SCase_idx).SpacingBetweenSlices = SpacingBetweenSlices; SCaseDicom(SCase_idx).SliceThickness = SliceThickness; SCaseDicom(SCase_idx).KVP = KVP; SCaseDicom(SCase_idx).XrayTubeCurrent = XrayTubeCurrent; SCaseDicom(SCase_idx).InstitutionName = InstitutionName; SCaseDicom(SCase_idx).Manufacturer = Manufacturer; SCaseDicom(SCase_idx).ManufacturerModelName = ManufacturerModelName; SCaseDicom(SCase_idx).ProtocolName = ProtocolName; SCaseDicom(SCase_idx).SeriesDescription = SeriesDescription; end fprintf(logFileID, '\n\nElapsed time (s): %s', num2str(toc)); % stopwatch timer %% Write csv file % This might be a function. The input would be a filename and a structure % data dataHeader = [... 'SCase_ID,' ... 'patient_IPP,' ... 'patient_gender,' ... 'patient_birthDate,' ... 'patient_age,' ... 'patient_height,' ... 'patient_weight,' ... 'CT_date,' ... 'CT_kernel,' ... 'CT_pixelSize,' ... 'CT_sliceSpacing,' ... 'CT_sliceThickness,' ... 'CT_tension,' ... 'CT_current,' ... 'CT_institution,' ... 'CT_manufacturer,' ... 'CT_model,' ... 'CT_protocol,' ... 'CT_SeriesDescription' ... ]; csvFilename = strcat(xlsDataDir, '/CTdicomInfo.csv'); % If dicominfoSCase is called with argument, we have to update the csv. if update % Read the csv and put data in a structure like SCaseDicom % We should chech that file exists csvTable = readtable(csvFilename,'DatetimeType', 'text'); % If 'DatetimeType' is specified as 'text', then the type for imported % date and time data depends on the value specified in the 'TextType' % parameter: If 'TextType' is 'char', then readtable returns dates as a % cell array of character vectors. If 'TextType' is 'string', then % readtable returns dates as an array of strings. % Set csv data in temporary SCaseDicomTmp structure SCaseDicom_csv = table2struct(csvTable); - + % Update the SCaseDicomTmp structure with new data for iSCaseDicom = 1:length(SCaseDicom) % loop on new SCase(s) iSCaseDicomTmp = strcmp({SCaseDicom_csv.SCase_ID}, SCaseDicom(iSCaseDicom).id); % 0/1 array iSCaseDicomTmp = find(iSCaseDicomTmp); % index with non-zero, correponding to SCaseID % We should test iSCaseDicomTmp => is overwriting the whole array % Must be changed by last SCase position +1 SCaseDicom_csv(iSCaseDicomTmp).patient_IPP = SCaseDicom(iSCaseDicom).PatientID; SCaseDicom_csv(iSCaseDicomTmp).patient_gender = SCaseDicom(iSCaseDicom).PatientSex; SCaseDicom_csv(iSCaseDicomTmp).patient_birthDate = SCaseDicom(iSCaseDicom).PatientBirthDate; SCaseDicom_csv(iSCaseDicomTmp).patient_age = SCaseDicom(iSCaseDicom).PatientAge; SCaseDicom_csv(iSCaseDicomTmp).patient_height = SCaseDicom(iSCaseDicom).PatientSize; SCaseDicom_csv(iSCaseDicomTmp).patient_weight = SCaseDicom(iSCaseDicom).PatientWeight; SCaseDicom_csv(iSCaseDicomTmp).CT_date = SCaseDicom(iSCaseDicom).AcquisitionDate; SCaseDicom_csv(iSCaseDicomTmp).CT_kernel = SCaseDicom(iSCaseDicom).ConvolutionKernel; SCaseDicom_csv(iSCaseDicomTmp).CT_pixelSize = SCaseDicom(iSCaseDicom).PixelSpacing; SCaseDicom_csv(iSCaseDicomTmp).CT_sliceSpacing = SCaseDicom(iSCaseDicom).SpacingBetweenSlices; SCaseDicom_csv(iSCaseDicomTmp).CT_sliceThickness = SCaseDicom(iSCaseDicom).SliceThickness; SCaseDicom_csv(iSCaseDicomTmp).CT_tension = SCaseDicom(iSCaseDicom).KVP; SCaseDicom_csv(iSCaseDicomTmp).CT_current = SCaseDicom(iSCaseDicom).XrayTubeCurrent; SCaseDicom_csv(iSCaseDicomTmp).CT_institution = SCaseDicom(iSCaseDicom).InstitutionName; SCaseDicom_csv(iSCaseDicomTmp).CT_manufacturer = SCaseDicom(iSCaseDicom).Manufacturer; SCaseDicom_csv(iSCaseDicomTmp).CT_model = SCaseDicom(iSCaseDicom).ManufacturerModelName; SCaseDicom_csv(iSCaseDicomTmp).CT_protocol = SCaseDicom(iSCaseDicom).ProtocolName; SCaseDicom_csv(iSCaseDicomTmp).CT_SeriesDescription = SCaseDicom(iSCaseDicom).SeriesDescription; end % SCaseDicom must now be updated for iSCaseDicom = 1:length(SCaseDicom) SCaseDicom(iSCaseDicom).id = SCaseDicom_csv(iSCaseDicom).SCase_ID; SCaseDicom(iSCaseDicom).PatientID = SCaseDicom_csv(iSCaseDicom).patient_IPP; SCaseDicom(iSCaseDicom).PatientSex = SCaseDicom_csv(iSCaseDicom).patient_gender; SCaseDicom(iSCaseDicom).PatientBirthDate = SCaseDicom_csv(iSCaseDicom).patient_birthDate; SCaseDicom(iSCaseDicom).PatientAge = SCaseDicom_csv(iSCaseDicom).patient_age; SCaseDicom(iSCaseDicom).PatientSize = SCaseDicom_csv(iSCaseDicom).patient_height; SCaseDicom(iSCaseDicom).PatientWeight = SCaseDicom_csv(iSCaseDicom).patient_weight; SCaseDicom(iSCaseDicom).AcquisitionDate = SCaseDicom_csv(iSCaseDicom).CT_date; SCaseDicom(iSCaseDicom).ConvolutionKernel = SCaseDicom_csv(iSCaseDicom).CT_kernel; SCaseDicom(iSCaseDicom).PixelSpacing = SCaseDicom_csv(iSCaseDicom).CT_pixelSize; SCaseDicom(iSCaseDicom).SpacingBetweenSlices = SCaseDicom_csv(iSCaseDicom).CT_sliceSpacing; SCaseDicom(iSCaseDicom).SliceThickness = SCaseDicom_csv(iSCaseDicom).CT_sliceThickness; SCaseDicom(iSCaseDicom).KVP = SCaseDicom_csv(iSCaseDicom).CT_tension; SCaseDicom(iSCaseDicom).XrayTubeCurrent = SCaseDicom_csv(iSCaseDicom).CT_current; SCaseDicom(iSCaseDicom).InstitutionName = SCaseDicom_csv(iSCaseDicom).CT_institution; SCaseDicom(iSCaseDicom).Manufacturer = SCaseDicom_csv(iSCaseDicom).CT_manufacturer; SCaseDicom(iSCaseDicom).ManufacturerModelName = SCaseDicom_csv(iSCaseDicom).CT_model; SCaseDicom(iSCaseDicom).ProtocolName = SCaseDicom_csv(iSCaseDicom).CT_protocol; SCaseDicom(iSCaseDicom).SeriesDescription = SCaseDicom_csv(iSCaseDicom).CT_SeriesDescription; end end % Write csv file csvFileId = fopen(csvFilename, 'w'); fprintf(csvFileId,dataHeader); for SCase_idx=1:1:numel(SCaseDicom) % Loop on all SCase provided by listSCase() fprintf(csvFileId,'\n%s,%s,%s,%s,%f,%f,%f,%s,%s,%f,%f,%f,%f,%f,%s,%s,%s,%s,%s',... SCaseDicom(SCase_idx).id,... SCaseDicom(SCase_idx).PatientID,... SCaseDicom(SCase_idx).PatientSex,... SCaseDicom(SCase_idx).PatientBirthDate,... SCaseDicom(SCase_idx).PatientAge,... SCaseDicom(SCase_idx).PatientSize,... SCaseDicom(SCase_idx).PatientWeight,... SCaseDicom(SCase_idx).AcquisitionDate,... SCaseDicom(SCase_idx).ConvolutionKernel,... SCaseDicom(SCase_idx).PixelSpacing,... SCaseDicom(SCase_idx).SpacingBetweenSlices,... SCaseDicom(SCase_idx).SliceThickness,... SCaseDicom(SCase_idx).KVP,... SCaseDicom(SCase_idx).XrayTubeCurrent,... SCaseDicom(SCase_idx).InstitutionName,... SCaseDicom(SCase_idx).Manufacturer,... SCaseDicom(SCase_idx).ManufacturerModelName,... SCaseDicom(SCase_idx).ProtocolName,... SCaseDicom(SCase_idx).SeriesDescription... ); end fclose(csvFileId); -fprintf(logFileID, ['\n\nSaved csv file: ' csvFilename]); +fprintf(logFileID, sprintf('\n\n%s %s', 'Saved csv file: ', csvFilename)); %% Write xls file (Windows only) [~,message] = csv2xlsSCase(csvFilename); fprintf(logFileID, message); %% Update MySQL % To be done %{ addpath('XLS_MySQL_DB'); MainExcel2SQL; %} %% Final log fprintf(logFileID, '\n\nSCases treated: %s', num2str(numel(SCaseDicom))); % Number of SCases treated fprintf(logFileID, '\nElapsed time (s): %s', num2str(toc)); % stopwatch timer fclose(logFileID); % Close log file outputArg = 1; % Might be extended end \ No newline at end of file diff --git a/listSCase_before20190211.m b/listSCase_before20190211.m new file mode 100644 index 0000000..f9bf487 --- /dev/null +++ b/listSCase_before20190211.m @@ -0,0 +1,232 @@ +function SCaseList = listSCase(varargin)%SCase_type,dataDir) +%LISTSCASE output list of sCases (id and path), accordind to inputList or +%filter arguments. + +% Each sCase directory should at least contain at +% directory with a name staring with 'CT' and ending with either +% '-1' : preop/shoulder/sharp/noPhantom +% '-2' : preop/shoulder/smooth/noPhantom +% '-3' : preop/shoulder/sharp/phantom +% '-4' : preop/shoulder/smooth/phantom +% and containing a 'dicom' subdirectory. The priority is '1', '2', '3', '4', +% unless there is a amira folder already present in one of these diretories. +% +% The optional input argument inputList if an array of SCaseID, or 'N' or +% 'P'. If empty, listSCase will search for all SCaseID. The optional +% input argument filter can restrict le list. + +% The fonction can be run from server (lbovenus) with +% cd /home/shoulder/methods/matlab/database; matlab -nojvm -nodisplay -nosplash -r "listSCase;quit" +% For 700 cases, it takes less than 60 seconds (when executed from server) + +% Progress can be be checked throuhth log file with following shell command +% cd /home/shoulder/methods/matlab/database;tail -f log/listSCase.log + +% Input: +% inputArg: can be nothing (=all), 'N', 'P', or SCase(s) +% +% Output: +% outputArg: 1 if ok and 0 otherwise +% +% Example: listSCase, listSCase('P'), listSCase('P001','P002') +% Other M-files required: None +% Subfunctions: None +% See also: Used by dicominfoSCase, measureSCase, +% +% Author: Alexandre Terrier +% EPFL-LBO +% Creation date: 2018-07-01 +% Revision date: 2018-12-30 +% +% TODO +% Two main looops might be merged in one, to avoid duplicagte post +% treatment + +%% Check input parameters + +dirL0Array = ''; +SCaseListIn = {}; + +if nargin == 0 + dirL0Array = ['N';'P']; % All SCase, default when no argument + fprintf(logFileID, '\nGet list of all SCase with a valid dicom folder'); +elseif nargin == 1 + if strcmp(varargin,'N') % All normal SCase + dirL0Array = 'N'; + fprintf(logFileID, '\nGet list of all normal SCase with a valid dicom folder'); + elseif strcmp(varargin,'P') % All pathological SCase + dirL0Array = 'P'; + fprintf(logFileID, '\nGet list of all pathological SCase with a valid dicom folder'); + else % A specific SCase + fprintf(logFileID, '\nGet list of a SCase with a valid dicom folder'); + SCaseListIn = varargin; + end +else % list of multiple SCase given in varargin + fprintf(logFileID, '\nGet list of a SCase with a valid dicom folder'); + SCaseListIn = varargin; +end + + +%% Open log file +logFileID = openLogFile('listSCase.log'); + +%% Set the data directory from the configuration file config.txt +dataDir = openConfigFile('config.txt', logFileID); + +%% Struct contains id and associated directory +SCaseList = struct(... + 'id' ,[],... + 'dir' ,[],... + 'comment' ,[]... % Might be used later + ); + + +%% Only one of the two loops below are performed +SCaseListIdx = 0; % Used in one of two loops below + +%% Loop over SCase (N, P, or both), if length(dirL0Array) > 1 +for dirL0idx = 1:length(dirL0Array) % N or P + for dirL1idx = 0:9 % Hunderts dir + for dirL2idx = 0:9 % Tens dir + dirL0Str = dirL0Array(dirL0idx); + dirL1Str = num2str(dirL1idx); + dirL2Str = num2str(dirL2idx); + dirTens = [dataDir '/' dirL0Str '/' dirL1Str '/' dirL2Str]; + dirTensList = dir(dirTens); % List all directories in tens dir + dirTensList = dirTensList(~ismember({dirTensList.name},{'.','..'})); % Remove . and .. + % We might add here a filter (only diretories, {N/P} followed + % by digits + for dirL3idx = 1:numel(dirTensList) + sCaseDirName = dirTensList(dirL3idx).name; % Root directory of this case + sCaseDirPath = dirTensList(dirL3idx).folder; + % Check if this SCaseID has a CT dir [CT?*-?] with a valid preoprative CT + SCaseID = strtok(sCaseDirName, '-'); % Get chars before '-' + CTdirList = dir([sCaseDirPath '/' sCaseDirName '/CT-*']); % List directories with CT + iCTdirAmira = 0; + iCTdir2use = 0; + for iCTdirList = length(CTdirList):-1:1 % Loop from last to first (4 to 1) + CTdir = CTdirList(iCTdirList); + % Check that dir name ends with -1,-2,-3,-4 + dirName = CTdir.name; + if strcmp(dirName(end-1),'-') % Exclude postoperative 'p' CT + CTnum = str2num(dirName(end)); + if CTnum <= 4 % Exlude non shoulder (elbow) CT + % Check that the dir contains a dicom dir + CTdir = [CTdir.folder '/' CTdir.name]; + dicomDir = [CTdir '/dicom']; + if exist(dicomDir, 'dir') + % Chech if amira dir exist + amiraDir = [CTdir '/amira']; + if exist(amiraDir, 'dir') + % This is the CT folder to use + iCTdirAmira = iCTdirList; + end + iCTdir2use = iCTdirList; + end + end + end + end + if iCTdir2use == 0 + fprintf(logFileID, ['\n', SCaseID, ' has no valid dicom directory']); + else + if iCTdirAmira % If amira dir exist, use it + iCTdir2use = iCTdirAmira; + end + CTdir = CTdirList(iCTdir2use); + SCaseListIdx = SCaseListIdx + 1; + SCaseList(SCaseListIdx).id = SCaseID; + CTdirPath = [CTdir.folder '/' CTdir.name]; + SCaseList(SCaseListIdx).dir = CTdirPath; + if CTnum ~= 1 + fprintf(logFileID, ['\n', SCaseID, ' dicom directory is CT-' num2str(CTnum)]); + end + end + end + end + end +end + +%% Loop over input SCase list (given as input argument), if length(SCaseListIn) > 0 +for iSCaseListIn = 1:length(SCaseListIn) + % Check if there is a valid dicom directory for this SCaseID + + SCaseID = char(SCaseListIn{iSCaseListIn}); + % Build directory from SCaseID (as in function ShoulderCase.path) + + % The validity of the format should be either Pnnn or Nnnn. + if (numel(regexp(SCaseID,'^[PN]\d{1,3}$')) == 0) + error(['Invalid format of SCaseID: ' SCaseID{1} '. CaseID must start with "P" or "N" and be followed by 1 to 3 digits.']); + end + + % Set the folder of the SCaseID + SCaseDirLevelPN = SCaseID(1); % Either 'P' or 'N' + strLengthSCaseID = strlength(SCaseID(2:end)); + if (strLengthSCaseID < 2) + SCaseDirLevel2 = '0'; % Hunderets + SCaseDirLevel1 = '0'; % Dozent + elseif (strLengthSCaseID < 3) + SCaseDirLevel2 = '0'; % Hunderets + SCaseDirLevel1 = SCaseID(2); % Dozent + else + SCaseDirLevel2 = SCaseID(2); % Hunderets + SCaseDirLevel1 = SCaseID(3); % Dozent + end + % Set SCaseID with fixed 3 digits after P/N (needed when id < + % 10 inloading of data in amira directory. + SCaseID4C = [SCaseDirLevelPN SCaseDirLevel2 SCaseDirLevel1 SCaseID(end)]; + + % Check if a (!unique! to be done) directory exists for this SCaseID + FindSCaseIDFolder = dir([dataDir '/' SCaseDirLevelPN '/' SCaseDirLevel2 '/' SCaseDirLevel1 '/' SCaseID '*']); + if (isempty(FindSCaseIDFolder)) % No directory for this SCaseID + error(['Missing directory for SCaseID: ' SCaseID]); + end + SCaseDirLevel0 = FindSCaseIDFolder.name; + SCaseDir = [dataDir '/' SCaseDirLevelPN '/' SCaseDirLevel2 '/' SCaseDirLevel1 '/' SCaseDirLevel0]; + + % Check if this SCaseID has a CT directory with a valid preoprative CT + CTdirList=dir([SCaseDir '/CT-*']); % List directories with CT + iCTdirAmira = 0; + iCTdir2use = 0; + for iCTdirList = length(CTdirList):-1:1 % Loop from last to first (4 to 1) + CTdir = CTdirList(iCTdirList); + % Check that dir name ends with -1,-2,-3,-4 + dirName = CTdir.name; + if strcmp(dirName(end-1),'-') % Exclude postoperative 'p' CT + CTnum = str2num(dirName(end)); + if CTnum <= 4 % Exlude non shoulder (elbow) CT + % Check that the dir contains a dicom dir + CTdir = [CTdir.folder '/' CTdir.name]; + dicomDir = [CTdir '/dicom']; + if exist(dicomDir, 'dir') + % Chech if amira dir exist + amiraDir = [CTdir '/amira']; + if exist(amiraDir, 'dir') + % This is the CT folder to use + iCTdirAmira = iCTdirList; + end + iCTdir2use = iCTdirList; + end + end + end + end + if iCTdir2use == 0 + fprintf(logFileID, ['\n', SCaseID, ' has no valid dicom directory']); + else + if iCTdirAmira % If amira dir exist, use it + iCTdir2use = iCTdirAmira; + end + CTdir = CTdirList(iCTdir2use); + SCaseListIdx = SCaseListIdx + 1; + SCaseList(SCaseListIdx).id = SCaseID; + CTdirPath = [CTdir.folder '/' CTdir.name]; + SCaseList(SCaseListIdx).dir = CTdirPath; + if CTnum ~= 1 + fprintf(logFileID, ['\n', SCaseID, ' dicom directory is CT-' num2str(CTnum)]); + end + end +end + +fprintf(logFileID, ['\nNumber of SCase: ' num2str(length(SCaseList))]); +fclose(logFileID); % Close log file +end + diff --git a/loadDicomCTscan.m b/loadDicomCTscan.m new file mode 100644 index 0000000..c5d1ce6 --- /dev/null +++ b/loadDicomCTscan.m @@ -0,0 +1,52 @@ +% This script tries to load any CT dicom scan + +dicomDir = '/Volumes/shoulder/data/N/1/8/N180-753671/CT-N180-753671-1/dicom'; % Not working +%dicomDir = '/Volumes/shoulder/data/N/0/2/N29-40883/CT-N29-40883-1/dicom'; % Working + +%% +dicomFiles = dir(dicomDir); +dicomFiles = dicomFiles(~ismember({dicomFiles.name},{'.','..'})); + +filename = fullfile(dicomFiles(10).folder, dicomFiles(10).name); + +try + dicomInfoRes = dicominfo(filename, 'UseVRHeuristic', true); + dicomInfoError = false; + disp('dicominfo ok'); + % If dicominfo didn't worked, try with matlab function dicomdisp +catch ME + dicomInfoError = true; + disp('dicominfo error --> using dicomdisp'); + disp(ME.message); + dicomdispStr = evalc('dicomdisp(filename)'); +end +%% +a=1; +b=2; +X = dicomread(filename); + +if ~dicomInfoError + X = dicomread(filename); + imshow(X); + source = dicomDir; + [V,spatial,dim] = dicomreadVolume(source); % Should be transformed to match Elham segmentation code +end + + +%% +tic; +source = dicomDir; + [V,spatial,dim] = dicomreadVolume(source); % Should be transformed to match Elham segmentation code +toc + +%% +Volume = squeeze(V); % Remove singleton dimension +% We shoudl set a grid before using isosurface +[fo,vo] = isosurface(Volume,150); % Could be used in measureSCase when no manual segmentation +figure +p1 = patch('Faces', fo, 'Vertices', vo); % draw the outside of the volume +p1.FaceColor = 'red'; +p1.EdgeColor = 'none'; + + +dicomBrowser(dicomDir); \ No newline at end of file diff --git a/log/dicominfoSCase.log b/log/dicominfoSCase.log new file mode 100644 index 0000000..a6a4904 --- /dev/null +++ b/log/dicominfoSCase.log @@ -0,0 +1,1153 @@ +dicominfoSCase log file +Date: 01-Mar-2019 14:59:06Data directory not found, check config.txt + +Read dicom meta data + +N29: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N32: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N34: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N37: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N41: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N42: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N49: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N52: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N53: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N56: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N57: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N60: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N61: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N63: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N66: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N68: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N71: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N73: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N75: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N76: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N77: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N79: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N83: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N85: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N87: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N90: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N91: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N93: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N95: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N98: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N101: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N102: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N104: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N107: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N110: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N112: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N116: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N118: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N120: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N122: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N124: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N125: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N126: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N128: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N129: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N131: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N134: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N135: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N137: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N139: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N141: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N144: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N146: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N148: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N150: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N152: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N154: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N157: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N158: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N159: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N160: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N161: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N163: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N164: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N165: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N166: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N167: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N168: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N169: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N170: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N171: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N172: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N173: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N174: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N175: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N176: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N177: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N178: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N179: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N180: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N181: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N182: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N183: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N184: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N185: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N186: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N187: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N188: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N189: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N190: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N191: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N192: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N193: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N194: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N195: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N196: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N197: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N198: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N199: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N200: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N201: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N202: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N203: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N204: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N205: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N206: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N207: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N208: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N209: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N210: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N211: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N212: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N213: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N214: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N215: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N216: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N218: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N219: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N220: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N221: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N222: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N223: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N224: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N225: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N226: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N227: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N228: dicominfo ok +N229: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N230: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N231: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N232: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N233: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N234: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N235: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N237: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N238: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N239: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N240: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N241: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N242: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N243: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N244: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N245: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N246: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N247: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N249: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N250: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N251: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N252: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N253: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N254: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N255: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N256: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N257: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N258: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N259: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N260: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N261: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N262: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N263: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N264: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N265: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N266: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N267: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N268: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N269: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N270: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N271: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N272: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N273: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N274: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N275: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N276: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N277: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N278: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N279: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N280: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N281: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N282: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N283: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N284: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N285: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N286: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N287: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N288: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N289: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N290: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N291: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N292: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +N293: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N294: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N295: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N296: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N297: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N298: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N299: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N300: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N301: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N302: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N303: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N304: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N305: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N306: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N307: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N308: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N309: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N310: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N311: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N312: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N313: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N314: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N315: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N316: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N317: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N318: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N319: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N320: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N321: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N322: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N323: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N324: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N325: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N326: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N327: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N328: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N329: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N330: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N331: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N332: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N333: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N334: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N335: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N336: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N337: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N338: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N339: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N340: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N341: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N342: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N343: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N344: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N345: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N346: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N347: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N348: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N349: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N350: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N351: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N352: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N353: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N354: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N355: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N356: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N357: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N358: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N359: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N360: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N361: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N362: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N363: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N364: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N365: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N366: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N367: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N368: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N369: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N370: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N371: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N372: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N373: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N374: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N375: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N376: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N377: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N378: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N379: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N380: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N381: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N382: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N383: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N384: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N385: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N386: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N387: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N388: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N389: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N390: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N391: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N392: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N393: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N394: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N395: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N396: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N397: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N398: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N399: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N400: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N401: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N402: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N403: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N404: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N405: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N406: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N407: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N408: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N409: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N410: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N411: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N412: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N413: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N414: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N415: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N416: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N417: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N418: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N419: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N420: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N421: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N422: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N423: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N424: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N425: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N426: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N427: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N428: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N429: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N430: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N431: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N432: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N433: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N434: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N435: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N436: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N437: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N438: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N439: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N440: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N441: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N442: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N443: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N444: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N445: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N446: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N447: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N448: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N449: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N450: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N451: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N452: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N453: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N454: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N455: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N456: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N457: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N458: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N459: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N460: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N461: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N462: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N463: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N464: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N465: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N466: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N467: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N468: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N469: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N470: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N471: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N472: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N473: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N474: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N475: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N476: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N477: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N478: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N479: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N480: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N481: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N482: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N483: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N484: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N485: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N486: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N487: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N488: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N489: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N490: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N491: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N492: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N493: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N494: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N495: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N496: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N497: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N498: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N499: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N500: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N501: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N502: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N503: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N504: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N505: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N506: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N507: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N508: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N509: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N510: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N511: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N512: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N513: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N514: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N515: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N516: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N517: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N518: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N519: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N520: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N521: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N522: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N523: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N524: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N525: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N526: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N527: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N528: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N529: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N530: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N531: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N532: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N533: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N534: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N535: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N536: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N537: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N538: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N539: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N540: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N541: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N542: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N543: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +N544: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P1: dicominfo ok +P2: dicominfo ok +P3: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P4: dicominfo ok +P5: dicominfo ok +P6: dicominfo ok +P8: dicominfo ok +P9: dicominfo ok +P10: dicominfo ok +P11: dicominfo ok +P12: dicominfo ok +P13: dicominfo ok +P14: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P15: dicominfo ok +P16: dicominfo ok +P18: dicominfo ok +P19: dicominfo ok +P20: dicominfo ok +P21: dicominfo ok +P22: dicominfo ok +P24: dicominfo ok +P28: dicominfo ok +P29: dicominfo ok +P30: dicominfo ok +P31: dicominfo ok +P32: dicominfo ok +P33: dicominfo ok +P34: dicominfo ok +P35: dicominfo ok +P36: dicominfo ok +P37: dicominfo ok +P38: dicominfo ok +P40: dicominfo ok +P41: dicominfo ok +P42: dicominfo ok +P43: dicominfo ok +P44: dicominfo ok +P45: dicominfo ok +P46: dicominfo ok +P47: dicominfo ok +P49: dicominfo ok +P50: dicominfo ok +P51: dicominfo ok +P53: dicominfo ok +P54: dicominfo ok +P55: dicominfo ok +P56: dicominfo ok +P58: dicominfo ok +P59: dicominfo ok +P60: dicominfo ok +P61: dicominfo ok +P63: dicominfo ok +P64: dicominfo ok +P65: dicominfo ok +P68: dicominfo ok +P71: dicominfo ok +P72: dicominfo ok +P73: dicominfo ok +P74: dicominfo ok +P75: dicominfo ok +P76: dicominfo ok +P77: dicominfo ok +P78: dicominfo ok +P80: dicominfo ok +P81: dicominfo ok +P82: dicominfo ok +P83: dicominfo ok +P84: dicominfo ok +P85: dicominfo ok +P86: dicominfo ok +P87: dicominfo ok +P88: dicominfo ok +P89: dicominfo ok +P90: dicominfo ok +P91: dicominfo ok +P92: dicominfo ok +P93: dicominfo ok +P94: dicominfo ok +P95: dicominfo ok +P96: dicominfo ok +P100: dicominfo ok +P101: dicominfo ok +P102: dicominfo ok +P103: dicominfo ok +P104: dicominfo ok +P105: dicominfo ok +P106: dicominfo ok +P107: dicominfo ok +P109: dicominfo ok +P110: dicominfo ok +P111: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P113: dicominfo ok +P114: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P115: dicominfo ok +P117: dicominfo ok +P118: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P119: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P120: dicominfo ok +P121: dicominfo ok +P122: dicominfo ok +P123: dicominfo ok +P124: dicominfo ok +P125: dicominfo ok +P126: dicominfo ok +P127: dicominfo ok +P128: dicominfo ok +P130: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P131: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P132: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P133: dicominfo ok +P134: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P135: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P136: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P137: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P138: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P139: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P140: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P141: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P142: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P143: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P144: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P145: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P146: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P147: dicominfo ok +P148: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P149: dicominfo ok +P150: dicominfo ok +P152: dicominfo ok +P153: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P154: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P155: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P156: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P157: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P158: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P159: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P160: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P161: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P162: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P163: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P164: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P165: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P166: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P167: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P168: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P169: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P170: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P171: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P172: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P173: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P174: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P175: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P176: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P177: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P178: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P179: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P180: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P181: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P182: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P183: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P184: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P185: dicominfo ok +P186: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P187: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P188: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P189: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P190: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P191: dicominfo ok +P192: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P193: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P194: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P195: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P196: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P197: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P198: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P199: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P200: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P201: dicominfo ok +P202: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P203: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P204: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P205: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P206: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P207: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P208: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P209: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P210: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P211: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P212: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P213: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P214: dicominfo ok +P215: dicominfo ok +P216: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P217: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P218: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P219: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P220: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P221: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P222: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P223: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P224: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P225: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P227: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P228: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P229: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P230: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P231: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P232: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P233: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P234: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P235: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P236: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P237: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P238: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P239: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P240: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P241: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P242: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P243: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P244: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P245: dicominfo ok +P247: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P248: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P249: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P250: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P251: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P252: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P253: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P254: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P255: dicominfo ok +P256: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P257: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P258: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P259: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P260: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P261: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P262: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P263: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P264: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P265: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P266: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P267: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P268: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P269: dicominfo ok +P270: dicominfo ok +P272: dicominfo ok +P273: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P274: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P275: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P276: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P277: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P278: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P279: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P280: dicominfo ok +P281: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P282: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P283: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P284: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P285: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P286: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P287: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P288: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P289: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P290: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P291: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P293: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P294: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P295: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P296: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P297: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P298: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P300: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P301: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P302: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P303: dicominfo ok +P304: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P306: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P307: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P313: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P315: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P318: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P319: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P320: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P321: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P322: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P323: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P324: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P325: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P326: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P327: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P328: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P329: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P330: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P331: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P332: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P333: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P334: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P335: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P336: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P337: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P338: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P339: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P340: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P343: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P344: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P345: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P346: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P347: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P348: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P349: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P350: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P351: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P352: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P354: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P355: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P356: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P357: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P358: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P360: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P361: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P362: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P363: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P364: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P365: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P366: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P367: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P368: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P369: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P370: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P371: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P373: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P374: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P375: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P376: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P377: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P378: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P379: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P380: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P381: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P382: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P383: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P384: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P385: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P386: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P387: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P388: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P389: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P390: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P391: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P392: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P393: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P394: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P395: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P396: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P397: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P398: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P399: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P400: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P401: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P402: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P403: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P404: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P405: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P406: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P407: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P408: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P409: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P410: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P411: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P412: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P413: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P414: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P415: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P416: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P417: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P418: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P419: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P420: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P421: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P422: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P423: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P424: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P425: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P426: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P427: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P428: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P429: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P430: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P431: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P432: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P433: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P434: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P435: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P436: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P437: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P438: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P439: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P440: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P441: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P442: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P443: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P444: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P445: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P446: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P447: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P448: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P449: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P450: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P451: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P452: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P453: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P454: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P455: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P456: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P457: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P458: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P459: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P460: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P461: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P462: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P463: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P464: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P465: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P466: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P467: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P468: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P469: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P470: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P471: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P472: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P473: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P474: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P475: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P476: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P477: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P478: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P479: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P480: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P481: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P482: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P483: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P484: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P485: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P486: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P487: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P488: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P489: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P490: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P491: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P492: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P493: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P494: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P495: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P496: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P497: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P498: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P499: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P500: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P501: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P502: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P503: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P504: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P505: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P506: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P507: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P508: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P509: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P510: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P511: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P512: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P513: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P514: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P515: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P516: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P517: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P518: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P519: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P520: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P521: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P523: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P524: +dicominfo error --> using dicomdisp +Index exceeds the number of array elements (16). +P525: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P526: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P527: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P528: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P529: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P530: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P531: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P532: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P533: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P534: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P535: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P536: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P537: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P538: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P539: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P540: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P541: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P542: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P543: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P544: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P545: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P546: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P547: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P548: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P549: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P550: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P551: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P552: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P553: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P554: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P555: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P556: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P557: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P558: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P559: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P560: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P561: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P562: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P563: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P564: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P565: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P566: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P567: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P568: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P569: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P570: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P571: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P572: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P573: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P574: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P575: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P576: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P577: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P578: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P579: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P580: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P581: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P582: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P583: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P584: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P585: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P586: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P587: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P588: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P589: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P590: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P591: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P592: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P593: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P594: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P595: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P596: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P597: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P598: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P599: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P600: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P601: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P602: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P603: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P604: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P605: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P606: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P607: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P608: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P609: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P610: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P611: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P612: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P613: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P614: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P615: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P616: dicominfo ok, SpacingBetweenSlices estimated from 2 slices +P617: dicominfo ok, SpacingBetweenSlices estimated from 2 slices + +Elapsed time (s): 81.3853 + +Saved csv file: Y: +Saved xls file: Y: + +SCases treated: 1014 +Elapsed time (s): 85.4853 \ No newline at end of file diff --git a/log/listSCase.log b/log/listSCase.log new file mode 100644 index 0000000..d2f8032 --- /dev/null +++ b/log/listSCase.log @@ -0,0 +1,11 @@ +listSCase.log +Date: 01-Mar-2019 14:59:17 +Get list of all SCase with a valid dicom folder +N438 dicom directory is CT-2 +5 has no valid dicom directory +data has no valid dicom directory +data has no valid dicom directory +P550 dicom directory is CT-3 +P581 dicom directory is CT-3 +P582 dicom directory is CT-3 +Number of SCase: 1014 \ No newline at end of file diff --git a/log/measureSCase.log b/log/measureSCase.log new file mode 100644 index 0000000..8aadb70 --- /dev/null +++ b/log/measureSCase.log @@ -0,0 +1,4 @@ +measureSCase.log +Date: 18-Feb-2019 15:09:32Data directory not found, check config.txt + +List of SCase \ No newline at end of file diff --git a/log/segmentScapula.log b/log/segmentScapula.log new file mode 100644 index 0000000..4b9781b --- /dev/null +++ b/log/segmentScapula.log @@ -0,0 +1,27 @@ +segmentScapula log file + +Date: 19-Aug-2018 21:37:39 + +Load statistical shape models + Right scapula + Left scapula + Done in 52.912 s + +SCaseId: P001 + Load CT + Done in 3.6103 s + Identify scapula side + CT contains only the right scapula + Done in 30.3494 s + Segment scapula + Done in 1047.8884 s + Write scapula surface file: ../../../../data/P/0/0/P1-490313/CT-P1-490313-1/matlab/scapulaSurfaceAutoR.ply + Done in 0.71538 s + +SCaseId: P002 + Load CT + Done in 6.0102 s + Identify scapula side + CT contains only the right scapula + Done in 37.5681 s + Segment scapula \ No newline at end of file diff --git a/muscles/MuscleInfoListPatient.m b/muscles/MuscleInfoListPatient.m new file mode 100644 index 0000000..a9ec509 --- /dev/null +++ b/muscles/MuscleInfoListPatient.m @@ -0,0 +1,401 @@ +% This script runs through the list of patients for which muscle % degeneration values were calculated (i.e. already put in Database) +% This script first creates a list of these patients and runs the script "muscleDegeneration" +% This script outputs a table where the values for each muscle is given in this way: +% Example for SS muscle: +% muscle.caseID = caseID; +% muscle.Stot %Stot: total area of the muscle fossa, manually delimited contours) +% muscle.Satrophy % Sa: area of the atrophy (Stot - Sm) +% muscle.Sinfiltration % Si: area of fat infiltration +% muscle.Sosteochondroma % So: area of osteochondroma +% muscle.Sdegeneration % Sm: area of the degenerated muscle, without atrophy, fat infiltration and osteochondroma +% muscle.atrophy % ratio of muscle atrophy (area atrophy over total area of muscle fossa) +% muscle.infiltration % ratio of fat infiltration in the muscle (area fat infiltration over total area of muscle fossa) +% muscle.osteochondroma % ratio of osteochondroma in the muscle (area osteochondroma over total area of muscle fossa) +% muscle.degeneration % the ratio of degeneration of the muscle +% are saved. For more details, check the function "muscleDegeneration" +% Important: Check location of your database!!! +% created with MATLAB ver.: 9.2.0.556344 (R2017a) on Windows 7 +% Author: K. Cosendey and Y. Boulanaache +% Date: 16-Oct-2017 + +HUThresholdMuscle = 0; +HUThresholdFat = 30; +HUThresholdOsteochondroma = 166; +listPatientNumber =[]; +listPathologic = []; +MatrixPCSA_SS = []; +MatrixPCSA_IS = []; +MatrixPCSA_SC = []; +MatrixPCSA_TM = []; + +AveragePCSA_SS=0; +AveragePCSA_IS=0; +AveragePCSA_SC=0; +AveragePCSA_TM=0; + +tabMuscle_SS=[]; +tabMuscle_IS=[]; +tabMuscle_SC=[]; +tabMuscle_TM=[]; + +% mainFolder = dir('../../../../'); +% %mainFolder = mainFolder(1).folder; %original +% +% XLSShoulderDatabaseLocation = [mainFolder '/methods/matlab/database/muscles/'];original +% %XLSShoulderDatabaseLocation = pwd; +% %output a column with only numbers in it +% +% CTDatabaseLocation = '../../../data'; % Location of the CT database +XLSShoulderDatabaseLocation = '../../../data/Excel/'; % Location of the XLS ShoulderDatabase + + +[~,~,raw] = xlsread([XLSShoulderDatabaseLocation '\ShoulderDataBase.xlsx'],2,'A:A'); +A= raw; % outputs all values, numbers and text + +%Now read out SS degeneration to check if this patient had a calculated degeneration of muscle or not +%If SS was calculated then the other muscles as well. +[~,~,raw] = xlsread([XLSShoulderDatabaseLocation '\ShoulderDataBase.xlsx'],2,'CD:CD'); +B = raw; %outputs all +%make a 2-columns matrix that has caseID and corresponding muscle values + +%Now create a list which contains only patients that have muscle values. +listCases = [A,B]; %for each non-number B value, we have corresponding value in column A. + +%Loop through +i=1; +while isnan(listCases{i,1}) == 0 + if isnumeric(listCases{i,2})==1 + listPathologic = [ listPathologic; listCases{i,1}(1)]; + listPatientNumber=[ listPatientNumber; str2num(listCases{i,1}(2:end))]; + + end + i=i+1; +end + +listPatient = cell(size(listPathologic,1),1); +listPatientCaseID = cell(size(listPathologic,1),1); + +for i=1:size(listPathologic,1) + if listPatientNumber(i) < 100 + if listPatientNumber(i)<10 + listPatient{i,1} = [listPathologic(i) '00' num2str(listPatientNumber(i))]; + else + listPatient{i,1} = [listPathologic(i) '0' num2str(listPatientNumber(i))]; + end + else + listPatient{i,1} = [listPathologic(i) num2str(listPatientNumber(i))]; + end + + listPatientCaseID{i,1} = [listPathologic(i) num2str(listPatientNumber(i))]; + +end +%FOR SUPRASPINATUS +muscleName = 'SS'; + + for i=1:size(listPatient,1) + + caseID = listPatientCaseID{i,1}; + caseID2 = listPatient{i,1}; + + %inputFile = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + inputFile = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + FindCaseCTFolder = dir(inputFile); + + CaseID_IPP = FindCaseCTFolder.name; +% CaseCTFolder = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + CaseCTFolder = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + + CaseMuscleFolder = [ CaseCTFolder '\muscles']; + %Launch script "muscleDegeneration.m" + muscle = muscleDegeneration (caseID, muscleName ,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma, CaseMuscleFolder); + + CaseAmiraFolder = [CaseCTFolder '\amira']; + muscle.pixel = infoPixel(CaseAmiraFolder); + muscle.pixeldetail=muscle.pixel(1,1); + muscle.totalarea=muscle.Stot*(muscle.pixeldetail*muscle.pixeldetail)*0.01; %multiply total area by number of pixels. Result in cm2 + muscle.ratio=muscle.degeneration; %take the percentage degenerated + muscle.degenerationarea=muscle.totalarea*muscle.ratio; %this is the degenerated area + muscle.effectivePCSA=muscle.totalarea-muscle.degenerationarea; %the effective usable PCSA + tabMuscle_SS=[tabMuscle_SS; muscle]; + + + end + + %Because of ambiguity in pixelsize for some cases, chosen manually the pixel size + tabMuscle_SS(1).totalarea=0.01*tabMuscle_SS(1).Stot*(0.244955)^2; %P2, , chose "new" for all + tabMuscle_SS(3).totalarea=0.01*tabMuscle_SS(3).Stot*(0.310913)^2; %P6 +% tabMuscle(30).totalarea=0.01*tabMuscle(30).Stot*()^2; %P64: no data! +% tabMuscle(31).totalarea=0.01*tabMuscle(31).Stot*()^2; %P65: no data! +% tabMuscle(32).totalarea=0.01*tabMuscle(32).Stot*()^2; %P68: no data! +% tabMuscle(34).totalarea=0.01*tabMuscle(34).Stot*()^2; %P73: no data! +% tabMuscle(35).totalarea=0.01*tabMuscle(35).Stot*()^2; %P75: no data! +% tabMuscle(36).totalarea=0.01*tabMuscle(36).Stot*()^2; %P76: no data! +% tabMuscle(38).totalarea=0.01*tabMuscle(38).Stot*()^2; %P78: no data! +% tabMuscle(41).totalarea=0.01*tabMuscle(41).Stot*()^2; %P89: no data! +% tabMuscle(42).totalarea=0.01*tabMuscle(42).Stot*()^2; %P90: no data! +% tabMuscle(43).totalarea=0.01*tabMuscle(43).Stot*()^2; %P91: no data! +% tabMuscle(44).totalarea=0.01*tabMuscle(44).Stot*()^2; %P92: no data! +% tabMuscle(45).totalarea=0.01*tabMuscle(45).Stot*()^2; %P93: no data! + tabMuscle_SS(46).totalarea=0.01*tabMuscle_SS(46).Stot*(0.277493)^2; %P103 + tabMuscle_SS(47).totalarea=0.01*tabMuscle_SS(47).Stot*(0.290182)^2; %P105 + tabMuscle_SS(48).totalarea=0.01*tabMuscle_SS(48).Stot*(0.31551)^2; %P107 + tabMuscle_SS(61).totalarea=0.01*tabMuscle_SS(61).Stot*(0.271774)^2; %P145 + tabMuscle_SS(69).totalarea=0.01*tabMuscle_SS(69).Stot*(0.28785)^2; %P163 + tabMuscle_SS(73).totalarea=0.01*tabMuscle_SS(73).Stot*(0.334435)^2; %P170 + tabMuscle_SS(75).totalarea=0.01*tabMuscle_SS(75).Stot*(0.230255)^2; %P173 + tabMuscle_SS(78).totalarea=0.01*tabMuscle_SS(78).Stot*(0.280144)^2; %P176 + tabMuscle_SS(83).totalarea=0.01*tabMuscle_SS(83).Stot*(0.155813)^2; %P188 + +for i=1:size(tabMuscle_SS) + %update the degeneration information + tabMuscle_SS(i).degenerationarea=tabMuscle_SS(i).totalarea*tabMuscle_SS(i).ratio; %this is the new degenerated area + tabMuscle_SS(i).effectivePCSA=tabMuscle_SS(i).totalarea-tabMuscle_SS(i).degenerationarea; + MatrixPCSA_SS=[MatrixPCSA_SS;tabMuscle_SS(i).totalarea,tabMuscle_SS(i).effectivePCSA,tabMuscle_SS(i).ratio]; +end +% %Update MatrixPCSA as well +% MatrixPCSA(1,1)=tabMuscle(1).totalarea; +% MatrixPCSA(3,1)=tabMuscle(3).totalarea; +% % tabMuscle(30).totalarea=0.01*tabMuscle(30).Stot*()^2; %P64: no data! +% % tabMuscle(31).totalarea=0.01*tabMuscle(31).Stot*()^2; %P65: no data! +% % tabMuscle(32).totalarea=0.01*tabMuscle(32).Stot*()^2; %P68: no data! +% % tabMuscle(34).totalarea=0.01*tabMuscle(34).Stot*()^2; %P73: no data! +% % tabMuscle(35).totalarea=0.01*tabMuscle(35).Stot*()^2; %P75: no data! +% % tabMuscle(36).totalarea=0.01*tabMuscle(36).Stot*()^2; %P76: no data! +% % tabMuscle(38).totalarea=0.01*tabMuscle(38).Stot*()^2; %P78: no data! +% % tabMuscle(41).totalarea=0.01*tabMuscle(41).Stot*()^2; %P89: no data! +% % tabMuscle(42).totalarea=0.01*tabMuscle(42).Stot*()^2; %P90: no data! +% % tabMuscle(43).totalarea=0.01*tabMuscle(43).Stot*()^2; %P91: no data! +% % tabMuscle(44).totalarea=0.01*tabMuscle(44).Stot*()^2; %P92: no data! +% % tabMuscle(45).totalarea=0.01*tabMuscle(45).Stot*()^2; %P93: no data! +% MatrixPCSA(46,1)=tabMuscle(46).totalarea; +% MatrixPCSA(47,1)=tabMuscle(47).totalarea; +% MatrixPCSA(48,1)=tabMuscle(48).totalarea; +% MatrixPCSA(61,1)=tabMuscle(61).totalarea; +% MatrixPCSA(69,1)=tabMuscle(69).totalarea; +% MatrixPCSA(73,1)=tabMuscle(73).totalarea; +% MatrixPCSA(75,1)=tabMuscle(75).totalarea; +% MatrixPCSA(78,1)=tabMuscle(78).totalarea; +% MatrixPCSA(83,1)=tabMuscle(83).totalarea; + + AveragePCSA_SS = mean(MatrixPCSA_SS); %Result Oct2017: 5.62615579051498 + StdDev_PCSA_SS = std (MatrixPCSA_SS); %Result Oct2017: 2.41206345030003 + +disp('end SS') + +%FOR Infraspinatus +muscleName = 'IS'; + + for i=1:size(listPatient,1) + + caseID = listPatientCaseID{i,1}; + caseID2 = listPatient{i,1}; + + %inputFile = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + inputFile = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + FindCaseCTFolder = dir(inputFile); + + CaseID_IPP = FindCaseCTFolder.name; +% CaseCTFolder = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + CaseCTFolder = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + + CaseMuscleFolder = [ CaseCTFolder '\muscles']; + %Launch script "muscleDegeneration.m" + muscle = muscleDegeneration (caseID, muscleName ,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma, CaseMuscleFolder); + + CaseAmiraFolder = [CaseCTFolder '\amira']; + muscle.pixel = infoPixel(CaseAmiraFolder); + muscle.pixeldetail=muscle.pixel(1,1); + muscle.totalarea=muscle.Stot*(muscle.pixeldetail*muscle.pixeldetail)*0.01; %multiply total area by number of pixels. Result in cm2 + muscle.ratio=muscle.degeneration; %take the percentage degenerated + muscle.degenerationarea=muscle.totalarea*muscle.ratio; %this is the degenerated area + muscle.effectivePCSA=muscle.totalarea-muscle.degenerationarea; %the effective usable PCSA + tabMuscle_IS=[tabMuscle_IS; muscle]; + + + end + + %Because of ambiguity in pixelsize for some cases, chosen manually the pixel size + tabMuscle_IS(1).totalarea=0.01*tabMuscle_IS(1).Stot*(0.244955)^2; %P2, , chose "new" for all + tabMuscle_IS(3).totalarea=0.01*tabMuscle_IS(3).Stot*(0.310913)^2; %P6 +% tabMuscle(30).totalarea=0.01*tabMuscle(30).Stot*()^2; %P64: no data! +% tabMuscle(31).totalarea=0.01*tabMuscle(31).Stot*()^2; %P65: no data! +% tabMuscle(32).totalarea=0.01*tabMuscle(32).Stot*()^2; %P68: no data! +% tabMuscle(34).totalarea=0.01*tabMuscle(34).Stot*()^2; %P73: no data! +% tabMuscle(35).totalarea=0.01*tabMuscle(35).Stot*()^2; %P75: no data! +% tabMuscle(36).totalarea=0.01*tabMuscle(36).Stot*()^2; %P76: no data! +% tabMuscle(38).totalarea=0.01*tabMuscle(38).Stot*()^2; %P78: no data! +% tabMuscle(41).totalarea=0.01*tabMuscle(41).Stot*()^2; %P89: no data! +% tabMuscle(42).totalarea=0.01*tabMuscle(42).Stot*()^2; %P90: no data! +% tabMuscle(43).totalarea=0.01*tabMuscle(43).Stot*()^2; %P91: no data! +% tabMuscle(44).totalarea=0.01*tabMuscle(44).Stot*()^2; %P92: no data! +% tabMuscle(45).totalarea=0.01*tabMuscle(45).Stot*()^2; %P93: no data! + tabMuscle_IS(46).totalarea=0.01*tabMuscle_IS(46).Stot*(0.277493)^2; %P103 + tabMuscle_IS(47).totalarea=0.01*tabMuscle_IS(47).Stot*(0.290182)^2; %P105 + tabMuscle_IS(48).totalarea=0.01*tabMuscle_IS(48).Stot*(0.31551)^2; %P107 + tabMuscle_IS(61).totalarea=0.01*tabMuscle_IS(61).Stot*(0.271774)^2; %P145 + tabMuscle_IS(69).totalarea=0.01*tabMuscle_IS(69).Stot*(0.28785)^2; %P163 + tabMuscle_IS(73).totalarea=0.01*tabMuscle_IS(73).Stot*(0.334435)^2; %P170 + tabMuscle_IS(75).totalarea=0.01*tabMuscle_IS(75).Stot*(0.230255)^2; %P173 + tabMuscle_IS(78).totalarea=0.01*tabMuscle_IS(78).Stot*(0.280144)^2; %P176 + tabMuscle_IS(83).totalarea=0.01*tabMuscle_IS(83).Stot*(0.155813)^2; %P188 + +for i=1:size(tabMuscle_IS) + %update the degeneration information + tabMuscle_IS(i).degenerationarea=tabMuscle_IS(i).totalarea*tabMuscle_IS(i).ratio; %this is the new degenerated area + tabMuscle_IS(i).effectivePCSA=tabMuscle_IS(i).totalarea-tabMuscle_IS(i).degenerationarea; + MatrixPCSA_IS=[MatrixPCSA_IS;tabMuscle_IS(i).totalarea,tabMuscle_IS(i).effectivePCSA,tabMuscle_IS(i).ratio]; +end + + AveragePCSA_IS = mean(MatrixPCSA_IS); + StdDev_PCSA_IS = std (MatrixPCSA_IS); + +disp('end IS') + + +%FOR Subscapularis +muscleName = 'SC'; + + for i=1:size(listPatient,1) + + caseID = listPatientCaseID{i,1}; + caseID2 = listPatient{i,1}; + + %inputFile = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + inputFile = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + FindCaseCTFolder = dir(inputFile); + + CaseID_IPP = FindCaseCTFolder.name; +% CaseCTFolder = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + CaseCTFolder = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + + CaseMuscleFolder = [ CaseCTFolder '\muscles']; + muscle = muscleDegeneration (caseID, muscleName ,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma, CaseMuscleFolder); + + CaseAmiraFolder = [CaseCTFolder '\amira']; + muscle.pixel = infoPixel(CaseAmiraFolder); + muscle.pixeldetail=muscle.pixel(1,1); + muscle.totalarea=muscle.Stot*(muscle.pixeldetail*muscle.pixeldetail)*0.01; %multiply total area by number of pixels. Result in cm2 + muscle.ratio=muscle.degeneration; %take the percentage degenerated + muscle.degenerationarea=muscle.totalarea*muscle.ratio; %this is the degenerated area + muscle.effectivePCSA=muscle.totalarea-muscle.degenerationarea; %the effective usable PCSA + tabMuscle_SC=[tabMuscle_SC; muscle]; + + + end + + %Because of ambiguity in pixelsize for some cases, chosen manually the pixel size + tabMuscle_SC(1).totalarea=0.01*tabMuscle_SC(1).Stot*(0.244955)^2; %P2, , chose "new" for all + tabMuscle_SC(3).totalarea=0.01*tabMuscle_SC(3).Stot*(0.310913)^2; %P6 +% tabMuscle(30).totalarea=0.01*tabMuscle(30).Stot*()^2; %P64: no data! +% tabMuscle(31).totalarea=0.01*tabMuscle(31).Stot*()^2; %P65: no data! +% tabMuscle(32).totalarea=0.01*tabMuscle(32).Stot*()^2; %P68: no data! +% tabMuscle(34).totalarea=0.01*tabMuscle(34).Stot*()^2; %P73: no data! +% tabMuscle(35).totalarea=0.01*tabMuscle(35).Stot*()^2; %P75: no data! +% tabMuscle(36).totalarea=0.01*tabMuscle(36).Stot*()^2; %P76: no data! +% tabMuscle(38).totalarea=0.01*tabMuscle(38).Stot*()^2; %P78: no data! +% tabMuscle(41).totalarea=0.01*tabMuscle(41).Stot*()^2; %P89: no data! +% tabMuscle(42).totalarea=0.01*tabMuscle(42).Stot*()^2; %P90: no data! +% tabMuscle(43).totalarea=0.01*tabMuscle(43).Stot*()^2; %P91: no data! +% tabMuscle(44).totalarea=0.01*tabMuscle(44).Stot*()^2; %P92: no data! +% tabMuscle(45).totalarea=0.01*tabMuscle(45).Stot*()^2; %P93: no data! + tabMuscle_SC(46).totalarea=0.01*tabMuscle_SC(46).Stot*(0.277493)^2; %P103 + tabMuscle_SC(47).totalarea=0.01*tabMuscle_SC(47).Stot*(0.290182)^2; %P105 + tabMuscle_SC(48).totalarea=0.01*tabMuscle_SC(48).Stot*(0.31551)^2; %P107 + tabMuscle_SC(61).totalarea=0.01*tabMuscle_SC(61).Stot*(0.271774)^2; %P145 + tabMuscle_SC(69).totalarea=0.01*tabMuscle_SC(69).Stot*(0.28785)^2; %P163 + tabMuscle_SC(73).totalarea=0.01*tabMuscle_SC(73).Stot*(0.334435)^2; %P170 + tabMuscle_SC(75).totalarea=0.01*tabMuscle_SC(75).Stot*(0.230255)^2; %P173 + tabMuscle_SC(78).totalarea=0.01*tabMuscle_SC(78).Stot*(0.280144)^2; %P176 + tabMuscle_SC(83).totalarea=0.01*tabMuscle_SC(83).Stot*(0.155813)^2; %P188 + +for i=1:size(tabMuscle_SC) + %update the degeneration information + tabMuscle_SC(i).degenerationarea=tabMuscle_SC(i).totalarea*tabMuscle_SC(i).ratio; %this is the new degenerated area + tabMuscle_SC(i).effectivePCSA=tabMuscle_SC(i).totalarea-tabMuscle_SC(i).degenerationarea; + MatrixPCSA_SC=[MatrixPCSA_SC;tabMuscle_SC(i).totalarea,tabMuscle_SC(i).effectivePCSA,tabMuscle_SC(i).ratio]; +end + + AveragePCSA_SC = mean(MatrixPCSA_SC); + StdDev_PCSA_SC = std (MatrixPCSA_SC); + +disp('end SC') + + +%FOR Teres Minor +muscleName = 'TM'; + + for i=1:size(listPatient,1) + + caseID = listPatientCaseID{i,1}; + caseID2 = listPatient{i,1}; + + %inputFile = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + inputFile = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' caseID '*']; + FindCaseCTFolder = dir(inputFile); + + CaseID_IPP = FindCaseCTFolder.name; +% CaseCTFolder = [mainFolder 'data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + CaseCTFolder = ['Y:\data\' caseID2(1) '\' caseID2(2) '\' caseID2(3) '\' CaseID_IPP '\CT-' CaseID_IPP '-1']; + + CaseMuscleFolder = [ CaseCTFolder '\muscles']; + muscle = muscleDegeneration (caseID, muscleName ,HUThresholdMuscle,HUThresholdFat,HUThresholdOsteochondroma, CaseMuscleFolder); + + CaseAmiraFolder = [CaseCTFolder '\amira']; + muscle.pixel = infoPixel(CaseAmiraFolder); + muscle.pixeldetail=muscle.pixel(1,1); + muscle.totalarea=muscle.Stot*(muscle.pixeldetail*muscle.pixeldetail)*0.01; %multiply total area by number of pixels. Result in cm2 + muscle.ratio=muscle.degeneration; %take the percentage degenerated + muscle.degenerationarea=muscle.totalarea*muscle.ratio; %this is the degenerated area + muscle.effectivePCSA=muscle.totalarea-muscle.degenerationarea; %the effective usable PCSA + tabMuscle_TM=[tabMuscle_TM; muscle]; + + + end + + %Because of ambiguity in pixelsize for some cases, chosen manually the pixel size + tabMuscle_TM(1).totalarea=0.01*tabMuscle_TM(1).Stot*(0.244955)^2; %P2, , chose "new" for all + tabMuscle_TM(3).totalarea=0.01*tabMuscle_TM(3).Stot*(0.310913)^2; %P6 +% tabMuscle(30).totalarea=0.01*tabMuscle(30).Stot*()^2; %P64: no data! +% tabMuscle(31).totalarea=0.01*tabMuscle(31).Stot*()^2; %P65: no data! +% tabMuscle(32).totalarea=0.01*tabMuscle(32).Stot*()^2; %P68: no data! +% tabMuscle(34).totalarea=0.01*tabMuscle(34).Stot*()^2; %P73: no data! +% tabMuscle(35).totalarea=0.01*tabMuscle(35).Stot*()^2; %P75: no data! +% tabMuscle(36).totalarea=0.01*tabMuscle(36).Stot*()^2; %P76: no data! +% tabMuscle(38).totalarea=0.01*tabMuscle(38).Stot*()^2; %P78: no data! +% tabMuscle(41).totalarea=0.01*tabMuscle(41).Stot*()^2; %P89: no data! +% tabMuscle(42).totalarea=0.01*tabMuscle(42).Stot*()^2; %P90: no data! +% tabMuscle(43).totalarea=0.01*tabMuscle(43).Stot*()^2; %P91: no data! +% tabMuscle(44).totalarea=0.01*tabMuscle(44).Stot*()^2; %P92: no data! +% tabMuscle(45).totalarea=0.01*tabMuscle(45).Stot*()^2; %P93: no data! + tabMuscle_TM(46).totalarea=0.01*tabMuscle_TM(46).Stot*(0.277493)^2; %P103 + tabMuscle_TM(47).totalarea=0.01*tabMuscle_TM(47).Stot*(0.290182)^2; %P105 + tabMuscle_TM(48).totalarea=0.01*tabMuscle_TM(48).Stot*(0.31551)^2; %P107 + tabMuscle_TM(61).totalarea=0.01*tabMuscle_TM(61).Stot*(0.271774)^2; %P145 + tabMuscle_TM(69).totalarea=0.01*tabMuscle_TM(69).Stot*(0.28785)^2; %P163 + tabMuscle_TM(73).totalarea=0.01*tabMuscle_TM(73).Stot*(0.334435)^2; %P170 + tabMuscle_TM(75).totalarea=0.01*tabMuscle_TM(75).Stot*(0.230255)^2; %P173 + tabMuscle_TM(78).totalarea=0.01*tabMuscle_TM(78).Stot*(0.280144)^2; %P176 + tabMuscle_TM(83).totalarea=0.01*tabMuscle_TM(83).Stot*(0.155813)^2; %P188 + +for i=1:size(tabMuscle_TM) + %update the degeneration information + tabMuscle_TM(i).degenerationarea=tabMuscle_TM(i).totalarea*tabMuscle_TM(i).ratio; %this is the new degenerated area + tabMuscle_TM(i).effectivePCSA=tabMuscle_TM(i).totalarea-tabMuscle_TM(i).degenerationarea; + MatrixPCSA_TM=[MatrixPCSA_TM;tabMuscle_TM(i).totalarea,tabMuscle_TM(i).effectivePCSA,tabMuscle_TM(i).ratio]; +end + + AveragePCSA_TM = mean(MatrixPCSA_TM); + StdDev_PCSA_TM = std (MatrixPCSA_TM); + +disp('endTM') + +%Save data just in case +save('D:\Data\FromdropboxOUtmemory\muscles\tabMuscle_SS.mat','tabMuscle_SS'); +save('D:\Data\FromdropboxOUtmemory\muscles\tabMuscle_IS.mat','tabMuscle_IS'); +save('D:\Data\FromdropboxOUtmemory\muscles\tabMuscle_SC.mat','tabMuscle_SC'); +save('D:\Data\FromdropboxOUtmemory\muscles\tabMuscle_TM.mat','tabMuscle_TM'); + +save('D:\Data\FromdropboxOUtmemory\muscles\MatrixPCSA_SS.mat','MatrixPCSA_SS'); +save('D:\Data\FromdropboxOUtmemory\muscles\MatrixPCSA_IS.mat','MatrixPCSA_IS'); +save('D:\Data\FromdropboxOUtmemory\muscles\MatrixPCSA_SC.mat','MatrixPCSA_SC'); +save('D:\Data\FromdropboxOUtmemory\muscles\MatrixPCSA_TM.mat','MatrixPCSA_TM'); \ No newline at end of file diff --git a/muscles/ShoulderDataBaseTest.xlsx b/muscles/ShoulderDataBaseTest.xlsx new file mode 100644 index 0000000..358e508 Binary files /dev/null and b/muscles/ShoulderDataBaseTest.xlsx differ diff --git a/muscles/infoPixel.m b/muscles/infoPixel.m new file mode 100644 index 0000000..95fee89 --- /dev/null +++ b/muscles/infoPixel.m @@ -0,0 +1,57 @@ +% Put the good CaseCTFolder (\amira) + +function pixel = infoPixel (CaseCTFolder) + + fileTif = [ CaseCTFolder '\*.tif']; + + numberInfoTif = dir (fileTif); + pixel = []; + numberLineTabPixel= 0; + + for i = 1: size(numberInfoTif,1) + + inputFile = [CaseCTFolder '\' numberInfoTif(i).name(1:end-3) 'info']; + + fileID = fopen(inputFile); + + if fileID ~= -1 + while size(pixel,1) <= numberLineTabPixel + + tline = fgetl(fileID); + + if length(tline)>4 + if strcmp (tline(1:5),'pixel') + + k = strfind (tline, ' '); + pixel = [pixel; str2num(tline(k(1)+1:k(1)+5)) str2num(tline(k(2)+1:k(2)+5))]; + + end + end + end + numberLineTabPixel= numberLineTabPixel+1; + fclose (fileID); + end + + end + + if isempty(pixel) + pixel = [0 0]; + end + + if size(pixel,1)>1 + + for j = 2:size(pixel,1) + + if abs(pixel(j-1,1)-pixel(j,1))>0.01 || abs(pixel(j-1,2)-pixel(j,2))>0.01 + + pixel(1,:)= 0; + + end + + end + + end + + pixel = pixel(1,:); + +end \ No newline at end of file diff --git a/openConfigFile_before20190211.m b/openConfigFile_before20190211.m new file mode 100644 index 0000000..a02eef6 --- /dev/null +++ b/openConfigFile_before20190211.m @@ -0,0 +1,40 @@ +function dataDir = openConfigFile(configFile, logFileID) +%OPENCONFIGFILE open file config.txt and read dataDir +% Config file must follow specific/restrictive rules + +% The file config.txt should be writen as below. +% One line should contain dataDir = + +%{ +% This configuration file contains the definition of variable dataDir for database access. +% It is used by functions: listSCase.m, measureSCase.m, plotScase.m +% /home/shoulder/data <-- acces data dir from lbovenus or lbomars (default) +% /home/shoulder/dataDev <-- acces dataDev dir from lbovenus or lbomars +% /Volumes/shoulder/data <-- acces data dir from lbovenus/lbomars mounted on macos +% /Volumes/shoulder/dataDev <-- acces dataDev dir from lbovenus mounted on macos +% Z:\data <-- acces data dir from lbovenus/lbomars mounted on windows +% Z:\dataDev <-- acces dataDev dir from lbovenus mounted on windows +dataDir = /Volumes/shoulder/dataDev +%} + +% Author: AT +% Date: 2019-01-15 +% TODO: Less restrict rules for writting config.txt + +if ~exist(configFile, 'file') + error([configFile, ' required']); +end +fileID = fopen(configFile,'r'); +dataDir = fscanf(fileID, '%s'); % Read config file without spaces +fclose(fileID); +k = strfind(dataDir, 'dataDir='); % Find definition of dataDir +k = k + 8; % Remove 'dataDir=' +dataDir = dataDir(k:end); % Assume that config file ends with dataDir content +% Check if dataDir exists +if ~exist(dataDir, 'dir') + fprintf(logFileID, ['Data directory not found, check ' configFile]); + error(['Data directory not found, check ' configFile]); +end + +end + diff --git a/readDicomMetaData.m b/readDicomMetaData.m new file mode 100644 index 0000000..dfab71f --- /dev/null +++ b/readDicomMetaData.m @@ -0,0 +1,111 @@ +%% Import data from text file. +% Script for importing data from the following text file: +% +% Y:\data\P\4\1\P411-2584882\dicomInfo411-3.txt +% +% To extend the code to different selected data or a different text file, +% generate a function instead of a script. + +% Auto-generated by MATLAB on 2018/09/26 15:05:09 + +basedir = '//lbovenus.epfl.ch/shoulder/data/'; +casedir = [basedir 'P/4/1/P411-2584882/']; +CTdir = [casedir 'CT-P411-2584882-2/']; +dicomFileName=[CTdir 'dicom/P411-2584882_CA-soft.0001.dcm']; + +dicomMetaData=evalc('dicomdisp(dicomFileName)'); +fid=fopen([CTdir 'metadata.txt'],'wt'); +fprintf(fid,'%s',string(dicomMetaData)); +fclose(fid); + + +%% Initialize variables. +filename = [CTdir 'metadata.txt']%'Y:\data\P\4\1\P411-2584882\dicomInfo411-3.txt'; +delimiter = {'\t',' '}; +startRow = 3; + +%% Read columns of data as text: +% For more information, see the TEXTSCAN documentation. +formatSpec = '%s%s%s%s%s%s%s%s%s%s%s%s%s%[^\n\r]'; + +%% Open the text file. +fileID = fopen(filename,'r'); + +%% Read columns of data according to the format. +% This call is based on the structure of the file used to generate this +% code. If an error occurs for a different file, try regenerating the code +% from the Import Tool. +dataArray = textscan(fileID, formatSpec, 'Delimiter', delimiter, 'MultipleDelimsAsOne', true, 'TextType', 'string', 'HeaderLines' ,startRow-1, 'ReturnOnError', false, 'EndOfLine', '\r\n'); + +%% Close the text file. +fclose(fileID); + +%% Convert the contents of columns containing numeric text to numbers. +% Replace non-numeric text with NaN. +raw = repmat({''},length(dataArray{1}),length(dataArray)-1); +for col=1:length(dataArray)-1 + raw(1:length(dataArray{col}),col) = mat2cell(dataArray{col}, ones(length(dataArray{col}), 1)); +end +numericData = NaN(size(dataArray{1},1),size(dataArray,2)); + +for col=[1,2,3,5] + % Converts text in the input cell array to numbers. Replaced non-numeric + % text with NaN. + rawData = dataArray{col}; + for row=1:size(rawData, 1) + % Create a regular expression to detect and remove non-numeric prefixes and + % suffixes. + regexstr = '(?.*?)(?([-]*(\d+[\,]*)+[\.]{0,1}\d*[eEdD]{0,1}[-+]*\d*[i]{0,1})|([-]*(\d+[\,]*)*[\.]{1,1}\d+[eEdD]{0,1}[-+]*\d*[i]{0,1}))(?.*)'; + try + result = regexp(rawData(row), regexstr, 'names'); + numbers = result.numbers; + + % Detected commas in non-thousand locations. + invalidThousandsSeparator = false; + if numbers.contains(',') + thousandsRegExp = '^\d+?(\,\d{3})*\.{0,1}\d*$'; + if isempty(regexp(numbers, thousandsRegExp, 'once')) + numbers = NaN; + invalidThousandsSeparator = true; + end + end + % Convert numeric text to numbers. + if ~invalidThousandsSeparator + numbers = textscan(char(strrep(numbers, ',', '')), '%f'); + numericData(row, col) = numbers{1}; + raw{row, col} = numbers{1}; + end + catch + raw{row, col} = rawData{row}; + end + end +end + + +%% Split data into numeric and string columns. +rawNumericColumns = raw(:, [1,2,3,5]); +rawStringColumns = string(raw(:, [4,6,7,8,9,10,11,12,13])); + + +%% Replace non-numeric cells with NaN +R = cellfun(@(x) ~isnumeric(x) && ~islogical(x),rawNumericColumns); % Find non-numeric cells +rawNumericColumns(R) = {NaN}; % Replace non-numeric cells + +%% Make sure any text containing is properly converted to an categorical +for catIdx = [1,2,3,6] + idx = (rawStringColumns(:, catIdx) == ""); + rawStringColumns(idx, catIdx) = ""; +end + +%% Create output variable +dicomMetaData = table; +dicomMetaData.Location = cell2mat(rawNumericColumns(:, 1)); +dicomMetaData.Level = cell2mat(rawNumericColumns(:, 2)); +dicomMetaData.Tag = cell2mat(rawNumericColumns(:, 3)); +dicomMetaData.VR = categorical(rawStringColumns(:, 1)); +dicomMetaData.Size = cell2mat(rawNumericColumns(:, 4)); +dicomMetaData.Name = categorical(rawStringColumns(:, 4)); +dicomMetaData.Data = categorical(rawStringColumns(:, 5)); + +%% Clear temporary variables +clearvars filename delimiter startRow formatSpec fileID dataArray ans raw col numericData rawData row regexstr result numbers invalidThousandsSeparator thousandsRegExp rawNumericColumns rawStringColumns R catIdx idx; \ No newline at end of file diff --git a/tempInfo.mat b/tempInfo.mat new file mode 100644 index 0000000..5714719 Binary files /dev/null and b/tempInfo.mat differ diff --git a/upsert/license.txt b/upsert/license.txt new file mode 100644 index 0000000..c154413 --- /dev/null +++ b/upsert/license.txt @@ -0,0 +1,24 @@ +Copyright (c) 2015, Sven +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/upsert/upsert.m b/upsert/upsert.m new file mode 100644 index 0000000..cee6bff --- /dev/null +++ b/upsert/upsert.m @@ -0,0 +1,247 @@ +function [insertMask, returnedKeys] = upsert(conn,tableName,fieldNames,keyFields,data, varargin) +% UPSERT inserts new and updates old data to a database table +% +% UPSERT(CONNECT,TABLENAME,FIELDNAMES,KEYFIELDS,DATA). +% CONNECT is a database connection object. TABLENAME is the database +% table. FIELDNAMES is a string array of database column names. KEYFIELDS +% is the list of primary key fields that must be matched to perform an +% UPDATE rather than an INSERT. It may be given as a logical (or 0s, 1s) +% array the same length as FIELDNAMES, or a string or cell array of +% strings of key column names (in which case KEYFIELDS must be a subset +% of FIELDNAMES). DATA is a MATLAB cell array. +% +% INSERTEDMASK = UPSERT(...) returns a logical vector with one element for +% each row of DATA, indicating whether the "upsert" operation meant that +% corresponding row of DATA was inserted (TRUE) or merely updated (FALSE). +% +% UPSERT(...,'dateFields',DATEFIELDS) allows a DATE type field to be used +% as one of the primary key fields. DATEFIELDS is specified equivalently to +% KEYFIELDS. Each primary key DATE type field's data MUST be given as an +% ANSI string literal (i.e., '1998-12-25'), rather than a MATLAB datenum +% number or a differently formatted date string. +% (see http://docs.oracle.com/cd/E11882_01/server.112/e26088/sql_elements003.htm#SQLRF51062) +% +% UPSERT(...,'updateFcn',FUNCTION_HANDLE) +% UPSERT(...,'insertFcn',FUNCTION_HANDLE) optionally allows replacement +% functions for the default use of MATLAB's "update" and "fastinsert". +% +% UPSERT(...,'debug',true) prints out diagnostic information. +% +% Example: +% +% Imagine a database table "PHONE_NOS" with data like: +% PERSONID | TYPE | NUMBER +% 1 'HOME' 1234567 +% 1 'MOB' 1222222 +% 2 'HOME' 9888888 +% +% Then the MATLAB commands: +% newNos = {1 'MOB' 4444444 +% 2 'MOB' 5555555}; +% INS = upsert(conn, 'PHONE_NOS', {'PERSONID','TYPE','NUMBER'}, [1 1 0], newNos) +% +% Would result in the table having contents: +% PERSONID | TYPE | NUMBER +% 1 'HOME' 1234567 +% 1 'MOB' 4444444 +% 2 'HOME' 9888888 +% 2 'MOB' 5555555 +% +% The returned variable (INS) would be [0; 1], meaning the second row was +% updated, the first row was inserted. + +% Author: Sven Holcombe, 2015-09-01 + +% Handle user configuration input +IP = inputParser; +IP.addParameter('updateFcn', @update, @(x)isa(x,'function_handle')); +IP.addParameter('insertFcn', @fastinsert, @(x)isa(x,'function_handle')); +IP.addParameter('dateFields', []); +IP.addParameter('returnKeys',false,@(x)iscellstr(x)||(isscalar(x)&&nnz(x==[0 1])==1)); +IP.addParameter('debug', false); +IP.parse(varargin{:}); +doPrint = IP.Results.debug; +updateFcn = IP.Results.updateFcn; +insertFcn = IP.Results.insertFcn; +doReturnKeys = iscellstr(IP.Results.returnKeys) || IP.Results.returnKeys; +if ~doReturnKeys && nargout>1 + warning('upsert:noKeys', 'Second output (returned keys) will be empty because ''returnKeys'' option was not set') +end + +% Firstly, handle large data sets. Later we will use an IN clause with a +% comma-separated list to check key fields. Oracle has a limit of 1000 +% items in a list, so let's process 1000 at a time at most. Not an optimal +% solution but best solutions will differ by database flavour so this is +% adequate for the moment. +numRows = size(data,1); +returnedKeys = zeros(numRows,0); +if numRows>1000 + insertMask = true(numRows,1); + chunks = unique([1:999:numRows numRows+1]); + for i = 1:length(chunks)-1 + dataInds = chunks(i):chunks(i+1)-1; + [insertMask(dataInds), rk] = ... + upsert(conn,tableName,fieldNames,keyFields,data(dataInds,:), varargin{:}); + if doReturnKeys + if i==1 + returnedKeys = zeros(numRows,size(rk,2)); + end + returnedKeys(dataInds,:) = rk; + end + end + return; +end + +% keyFields may be input as: +% - A string: 'id' +% - A cellstr: {'id','groupNo'} +% - A logical mask the same size as fieldNames (true where field is a key) +% - Indices into fieldNames of the key fields +% keyFields will be transformed to the indices representation below +keyFields = convertSubsetOfFieldsToIndices(keyFields, fieldNames); +% Get a numeric array of which fields are DATE types for comparison +dateFields = convertSubsetOfFieldsToIndices(IP.Results.dateFields, fieldNames); + +if isempty(data) + insertMask = true(0,1); + return; +end + +% Currently it's easier (if perhaps slightly slower) to treat data as a +% cell regardless of what format it was provided as. +if isnumeric(data) + data = num2cell(data); +end + +% Which fields are keyFields? Build lists of them for an SQL fetch +keyFieldsCell = fieldNames(keyFields); +keyFieldsIsnumeric = cellfun(@(x)isnumeric(x)||islogical(x), data(1,keyFields)); +keyFieldsIsdatestr = ismember(keyFields, dateFields); +keyFieldsListStr = sprintf('%s,',keyFieldsCell{:}); + +% We don't want to gather the whole table. Only the rows matching the +% primary key fields. This is most generalised by building IN () lists from +% a single database query, rather than sending/recieving one query for +% every row of "data" being upserted. +inClauses = cell(length(keyFields),1); +for i=1:length(keyFields) + if keyFieldsIsnumeric(i) + inSet = unique([data{:,keyFields(i)}]); + if all(inSet==round(inSet)) + inStr = sprintf('%d,',inSet); + else + inStr = sprintf('%g,',inSet); + end + elseif ischar(data{1,keyFields(i)}) + inSet = unique(data(:,keyFields(i))); + if keyFieldsIsdatestr(i) + inStr = sprintf('date ''%s'',',inSet{:}); + else + inStr = sprintf('''%s'',',inSet{:}); + end + else + error('upsert:badKey', 'Primary key field cannot contain %s data',class(data{1,keyFields(i)})) + end + if length(inSet)>1 + inClauses{i} = sprintf('%s IN (%s)', keyFieldsCell{i}, inStr(1:end-1)); + else + inClauses{i} = sprintf('%s = %s', keyFieldsCell{i}, inStr(1:end-1)); + end +end + +% Fetch all table rows potentially matching the data we want to upsert +fetchWhereClause = sprintf(' %s AND', inClauses{:}); +fetchSqlStr = sprintf('SELECT %s FROM %s WHERE %s', keyFieldsListStr(1:end-1), tableName, fetchWhereClause(1:end-3)); +if doPrint, fprintf('Fetching %s data in %s matching given data...', keyFieldsListStr(1:end-1), tableName), end +fetchedData = fetch(conn, fetchSqlStr); +if doPrint, fprintf(' done. (%d potential matches found)\n', size(fetchedData,1)), end + +% Build a map of which rows to be upserted already exist in the table. +insertMask = true(size(data,1),1); % One +if ~isempty(fetchedData) + eqMap = false(size(data,1), size(fetchedData,1), length(keyFields)); + for i = 1:length(keyFields) + if keyFieldsIsnumeric(i) + thisUpsertData = cell2mat(data(:,keyFields(i))); + thisFetchedData = cast(cell2mat(fetchedData(:,i)), 'like',thisUpsertData); + eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); + elseif keyFieldsIsdatestr(i) + thisUpsertData = datenum(data(:,keyFields(i))); + thisFetchedData = datenum(fetchedData(:,i)); + eqMap(:,:,i) = bsxfun(@eq, thisUpsertData, thisFetchedData'); + else + thisUpsertData = data(:,keyFields(i)); + thisFetchedData = fetchedData(:,i)'; + eqCell = cellfun(@(x)strcmp(x, thisFetchedData), thisUpsertData, 'Un',0); + eqMap(:,:,i) = cat(1, eqCell{:}); + end + end + pkeysMatchMap = all(eqMap,3); + insertMask = ~any(pkeysMatchMap,2); +end + +% First find any data rows that do NOT yet exist in table. Insert them. +if any(insertMask) + if doPrint, fprintf('Inserting %d data rows not currently in %s...', nnz(insertMask), tableName), end + if doReturnKeys + insertedRK = insertFcn(conn,tableName,fieldNames,data(insertMask,:),'returnKeys',IP.Results.returnKeys); + else + insertFcn(conn,tableName,fieldNames,data(insertMask,:)); + end + if doPrint, fprintf(' done.\n'), end +else + insertedRK = []; +end + +% Next, update ALL rows to the values given in data. First build WHERE. +whereEqClauses = cell(numRows, length(keyFields)); +for i=1:length(keyFields) + if keyFieldsIsnumeric(i) + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = %g', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + elseif keyFieldsIsdatestr(i) + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = date ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + else + whereEqClauses(:,i) = cellfun(@(dat)sprintf('%s = ''%s''', keyFieldsCell{i}, dat), data(:,keyFields(i)),'Un',0); + end +end +dataWhereClauses = cellfun(@(strs)sprintf(' %s AND',strs{:}), num2cell(whereEqClauses,2),'Un',0); +dataWhereClauses = cellfun(@(str)['WHERE ' str(1:end-3)], dataWhereClauses, 'Un',0); + +% Next, run the update on all the NON-keyField fields (since the key fields +% themselves are being matched, so won't change). Note that the "update" +% function can be replaced by a user's modified update function. +otherFields = setdiff(1:length(fieldNames), keyFields); +if doPrint, fprintf('Updating %d data rows (%d new, %d old) in %s...', length(insertMask), nnz(insertMask), nnz(~insertMask), tableName), end +if doReturnKeys + updatedRK = updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask),'returnKeys',IP.Results.returnKeys); + returnedKeys = zeros(size(cat(1,insertedRK, updatedRK))); + if any(insertMask) + returnedKeys(insertMask,:) = insertedRK; + end + if any(~insertMask) + returnedKeys(~insertMask,:) = updatedRK; + end +else + updateFcn(conn,tableName,fieldNames(otherFields),data(~insertMask,otherFields), dataWhereClauses(~insertMask)); +end + +if doPrint, fprintf(' done.\n'); end + + +function subFields = convertSubsetOfFieldsToIndices(subFields, allFields) +% convert subFields from various classes to a numeric index of allFields +% may be input as: +% - A string: 'id' +% - A cellstr: {'id','groupNo'} +% - A logical (or 0,1) mask the same size as fieldNames +% - Indices into fieldNames of the key fields +% keyFields will be transformed to the indices representation below +if ischar(subFields) || iscellstr(subFields) + subFields = ismember(upper(allFields), upper(subFields)); +end +if isnumeric(subFields) && (any(subFields==0) || nnz(subFields==1)>1) + subFields = logical(subFields); +end +if islogical(subFields) + subFields = find(subFields); +end \ No newline at end of file