edu.stanford.epad.epadws.queries.Dcm4CheeQueries.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.epad.epadws.queries.Dcm4CheeQueries.java

Source

/*******************************************************************************
 * Copyright (c) 2015 The Board of Trustees of the Leland Stanford Junior University
 * BY CLICKING ON "ACCEPT," DOWNLOADING, OR OTHERWISE USING EPAD, YOU AGREE TO THE FOLLOWING TERMS AND CONDITIONS:
 * STANFORD ACADEMIC SOFTWARE SOURCE CODE LICENSE FOR
 * "ePAD Annotation Platform for Radiology Images"
 *
 * This Agreement covers contributions to and downloads from the ePAD project ("ePAD") maintained by The Board of Trustees 
 * of the Leland Stanford Junior University ("Stanford"). 
 *
 * *   Part A applies to downloads of ePAD source code and/or data from ePAD. 
 *
 * *   Part B applies to contributions of software and/or data to ePAD (including making revisions of or additions to code 
 * and/or data already in ePAD), which may include source or object code. 
 *
 * Your download, copying, modifying, displaying, distributing or use of any ePAD software and/or data from ePAD 
 * (collectively, the "Software") is subject to Part A. Your contribution of software and/or data to ePAD (including any 
 * that occurred prior to the first publication of this Agreement) is a "Contribution" subject to Part B. Both Parts A and 
 * B shall be governed by and construed in accordance with the laws of the State of California without regard to principles 
 * of conflicts of law. Any legal action involving this Agreement or the Research Program will be adjudicated in the State 
 * of California. This Agreement shall supersede and replace any license terms that you may have agreed to previously with 
 * respect to ePAD.
 *
 * PART A. DOWNLOADING AGREEMENT - LICENSE FROM STANFORD WITH RIGHT TO SUBLICENSE ("SOFTWARE LICENSE").
 * 1. As used in this Software License, "you" means the individual downloading and/or using, reproducing, modifying, 
 * displaying and/or distributing Software and the institution or entity which employs or is otherwise affiliated with you. 
 * Stanford  hereby grants you, with right to sublicense, with respect to Stanford's rights in the Software, a 
 * royalty-free, non-exclusive license to use, reproduce, make derivative works of, display and distribute the Software, 
 * provided that: (a) you adhere to all of the terms and conditions of this Software License; (b) in connection with any 
 * copy, distribution of, or sublicense of all or any portion of the Software, the terms and conditions in this Software 
 * License shall appear in and shall apply to such copy and such sublicense, including without limitation all source and 
 * executable forms and on any user documentation, prefaced with the following words: "All or portions of this licensed 
 * product  have been obtained under license from The Board of Trustees of the Leland Stanford Junior University. and are 
 * subject to the following terms and conditions" AND any user interface to the Software or the "About" information display 
 * in the Software will display the following: "Powered by ePAD http://epad.stanford.edu;" (c) you preserve and maintain 
 * all applicable attributions, copyright notices and licenses included in or applicable to the Software; (d) modified 
 * versions of the Software must be clearly identified and marked as such, and must not be misrepresented as being the 
 * original Software; and (e) you consider making, but are under no obligation to make, the source code of any of your 
 * modifications to the Software freely available to others on an open source basis.
 *
 * 2. The license granted in this Software License includes without limitation the right to (i) incorporate the Software 
 * into your proprietary programs (subject to any restrictions applicable to such programs), (ii) add your own copyright 
 * statement to your modifications of the Software, and (iii) provide additional or different license terms and conditions 
 * in your sublicenses of modifications of the Software; provided that in each case your use, reproduction or distribution 
 * of such modifications otherwise complies with the conditions stated in this Software License.
 * 3. This Software License does not grant any rights with respect to third party software, except those rights that 
 * Stanford has been authorized by a third party to grant to you, and accordingly you are solely responsible for (i) 
 * obtaining any permissions from third parties that you need to use, reproduce, make derivative works of, display and 
 * distribute the Software, and (ii) informing your sublicensees, including without limitation your end-users, of their 
 * obligations to secure any such required permissions.
 * 4. You agree that you will use the Software in compliance with all applicable laws, policies and regulations including, 
 * but not limited to, those applicable to Personal Health Information ("PHI") and subject to the Institutional Review 
 * Board requirements of the your institution, if applicable. Licensee acknowledges and agrees that the Software is not 
 * FDA-approved, is intended only for research, and may not be used for clinical treatment purposes. Any commercialization 
 * of the Software is at the sole risk of you and the party or parties engaged in such commercialization. You further agree 
 * to use, reproduce, make derivative works of, display and distribute the Software in compliance with all applicable 
 * governmental laws, regulations and orders, including without limitation those relating to export and import control.
 * 5. You or your institution, as applicable, will indemnify, hold harmless, and defend Stanford against any third party 
 * claim of any kind made against Stanford arising out of or related to the exercise of any rights granted under this 
 * Agreement, the provision of Software, or the breach of this Agreement. Stanford provides the Software AS IS and WITH ALL 
 * FAULTS.  Stanford makes no representations and extends no warranties of any kind, either express or implied.  Among 
 * other things, Stanford disclaims any express or implied warranty in the Software:
 * (a)  of merchantability, of fitness for a particular purpose,
 * (b)  of non-infringement or 
 * (c)  arising out of any course of dealing.
 *
 * Title and copyright to the Program and any associated documentation shall at all times remain with Stanford, and 
 * Licensee agrees to preserve same. Stanford reserves the right to license the Program at any time for a fee.
 * 6. None of the names, logos or trademarks of Stanford or any of Stanford's affiliates or any of the Contributors, or any 
 * funding agency, may be used to endorse or promote products produced in whole or in part by operation of the Software or 
 * derived from or based on the Software without specific prior written permission from the applicable party.
 * 7. Any use, reproduction or distribution of the Software which is not in accordance with this Software License shall 
 * automatically revoke all rights granted to you under this Software License and render Paragraphs 1 and 2 of this 
 * Software License null and void.
 * 8. This Software License does not grant any rights in or to any intellectual property owned by Stanford or any 
 * Contributor except those rights expressly granted hereunder.
 *
 * PART B. CONTRIBUTION AGREEMENT - LICENSE TO STANFORD WITH RIGHT TO SUBLICENSE ("CONTRIBUTION AGREEMENT").
 * 1. As used in this Contribution Agreement, "you" means an individual providing a Contribution to ePAD and the 
 * institution or entity which employs or is otherwise affiliated with you.
 * 2. This Contribution Agreement applies to all Contributions made to ePAD at any time. By making a Contribution you 
 * represent that: (i) you are legally authorized and entitled by ownership or license to make such Contribution and to 
 * grant all licenses granted in this Contribution Agreement with respect to such Contribution; (ii) if your Contribution 
 * includes any patient data, all such data is de-identified in accordance with U.S. confidentiality and security laws and 
 * requirements, including but not limited to the Health Insurance Portability and Accountability Act (HIPAA) and its 
 * regulations, and your disclosure of such data for the purposes contemplated by this Agreement is properly authorized and 
 * in compliance with all applicable laws and regulations; and (iii) you have preserved in the Contribution all applicable 
 * attributions, copyright notices and licenses for any third party software or data included in the Contribution.
 * 3. Except for the licenses you grant in this Agreement, you reserve all right, title and interest in your Contribution.
 * 4. You hereby grant to Stanford, with the right to sublicense, a perpetual, worldwide, non-exclusive, no charge, 
 * royalty-free, irrevocable license to use, reproduce, make derivative works of, display and distribute the Contribution. 
 * If your Contribution is protected by patent, you hereby grant to Stanford, with the right to sublicense, a perpetual, 
 * worldwide, non-exclusive, no-charge, royalty-free, irrevocable license under your interest in patent rights embodied in 
 * the Contribution, to make, have made, use, sell and otherwise transfer your Contribution, alone or in combination with 
 * ePAD or otherwise.
 * 5. You acknowledge and agree that Stanford ham may incorporate your Contribution into ePAD and may make your 
 * Contribution as incorporated available to members of the public on an open source basis under terms substantially in 
 * accordance with the Software License set forth in Part A of this Agreement. You further acknowledge and agree that 
 * Stanford shall have no liability arising in connection with claims resulting from your breach of any of the terms of 
 * this Agreement.
 * 6. YOU WARRANT THAT TO THE BEST OF YOUR KNOWLEDGE YOUR CONTRIBUTION DOES NOT CONTAIN ANY CODE OBTAINED BY YOU UNDER AN 
 * OPEN SOURCE LICENSE THAT REQUIRES OR PRESCRIBES DISTRBUTION OF DERIVATIVE WORKS UNDER SUCH OPEN SOURCE LICENSE. (By way 
 * of non-limiting example, you will not contribute any code obtained by you under the GNU General Public License or other 
 * so-called "reciprocal" license.)
 *******************************************************************************/
