001/**
002 * Copyright (C) 2014  Universidade de Aveiro, DETI/IEETA, Bioinformatics Group - http://bioinformatics.ua.pt/
003 *
004 * This file is part of Dicoogle/dicoogle.
005 *
006 * Dicoogle/dicoogle is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * Dicoogle/dicoogle is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with Dicoogle.  If not, see <http://www.gnu.org/licenses/>.
018 */
019package pt.ua.dicoogle.core.dim;
020
021
022import java.io.IOException;
023import java.io.StringWriter;
024import java.io.Writer;
025import java.net.URI;
026import java.util.*;
027
028import org.json.JSONException;
029import org.json.JSONWriter;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import javax.xml.transform.OutputKeys;
034import javax.xml.transform.Transformer;
035import javax.xml.transform.TransformerConfigurationException;
036import javax.xml.transform.TransformerFactory;
037import javax.xml.transform.sax.SAXTransformerFactory;
038import javax.xml.transform.sax.TransformerHandler;
039import javax.xml.transform.stream.StreamResult;
040
041import net.sf.json.JSONArray;
042import net.sf.json.JSONObject;
043
044import org.apache.commons.lang3.StringUtils;
045import org.xml.sax.SAXException;
046import org.xml.sax.helpers.AttributesImpl;
047
048import pt.ua.dicoogle.sdk.datastructs.SearchResult;
049
050/**
051 *
052 * @author Luís A. Bastião Silva <bastiao@ua.pt>
053 * @author Frederico Silva <fredericosilva@ua.pt>
054 */
055public class DIMGeneric
056{
057    /**
058     * There are double space to save the results but it decrease
059     * the search time and it is important because a querySearch should be
060     * little enough.
061     */
062    private ArrayList<Patient> patients = new ArrayList<>();
063    private HashMap<String, Patient> patientsHash  = new HashMap<>();
064    
065    private static String toTrimmedString(Object o, boolean allowNull){
066        if(o == null)
067                if(allowNull)
068                        return null;
069                else return "";
070                
071        if(allowNull)
072                return StringUtils.trimToNull(o.toString());
073        
074        return StringUtils.trimToEmpty(o.toString());
075    }
076
077    private ConcatTags tags;
078
079    private static final Logger logger = LoggerFactory.getLogger(DIMGeneric.class);
080
081    public DIMGeneric(ConcatTags tags, Iterable<SearchResult> arr) throws Exception
082    {
083        this.tags = tags;
084        fill(arr, 4);
085
086    }
087
088    /**
089     * it is allow to handle a ArrayList of Strings or SearchResults
090     * @param arr
091     */
092    public DIMGeneric(Iterable<SearchResult> arr, int depth) throws Exception{
093        fill(arr, depth);
094    }
095
096    public DIMGeneric(Iterable<SearchResult> arr) throws Exception{
097        this(arr, 4);
098    }
099
100    /**
101     * it is allow to handle a ArrayList of Strings or SearchResults
102     * @param arr
103     */
104    private void fill(Iterable<SearchResult> arr, int depth) {
105            //DebugManager.getInstance().debug("Looking search results: " + arr.size() );
106
107            Map<String, String> descriptions = new HashMap<String, String>();
108
109            for(SearchResult r : arr){
110
111                /**
112                 * Looking for SearchResults and put it in right side :)
113                 */
114
115                //SearchResult r = (SearchResult) arr.get(i);
116                final HashMap<String, Object> extra = r.getExtraData();
117
118                /** Get data from Study */
119                final String studyUID = toTrimmedString(extra.get("StudyInstanceUID"),false);
120                final String modality = toTrimmedString( extra.get("Modality"), false);
121                final String patientID =  toTrimmedString( extra.get("PatientID"), false);
122                final String patientName =  toTrimmedString(extra.get("PatientName"), false);
123                String StudyDescription = toTrimmedString( extra.get("StudyDescription"), false);
124
125                /**
126                 * Get data to Image
127                 */
128                //TODO:Error checking here... but according to standard, all images
129                //must have one of these...
130                String sopInstUID =  toTrimmedString(extra.get("SOPInstanceUID"), true);
131                         
132                if(sopInstUID == null)
133                    sopInstUID ="no uid";
134
135                logger.debug("StudyDescription: {}", StudyDescription);
136                
137                // This is a quick fix for changing the parameters for the query.
138                if ((StudyDescription==null||StudyDescription.equals("")||StudyDescription.toLowerCase().contains("fuji"))
139                        &&
140                        this.tags!=null) {
141                    logger.debug("Checking if the Rule applies: {}", studyUID);
142                    String description = "";
143
144                    if (descriptions.containsKey(studyUID)) {
145                        description = descriptions.get(studyUID);
146                    }
147
148                    for (ConcatTags.Rule rule : this.tags.getRules())
149                    {
150                        logger.debug("Checking if the Rule applies: {}", modality);
151
152                        if (modality.equals(rule.getModality())) {
153                            String valueTagToReplace = (String) extra.get(rule.getTagToReplace());
154                            logger.debug("Checking if the Rule value. {}", valueTagToReplace);
155                            logger.debug("Checking if the Rule value. {}", rule.getTagToReplace());
156
157                            if (valueTagToReplace!=null)
158                            {
159                                // Required to production enviroment. I'm not changing to toTrimmedString until get a stable release.
160                                // complete ported.
161                                valueTagToReplace = valueTagToReplace.trim().replaceAll("[^a-zA-Z0-9\\. ÉéàÀÃ;,]+","");
162                                description = description + valueTagToReplace + "; ";
163                            }
164                        }
165
166                    }
167                    StudyDescription = description;
168                    descriptions.put(studyUID, StudyDescription);
169
170                }
171
172                String patientIdentifier = patientID;
173                if (patientID.equals("")) {
174                    patientIdentifier = patientName;
175                }
176
177                /** Verify if Patient already exists */
178
179                if (this.patientsHash.containsKey(patientIdentifier))
180                {
181                    if (depth >= 1) {
182                        /**
183                         * Patient Already exists, let's check studys
184                         */
185                        // Real data does not have Patient Id - sometimes.
186                        Patient p = this.patientsHash.get(patientIdentifier);
187
188                        Study s = this.fillStudy(p, extra, StudyDescription);
189                        if (depth > 2) {
190                            Serie serie = this.fillSeries(s, extra);
191                            if (depth > 3) {
192                                serie.addImage(r.getURI(), sopInstUID);
193                            }
194                        }
195                    }
196                }
197                else {
198                    /**
199                     * Patient does not exist
200                     */
201                    Patient p = new Patient(patientID, patientName);
202
203                    /* Get Patient Data */
204                    String patientSex = toTrimmedString(  extra.get("PatientSex"), false);
205                    p.setPatientSex(patientSex);
206
207                    String patientBirthDate = toTrimmedString(  extra.get("PatientBirthDate"), false);
208                    p.setPatientBirthDate(patientBirthDate);
209
210                    if (depth >= 1) {
211                        /**
212                         * Create Study
213                         */
214                        Study s = this.fillStudy(p, extra, StudyDescription);
215                        if (depth > 2) {
216                            Serie serie = this.fillSeries(s, extra);
217                            if (depth > 3) {
218                                serie.addImage(r.getURI(), sopInstUID);
219                            }
220                        }
221                    }
222                    this.patients.add(p);
223                    this.patientsHash.put(patientIdentifier, p);
224                }
225            }
226
227    }
228
229    /**
230     * Add Study
231     * It also verify if it exists
232     * In the last case Object will be discarded and the
233     * data will be added for the Study that already exists
234     */
235    public Study fillStudy(Patient parent, Map<String, Object> extra, String StudyDescription) {
236        /** Get data from Study */
237        String studyUID = toTrimmedString(extra.get("StudyInstanceUID"),false);
238        String studyID = toTrimmedString(extra.get("StudyID"), false);
239        String studyDate = toTrimmedString( extra.get("StudyDate"), false);
240        String studyTime = toTrimmedString( extra.get("StudyTime"), true);
241        String AccessionNumber = toTrimmedString( extra.get("AccessionNumber"), false);
242        String InstitutionName = toTrimmedString( extra.get("InstitutionName"), false);
243        String operatorsName = toTrimmedString (extra.get("OperatorsName"), false);
244        String RequestingPhysician = toTrimmedString (extra.get("RequestingPhysician"), false);
245
246        Study s = parent.getStudy(studyUID);
247        if (s == null) {
248            s = new Study(parent, studyUID, studyDate);
249            parent.addStudy(s);
250        }
251        s.setInstitutuionName(InstitutionName);
252        s.setAccessionNumber(AccessionNumber);
253        s.setStudyTime(studyTime);
254        s.setStudyID(studyID);
255        s.setStudyDescription(StudyDescription);
256        s.setPatientName(parent.getPatientName());
257        s.setOperatorsName(operatorsName);
258        s.setRequestingPhysician(RequestingPhysician);
259        return s;
260    }
261
262    public Serie fillSeries(Study parent, Map<String, Object> extra) {
263        String seriesUID =  toTrimmedString( extra.get("SeriesInstanceUID"), false);
264        String modality = toTrimmedString( extra.get("Modality"), false);
265        Serie s = parent.getSeries(seriesUID);
266        if (s == null) {
267            s = new Serie(parent, seriesUID, modality);
268            parent.addSerie(s);
269        }
270
271        String serieNumber = toTrimmedString(extra.get("SeriesNumber"), true);
272        if (serieNumber != null && !serieNumber.equals("")) {
273            s.setSerieNumber((int) Float.parseFloat(serieNumber));
274        }
275
276        String serieDescription = toTrimmedString(  extra.get("SeriesDescription"), false);
277        s.setSeriesDescription(serieDescription);
278
279        String SeriesDate =  toTrimmedString(extra.get("SeriesDate"), false);
280        s.setSeriesDate(SeriesDate);
281
282        String ProtocolName =  toTrimmedString(extra.get("ProtocolName"), false);
283        s.setProtocolName(ProtocolName);
284
285        String ViewPosition = toTrimmedString( extra.get("ViewPosition"), false);
286        s.setViewPosition(ViewPosition);
287
288        String ImageLaterality = toTrimmedString( extra.get("ImageLaterality"), false);
289        s.setImageLaterality(ImageLaterality);
290
291        String ViewCodeSequence_CodeValue =  toTrimmedString( extra.get("ViewCodeSequence_CodeValue"), false);
292        s.setViewCodeSequence_CodeValue(ViewCodeSequence_CodeValue);
293
294        String ViewCodeSequence_CodingSchemeDesignator =  toTrimmedString( extra.get("ViewCodeSequence_CodingSchemeDesignator"), false);
295        s.setViewCodeSequence_CodingSchemeDesignator(ViewCodeSequence_CodingSchemeDesignator);
296
297        String ViewCodeSequence_CodingSchemeVersion =  toTrimmedString( extra.get("ViewCodeSequence_CodingSchemeVersion"), false);
298        s.setViewCodeSequence_CodingSchemeVersion(ViewCodeSequence_CodingSchemeVersion);
299
300        String ViewCodeSequence_CodeMeaning =  toTrimmedString( extra.get("ViewCodeSequence_CodeMeaning"), false);
301        s.setViewCodeSequence_CodeMeaning(ViewCodeSequence_CodeMeaning);
302
303        String AcquisitionDeviceProcessingDescription = toTrimmedString( extra.get("AcquisitionDeviceProcessingDescription"), false);
304        s.setAcquisitionDeviceProcessingDescription(AcquisitionDeviceProcessingDescription);
305
306        return s;
307    }
308
309    public void writeJSON(Writer destinationWriter, long elapsedTime) throws IOException {
310        this.writeJSON(destinationWriter, elapsedTime, 4, 0, Integer.MAX_VALUE);
311    }
312
313    public void writeJSON(Writer destinationWriter, long elapsedTime, int depth, int offset, int psize) throws IOException {
314        JSONWriter writer = new JSONWriter(destinationWriter);
315        try {
316            writer.object()
317                    .key("numResults").value(this.patients.size());
318            if (depth > 0) {
319                writer.key("results").array();
320
321                for (Patient p : this.patients) {
322                    if (offset-- > 0) continue;
323
324                    writer.object()
325                            .key("id").value(p.getPatientID())
326                            .key("name").value(p.getPatientName())
327                            .key("gender").value(p.getPatientSex())
328                            .key("birthdate").value(p.getPatientBirthDate())
329                            .key("nStudies").value(p.getStudies().size());
330
331                    if (depth > 1) {
332                        writer.key("studies").array(); // begin studies
333
334                        for (Study study : p.getStudies()) {
335                            writer.object()    // begin study
336                                    .key("studyInstanceUID").value(study.getStudyInstanceUID())
337                                    .key("studyDescription").value(study.getStudyDescription())
338                                    .key("studyDate").value(study.getStudyData())
339                                    .key("institutionName").value(study.getInstitutuionName())
340                                    //.key("nSeries").value(study.getSeries().size())
341                            ;
342                            Set<String> modalities = new HashSet<>();
343
344                            if (depth > 2) {
345                                writer.key("series").array(); // begin series (array)
346                                for (Serie series : study.getSeries()) {
347                                    String modality = series.getModality();
348                                    int nimages = series.getSOPInstanceUIDList().size();
349                                    writer.object() // begin series
350                                            .key("serieNumber").value(series.getSerieNumber())
351                                            .key("serieInstanceUID").value(series.getSerieInstanceUID())
352                                            .key("serieDescription").value(series.getSeriesDescription())
353                                            .key("serieModality").value(modality)
354                                            //.key("nImages").value(nimages)
355                                    ;
356                                    if (depth > 3) {
357                                        writer.key("images").array(); // begin images
358                                        for (int i = 0; i < nimages; i++) {
359                                            String rawPath = series.getImageList().get(i).getRawPath();
360                                            writer.object() // begin image
361                                                    .key("sopInstanceUID").value(series.getSOPInstanceUIDList().get(i))
362                                                    .key("rawPath").value(rawPath)
363                                                    .key("uri").value(series.getImageList().get(i).toString())
364                                                    .key("filename").value(rawPath.substring(rawPath.lastIndexOf("/")+1, rawPath.length()))
365                                                    .endObject(); // end image
366                                        }
367                                        writer.endArray(); // end images
368                                    }
369
370                                    modalities.add(modality);
371                                    writer.endObject(); // end series
372                                }
373                                writer.endArray(); // end series (array)
374                                writer.key("modalities");
375                                if (modalities.size() == 1) {
376                                    writer.value(modalities.iterator().next());
377                                } else {
378                                    writer.array(); // begin modalities in study
379                                    for (String m : modalities) {
380                                        writer.value(m);
381                                    }
382                                    writer.endArray(); // end modalities in study
383                                }
384                            }
385                            writer.endObject(); // end study
386                        }
387                        writer.endArray(); // end studies
388                    }
389                    writer.endObject(); // end patient
390                    if (--psize <= 0) break;
391                }
392                writer.endArray(); // end patients
393            }
394            writer
395                    .key("elapsedTime").value(elapsedTime)
396                    .endObject(); // end output
397        } catch (JSONException e) {
398            throw new IOException("JSON serialization error", e);
399        }
400    }
401
402    public String getJSON(){
403        JSONObject result = new JSONObject();
404        result.put("numResults", this.patients.size());
405        JSONArray patients = new JSONArray();
406        
407        for (Patient p : this.patients){
408                JSONObject patient = new JSONObject();
409                patient.put("id", p.getPatientID());
410                patient.put("name", p.getPatientName());
411                patient.put("gender", p.getPatientSex());
412                patient.put("nStudies", p.getStudies().size());
413                patient.put("birthdate", p.getPatientBirthDate());
414                
415                JSONArray studies = new JSONArray();
416                for(Study s: p.getStudies())
417                {
418                        JSONObject study = new JSONObject();
419                        study.put("studyDate", s.getStudyData());
420                        study.put("studyDescription", s.getStudyDescription());
421                        study.put("studyInstanceUID", s.getStudyInstanceUID());
422                        study.put("institutionName", s.getInstitutuionName());
423                        
424                        JSONArray modalities = new JSONArray();
425                        JSONArray series = new JSONArray();
426                        
427                        Set<String> modalitiesSet = new HashSet<>();
428                        for(Serie serie : s.getSeries())
429                        {
430                                modalitiesSet.add(serie.getModality());
431                                modalities.add(serie.getModality());
432                                
433                                JSONObject _serie = new JSONObject();
434                                _serie.put("serieNumber", serie.getSerieNumber());
435                                _serie.put("serieInstanceUID", serie.getSerieInstanceUID());
436                                _serie.put("serieDescription", serie.getSeriesDescription());
437                                _serie.put("serieModality", serie.getModality());
438                                
439                                JSONArray _sopInstanceUID = new JSONArray();
440                                for(int i=0; i<serie.getSOPInstanceUIDList().size();i++){
441                                        JSONObject image = new JSONObject();
442                                        image.put("sopInstanceUID", serie.getSOPInstanceUIDList().get(i));
443                                        String rawPath = serie.getImageList().get(i).getRawPath();
444                                        image.put("rawPath", rawPath);                                          
445                                        image.put("uri", serie.getImageList().get(i).toString());
446                        image.put("filename", rawPath.substring(rawPath.lastIndexOf("/")+1, rawPath.length()));
447                                        
448                                        _sopInstanceUID.add(image);
449                                }
450                                _serie.put("images", _sopInstanceUID);
451    
452                                series.add(_serie);
453                        }
454                        study.put("modalities", StringUtils.join(modalitiesSet,","));
455                        study.put("series", series);
456                        
457                        
458                        
459                        
460                        studies.add(study);
461                        
462                }
463                patient.put("studies", studies);
464                
465                
466                patients.add(patient);
467        }
468        
469        result.put("results", patients);
470        
471        return result.toString();
472    }
473    
474    public String getXML()
475    {
476
477        StringWriter writer = new StringWriter();
478
479
480        StreamResult streamResult = new StreamResult(writer);
481        SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance();
482        //      SAX2.0 ContentHandler.
483        TransformerHandler hd = null;
484        try {
485            hd = tf.newTransformerHandler();
486        } catch (TransformerConfigurationException ex) {
487            LoggerFactory.getLogger(DIMGeneric.class).error(ex.getMessage(), ex);
488        }
489        Transformer serializer = hd.getTransformer();
490        serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
491        serializer.setOutputProperty(OutputKeys.METHOD, "xml");
492        serializer.setOutputProperty(OutputKeys.INDENT, "yes");
493        serializer.setOutputProperty(OutputKeys.STANDALONE, "yes");
494        hd.setResult(streamResult);
495        try {
496            hd.startDocument();
497
498            AttributesImpl atts = new AttributesImpl();
499            hd.startElement("", "", "DIM", atts);
500
501            for (Patient p : this.patients)
502            {
503                atts.clear();
504                atts.addAttribute("", "", "name", "",p.getPatientName().trim());
505                atts.addAttribute("", "", "id", "",p.getPatientID().trim());
506                hd.startElement("", "", "Patient", atts);
507
508
509                for (Study s: p.getStudies())
510                {
511                    atts.clear();
512                    atts.addAttribute("", "", "date", "",s.getStudyData().trim());
513                    atts.addAttribute("", "", "id", "", s.getStudyInstanceUID().trim());
514
515                    hd.startElement("", "", "Study", atts);
516
517                    for (Serie serie : s.getSeries()){
518                        atts.clear();
519                        atts.addAttribute("", "", "modality", "", serie.getModality().trim());
520                        atts.addAttribute("", "", "id", "", serie.getSerieInstanceUID().trim());
521
522                        hd.startElement("", "", "Serie", atts);
523
524                        ArrayList<URI> img = serie.getImageList();
525                        ArrayList<String> uid = serie.getSOPInstanceUIDList();
526                        int size = img.size();
527                        for (int i=0;i<size;i++){
528                            atts.clear();
529                            atts.addAttribute("", "", "path", "", img.get(i).toString().trim());
530                            atts.addAttribute("", "", "uid", "", uid.get(i).trim());
531
532                            hd.startElement("", "", "Image", atts);
533                            hd.endElement("", "", "Image");
534                        }
535                        hd.endElement("", "", "Serie");
536                    }
537                    hd.endElement("", "", "Study");
538                }
539                hd.endElement("", "", "Patient");
540            }
541            hd.endElement("", "", "DIM");
542
543        } catch (SAXException ex) {
544            LoggerFactory.getLogger(DIMGeneric.class.getName()).error(ex.getMessage(), ex);
545        }
546
547        return writer.toString() ;
548    }
549
550    /**
551     * @return the patients
552     */
553    public ArrayList<Patient> getPatients() {
554        return patients;
555    }
556}