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}