package edu.stanford.epad.epadws.queries;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;

import edu.stanford.epad.common.dicom.DCM4CHEEUtil;
import edu.stanford.epad.common.pixelmed.SegmentedProperty;
import edu.stanford.epad.common.pixelmed.SegmentedPropertyHelper;
import edu.stanford.epad.common.util.EPADLogger;
import edu.stanford.epad.dtos.internal.DCM4CHEESeries;
import edu.stanford.epad.dtos.internal.DCM4CHEESeriesList;
import edu.stanford.epad.dtos.internal.DCM4CHEEStudy;
import edu.stanford.epad.dtos.internal.DCM4CHEEStudyList;
import edu.stanford.epad.dtos.internal.DCM4CHEEStudySearchType;
import edu.stanford.epad.dtos.internal.DICOMElement;
import edu.stanford.epad.dtos.internal.DICOMElementList;
import edu.stanford.epad.epadws.dcm4chee.Dcm4CheeDatabase;
import edu.stanford.epad.epadws.dcm4chee.Dcm4CheeDatabaseOperations;
import edu.stanford.epad.epadws.processing.pipeline.task.DicomHeadersTask;

/**
 * @author martin
 */
public class Dcm4CheeQueries {
    private static EPADLogger log = EPADLogger.getInstance();

    public static DCM4CHEEStudy getStudy(String studyUID) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();

        Map<String, String> dcm4CheeStudyData = dcm4CheeDatabaseOperations.studySearch(studyUID);

        if (!dcm4CheeStudyData.isEmpty())
            return extractDCM4CHEEStudyFromData(dcm4CheeStudyData);
        else
            return null;
    }

    public static DCM4CHEEStudyList getStudies(Set<String> studyUIDs) {
        DCM4CHEEStudyList dcm4CheeStudyList = new DCM4CHEEStudyList();

        for (String studyUID : studyUIDs) {
            DCM4CHEEStudy dcm4CheeStudy = getStudy(studyUID);
            if (dcm4CheeStudy != null)
                dcm4CheeStudyList.addDCM4CHEEStudy(dcm4CheeStudy);
        }

        return dcm4CheeStudyList;
    }

    public static int getNumberOfStudiesForPatient(String patientID) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();

        return dcm4CheeDatabaseOperations.getNumberOfStudiesForPatient(patientID);
    }

    public static Set<String> getStudyUIDsForPatient(String patientID) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();

        return dcm4CheeDatabaseOperations.getStudyUIDsForPatient(patientID);
    }

    public static int getNumberOfStudiesForPatients(Set<String> patientIDs) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();

        return dcm4CheeDatabaseOperations.getNumberOfStudiesForPatients(patientIDs);
    }

    /**
     * Query the DCM4CHEE database and return a list of study descriptions.
     * <p>
     * The {@link DCM4CHEEStudySearchType} enum specifies the search type, e.g, patientName, patientID.
     * 
     */
    public static DCM4CHEEStudyList studySearch(DCM4CHEEStudySearchType searchType, String searchValue) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();
        List<Map<String, String>> dcm4CheeStudySearchResult = dcm4CheeDatabaseOperations.studySearch(searchType,
                searchValue);
        DCM4CHEEStudyList dcm4CheeStudyList = new DCM4CHEEStudyList();

        for (Map<String, String> dcm4CheeStudyData : dcm4CheeStudySearchResult) {
            DCM4CHEEStudy dcm4CheeStudy = extractDCM4CHEEStudyFromData(dcm4CheeStudyData);
            dcm4CheeStudyList.addDCM4CHEEStudy(dcm4CheeStudy);
        }
        return dcm4CheeStudyList;
    }

    /**
     * Get all the series for a study from DCM4CHEE.
     * 
     * @param queryString String - query string which contains the study id. The query line looks like the following:
     *          http://[ip:port]/search?searchType=series&studyUID=[studyUID].
     * 
     */
    public static DCM4CHEESeriesList getSeriesInStudy(String studyUID) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();
        List<Map<String, String>> series = dcm4CheeDatabaseOperations.getAllSeriesInStudy(studyUID);
        DCM4CHEESeriesList dcm4cheeSeriesList = new DCM4CHEESeriesList();
        for (Map<String, String> dcm4CheeSeriesData : series) {
            DCM4CHEESeries dcm4cheeSeries = extractDCM4CHEESeriesFromSeriesData(dcm4CheeSeriesData);
            dcm4cheeSeriesList.addDCM4CHEESeries(dcm4cheeSeries);
        }
        return dcm4cheeSeriesList;
    }

    public static DCM4CHEESeries getSeries(String seriesUID) {
        Dcm4CheeDatabaseOperations dcm4CheeDatabaseOperations = Dcm4CheeDatabase.getInstance()
                .getDcm4CheeDatabaseOperations();
        Map<String, String> dcm4CheeSeriesData = dcm4CheeDatabaseOperations.getSeriesData(seriesUID);

        if (!dcm4CheeSeriesData.isEmpty())
            return extractDCM4CHEESeriesFromSeriesData(dcm4CheeSeriesData);
        else {
            log.warning("Could not find series " + seriesUID + " in dcm4chee's database");
            return null;
        }
    }

    public static DICOMElementList getDICOMElementsFromWADO(String studyUID, String seriesUID, String imageUID) {
        return getDICOMElementsFromWADO(studyUID, seriesUID, imageUID, null);
    }

    public static DICOMElementList getDICOMElementsFromWADO(String studyUID, String seriesUID, String imageUID,
            SegmentedProperty catTypeProp) {
        String catCode = "";
        String typeCode = "";
        DICOMElementList dicomElementList = new DICOMElementList();
        DICOMElementList dicomElementListNoSkip = new DICOMElementList();
        boolean skipThumbnail = false;
        try {
            File temporaryDICOMFile = File.createTempFile(imageUID, ".tmp");
            int wadoStatusCode = DCM4CHEEUtil.downloadDICOMFileFromWADO(studyUID, seriesUID, imageUID,
                    temporaryDICOMFile);
            if (wadoStatusCode == HttpServletResponse.SC_OK) {
                File tempTag = File.createTempFile(imageUID, "_tag.tmp");
                ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
                taskExecutor.execute(new DicomHeadersTask(seriesUID, temporaryDICOMFile, tempTag));
                taskExecutor.shutdown();
                try {
                    taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
                    BufferedReader tagReader = null;
                    try {
                        String dicomElementString;
                        FileReader tagFileReader = new FileReader(tempTag.getAbsolutePath());
                        tagReader = new BufferedReader(tagFileReader);
                        skipThumbnail = false;
                        String currentSequence = "";
                        while ((dicomElementString = tagReader.readLine()) != null) {

                            if (dicomElementString.contains("(0009,1110)")) // hard code for now TODO:???
                                skipThumbnail = true;
                            if (dicomElementString.contains("(FFFE,E0DD)"))
                                skipThumbnail = false;
                            int sequence = dicomElementString.indexOf("SQ #-1");
                            if (sequence != -1)
                                currentSequence = dicomElementString.substring(sequence + 7);
                            if (dicomElementString.contains("Sequence Delimitation Item"))
                                currentSequence = "";
                            DICOMElement dicomElement = decodeDICOMElementString(dicomElementString);
                            DICOMElement dicomElementNoSkip = decodeDICOMElementString(dicomElementString);
                            if (dicomElement != null) {
                                if (!skipThumbnail) {
                                    dicomElement.parentSequenceName = currentSequence;
                                    dicomElementList.addDICOMElement(dicomElement);
                                    if (dicomElementString.contains("(0008,0100)")) {
                                        if (dicomElement.parentSequenceName != null
                                                && dicomElement.parentSequenceName.equalsIgnoreCase(
                                                        "Segmented Property Category Code Sequence"))//category code
                                        {
                                            catCode = dicomElement.value.trim();
                                            log.info("cat code is " + catCode);
                                        } else if (dicomElement.parentSequenceName != null
                                                && dicomElement.parentSequenceName
                                                        .equalsIgnoreCase("Segmented Property Type Code Sequence"))//category code
                                        {
                                            typeCode = dicomElement.value.trim();
                                            log.info("type code is " + typeCode);
                                        }
                                    }
                                }
                                //make a list with all the skip items
                                //at the end if the skip is not closed then use this list
                                else {
                                    log.warning("Warning: skip sequence. skipping " + dicomElementString);
                                    dicomElementNoSkip.parentSequenceName = currentSequence;
                                    dicomElementListNoSkip.addDICOMElement(dicomElementNoSkip);
                                }
                            } else {
                                //too much log
                                //                         log.warning("Warning: could not decode DICOM element " + dicomElementString + "");
                            }
                        }
                    } finally {
                        IOUtils.closeQuietly(tagReader);
                        try {
                            temporaryDICOMFile.delete();
                            tempTag.delete();
                        } catch (Exception x) {
                        }
                        ;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warning("DICOM headers task for series " + seriesUID + " interrupted!");
                }
            } else {
                log.warning("Error invoking dcm4chee to get DICOM headers for series " + seriesUID
                        + "; status code=" + wadoStatusCode);
            }
        } catch (IOException e) {
            log.warning("IOException retrieving DICOM headers for image " + imageUID + " in series " + seriesUID,
                    e);
        }
        try {
            if (catTypeProp != null && !catCode.equals("") && !typeCode.equals("")) {
                SegmentedPropertyHelper helper = new SegmentedPropertyHelper();
                SegmentedProperty prop = helper.getProperty(catCode, typeCode);
                if (prop != null) {
                    catTypeProp.copyValuesFrom(prop);
                } else {
                    log.info("Category-type pair not found");
                }
            }
        } catch (Exception ex) {
            log.warning("Exception in getting category type ", ex);
        }

        if (skipThumbnail) {
            log.warning("End of skip not found returning noskip data. ");
            return dicomElementListNoSkip;
        }
        return dicomElementList;
    }

    private static DCM4CHEEStudy extractDCM4CHEEStudyFromData(Map<String, String> dcm4CheeStudyData) {
        String studyUID = getStringValueFromRow(dcm4CheeStudyData, "study_iuid");
        String patientName = getStringValueFromRow(dcm4CheeStudyData, "pat_name");
        String patientID = getStringValueFromRow(dcm4CheeStudyData, "pat_id");
        String examType = getStringValueFromRow(dcm4CheeStudyData, "modality");
        String dateAcquired = getStringValueFromRow(dcm4CheeStudyData, "study_datetime");
        int studyStatus = getIntegerFromRow(dcm4CheeStudyData, "study_status");
        int seriesCount = getIntegerFromRow(dcm4CheeStudyData, "number_series");
        String firstSeriesUID = getStringValueFromRow(dcm4CheeStudyData, "series_iuid");
        String firstSeriesDateAcquired = getStringValueFromRow(dcm4CheeStudyData, "pps_start");
        if (firstSeriesDateAcquired == null || firstSeriesDateAcquired.length() == 0)
            firstSeriesDateAcquired = getStringValueFromRow(dcm4CheeStudyData, "study_datetime");
        String studyAccessionNumber = getStringValueFromRow(dcm4CheeStudyData, "accession_no");
        int imagesCount = getIntegerFromRow(dcm4CheeStudyData, "sum_images");
        String stuidID = getStringValueFromRow(dcm4CheeStudyData, "study_id");
        String studyDescription = getStringValueFromRow(dcm4CheeStudyData, "study_desc");
        String physicianName = getStringValueFromRow(dcm4CheeStudyData, "ref_physician");
        String birthdate = getStringValueFromRow(dcm4CheeStudyData, "pat_birthdate");
        String createdTime = getStringValueFromRow(dcm4CheeStudyData, "created_time");
        String sex = getStringValueFromRow(dcm4CheeStudyData, "pat_sex");
        DCM4CHEEStudy dcm4CheeStudy = new DCM4CHEEStudy(studyUID, patientName, patientID, examType, dateAcquired,
                studyStatus, seriesCount, firstSeriesUID, firstSeriesDateAcquired, studyAccessionNumber,
                imagesCount, stuidID, studyDescription, physicianName, birthdate, sex, createdTime);
        return dcm4CheeStudy;
    }

    private static DCM4CHEESeries extractDCM4CHEESeriesFromSeriesData(Map<String, String> dcm4CheeSeriesData) {
        String studyUID = getStringValueFromRow(dcm4CheeSeriesData, "study_iuid");
        String seriesUID = getStringValueFromRow(dcm4CheeSeriesData, "series_iuid");
        String patientID = getStringValueFromRow(dcm4CheeSeriesData, "pat_id");
        String patientName = getStringValueFromRow(dcm4CheeSeriesData, "pat_name");
        String seriesDate = getStringValueFromRow(dcm4CheeSeriesData, "pps_start");
        if (seriesDate == null || seriesDate.length() == 0)
            seriesDate = getStringValueFromRow(dcm4CheeSeriesData, "study_datetime");
        String examType = getStringValueFromRow(dcm4CheeSeriesData, "modality");
        String thumbnailURL = getStringValueFromRow(dcm4CheeSeriesData, "thumbnail_url");
        String seriesDescription = getStringValueFromRow(dcm4CheeSeriesData, "series_desc");
        int numberOfSeriesRelatedInstances = Integer
                .parseInt(getStringValueFromRow(dcm4CheeSeriesData, "num_instances"));
        int imagesInSeries = getIntegerFromRow(dcm4CheeSeriesData, "num_instances");
        int seriesStatus = getIntegerFromRow(dcm4CheeSeriesData, "series_status");
        String bodyPart = getStringValueFromRow(dcm4CheeSeriesData, "body_part");
        String institution = getStringValueFromRow(dcm4CheeSeriesData, "institution");
        String stationName = getStringValueFromRow(dcm4CheeSeriesData, "station_name");
        String department = getStringValueFromRow(dcm4CheeSeriesData, "department");
        String accessionNumber = getStringValueFromRow(dcm4CheeSeriesData, "accession_no");
        String createdTime = getTimestampFromRow(dcm4CheeSeriesData, "created_time");
        boolean isDSO = "SEG".equalsIgnoreCase(getStringValueFromRow(dcm4CheeSeriesData, "modality"));
        DCM4CHEESeries dcm4cheeSeries = new DCM4CHEESeries(studyUID, seriesUID, patientID, patientName, seriesDate,
                examType, thumbnailURL, seriesDescription, numberOfSeriesRelatedInstances, imagesInSeries,
                seriesStatus, bodyPart, institution, stationName, department, accessionNumber, createdTime, isDSO);
        return dcm4cheeSeries;

    }

    // TODO This code is very brittle. Rewrite to make more robust. Also ignores DICOM sequences.
    private static DICOMElement decodeDICOMElementString(String dicomElement) {
        //      log.info("dicom element "+dicomElement);
        String[] fields = dicomElement.split(" ");

        int valueFieldStartIndex = valueFieldStartIndex(fields);
        if (valueFieldStartIndex != -1) {
            int valueFieldEndIndex = valueFieldEndIndex(fields);

            if (valueFieldEndIndex != -1 && ((valueFieldEndIndex - valueFieldStartIndex) < 10)) {
                String tagCode = extractTagCodeFromField(fields[0]);
                String value = stripBraces(assembleValue(fields, valueFieldStartIndex, valueFieldEndIndex));
                String tagName = assembleValue(fields, valueFieldEndIndex + 1, fields.length - 1);

                return new DICOMElement(tagCode, tagName, value);
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    private static String extractTagCodeFromField(String field) {
        String subFields[] = field.split(":>*");

        if (subFields.length == 2) {
            return subFields[1];
        } else
            return "";
    }

    private static String assembleValue(String[] fields, int startIndex, int finishIndex) {
        String value = "";
        for (int i = startIndex; i <= finishIndex; i++) {
            if (i > startIndex)
                value += " ";
            value += fields[i];
        }
        return value;
    }

    private static int valueFieldStartIndex(String[] fields) {
        for (int i = 0; i < fields.length; i++)
            if (fields[i].startsWith("["))
                return i;
        return -1;
    }

    private static int valueFieldEndIndex(String[] fields) {
        for (int i = 0; i < fields.length; i++)
            if (fields[i].endsWith("]"))
                return i;
        return -1;
    }

    private static String stripBraces(String valueField) {
        if (valueField.startsWith("[") && valueField.endsWith("]")) {
            return valueField.substring(1, valueField.length() - 1);
        } else {
            return "";
        }
    }

    private static String getStringValueFromRow(Map<String, String> row, String columnName) {
        String value = row.get(columnName);

        if (value == null)
            return "";
        else
            return value;
    }

    private static String getTimestampFromRow(Map<String, String> row, String columnName) {
        String value = row.get(columnName);

        if (value == null)
            return null;
        else {
            try {
                return value;
            } catch (IllegalArgumentException e) {
                log.warning("Invalid timestamp " + value + " in column " + columnName, e);
                return null;
            }
        }
    }

    // TODO Perhaps throw exception rather than returning -1 for missing or erroneous values.
    private static int getIntegerFromRow(Map<String, String> row, String columnName) {
        String value = row.get(columnName);

        if (value == null)
            return -1;
        else {
            try {
                return Integer.parseInt(value);
            } catch (NumberFormatException e) {
                log.warning("expecting integer value in column " + columnName + " got " + value);
                return -1;
            }
        }
    }

    /**
     * The DCM4CHEE MySql table returns the series date in the format: YYYY-MM-DD HH:MM:SS.sss
     * 
     * We want it in the format: YYYYMMDD
     * 
     * @param seriesDate YYYY-MM-DD HH:MM:SS.sss
     * @return YYYYMMDD
     */
    private static String reformatSeriesDate(String seriesDate) {
        try {
            if (seriesDate != null) {
                String[] parts = seriesDate.split(" ");
                return cleanString(parts[0], "-").replaceAll(" ", "");
            } else {
                return "00000000";
            }
        } catch (Exception e) {
            log.warning("cleanSeriesDate parse error for: " + seriesDate, e);
        }
        return seriesDate;
    }

    /**
     * Remove a specific character from a from the string.
     * 
     * @param input String
     * @param removeChar String character to remove
     * @return String cleaned of character and trimmed.
     */
    private static String cleanString(String input, String removeChar) {
        if (input == null)
            return null;
        if (removeChar == null)
            return input;

        return input.replaceAll(removeChar, " ").trim();
    }
}