private void echo(ActiveAssociation aa, int nrOfTests) throws Exception { AssociationFactory aFact = AssociationFactory.getInstance(); DcmObjectFactory oFact = DcmObjectFactory.getInstance(); for (int i = 0; i < nrOfTests; i++) { aa.invoke(aFact.newDimse(PCID_ECHO, oFact.newCommand().initCEchoRQ(i)), null); } }
private Collection getObserverContextItems(String personName) { Dataset ds = dof.newDataset(); ds.putCS(Tags.RelationshipType, "HAS OBS CONTEXT"); ds.putCS(Tags.ValueType, "CODE"); DcmElement cnSq = ds.putSQ(Tags.ConceptNameCodeSeq); Dataset cnDS = cnSq.addNewItem(); cnDS.putSH(Tags.CodeValue, "121005"); cnDS.putSH(Tags.CodingSchemeDesignator, "DCM"); cnDS.putLO(Tags.CodeMeaning, "ObserverType"); DcmElement ccSq = ds.putSQ(Tags.ConceptCodeSeq); Dataset ccDS = ccSq.addNewItem(); ccDS.putSH(Tags.CodeValue, "121006"); ccDS.putSH(Tags.CodingSchemeDesignator, "DCM"); ccDS.putLO(Tags.CodeMeaning, "Person"); Dataset ds1 = dof.newDataset(); ds1.putCS(Tags.RelationshipType, "HAS OBS CONTEXT"); ds1.putCS(Tags.ValueType, "PNAME"); DcmElement cnSq1 = ds1.putSQ(Tags.ConceptNameCodeSeq); Dataset cnDS1 = cnSq1.addNewItem(); cnDS1.putSH(Tags.CodeValue, "121008"); cnDS1.putSH(Tags.CodingSchemeDesignator, "DCM"); cnDS1.putLO(Tags.CodeMeaning, "Person Observer Name"); ds1.putPN(Tags.PersonName, personName); ArrayList col = new ArrayList(); col.add(ds); col.add(ds1); return col; }
/** * Prepares the Dataset representing the search key in C-FIND. As no values are set, the keys * match to every content in the archive. The user has to specify concret values to limit the * searchSee PS 3.4 - C.6.2.1.2 Study level. * * <p>As the result for C-FIND these keys are filled with the values found in the archive. * * @param cfg the configuration properties for this class. * @throws ParseException if a given properties for the keys was not found. */ private void initKeys(ConfigProperties cfg) throws ParseException { // Remove all keys keys = dof.newDataset(); // Query/Retrieve Level. PS 3.4 - C.6.2 Study Root SOP Class Group keys.putCS(Tags.QueryRetrieveLevel, getQueryRetrieveLevel(STUDY_LEVEL)); // UNIQUE STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putUI(Tags.StudyInstanceUID); // REQUIRED STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putDA(Tags.StudyDate); // Not defined: StudyTime // Not defined: AccessionNumber keys.putPN(Tags.PatientName); keys.putLO(Tags.PatientID); // Not defined: StudyID // OPTIONAL STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putUS(Tags.NumberOfStudyRelatedSeries); keys.putUS(Tags.NumberOfStudyRelatedInstances); // mutch more defined... // Add the keys found in the configuration properties addQueryKeys(cfg); }
private Map string2Codes(String codes, String defaultDesign) { StringTokenizer st = new StringTokenizer(codes, ","); Map map = new HashMap(); int nrOfTokens; StringTokenizer stCode; Dataset ds; String codeValue; while (st.hasMoreTokens()) { stCode = new StringTokenizer(st.nextToken(), "^"); nrOfTokens = stCode.countTokens(); if (nrOfTokens < 2) { throw new IllegalArgumentException( "Wrong format of human performer configuration! (<codeValue>[^<designator>]^<meaning>)"); } ds = dof.newDataset(); codeValue = stCode.nextToken(); ds.putSH(Tags.CodeValue, codeValue); if (nrOfTokens > 2) { ds.putSH(Tags.CodingSchemeDesignator, stCode.nextToken()); } else if (defaultDesign != null) { ds.putSH(Tags.CodingSchemeDesignator, defaultDesign); } ds.putLO(Tags.CodeMeaning, stCode.nextToken()); map.put(codeValue, ds); } return map; }
/** * Sets the Dataset representing the query keys in C-FIND. The user can specify concret values to * limit the search. * * <p>Wildcards '*','?', '-' and '\' are allowed as element-values (see PS 3.4 - C.2.2.2 Attribute * Matching). If an key attribute is defined, but has an empty value, it matches every value at * the storage side. * * <p>At a minimum the key "QueryRetrieveLevel" must be set ("STUDY", "SERIES" or "IMAGE"). See PS * 3.4 - C.6.2 Study Root SOP Class Group * * <p>As the result for C-FIND these keys are filled with the values found in the archive. * * @param cfg the configuration properties for this class. * @throws ParseException if a given properties for the keys was not found. */ public void setQueryKeys(ConfigProperties cfg) throws ParseException { // Remove all keys keys = dof.newDataset(); // Add the keys found in the configuration properties addQueryKeys(cfg); }
/** * @param xdsiModel * @return */ private Dataset getRootInfo(XDSIModel xdsiModel) { Dataset rootInfo = DcmObjectFactory.getInstance().newDataset(); DcmElement sq = rootInfo.putSQ(Tags.ConceptNameCodeSeq); Dataset item = sq.addNewItem(); CodeItem selectedDocTitle = xdsiModel.selectedDocTitle(); item.putSH(Tags.CodeValue, selectedDocTitle.getCodeValue()); item.putSH(Tags.CodingSchemeDesignator, selectedDocTitle.getCodeDesignator()); item.putLO(Tags.CodeMeaning, selectedDocTitle.getCodeMeaning()); return rootInfo; }
private PersonName newPersonName(String pname) { try { return DcmObjectFactory.getInstance().newPersonName(pname); } catch (IllegalArgumentException e) { LOG.warn( "Cannot generate Soundex code for Illegal Person Name: " + pname + " - treat as unknown."); return null; } }
public static boolean equalsPixelData(File f1, File f2) throws IOException { InputStream in1 = new BufferedInputStream(new FileInputStream(f1)); try { InputStream in2 = new BufferedInputStream(new FileInputStream(f2)); try { Dataset attrs = DcmObjectFactory.getInstance().newDataset(); DcmParserFactory pf = DcmParserFactory.getInstance(); DcmParser p1 = pf.newDcmParser(in1); DcmParser p2 = pf.newDcmParser(in2); p1.setDcmHandler(attrs.getDcmHandler()); p1.parseDcmFile(FileFormat.DICOM_FILE, Tags.PixelData); p2.parseDcmFile(FileFormat.DICOM_FILE, Tags.PixelData); int samples = attrs.getInt(Tags.SamplesPerPixel, 1); int frames = attrs.getInt(Tags.NumberOfFrames, 1); int rows = attrs.getInt(Tags.Rows, 1); int columns = attrs.getInt(Tags.Columns, 1); int bitsAlloc = attrs.getInt(Tags.BitsAllocated, 8); int bitsStored = attrs.getInt(Tags.BitsStored, bitsAlloc); int frameLength = rows * columns * samples * bitsAlloc / 8; int pixelDataLength = frameLength * frames; if (pixelDataLength > p1.getReadLength() || pixelDataLength > p2.getReadLength()) { return false; } byte[] b1 = new byte[BUFFER_SIZE]; byte[] b2 = new byte[BUFFER_SIZE]; int[] mask = {0xff, 0xff}; int len, len2; if (bitsAlloc == 16 && bitsStored < 16) { mask[p1.getDcmDecodeParam().byteOrder == ByteOrder.LITTLE_ENDIAN ? 1 : 0] = 0xff >>> (16 - bitsStored); } int pos = 0; while (pos < pixelDataLength) { len = in1.read(b1, 0, Math.min(pixelDataLength - pos, BUFFER_SIZE)); if (len < 0) // EOF return false; int off = 0; while (off < len) { off += len2 = in2.read(b2, off, len - off); if (len2 < 0) // EOF return false; } for (int i = 0; i < len; i++, pos++) if (((b1[i] - b2[i]) & mask[pos & 1]) != 0) return false; } return true; } finally { in2.close(); } } finally { in1.close(); } }
/** * Implements the ECHO service. The C-ECHO service is invoked by a DIMSE-service-user to verify * end-to-end communications with a peer DIMSE-service-user. See PS 3.7 - 9.1.5 C-ECHO SERVICE * * @exception ConnectException * @exception InterruptedException * @exception IOException */ public long cECHO() throws ConnectException, InterruptedException, IOException { PresContext pc; long t1 = System.currentTimeMillis(); // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-ECHO is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) if ((pc = aassoc .getAssociation() .getAcceptedPresContext(UIDs.Verification, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context: Verification SOP/ImplicitVRLittleEndian."); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command cEchoRQ = oFact.newCommand(); // API doc: Command.initCEchoRQ(int msgID) cEchoRQ.initCEchoRQ(1); // API doc: AssociationFactorynewDimse(int pcid, Command cmd) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse echoRq = aFact.newDimse(pc.pcid(), cEchoRQ); // PS 3.7 - 9.3.5 C-ECHO PROTOCOL, 9.3.5.2 C-ECHO-RSP // Always returns SUCESS result code. // Invoke active association with echo request Dimse FutureRSP future = aassoc.invoke(echoRq); // Response to the C-ECHO request. // The result cannot be accessed until it has been set. Dimse echoRsp = future.get(); Command rspCmd = echoRsp.getCommand(); // PS 3.7 - 9.3.5 C-MOVE PROTOCOL, 9.3.5.2 C-ECHO-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // Success break; default: log.error("C-ECHO failed: " + Integer.toHexString(status)); break; } return System.currentTimeMillis() - t1; }
void sendPPS(boolean create, Dataset pps, String aet) throws Exception { ActiveAssociation aa = openAssociation(aet, UIDs.GeneralPurposePerformedProcedureStepSOPClass); try { Association a = aa.getAssociation(); DcmObjectFactory dof = DcmObjectFactory.getInstance(); Command cmdRq = dof.newCommand(); final String iuid = pps.getString(Tags.SOPInstanceUID); if (create) { cmdRq.initNCreateRQ(a.nextMsgID(), UIDs.GeneralPurposePerformedProcedureStepSOPClass, iuid); } else { cmdRq.initNSetRQ(a.nextMsgID(), UIDs.GeneralPurposePerformedProcedureStepSOPClass, iuid); } Dimse dimseRq = AssociationFactory.getInstance().newDimse(PCID_GPPPS, cmdRq, pps.exclude(SOP_IUID)); if (log.isDebugEnabled()) { log.debug("GP-PPS Attributes:"); log.debug(pps); } final Dimse dimseRsp = aa.invoke(dimseRq).get(); final Command cmdRsp = dimseRsp.getCommand(); final int status = cmdRsp.getStatus(); switch (status) { case 0x0000: break; case 0x0116: log.warn( "Received Warning Status 116H (=Attribute Value Out of Range) from remote AE " + aet); break; default: throw new DcmServiceException(status, cmdRsp.getString(Tags.ErrorComment)); } } finally { try { aa.release(true); } catch (Exception e) { log.warn("Failed to release " + aa.getAssociation()); } } }
private Dataset loadDataset(File file) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); Dataset ds = factory.newDataset(); try { ds.readFile(bis, null, -1); } finally { try { bis.close(); } catch (IOException ignore) { } } if (log.isDebugEnabled()) log.debug("Dataset for file " + file + " :" + ds); return ds; }
/** * Imports a DICOM file. * * <p>The FileDTO object refers to an existing DICOM file (This method does NOT check this file!) * and the Dataset object holds the meta data for database. * * <p> * * @param fileDTO Refers the DICOM file. * @param ds Dataset with metadata for database. * @param last last file to import */ public void importFile(FileDTO fileDTO, Dataset ds, String prevseriuid, boolean last) throws Exception { Storage store = getStorage(); String seriud = ds.getString(Tags.SeriesInstanceUID); if (prevseriuid != null && !prevseriuid.equals(seriud)) { SeriesStored seriesStored = store.makeSeriesStored(prevseriuid); if (seriesStored != null) { log.debug("Send SeriesStoredNotification - series changed"); scp.doAfterSeriesIsStored(store, null, seriesStored); } } String cuid = ds.getString(Tags.SOPClassUID); String iuid = ds.getString(Tags.SOPInstanceUID); FileMetaInfo fmi = DcmObjectFactory.getInstance().newFileMetaInfo(cuid, iuid, fileDTO.getFileTsuid()); ds.setFileMetaInfo(fmi); String fsPath = fileDTO.getDirectoryPath(); String filePath = fileDTO.getFilePath(); File f = FileUtils.toFile(fsPath, filePath); // Modified by [email protected] on 01.09.2009 // In case the file is not stored in file system but somewhere else // scp.updateDB(store, ds, fileDTO.getFileSystemPk(), filePath, f.length(), // fileDTO.getFileMd5(), true); scp.updateDB( store, ds, fileDTO.getFileSystemPk(), filePath, fileDTO.getFileSize(), fileDTO.getFileMd5(), true); if (last) { SeriesStored seriesStored = store.makeSeriesStored(seriud); if (seriesStored != null) { scp.doAfterSeriesIsStored(store, null, seriesStored); } } }
private Dataset getStudyMgtDataset(StudyLocal study, Map mapSeries) { Dataset ds = dof.newDataset(); ds.putUI(Tags.StudyInstanceUID, study.getStudyIuid()); ds.putOB(PrivateTags.StudyPk, Convert.toBytes(study.getPk().longValue())); ds.putSH(Tags.AccessionNumber, study.getAccessionNumber()); ds.putLO(Tags.PatientID, study.getPatient().getPatientId()); ds.putLO(Tags.IssuerOfPatientID, study.getPatient().getIssuerOfPatientId()); ds.putPN(Tags.PatientName, study.getPatient().getPatientName()); log.debug("getStudyMgtDataset: studyIUID:" + study.getStudyIuid()); DcmElement refSeriesSeq = ds.putSQ(Tags.RefSeriesSeq); Iterator iter = (mapSeries == null) ? study.getSeries().iterator() : mapSeries.keySet().iterator(); while (iter.hasNext()) { SeriesLocal sl = (SeriesLocal) iter.next(); Dataset dsSer = refSeriesSeq.addNewItem(); dsSer.putUI(Tags.SeriesInstanceUID, sl.getSeriesIuid()); Collection instances = (mapSeries == null) ? sl.getInstances() : (Collection) mapSeries.get(sl); Iterator iter2 = instances.iterator(); DcmElement refSopSeq = null; if (iter2.hasNext()) refSopSeq = dsSer.putSQ(Tags.RefSOPSeq); while (iter2.hasNext()) { InstanceLocal il = (InstanceLocal) iter2.next(); Dataset dsInst = refSopSeq.addNewItem(); dsInst.putUI(Tags.RefSOPClassUID, il.getSopCuid()); dsInst.putUI(Tags.RefSOPInstanceUID, il.getSopIuid()); dsInst.putAE(Tags.RetrieveAET, il.getRetrieveAETs()); } } if (log.isDebugEnabled()) { log.debug("return StgMgtDataset:"); log.debug(ds); } return ds; }
/** * @author [email protected] * @version $Revision: 3345 $ $Date: 2007-05-18 10:53:26 +0200 (Fr, 18. Mai 2007) $ * @since 05.04.2005 */ public class GPWLFeedService extends ServiceMBeanSupport { private static final int[] PAT_ATTR_TAGS = { Tags.PatientName, Tags.PatientID, Tags.PatientBirthDate, Tags.PatientSex, }; private Map humanPerformer = null; private List templates = null; private File templatePath = null; private static DcmObjectFactory dof = DcmObjectFactory.getInstance(); /** @return Returns the physicians. */ public String getHumanPerformer() { return codes2String(humanPerformer); } /** @param performer The human performer(s) to set. */ public void setHumanPerformer(String performer) { this.humanPerformer = string2Codes(performer, "DCM4CHEE"); } /** @return Returns the configURL. */ public String getTemplatePath() { return templatePath.getPath(); } /** * @param configURL The configURL to set. * @throws MalformedURLException */ public void setTemplatePath(String path) throws MalformedURLException { templatePath = new File(path.replace('/', File.separatorChar)); } private String codes2String(Map codes) { if (codes == null || codes.isEmpty()) return ""; StringBuffer sb = new StringBuffer(); Dataset ds; String design; for (Iterator iter = codes.values().iterator(); iter.hasNext(); ) { ds = (Dataset) iter.next(); design = ds.getString(Tags.CodingSchemeDesignator); sb.append(ds.getString(Tags.CodeValue)).append("^"); if (design != null) sb.append(design).append("^"); sb.append(ds.getString(Tags.CodeMeaning)).append(","); } return sb.substring(0, sb.length() - 1); } private Map string2Codes(String codes, String defaultDesign) { StringTokenizer st = new StringTokenizer(codes, ","); Map map = new HashMap(); int nrOfTokens; StringTokenizer stCode; Dataset ds; String codeValue; while (st.hasMoreTokens()) { stCode = new StringTokenizer(st.nextToken(), "^"); nrOfTokens = stCode.countTokens(); if (nrOfTokens < 2) { throw new IllegalArgumentException( "Wrong format of human performer configuration! (<codeValue>[^<designator>]^<meaning>)"); } ds = dof.newDataset(); codeValue = stCode.nextToken(); ds.putSH(Tags.CodeValue, codeValue); if (nrOfTokens > 2) { ds.putSH(Tags.CodingSchemeDesignator, stCode.nextToken()); } else if (defaultDesign != null) { ds.putSH(Tags.CodingSchemeDesignator, defaultDesign); } ds.putLO(Tags.CodeMeaning, stCode.nextToken()); map.put(codeValue, ds); } return map; } public List listTemplates() { if (templates == null) { File tmplPath = FileUtils.resolve(templatePath); File[] files = tmplPath.listFiles(); templates = new ArrayList(); String fn; for (int i = 0; i < files.length; i++) { fn = files[i].getName(); if (fn.endsWith(".xml")) { templates.add(fn.substring(0, fn.length() - 4)); } } } log.info("Template List:" + templates); return templates; } public void clearTemplateList() { templates = null; } public void addWorklistItem( Long studyPk, String templateFile, String humanPerformerCode, Long scheduleDate) throws Exception { String uri = FileUtils.resolve(new File(templatePath, templateFile + ".xml")).toURI().toString(); if (log.isDebugEnabled()) log.debug("load template file: " + uri); Dataset ds = DatasetUtils.fromXML(new InputSource(uri)); ContentManager cm = getContentManager(); // patient Dataset patDS = cm.getPatientForStudy(studyPk.longValue()); if (log.isDebugEnabled()) { log.debug("Patient Dataset:"); log.debug(patDS); } ds.putAll(patDS.subSet(PAT_ATTR_TAGS)); // Dataset sopInstRef = cm.getSOPInstanceRefMacro(studyPk.longValue(), false); String studyIUID = sopInstRef.getString(Tags.StudyInstanceUID); ds.putUI(Tags.SOPInstanceUID, UIDGenerator.getInstance().createUID()); ds.putUI(Tags.StudyInstanceUID, studyIUID); DcmElement inSq = ds.putSQ(Tags.InputInformationSeq); inSq.addItem(sopInstRef); // Scheduled Human Performer Seq DcmElement schedHPSq = ds.putSQ(Tags.ScheduledHumanPerformersSeq); Dataset item = schedHPSq.addNewItem(); DcmElement hpCodeSq = item.putSQ(Tags.HumanPerformerCodeSeq); Dataset dsCode = (Dataset) this.humanPerformer.get(humanPerformerCode); log.info(dsCode); if (dsCode != null) { hpCodeSq.addItem(dsCode); item.putPN(Tags.HumanPerformerName, dsCode.getString(Tags.CodeMeaning)); } // Scheduled Procedure Step Start Date and Time ds.putDT(Tags.SPSStartDateAndTime, new Date(scheduleDate.longValue())); if (log.isDebugEnabled()) { log.debug("GPSPS Dataset:"); log.debug(ds); } addWorklistItem(ds); } private void addWorklistItem(Dataset ds) { if (ds == null) return; try { getGPWLManager().addWorklistItem(ds); } catch (Exception e) { log.error("Failed to add Worklist Item:", e); } } private GPWLManager getGPWLManager() throws CreateException, RemoteException, HomeFactoryException { return ((GPWLManagerHome) EJBHomeFactory.getFactory().lookup(GPWLManagerHome.class, GPWLManagerHome.JNDI_NAME)) .create(); } private ContentManager getContentManager() throws Exception { ContentManagerHome home = (ContentManagerHome) EJBHomeFactory.getFactory() .lookup(ContentManagerHome.class, ContentManagerHome.JNDI_NAME); return home.create(); } }
/** * Convert an image to RGB. * * @param inFile the file to convert. * @param outFile the output file, which may be same as inFile. * @return the static status result */ public static AnonymizerStatus convert(File inFile, File outFile) { long fileLength = inFile.length(); logger.debug("Entering DICOMPaletteImageConverter.convert"); logger.debug("File length = " + fileLength); BufferedInputStream in = null; BufferedOutputStream out = null; File tempFile = null; byte[] buffer = new byte[4096]; try { // Check that this is a known format. in = new BufferedInputStream(new FileInputStream(inFile)); DcmParser parser = pFact.newDcmParser(in); FileFormat fileFormat = parser.detectFileFormat(); if (fileFormat == null) { throw new IOException("Unrecognized file format: " + inFile); } // Get the dataset (excluding pixels) and leave the input stream open Dataset dataset = oFact.newDataset(); parser.setDcmHandler(dataset.getDcmHandler()); parser.parseDcmFile(fileFormat, Tags.PixelData); // Make sure this is an image if (parser.getReadTag() != Tags.PixelData) { close(in); return AnonymizerStatus.SKIP(inFile, "Not an image"); } // Get the required parameters and make sure they are okay int numberOfFrames = getInt(dataset, Tags.NumberOfFrames, 1); int rows = getInt(dataset, Tags.Rows, 0); int columns = getInt(dataset, Tags.Columns, 0); String photometricInterpretation = getString(dataset, Tags.PhotometricInterpretation, ""); if ((rows == 0) || (columns == 0)) { close(in); return AnonymizerStatus.SKIP(inFile, "Unable to get the rows and columns"); } if (!photometricInterpretation.equals("PALETTE COLOR")) { close(in); return AnonymizerStatus.SKIP( inFile, "Unsupported PhotometricInterpretation: " + photometricInterpretation); } if (parser.getReadTag() != Tags.PixelData) { close(in); return AnonymizerStatus.SKIP(inFile, "No pixels"); } // Get the encoding and set the parameters DcmDecodeParam fileParam = parser.getDcmDecodeParam(); String fileEncodingUID = UIDs.ImplicitVRLittleEndian; FileMetaInfo fmi = dataset.getFileMetaInfo(); if (fmi != null) fileEncodingUID = fmi.getTransferSyntaxUID(); boolean isBigEndian = fileEncodingUID.equals(UIDs.ExplicitVRBigEndian); String encodingUID = UIDs.ExplicitVRLittleEndian; DcmEncodeParam encoding = (DcmEncodeParam) DcmDecodeParam.valueOf(encodingUID); boolean swap = (fileParam.byteOrder != encoding.byteOrder); if (encoding.encapsulated) { logger.debug("Encapsulated pixel data found"); close(in); return AnonymizerStatus.SKIP(inFile, "Encapsulated pixel data not supported"); } // Get the LUTs LUT red = new LUT( dataset.getInts(Tags.RedPaletteColorLUTDescriptor), dataset.getInts(Tags.RedPaletteColorLUTData)); LUT green = new LUT( dataset.getInts(Tags.GreenPaletteColorLUTDescriptor), dataset.getInts(Tags.GreenPaletteColorLUTData)); LUT blue = new LUT( dataset.getInts(Tags.BluePaletteColorLUTDescriptor), dataset.getInts(Tags.BluePaletteColorLUTData)); // Set the PlanarConfiguration to 0 dataset.putUS(Tags.PlanarConfiguration, 0); // Set the PhotometricInterpretation to RGB dataset.putCS(Tags.PhotometricInterpretation, "RGB"); // Set the pixel parameters dataset.putUS(Tags.SamplesPerPixel, 3); dataset.putUS(Tags.BitsAllocated, 8); dataset.putUS(Tags.BitsStored, 8); dataset.putUS(Tags.HighBit, 7); // Remove the lookup tables and their descriptors dataset.remove(Tags.RedPaletteColorLUTDescriptor); dataset.remove(Tags.GreenPaletteColorLUTDescriptor); dataset.remove(Tags.BluePaletteColorLUTDescriptor); dataset.remove(Tags.RedPaletteColorLUTData); dataset.remove(Tags.GreenPaletteColorLUTData); dataset.remove(Tags.BluePaletteColorLUTData); // Save the dataset to a temporary file, and rename at the end. File tempDir = outFile.getParentFile(); tempFile = File.createTempFile("DCMtemp-", ".anon", tempDir); out = new BufferedOutputStream(new FileOutputStream(tempFile)); // Create and write the metainfo for the encoding we are using fmi = oFact.newFileMetaInfo(dataset, encodingUID); dataset.setFileMetaInfo(fmi); fmi.write(out); // Write the dataset as far as was parsed dataset.writeDataset(out, encoding); // Process the pixels int nPixels = numberOfFrames * rows * columns; int nPixelBytes = nPixels * 3 /*samplesPerPixel*/; int pad = nPixelBytes & 1; dataset.writeHeader(out, encoding, parser.getReadTag(), VRs.OB, nPixelBytes + pad); int pd; int b1, b2; int bytesPerFrame = rows * columns * 2; byte[] frameBytes = new byte[bytesPerFrame]; for (int frame = 0; frame < numberOfFrames; frame++) { if (in.read(frameBytes, 0, frameBytes.length) != bytesPerFrame) throw new Exception("End of File"); for (int p = 0; p < bytesPerFrame; ) { b1 = frameBytes[p++]; b2 = frameBytes[p++]; if (!swap) { pd = ((b2 & 0xff) << 8) | (b1 & 0xff); } else { pd = ((b1 & 0xff) << 8) | (b2 & 0xff); } out.write(red.get(pd)); out.write(green.get(pd)); out.write(blue.get(pd)); } } if (pad != 0) out.write(0); logger.debug("Finished writing the pixels"); // Skip everything after the pixels out.flush(); out.close(); in.close(); outFile.delete(); tempFile.renameTo(outFile); return AnonymizerStatus.OK(outFile, ""); } catch (Exception e) { logger.debug("Exception while processing image.", e); // Close the input stream if it actually got opened. close(in); // Close the output stream if it actually got opened, // and delete the tempFile in case it is still there. try { if (out != null) { out.close(); tempFile.delete(); } } catch (Exception ex) { logger.warn("Unable to close the output stream."); } // Quarantine the object return AnonymizerStatus.QUARANTINE(inFile, e.getMessage()); } }
/** * @author [email protected] * @version $Revision: 7709 $ $Date: 2008-10-22 11:41:47 -0300 (qua, 22 out 2008) $ */ public class XDSIExportDelegate { private static final String XDSI_DELEGATE_ATTR_NAME = "xdsiDelegate"; private static MBeanServer server; private static ObjectName keyObjectServiceName; private static ObjectName xdsiServiceName; private static final DcmObjectFactory dof = DcmObjectFactory.getInstance(); private static Logger log = Logger.getLogger(XDSIExportDelegate.class.getName()); public XDSIExportDelegate() {} public static final XDSIExportDelegate getInstance(ControllerContext ctx) { HttpSession session = ctx.getRequest().getSession(); XDSIExportDelegate delegate = (XDSIExportDelegate) session.getAttribute(XDSI_DELEGATE_ATTR_NAME); if (delegate == null) { delegate = new XDSIExportDelegate(); try { delegate.init(ctx); } catch (Exception e) { throw new NullPointerException("Cant initialize XDSIExportDelegate!"); } session.setAttribute(XDSI_DELEGATE_ATTR_NAME, delegate); } return delegate; } public void init(ControllerContext ctx) throws Exception { if (keyObjectServiceName != null) return; server = MBeanServerLocator.locate(); String s = ctx.getServletConfig().getInitParameter("keyObjectServiceName"); keyObjectServiceName = new ObjectName(s); s = ctx.getServletConfig().getInitParameter("xdsiServiceName"); xdsiServiceName = new ObjectName(s); } public boolean exportXDSI(XDSIModel xdsiModel) throws Exception { if (xdsiModel.getNumberOfInstances() < 1) { throw new IllegalArgumentException("No Instances selected!"); } Collection items = getObserverContextItems(getAuthorPerson(xdsiModel.getUser())); Dataset rootInfo = getRootInfo(xdsiModel); List contentItems = getContentItems(xdsiModel); contentItems.addAll(items); try { Boolean b; if (!xdsiModel.isPdfExport()) { Dataset keyObjectDS = getKeyObject(xdsiModel.getInstances(), rootInfo, contentItems); b = (Boolean) server.invoke( xdsiServiceName, "sendSOAP", new Object[] {keyObjectDS, xdsiModel.listMetadataProperties()}, new String[] {Dataset.class.getName(), Properties.class.getName()}); } else { String docUID = (String) xdsiModel.getInstances().iterator().next(); b = (Boolean) server.invoke( xdsiServiceName, "exportPDF", new Object[] {docUID, xdsiModel.listMetadataProperties()}, new String[] {String.class.getName(), Properties.class.getName()}); } return b.booleanValue(); } catch (Exception e) { log.warn("Failed to export Selection:", e); throw e; } } public boolean exportPDF(XDSIModel xdsiModel) throws Exception { if (xdsiModel.getNumberOfInstances() < 1) { throw new IllegalArgumentException("No Instances selected!"); } Collection items = getObserverContextItems(getAuthorPerson(xdsiModel.getUser())); Dataset rootInfo = getRootInfo(xdsiModel); List contentItems = getContentItems(xdsiModel); contentItems.addAll(items); String docUID = (String) xdsiModel.getInstances().iterator().next(); try { Boolean b = (Boolean) server.invoke( xdsiServiceName, "exportPDF", new Object[] {docUID, xdsiModel.listMetadataProperties()}, new String[] {String.class.getName(), Properties.class.getName()}); return b.booleanValue(); } catch (Exception e) { log.warn("Failed to export Selection:", e); throw e; } } public boolean createFolder(XDSIModel model) throws Exception { try { Boolean b = (Boolean) server.invoke( xdsiServiceName, "createFolder", new Object[] {model.listMetadataProperties()}, new String[] {Properties.class.getName()}); return b.booleanValue(); } catch (Exception e) { log.warn("Failed to create Folder:", e); throw e; } } private Collection getObserverContextItems(String personName) { Dataset ds = dof.newDataset(); ds.putCS(Tags.RelationshipType, "HAS OBS CONTEXT"); ds.putCS(Tags.ValueType, "CODE"); DcmElement cnSq = ds.putSQ(Tags.ConceptNameCodeSeq); Dataset cnDS = cnSq.addNewItem(); cnDS.putSH(Tags.CodeValue, "121005"); cnDS.putSH(Tags.CodingSchemeDesignator, "DCM"); cnDS.putLO(Tags.CodeMeaning, "ObserverType"); DcmElement ccSq = ds.putSQ(Tags.ConceptCodeSeq); Dataset ccDS = ccSq.addNewItem(); ccDS.putSH(Tags.CodeValue, "121006"); ccDS.putSH(Tags.CodingSchemeDesignator, "DCM"); ccDS.putLO(Tags.CodeMeaning, "Person"); Dataset ds1 = dof.newDataset(); ds1.putCS(Tags.RelationshipType, "HAS OBS CONTEXT"); ds1.putCS(Tags.ValueType, "PNAME"); DcmElement cnSq1 = ds1.putSQ(Tags.ConceptNameCodeSeq); Dataset cnDS1 = cnSq1.addNewItem(); cnDS1.putSH(Tags.CodeValue, "121008"); cnDS1.putSH(Tags.CodingSchemeDesignator, "DCM"); cnDS1.putLO(Tags.CodeMeaning, "Person Observer Name"); ds1.putPN(Tags.PersonName, personName); ArrayList col = new ArrayList(); col.add(ds); col.add(ds1); return col; } /** * @param user * @return */ public String getAuthorPerson(String user) { try { return (String) server.invoke( xdsiServiceName, "getAuthorPerson", new Object[] {user}, new String[] {String.class.getName()}); } catch (Exception e) { log.warn("Failed to get author person for user " + user + " ! Reason:" + e.getCause()); return null; } } /** * @param xdsiModel * @return */ private List getContentItems(XDSIModel xdsiModel) { List items = new ArrayList(); return items; } /** * @param xdsiModel * @return */ private Dataset getRootInfo(XDSIModel xdsiModel) { Dataset rootInfo = DcmObjectFactory.getInstance().newDataset(); DcmElement sq = rootInfo.putSQ(Tags.ConceptNameCodeSeq); Dataset item = sq.addNewItem(); CodeItem selectedDocTitle = xdsiModel.selectedDocTitle(); item.putSH(Tags.CodeValue, selectedDocTitle.getCodeValue()); item.putSH(Tags.CodingSchemeDesignator, selectedDocTitle.getCodeDesignator()); item.putLO(Tags.CodeMeaning, selectedDocTitle.getCodeMeaning()); return rootInfo; } public Dataset getKeyObject(Collection iuids, Dataset rootInfo, List contentItems) { Object o = null; try { o = server.invoke( keyObjectServiceName, "getKeyObject", new Object[] {iuids, rootInfo, contentItems}, new String[] { Collection.class.getName(), Dataset.class.getName(), Collection.class.getName() }); } catch (RuntimeMBeanException x) { log.warn("RuntimeException thrown in KeyObject Service:" + x.getCause()); throw new IllegalArgumentException(x.getCause().getMessage()); } catch (Exception e) { log.warn("Failed to create Key Object:", e); throw new IllegalArgumentException( "Error: KeyObject Service cant create manifest Key Object! Reason:" + e.getClass().getName()); } return (Dataset) o; } private ContentManager lookupContentManager() throws Exception { ContentManagerHome home = (ContentManagerHome) EJBHomeFactory.getFactory() .lookup(ContentManagerHome.class, ContentManagerHome.JNDI_NAME); return home.create(); } /** @return */ public CodeItem[] getConfiguredAuthorRoles() { return getCodeItems("listAuthorRoles"); } public CodeItem[] getConfiguredClassCodes() { return getCodeItems("listClassCodes"); } public CodeItem[] getConfiguredContentTypeCodes() { return getCodeItems("listContentTypeCodes"); } public CodeItem[] getConfiguredHealthCareFacilityTypeCodes() { return getCodeItems("listHealthCareFacilityTypeCodes"); } public CodeItem[] getConfiguredEventCodes() { return getCodeItems("listEventCodes"); } public CodeItem[] getConfiguredDocTitles() { return getCodeItems("listDocTitleCodes"); } public CodeItem[] getConfiguredConfidentialityCodes() { return getCodeItems("listConfidentialityCodes"); } private CodeItem[] getCodeItems(String methodName) { try { List l = (List) server.invoke(xdsiServiceName, methodName, new Object[] {}, new String[] {}); CodeItem[] items = new CodeItem[l.size()]; for (int i = 0, len = l.size(); i < len; i++) { items[i] = CodeItem.valueofDCM(l.get(i).toString()); // DCM (D)esignator(C)odevalue(M)eaning } return items; } catch (Exception e) { log.error("Failed to get list of configured Codes! method:" + methodName, e); return null; } } /** @return */ public Properties joinMetadataProperties(Properties props) { try { return (Properties) server.invoke( xdsiServiceName, "joinMetadataProperties", new Object[] {props}, new String[] {Properties.class.getName()}); } catch (Exception e) { log.error("Failed to get XDS-I Metadata Properties:", e); return null; } } }
/** * Implementation of C-DIMSE services. * * <p> * * <p>Usage: * * <p>1. Create a new instance of this class. * * <p>2. Use the aASSOCIATE method to establish an association. * * <p>3. Use the cFIND method to query the archive. * * <p>4. Use the cMOVE method to move an object from the archive to a destination server. * * <p>5. Use the cSTORE to store an object into an archive. * * <p>6. Use the cECHO to verfy a association. * * <p>7. If you are ready with the C-DIMSE services use the aRELEASE method to close the * association. * * <p> * * <p>The query/retrieve levels used by C-FIND and C-MOVE are defined as enummerated constants. Use * the method getQueryRetrieveLevel to convert these values to the String representation used in the * DICOM element QueryRetrieveLevel (0008,0052). * * <p> * * <p>Based on dcm4che 1.4.0 sample: MoveStudy.java revision date 2005-10-05 * * <p>Based on dcm4che 1.4.0 sample: DcmSnd.java revision date 2005-10-05 * * <p> * * <p>See: PS 3.4 - Annex B STORAGE SERVICE CLASS * * <p>See: PS 3.4 - Annex C QUERY/RETRIEVE SERVICE CLASS * * <p>See: PS 3.4 - C.6 SOP CLASS DEFINITIONS * * <p>See: PS 3.4 - C.6.2 Study Root SOP Class Group * * <p> * * <p>Details of how to run the services is given in a configuration property file. A sample may be * found at "./resources/CDimseService.cfg". * * @author Thomas Hacklaender * @version 2006-08-28 */ public class CDimseService { static final Logger log = Logger.getLogger("CDimseService"); /** The DEBUG flag is set, if the logging level of this class is Debug */ static final boolean DEBUG = log.isDebugEnabled(); // >>>> Factorys >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> private static final UIDDictionary uidDict = DictionaryFactory.getInstance().getDefaultUIDDictionary(); private static final AssociationFactory aFact = AssociationFactory.getInstance(); private static final DcmObjectFactory oFact = DcmObjectFactory.getInstance(); private static final DcmParserFactory pFact = DcmParserFactory.getInstance(); private static final DcmObjectFactory dof = DcmObjectFactory.getInstance(); /** Default AE title used for the association if it is not explicit given in the url filed. */ private static final String DEFAULT_CALLING_AET = "OVIYAM"; /** Query/Retrieve Level Values for Study = "PATIENT". See PS 3.4 - C.6 SOP CLASS DEFINITIONS */ public static final int PATIENT_LEVEL = 0; /** Query/Retrieve Level Values for Study = "STUDY". See PS 3.4 - C.6. SOP CLASS DEFINITIONS */ public static final int STUDY_LEVEL = 1; /** Query/Retrieve Level Values for Series = "SERIES". See PS 3.4 - C.6 SOP CLASS DEFINITIONS */ public static final int SERIES_LEVEL = 2; /** Query/Retrieve Level Values for Image = "IMAGE". See PS 3.4 - C.6 SOP CLASS DEFINITIONS */ public static final int IMAGE_LEVEL = 3; PresContext pc; /** * DICOM URL to define a communication partner for an association. * * <p>PROTOCOL://CALLED[:CALLING]@HOST[:PORT] * * <p> * * <p>PROTOCOL Specifies protocol. Possible values: * * <p>- dicom DICOM default (without TLS) * * <p>- dicom-tls DICOM on TLS (offer AES and DES encryption) * * <p>- dicom-tls.aes DICOM on TLS (force AES or DES encryption) * * <p>- dicom-tls.3des DICOM on TLS (force DES encryption) * * <p>- dicom-tls.nodes DICOM on TLS (no encryption) * * <p>CALLED Called AET in association request (max 16 chars) * * <p>CALLING Calling AET in association request (max 16 chars) [default id final field * DEFAULT_CALLING_AET = MYAET] * * <p>HOST Name or IP address of host, where the server is running * * <p>PORT TCP port address, on which the server is listing for * * <p>incoming TCP Transport Connection Indication [default=104] */ private DcmURL url = null; /** Message priority. Possible values Command.LOW = 2, Command.MEDIUM = 0, Command.HIGH = 1 */ private int priority = Command.MEDIUM; /** * Time-out waiting [in msec] for A-ASSOCIATE-AC acknowlage, 0 is interpreted as an infinite * timeout [default=5000]. */ private int acTimeout = 5000; /** * Time-out waiting [in msec] for DIMSE on aASSOCIATE association, 0 is interpreted as an infinite * timeout [default=0]. */ private int dimseTimeout = 0; /** Time delay [in msec] for socket aRELEASE after sending A-ABORT [default=500]. */ private int soCloseDelay = 500; /** * Association Request package (A-ASSOCIATE-RQ) is part of the connection service ACSE * (Association Control Service Element). In TCP/IP networks connection services are emulated by * the "DICOM Upper Layer Service". This presentation srvice encapsulats the data in PDUs * (Protocol Data Unit). */ private AAssociateRQ assocRQ = aFact.newAAssociateRQ(); /** Association object for establishing an active association. */ private Association assoc = null; /** Accepted association */ private ActiveAssociation aassoc = null; /** * Activates packing of command PDV (Presentation Data Value) + (first) data PDV into one * P-DATA-TF PDU (Protocol Data Unit) */ private boolean packPDVs = false; /** TLS context. Set by method initTLS */ private SSLContextAdapter tls = null; /** An array of implemented ciphers for the communication protocol (given in url). */ private String[] cipherSuites = null; /** * Specifies Key Attributes used for C-FIND. Wildcards '*','?', '-' and '\' are allowed as * element-values (see PS 3.4 - C.2.2.2 Attribute Matching). If an key attribute is defined, but * has an empty value, it matches every value at the storage side. * * <p>Key attributes have a type (PS 3.4 - C.2.2.1 Attribute Types) * * <p>- U = one Attribute shall be defined as a Unique Key. * * <p>- R = a set of Attributes shall be defined as Required Keys. * * <p>- O = a set of Attributes shall be defined as Optional Keys. * * <p>The complete list of Key Attributes can be found at PS 3.4 - C.6.2 Study Root SOP Class * Group. * * <p>As a result of C-FIND for each matching item in the archive a DIMSE object containing the * Key Attributes is send back. In this object the Key Attributes are set corresponding to values * found in the archive. Attributes of type "O" may be send back with empty value. Only used by * C-FIND. */ private Dataset keys = dof.newDataset(); /** * Application Entity Title (AET) of the destination for the C-MOVE. The AET must be known by the * archive together with the host IP adress and the port number. Only used by C-MOVE. */ private String dest; // >>>> Constructor >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /** * Constructor for the StorageSCUServiceClass object. Initializes everything. * * <p>Details of how to run the server is given in another configuration property file. A sample * may be found at "./resources/StorageSCUServiceClass.cfg". * * @param cfg the configuration properties for this class. * @param url the DcmURL of the communication partner. * @throws ParseException */ public CDimseService(ConfigProperties cfg, DcmURL url) throws ParseException { this.url = url; this.priority = Integer.parseInt(cfg.getProperty("prior", "0")); this.packPDVs = "true".equalsIgnoreCase(cfg.getProperty("pack-pdvs", "false")); initAssocParam(cfg, url); // initEchoAssocParam(cfg, url); initTLS(cfg); // Only used by C-FIND initKeys(cfg); // Only used by C-MOVE this.dest = cfg.getProperty("dest"); } // >>>> Methods >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /** * Initializes Association related parameters. * * @param cfg the configuration properties for this class. * @param url the DcmURL of the communication partner. */ private final void initAssocParam(ConfigProperties cfg, DcmURL url) { String callingAET = null; // >>>> Get data for filling the Association object for establishing an // >>>> active association from configuration file acTimeout = Integer.parseInt(cfg.getProperty("ac-timeout", "5000")); dimseTimeout = Integer.parseInt(cfg.getProperty("dimse-timeout", "0")); soCloseDelay = Integer.parseInt(cfg.getProperty("so-close-delay", "500")); // >>>> Fill the Association Request package (A-ASSOCIATE-RQ) // Identifying the communication partner by an AET (Application Entity Title) assocRQ.setCalledAET(url.getCalledAET()); // Identifying ourselves by an AET (Application Entity Title) if (url.getCallingAET() != null) { callingAET = url.getCallingAET(); } else { callingAET = DEFAULT_CALLING_AET; } assocRQ.setCallingAET(callingAET); // Maximum size of one PDU (Protocol Data Unit) assocRQ.setMaxPDULength(Integer.parseInt(cfg.getProperty("max-pdu-len", "16352"))); // Defines possibilities for asynchron DIMSE communication. Noramly synchron DIMSE communication // is used. // API doc: AssociationFactory.newAsyncOpsWindow(int maxOpsInvoked, int maxOpsPerfomed) // PS 3.7 - Annex D.3.3.3 ASYNCHRONOUS OPERATIONS WINDOW NEGOTIATION // maxOpsInvoked: This field shall contain the Maximum-number-operationsinvoked as defined for // the Association-requester // maxOpsPerfomed: This field shall contain the Maximum-number-operationsperformed as defined // for the Association-requester assocRQ.setAsyncOpsWindow( aFact.newAsyncOpsWindow(Integer.parseInt(cfg.getProperty("max-op-invoked", "0")), 1)); for (Enumeration it = cfg.keys(); it.hasMoreElements(); ) { String key = (String) it.nextElement(); // Setup available transfer syntaces for storage SOP classes // PS 3.4 - Annex B STORAGE SERVICE CLASS // PS 3.4 - B.5 STANDARD SOP CLASSES if (key.startsWith("pc.")) { initPresContext( Integer.parseInt(key.substring(3)), cfg.tokenize(cfg.getProperty(key), new LinkedList())); } } } /** * Only used by method initAssocParam: Sets up available transfer syntaces for storage SOP * classes. * * @param pcid is a for the association unique odd number between 1 and 255. * @param val a list: First element is the symbolic name of the UID of the SOP to transmit, the * following elements are the supportet transfer syntax for that SOP. */ private final void initPresContext(int pcid, List val) { Iterator it = val.iterator(); String as = UIDs.forName((String) it.next()); String[] tsUIDs = new String[val.size() - 1]; for (int i = 0; i < tsUIDs.length; ++i) { tsUIDs[i] = UIDs.forName((String) it.next()); } // API doc: AssociationFactory.newPresContext(int pcid, String asuid, String[] tsuid) // pcid is a for the association unique odd number between 1 and 255. // asuid is the UID of a SOP class // TS list of transfer syntaces supported by this class for asuid. assocRQ.addPresContext(aFact.newPresContext(pcid, as, tsUIDs)); } /** * Initializes TLS (Transport Layer Security, predecessor of SSL, Secure Sockets Layer) connection * related parameters. TLS expects RSA (Ron Rivest, Adi Shamir and Len Adleman) encoded keys and * certificates. * * <p>Keys and certificates may be stored in PKCS #12 (Public Key Cryptography Standards) or JKS * (Java Keystore) containers. * * <p>TSL is used to encrypt data during transmission, which is accomplished when the connection * between the two partners A (normally the client) and B (normally the server) is established. If * A asks B to send TSL encrypted data, both partners exchange some handshake information. In a * first step B tries to authentify itself against A (server authentification, optional in TSL but * implementet in this way in dcm4che). For that B presents its public key for A to accept or * deny. If the authentification is accepted, A tries to authentify itself against B (client * authentification, optional in TSL but implementet in this way in dcm4che).If B accepts the * authentification A and B agree on a hash (symmetric key, which is independent of the * private/public keys used for authentification) for the duration of their conversation, which is * used to encrypt the data. * * <p>To be able to establish a TSL connection B needs a privat/public key pair, which identifies * B unambiguously. For that the private key is generated first; than the root-public key is * derived from that private key. To bind the root-public key with the identity of B a Certificate * Authority (CA) is used: The root-public key is send to CA, which returns a certified-public * key, which includes information about the CA as well as the root-public key. Optionally this * process can be repeated several times so that the certified-public key contains a chain of * certificates of different CA's. * * <p>The certified-public key of B is presented to A during server authentification. Partner A * should accept this key, if it can match the last certificate in the chain with a * root-certificat found in its local list of root-certificates of CA's. That means, that A does * not check the identity of B, but "trusts" B because it was certified by an authority. The same * happens for client authentification. Handling of authentification of the identity of the * communication partner is subject of PS 3.15 - Security and System Management Profiles. * * <p>In the configuration files of this method the certified-public key is stored in the property * "tls-key" and the root-certificates of the known CA's in "tls-cacerts". * * <p>Usually the certified-public keys of A and B are different, but they also may be the same. * In this case the root-certificates are also the same. * * <p>It is possible to establish a TLS connection without using a CA: In this case both partners * creates a individual container holding their private and root-public key. These containers * could be used for certifying also, because the length of the certifying chain is one and * therefore the root-public key is also the last certified-public key. Therefore the root-public * key works in this scenario also as the root-certificate of the "certifying authoroty". * * <p>If no keystores are specified in the configuration properties, the not-certified * default-keystore "resources/identityJava.jks" is used for "tls-key" and "tls-cacerts" when * establishing the connection. * * @param cfg the configuration properties for this class. * @throws ParseException */ private void initTLS(ConfigProperties cfg) throws ParseException { char[] keystorepasswd; char[] keypasswd; char[] cacertspasswd; URL keyURL; URL cacertURL; String value; try { // Cipher suites for protokoll: // dicom = null // dicom-tls = SSL_RSA_WITH_NULL_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, // SSL_RSA_WITH_3DES_EDE_CBC_SHA // dicom-tls.3des = SSL_RSA_WITH_3DES_EDE_CBC_SHA // dicom-tls.aes = TLS_RSA_WITH_AES_128_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA // dicom-tls.nodes = SSL_RSA_WITH_NULL_SHA cipherSuites = url.getCipherSuites(); if (cipherSuites == null) { return; } // Get a new TLS context tls = SSLContextAdapter.getInstance(); // >>>> Managing the keystore file containing the privat key and // >>>> certified-public key to establish the communication // Password of the keystore [default: secret] keystorepasswd = cfg.getProperty("tls-keystore-passwd", "secret").toCharArray(); // Password of the private key [default: secret] keypasswd = cfg.getProperty("tls-key-passwd", "secret").toCharArray(); // URL of the file containing the default-keystore keyURL = CDimseService.class.getResource("/resources/identityJava.jks"); // If availabel, replace URL with the one specified in the configuration file if ((value = cfg.getProperty("tls-key")) != null) { try { // Property specified, try to set to specified value keyURL = ConfigProperties.fileRefToURL(CDimseService.class.getResource(""), value); } catch (Exception e) { log.warn("Wrong value for tls-key: " + value + ". tls-key was set to default value."); } } // log.info("Key URL: " + keyURL.toString()); // Sets the key attribute of the SSLContextAdapter object // API doc: SSLContextAdapter.loadKeyStore(java.net.URL url, char[] password) // API doc: SSLContextAdapter.setKey(java.security.KeyStore key, char[] password) tls.setKey(tls.loadKeyStore(keyURL, keystorepasswd), keypasswd); // >>>> Managing the keystore containing the root-certificates of the Ceritifying Authorities // >>>> used for signing the public key // Password of the keystore [default: secret] cacertspasswd = cfg.getProperty("tls-cacerts-passwd", "secret").toCharArray(); // URL of the file containing the default-keystore cacertURL = CDimseService.class.getResource("/resources/identityJava.jks"); // If availabel, replace URL with the one specified in the configuration file if ((value = cfg.getProperty("tls-cacerts")) != null) { try { // Property specified, try to set to specified value cacertURL = ConfigProperties.fileRefToURL(CDimseService.class.getResource(""), value); } catch (Exception e) { log.warn( "Wrong value for tls-cacerts: " + value + ". tls-cacerts was set to default value."); } } // log.info("Root-certificat of CA URL: " + cacertURL.toString()); // Sets the trust attribute of the SSLContextAdapter object // API doc: SSLContextAdapter.loadKeyStore(java.net.URL url, char[] password) // API doc: SSLContextAdapter.setTrust(java.security.KeyStore cacerts) tls.setTrust(tls.loadKeyStore(cacertURL, cacertspasswd)); // Init TLS context adapter tls.init(); } catch (Exception ex) { throw new ParseException("Could not initalize TLS configuration.", 0); } } /** * Prepares the Dataset representing the search key in C-FIND. As no values are set, the keys * match to every content in the archive. The user has to specify concret values to limit the * searchSee PS 3.4 - C.6.2.1.2 Study level. * * <p>As the result for C-FIND these keys are filled with the values found in the archive. * * @param cfg the configuration properties for this class. * @throws ParseException if a given properties for the keys was not found. */ private void initKeys(ConfigProperties cfg) throws ParseException { // Remove all keys keys = dof.newDataset(); // Query/Retrieve Level. PS 3.4 - C.6.2 Study Root SOP Class Group keys.putCS(Tags.QueryRetrieveLevel, getQueryRetrieveLevel(STUDY_LEVEL)); // UNIQUE STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putUI(Tags.StudyInstanceUID); // REQUIRED STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putDA(Tags.StudyDate); // Not defined: StudyTime // Not defined: AccessionNumber keys.putPN(Tags.PatientName); keys.putLO(Tags.PatientID); // Not defined: StudyID // OPTIONAL STUDY LEVEL KEY FOR THE STUDY. See PS 3.4 - C.6.2.1.2 Study level keys.putUS(Tags.NumberOfStudyRelatedSeries); keys.putUS(Tags.NumberOfStudyRelatedInstances); // mutch more defined... // Add the keys found in the configuration properties addQueryKeys(cfg); } /** * Add the query keys found in the configuration properties to the Dataset "keys" used bei the * cFIND method. * * @param cfg the configuration properties for this class. * @throws ParseException if a given properties for the keys was not found. */ private void addQueryKeys(ConfigProperties cfg) throws ParseException { // Add/replace keys found in the configuration file. Syntax key.<element name> = <element value> for (Enumeration it = cfg.keys(); it.hasMoreElements(); ) { String key = (String) it.nextElement(); if (key.startsWith("key.")) { try { keys.putXX(Tags.forName(key.substring(4)), cfg.getProperty(key)); } catch (Exception e) { throw new ParseException( "Illegal entry in configuration filr: " + key + "=" + cfg.getProperty(key), 0); } } } } /** * Sets the Dataset representing the query keys in C-FIND. The user can specify concret values to * limit the search. * * <p>Wildcards '*','?', '-' and '\' are allowed as element-values (see PS 3.4 - C.2.2.2 Attribute * Matching). If an key attribute is defined, but has an empty value, it matches every value at * the storage side. * * <p>At a minimum the key "QueryRetrieveLevel" must be set ("STUDY", "SERIES" or "IMAGE"). See PS * 3.4 - C.6.2 Study Root SOP Class Group * * <p>As the result for C-FIND these keys are filled with the values found in the archive. * * @param ds the Dataset containig the search keys. */ public void setQueryKeys(Dataset ds) { keys = ds; } /** * Sets the Dataset representing the query keys in C-FIND. The user can specify concret values to * limit the search. * * <p>Wildcards '*','?', '-' and '\' are allowed as element-values (see PS 3.4 - C.2.2.2 Attribute * Matching). If an key attribute is defined, but has an empty value, it matches every value at * the storage side. * * <p>At a minimum the key "QueryRetrieveLevel" must be set ("STUDY", "SERIES" or "IMAGE"). See PS * 3.4 - C.6.2 Study Root SOP Class Group * * <p>As the result for C-FIND these keys are filled with the values found in the archive. * * @param cfg the configuration properties for this class. * @throws ParseException if a given properties for the keys was not found. */ public void setQueryKeys(ConfigProperties cfg) throws ParseException { // Remove all keys keys = dof.newDataset(); // Add the keys found in the configuration properties addQueryKeys(cfg); } /** * Starts a active association to a communication partner. See PS 3.8 - 7.1 A-ASSOCIATE SERVICE * * @return true, if association was successful established. * @exception ConnectException * @exception IOException * @exception GeneralSecurityException */ public boolean aASSOCIATE() throws ConnectException, IOException, GeneralSecurityException { // No association may be active if (assoc != null) { throw new ConnectException("Association already established"); } // New Association object for establishing an active association assoc = aFact.newRequestor(newSocket(url.getHost(), url.getPort())); // >>>> Fill the Association object with relevant data assoc.setAcTimeout(acTimeout); assoc.setDimseTimeout(dimseTimeout); assoc.setSoCloseDelay(soCloseDelay); assoc.setPackPDVs(packPDVs); // 1. Create an communication channel to the communication partner defined in the Association // object // 2. Send the A-ASSOCIATE-RQ package // 3. Receive the aAssociation acknowlage/reject package from the communication partner as a PDU // (Protocol Data Unit) PDU assocAC = assoc.connect(assocRQ); if (!(assocAC instanceof AAssociateAC)) { // Acknowlage is A-ASSOCIATE-RJ // Association rejected assoc = null; // Return aASSOCIATE faild return false; } // Acknowlage is A-ASSOCIATE_AC // Association accepted // Start the accepted association // API doc: AssociationFactory.newActiveAssociation(Association assoc, DcmServiceRegistry // services) aassoc = aFact.newActiveAssociation(assoc, null); aassoc.start(); // Return successfull opened return true; } /** * Creates a stream socket and connects it to the specified port number on the named host. * * @param host the IP address. * @param port the port number. * @return the Socket. * @exception IOException * @exception GeneralSecurityException */ private Socket newSocket(String host, int port) throws IOException, GeneralSecurityException { // Test, if a secured connection is needed if (cipherSuites != null) { // Creates a socket for secured connection. // The SSLContextAdapter tls uses the javax.net.ssl package for establishing // the connection. return tls.getSocketFactory(cipherSuites).createSocket(host, port); } else { // Creates a standard Java socket for unsecured connection. return new Socket(host, port); } } /** * Releases the active association. See PS 3.8 - 7.2 A-RELEASE SERVICE * * @param waitOnRSP if true, method waits until it receives the responds to the release request. * @exception InterruptedException Description of the Exception * @exception IOException Description of the Exception */ public void aRELEASE(boolean waitOnRSP) throws InterruptedException, IOException { if (assoc != null) { try { aassoc.release(waitOnRSP); } finally { assoc = null; aassoc = null; } } } /** * Stores a DICOM object in an archive (Storage SCP). * * <p>See PS 3.4 - Annex B STORAGE SERVICE CLASS. * * @param ds the Dataset to store. * @throws ConnectException * @throws ParseException * @throws IOException * @throws InterruptedException * @throws IllegalStateException */ public void cSTORE(Dataset ds) throws InterruptedException, IOException, ConnectException, ParseException { String sopClassUID; String sopInstUID; PresContext pc = null; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // SOP Class UID must be given if ((sopClassUID = ds.getString(Tags.SOPClassUID)) == null) { throw new ParseException("No SOP Class UID in Dataset", 0); } // SOP Instance UID must be given if ((sopInstUID = ds.getString(Tags.SOPInstanceUID)) == null) { throw new ParseException("No SOP Instance UID in Dataset", 0); } // Test, if applicable presentation context was found if ((pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ImplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ExplicitVRBigEndian)) == null) { throw new ConnectException("No applicable presentation context found"); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command cStoreRQ = oFact.newCommand(); // API doc: Command.initCStoreRQ(int msgID, String sopClassUID, String sopInstUID, int priority) cStoreRQ.initCStoreRQ(assoc.nextMsgID(), sopClassUID, sopInstUID, priority); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse storeRq = aFact.newDimse(pc.pcid(), cStoreRQ, ds); // PS 3.7 - 9.3.1 C-STORE PROTOCOL, 9.3.1.2 C-STORE-RSP // Always returns SUCESS result code. // Invoke active association with echo request Dimse FutureRSP future = aassoc.invoke(storeRq); System.out.println("store is going in this AE"); // Response to the C-ECHO request. // The result cannot be accessed until it has been set. Dimse storeRsp = future.get(); Command rspCmd = storeRsp.getCommand(); // PS 3.7 - 9.3.5 C-MOVE PROTOCOL, 9.3.5.2 C-ECHO-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // Success break; default: log.error("C-STORE failed: " + Integer.toHexString(status)); break; } } public Vector cGET(Dataset ds) throws ConnectException, IOException, InterruptedException { PresContext pc; List dimseList; Vector datasetVector; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-MOVE is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) if ((pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelGET, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelGET, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context for StudyRootQueryRetrieveInformationModelMOVE SOP."); } // Get the Study Instance UID of the study to mode String suid = ds.getString(Tags.SOPInstanceUID); // Prepare info for logging String patName = ds.getString(Tags.PatientName); String patID = ds.getString(Tags.PatientID); String studyDate = ds.getString(Tags.StudyDate); String prompt = "Study[" + suid + "] from " + studyDate + " for Patient[" + patID + "]: " + patName; // log.info("Moving: " + prompt); // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command rqCmd = dof.newCommand(); // API doc: Command.initCMoveRQ(int msgID, String sopClassUID, int priority, String moveDest) rqCmd.initCGetRSP(assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelGET, priority); Dataset rqDs = dof.newDataset(); rqDs.putCS(Tags.QueryRetrieveLevel, getQueryRetrieveLevel(STUDY_LEVEL)); // Only Unique Key allowed in C-MOVE. PS 3.4 -C.2.2.1 Attribute Types rqDs.putUI(Tags.SOPInstanceUID, suid); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse moveRq = aFact.newDimse(pc.pcid(), rqCmd, rqDs); // Invoke active association with move request Dimse FutureRSP future = aassoc.invoke(moveRq); // Response to the C-MOVE request. // The result cannot be accessed until it has been set. Dimse moveRsp = future.get(); Command rspCmd = moveRsp.getCommand(); if (DEBUG) { StringWriter w = new StringWriter(); w.write("C-FIND RQ Identifier:\n"); keys.dumpDataset(w, null); log.debug(w.toString()); } // Invoke active association with find request Dimse // Response to the C-FIND request. // The result cannot be accessed until it has been set. // Get the list of found objects dimseList = future.listPending(); // >>>> Extract Dataset from Dimse datasetVector = new Vector(); // If no List of DIMSE objects was generated or it is empty return an empty Vector if (dimseList == null || dimseList.isEmpty()) { return datasetVector; } // Process all elements for (int i = 0; i < dimseList.size(); i++) { datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset()); if (((Dimse) dimseList.get(i)).getDataset() == null) System.out.println(" Dataset created succesffullyu "); } // PS 3.7 - 9.3.4 C-MOVE PROTOCOL, 9.3.4.2 C-MOVE-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // log.info("Moved: " + prompt); break; case 0xB000: log.error("One or more failures during move of " + prompt); break; default: log.error("Failed to move " + prompt + "\n\terror tstatus: " + Integer.toHexString(status)); break; } System.out.println("The move sise is : " + datasetVector.size()); return datasetVector; } /** * Queries the archive for DICOM objects matching Attribute Keys defined in the loacal field * "keys". This field is set by the constructor out of the configuration parameters or by the * methods setQueryKeys(Configuration) and setQueryKeys(Dataset). See PS 3.4 - Annex C * QUERY/RETRIEVE SERVICE CLASS. * * <p>The method returns, when the result is received from the communication partner. * * @return the result of the cFIND as a Vector of Dataset objects each specifying one matching * DICOM object. If no matching objects are found an empty Vector is returned. * @throws ConnectException * @throws IOException */ public Vector cFIND() throws ConnectException, IOException, InterruptedException { List dimseList; Vector datasetVector; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-FIND is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) // UIDs.StudyRootQueryRetrieveInformationModelGET if ((pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelFIND, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelFIND, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context for StudyRootQueryRetrieveInformationModelFIND SOP."); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command rqCmd = dof.newCommand(); // API doc: Command.initCFindRQ(int msgID, String sopClassUID, int priority) rqCmd.initCFindRQ(assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelFIND, priority); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse findRq = aFact.newDimse(pc.pcid(), rqCmd, keys); if (DEBUG) { StringWriter w = new StringWriter(); w.write("C-FIND RQ Identifier:\n"); keys.dumpDataset(w, null); log.debug(w.toString()); } // Invoke active association with find request Dimse FutureRSP future = aassoc.invoke(findRq); // Response to the C-FIND request. // The result cannot be accessed until it has been set. Dimse findRsp = future.get(); // Get the list of found objects dimseList = future.listPending(); // >>>> Extract Dataset from Dimse datasetVector = new Vector(); // If no List of DIMSE objects was generated or it is empty return an empty Vector if (dimseList == null || dimseList.isEmpty()) { return datasetVector; } // Process all elements for (int i = 0; i < dimseList.size(); i++) { datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset()); } return datasetVector; } /** * Ask archive to move one DICOM object to the destination (C-MOVE). See PS 3.4 - Annex C * QUERY/RETRIEVE SERVICE CLASS. * * <p>Use the Study Root Query/Retrieve Information Model to communicate with the archive. See PS * 3.4 - C.6.2 Study Root SOP Class Group. * * <p>PS 3.4 - C.4.2.1.4.1 Request Identifier Structure (for C-MOVE): An Identifier in a C-MOVE * request shall contain: * * <p>- the Query/Retrieve Level (0008,0052) which defines the level of the retrieval * * <p>- Unique Key Attributes which may include Patient ID, Study Instance UIDs, Series Instance * UIDs, and the SOP Instance UIDs * * <p>PS 3.4 - C.4.2.2.1 Baseline Behavior of SCU (of C-MOVE): * * <p>The SCU shall supply a single value in the Unique Key Attribute for each level above the * Query/Retrieve level. For the level of retrieve, the SCU shall supply one unique key if the * level of retrieve is above the STUDY level and shall supply one UID, or a list of UIDs if a * retrieval of several items is desired and the retrieve level is STUDY, SERIES or IMAGE. * * @param ds the DICOM object represented as a Dataset. * @return a result-code: 0x0000 = SUCCESS sub-operations complete no failures, 0xB000 = WARNING * sub-operations complete one or more failures, other = errors defined in PS 3.4 - C.4.2.1.5 * Status * @throws ConnectException * @throws InterruptedException * @throws IOException */ public Vector cMOVE(Dataset ds) throws ConnectException, InterruptedException, IOException { PresContext pc; List dimseList; Vector datasetVector; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-MOVE is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) if ((pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelMOVE, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelMOVE, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context for StudyRootQueryRetrieveInformationModelMOVE SOP."); } // Get the Study Instance UID of the study to mode String suid = ds.getString(Tags.StudyInstanceUID); // Prepare info for logging String patName = ds.getString(Tags.PatientName); String patID = ds.getString(Tags.PatientID); String studyDate = ds.getString(Tags.StudyDate); String prompt = "Study[" + suid + "] from " + studyDate + " for Patient[" + patID + "]: " + patName; // log.info("Moving: " + prompt); // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command rqCmd = dof.newCommand(); // API doc: Command.initCMoveRQ(int msgID, String sopClassUID, int priority, String moveDest) rqCmd.initCMoveRQ( assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelMOVE, priority, dest); Dataset rqDs = dof.newDataset(); rqDs.putCS(Tags.QueryRetrieveLevel, getQueryRetrieveLevel(STUDY_LEVEL)); // Only Unique Key allowed in C-MOVE. PS 3.4 -C.2.2.1 Attribute Types rqDs.putUI(Tags.StudyInstanceUID, suid); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse moveRq = aFact.newDimse(pc.pcid(), rqCmd, rqDs); // Invoke active association with move request Dimse FutureRSP future = aassoc.invoke(moveRq); // Response to the C-MOVE request. // The result cannot be accessed until it has been set. Dimse moveRsp = future.get(); Command rspCmd = moveRsp.getCommand(); Dataset dds = moveRsp.getDataset(); if (DEBUG) { StringWriter w = new StringWriter(); w.write("C-FIND RQ Identifier:\n"); keys.dumpDataset(w, null); log.debug(w.toString()); } // Invoke active association with find request Dimse // Response to the C-FIND request. // The result cannot be accessed until it has been set. // Get the list of found objects Dimse dms = future.get(); // >>>> Extract Dataset from Dimse datasetVector = new Vector(); // If no List of DIMSE objects was generated or it is empty return an empty Vector // if (dimseList == null || dimseList.isEmpty()) { // return datasetVector; // } // Process all elements // for (int i = 0; i < dimseList.size(); i++) { // datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset()); // if(((Dimse) dimseList.get(i)).getDataset()==null) // System.out.println(" Dataset created succesffullyu "); // } // PS 3.7 - 9.3.4 C-MOVE PROTOCOL, 9.3.4.2 C-MOVE-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // log.info("Moved: " + prompt); break; case 0xB000: log.error("One or more failures during move of " + prompt); break; default: log.error("Failed to move " + prompt + "\n\terror tstatus: " + Integer.toHexString(status)); break; } // System.out.println("The move sise is : "+datasetVector.size()); return datasetVector; } /** * Implements the ECHO service. The C-ECHO service is invoked by a DIMSE-service-user to verify * end-to-end communications with a peer DIMSE-service-user. See PS 3.7 - 9.1.5 C-ECHO SERVICE * * @exception ConnectException * @exception InterruptedException * @exception IOException */ public long cECHO() throws ConnectException, InterruptedException, IOException { PresContext pc; long t1 = System.currentTimeMillis(); // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-ECHO is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) if ((pc = aassoc .getAssociation() .getAcceptedPresContext(UIDs.Verification, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context: Verification SOP/ImplicitVRLittleEndian."); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command cEchoRQ = oFact.newCommand(); // API doc: Command.initCEchoRQ(int msgID) cEchoRQ.initCEchoRQ(1); // API doc: AssociationFactorynewDimse(int pcid, Command cmd) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse echoRq = aFact.newDimse(pc.pcid(), cEchoRQ); // PS 3.7 - 9.3.5 C-ECHO PROTOCOL, 9.3.5.2 C-ECHO-RSP // Always returns SUCESS result code. // Invoke active association with echo request Dimse FutureRSP future = aassoc.invoke(echoRq); // Response to the C-ECHO request. // The result cannot be accessed until it has been set. Dimse echoRsp = future.get(); Command rspCmd = echoRsp.getCommand(); // PS 3.7 - 9.3.5 C-MOVE PROTOCOL, 9.3.5.2 C-ECHO-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // Success break; default: log.error("C-ECHO failed: " + Integer.toHexString(status)); break; } return System.currentTimeMillis() - t1; } /** * Gets the String value used for DICOM element QueryRetrieveLevel (0008,0052). * * <p>See PS 3.4 - C.6 SOP CLASS DEFINITIONS * * @param queryRetrieveLevel query/retrieve level as a enumerated value. * @return the String value assiciated to enumerated query/retrieve level. */ public static String getQueryRetrieveLevel(int queryRetrieveLevel) { switch (queryRetrieveLevel) { case PATIENT_LEVEL: return "PATIENT"; case STUDY_LEVEL: return "STUDY"; case SERIES_LEVEL: return "SERIES"; case IMAGE_LEVEL: return "IMAGE"; default: return ""; } } }
/** * Stores a DICOM object in an archive (Storage SCP). * * <p>See PS 3.4 - Annex B STORAGE SERVICE CLASS. * * @param ds the Dataset to store. * @throws ConnectException * @throws ParseException * @throws IOException * @throws InterruptedException * @throws IllegalStateException */ public void cSTORE(Dataset ds) throws InterruptedException, IOException, ConnectException, ParseException { String sopClassUID; String sopInstUID; PresContext pc = null; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // SOP Class UID must be given if ((sopClassUID = ds.getString(Tags.SOPClassUID)) == null) { throw new ParseException("No SOP Class UID in Dataset", 0); } // SOP Instance UID must be given if ((sopInstUID = ds.getString(Tags.SOPInstanceUID)) == null) { throw new ParseException("No SOP Instance UID in Dataset", 0); } // Test, if applicable presentation context was found if ((pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ImplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext(sopClassUID, UIDs.ExplicitVRBigEndian)) == null) { throw new ConnectException("No applicable presentation context found"); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command cStoreRQ = oFact.newCommand(); // API doc: Command.initCStoreRQ(int msgID, String sopClassUID, String sopInstUID, int priority) cStoreRQ.initCStoreRQ(assoc.nextMsgID(), sopClassUID, sopInstUID, priority); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse storeRq = aFact.newDimse(pc.pcid(), cStoreRQ, ds); // PS 3.7 - 9.3.1 C-STORE PROTOCOL, 9.3.1.2 C-STORE-RSP // Always returns SUCESS result code. // Invoke active association with echo request Dimse FutureRSP future = aassoc.invoke(storeRq); System.out.println("store is going in this AE"); // Response to the C-ECHO request. // The result cannot be accessed until it has been set. Dimse storeRsp = future.get(); Command rspCmd = storeRsp.getCommand(); // PS 3.7 - 9.3.5 C-MOVE PROTOCOL, 9.3.5.2 C-ECHO-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // Success break; default: log.error("C-STORE failed: " + Integer.toHexString(status)); break; } }
/** * @author franz.willer * <p>TODO To change the template for this generated type comment go to Window - Preferences - * Java - Code Style - Code Templates */ public class ECGSupport { public static final String CONTENT_TYPE_SVGXML = "image/svg+xml"; private static Logger log = Logger.getLogger(ECGSupport.class.getName()); private static final DcmObjectFactory factory = DcmObjectFactory.getInstance(); private RIDSupport ridSupport; private RIDStorageDelegate storage = RIDStorageDelegate.getInstance(); public ECGSupport(RIDSupport ridSupport) { this.ridSupport = ridSupport; } /** * @param reqObj * @param ds * @return */ public RIDResponseObject getECGDocument(RIDRequestObject reqObj, Dataset ds) { String contentType = reqObj.getParam("preferredContentType"); if (contentType.equals(CONTENT_TYPE_SVGXML) || contentType.equals("image/svg")) { contentType = CONTENT_TYPE_SVGXML; if (ridSupport.checkContentType(reqObj, new String[] {CONTENT_TYPE_SVGXML}) == null) { return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_BAD_REQUEST, "Display actor doesnt accept preferred content type!"); } } else if (contentType.equals(RIDSupport.CONTENT_TYPE_PDF)) { if (ridSupport.checkContentType(reqObj, new String[] {RIDSupport.CONTENT_TYPE_PDF}) == null) { return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_BAD_REQUEST, "Display actor doesnt accept preferred content type!"); } } else { return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_NOT_ACCEPTABLE, "preferredContentType '" + contentType + "' is not supported! Only 'application/pdf' and 'image/svg+xml' are supported !"); } String docUID = reqObj.getParam("documentUID"); log.info("get ECG document! docUID:" + docUID); BaseDocument doc = storage.getDocument(docUID, contentType); try { if (doc != null) { return new RIDStreamResponseObjectImpl( doc.getInputStream(), contentType, HttpServletResponse.SC_OK, null); } } catch (Exception x) { log.error("Getting InputStream for RID Response of document " + docUID + " failed!", x); } try { Dataset fullDS = getDataset(ds); doc = storage.createDocument(docUID, contentType); if (fullDS == null) { return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_NOT_FOUND, "Requested document not found::" + ds.getString(Tags.SOPClassUID)); } else { if (contentType.equals(RIDSupport.CONTENT_TYPE_PDF)) { return handlePDF(fullDS, doc); } else { return handleSVG(fullDS, doc); } } } catch (Exception e) { return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Internal server error:" + e.getMessage()); } } /** * @param string * @return * @throws NeedRedirectionException * @throws IOException */ private Dataset getDataset(Dataset ds) throws IOException { String iuid = ds.getString(Tags.SOPInstanceUID); File file = ridSupport.getDICOMFile(iuid); if (log.isDebugEnabled()) log.debug("DCM file for " + ds.getString(Tags.SOPInstanceUID) + ":" + file); if (file == null) return null; Dataset dsFile; if (!ridSupport.isUseOrigFile()) { FileDataSource dsrc = null; try { dsrc = (FileDataSource) ridSupport .getMBeanServer() .invoke( ridSupport.getQueryRetrieveScpName(), "getDatasourceOfInstance", new Object[] {iuid}, new String[] {String.class.getName()}); } catch (Exception e) { log.error("Failed to get updated DICOM file", e); } file = new File(file + ".dcm"); file.deleteOnExit(); OutputStream os = new BufferedOutputStream(new FileOutputStream(file)); dsrc.isWriteFile(); dsrc.writeTo(os, null); os.close(); dsFile = loadDataset(file); file.delete(); } else { dsFile = loadDataset(file); } return dsFile; } private Dataset loadDataset(File file) throws IOException { BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); Dataset ds = factory.newDataset(); try { ds.readFile(bis, null, -1); } finally { try { bis.close(); } catch (IOException ignore) { } } if (log.isDebugEnabled()) log.debug("Dataset for file " + file + " :" + ds); return ds; } /** * @param ds * @param outFile * @return * @throws IOException */ private RIDResponseObject handleSVG(Dataset ds, BaseDocument doc) throws IOException { OutputStream out = doc.getOutputStream(); try { DcmElement elem = ds.get(Tags.WaveformSeq); WaveformGroup wfgrp = new WaveformGroup( ds.getString(Tags.SOPClassUID), elem, 0, ridSupport.getWaveformCorrection()); // TODO all groups if (log.isDebugEnabled()) log.debug(wfgrp); WaveformInfo wfInfo = new WaveformInfo(ds); SVGCreator svgCreator = new SVGCreator(wfgrp, wfInfo, new Float(27.6f), new Float(20.3f)); svgCreator.toXML(out); out.close(); return new RIDStreamResponseObjectImpl( doc.getInputStream(), CONTENT_TYPE_SVGXML, HttpServletResponse.SC_OK, null); } catch (Throwable t) { if (out != null) try { out.close(); } catch (IOException e) { } log.error("Cant create SVG for Waveform!", t); log.error("Waveform Dataset:"); log.error(ds); return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while creating waveform SVG! Reason:" + t.getMessage()); } } private RIDResponseObject handlePDF(Dataset ds, BaseDocument doc) throws IOException { OutputStream out = doc.getOutputStream(); OutputStream tmpOut = null; File tmpFile = null; try { tmpFile = File.createTempFile("fop_", null); tmpFile.deleteOnExit(); tmpOut = new FileOutputStream(tmpFile); DcmElement elem = ds.get(Tags.WaveformSeq); WaveformGroup[] wfgrps = getWaveformGroups(elem, ds.getString(Tags.SOPClassUID)); WaveformInfo wfInfo = new WaveformInfo(ds); FOPCreator fopCreator = new FOPCreator(wfgrps, wfInfo, new Float(28.6f), new Float(20.3f)); fopCreator.toXML(tmpOut); tmpOut.close(); Fop fop = ridSupport.newFop(MimeConstants.MIME_PDF, out); SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance(); Transformer t = tf.newTransformer(); t.transform( new StreamSource(new FileInputStream(tmpFile)), new SAXResult(fop.getDefaultHandler())); out.close(); tmpFile.delete(); InputStream in = doc.getInputStream(); return new RIDStreamResponseObjectImpl( in, in.available(), RIDSupport.CONTENT_TYPE_PDF, HttpServletResponse.SC_OK, null); } catch (Throwable t) { try { if (out != null) out.close(); if (tmpOut != null) tmpOut.close(); } catch (IOException e) { } if (tmpFile.exists()) tmpFile.delete(); log.error("Cant create PDF for Waveform!", t); log.error("Waveform Dataset:"); log.error(ds); return new RIDStreamResponseObjectImpl( null, RIDSupport.CONTENT_TYPE_HTML, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while creating waveform PDF! Reason:" + t.getMessage()); } } /** * @param cuid * @param elem * @return */ private WaveformGroup[] getWaveformGroups(DcmElement elem, String cuid) { float corr = ridSupport.getWaveformCorrection(); int nrOfWFGroups = elem.countItems(); if (nrOfWFGroups == 1) return new WaveformGroup[] {new WaveformGroup(cuid, elem, 0, corr)}; // dont // check // the // only // one ArrayList l = new ArrayList(nrOfWFGroups); for (int i = 0; i < nrOfWFGroups; i++) { try { l.add(new WaveformGroup(cuid, elem, i, corr)); } catch (Exception x) { log.warn("Item " + i + " in Waveform Sequence is not valid! Ignored!!"); } } return (WaveformGroup[]) l.toArray(new WaveformGroup[l.size()]); } }
public Vector cGET(Dataset ds) throws ConnectException, IOException, InterruptedException { PresContext pc; List dimseList; Vector datasetVector; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-MOVE is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) if ((pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelGET, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelGET, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context for StudyRootQueryRetrieveInformationModelMOVE SOP."); } // Get the Study Instance UID of the study to mode String suid = ds.getString(Tags.SOPInstanceUID); // Prepare info for logging String patName = ds.getString(Tags.PatientName); String patID = ds.getString(Tags.PatientID); String studyDate = ds.getString(Tags.StudyDate); String prompt = "Study[" + suid + "] from " + studyDate + " for Patient[" + patID + "]: " + patName; // log.info("Moving: " + prompt); // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command rqCmd = dof.newCommand(); // API doc: Command.initCMoveRQ(int msgID, String sopClassUID, int priority, String moveDest) rqCmd.initCGetRSP(assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelGET, priority); Dataset rqDs = dof.newDataset(); rqDs.putCS(Tags.QueryRetrieveLevel, getQueryRetrieveLevel(STUDY_LEVEL)); // Only Unique Key allowed in C-MOVE. PS 3.4 -C.2.2.1 Attribute Types rqDs.putUI(Tags.SOPInstanceUID, suid); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse moveRq = aFact.newDimse(pc.pcid(), rqCmd, rqDs); // Invoke active association with move request Dimse FutureRSP future = aassoc.invoke(moveRq); // Response to the C-MOVE request. // The result cannot be accessed until it has been set. Dimse moveRsp = future.get(); Command rspCmd = moveRsp.getCommand(); if (DEBUG) { StringWriter w = new StringWriter(); w.write("C-FIND RQ Identifier:\n"); keys.dumpDataset(w, null); log.debug(w.toString()); } // Invoke active association with find request Dimse // Response to the C-FIND request. // The result cannot be accessed until it has been set. // Get the list of found objects dimseList = future.listPending(); // >>>> Extract Dataset from Dimse datasetVector = new Vector(); // If no List of DIMSE objects was generated or it is empty return an empty Vector if (dimseList == null || dimseList.isEmpty()) { return datasetVector; } // Process all elements for (int i = 0; i < dimseList.size(); i++) { datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset()); if (((Dimse) dimseList.get(i)).getDataset() == null) System.out.println(" Dataset created succesffullyu "); } // PS 3.7 - 9.3.4 C-MOVE PROTOCOL, 9.3.4.2 C-MOVE-RSP int status = rspCmd.getStatus(); switch (status) { case 0x0000: // log.info("Moved: " + prompt); break; case 0xB000: log.error("One or more failures during move of " + prompt); break; default: log.error("Failed to move " + prompt + "\n\terror tstatus: " + Integer.toHexString(status)); break; } System.out.println("The move sise is : " + datasetVector.size()); return datasetVector; }
/** * Queries the archive for DICOM objects matching Attribute Keys defined in the loacal field * "keys". This field is set by the constructor out of the configuration parameters or by the * methods setQueryKeys(Configuration) and setQueryKeys(Dataset). See PS 3.4 - Annex C * QUERY/RETRIEVE SERVICE CLASS. * * <p>The method returns, when the result is received from the communication partner. * * @return the result of the cFIND as a Vector of Dataset objects each specifying one matching * DICOM object. If no matching objects are found an empty Vector is returned. * @throws ConnectException * @throws IOException */ public Vector cFIND() throws ConnectException, IOException, InterruptedException { List dimseList; Vector datasetVector; // An association must be active if (aassoc == null) { throw new ConnectException("No Association established"); } // Test, if Presentation Context for C-FIND is supported // API doc: Association.getAcceptedPresContext(String asuid, String tsuid) // UIDs.StudyRootQueryRetrieveInformationModelGET if ((pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelFIND, UIDs.ExplicitVRLittleEndian)) == null && (pc = aassoc .getAssociation() .getAcceptedPresContext( UIDs.StudyRootQueryRetrieveInformationModelFIND, UIDs.ImplicitVRLittleEndian)) == null) { throw new ConnectException( "Association does not support presentation context for StudyRootQueryRetrieveInformationModelFIND SOP."); } // New Cammand Set, see: DICOM Part 7: Message Exchange, 6.3.1 Command Set Structure Command rqCmd = dof.newCommand(); // API doc: Command.initCFindRQ(int msgID, String sopClassUID, int priority) rqCmd.initCFindRQ(assoc.nextMsgID(), UIDs.StudyRootQueryRetrieveInformationModelFIND, priority); // API doc: AssociationFactorynewDimse(int pcid, Command cmd, Dataset ds) // DIMSE (DICOM Message Service Element) ist ein Nachrichtendienst in DICOM Dimse findRq = aFact.newDimse(pc.pcid(), rqCmd, keys); if (DEBUG) { StringWriter w = new StringWriter(); w.write("C-FIND RQ Identifier:\n"); keys.dumpDataset(w, null); log.debug(w.toString()); } // Invoke active association with find request Dimse FutureRSP future = aassoc.invoke(findRq); // Response to the C-FIND request. // The result cannot be accessed until it has been set. Dimse findRsp = future.get(); // Get the list of found objects dimseList = future.listPending(); // >>>> Extract Dataset from Dimse datasetVector = new Vector(); // If no List of DIMSE objects was generated or it is empty return an empty Vector if (dimseList == null || dimseList.isEmpty()) { return datasetVector; } // Process all elements for (int i = 0; i < dimseList.size(); i++) { datasetVector.addElement(((Dimse) dimseList.get(i)).getDataset()); } return datasetVector; }
public void handleNotification(Notification notif, Object handback) { String spsuid = (String) notif.getUserData(); Dataset pps = DcmObjectFactory.getInstance().newDataset(); try { Dataset sps; GPWLManager gpwlmgr = getGPWLManager(); sps = gpwlmgr.getWorklistItem(spsuid); String ppsiuid = spsuid + ppsuidSuffix; String status = sps.getString(Tags.GPSPSStatus); pps.putCS(Tags.GPPPSStatus, status); pps.putUI(Tags.SOPInstanceUID, ppsiuid); Date now = new Date(); if ("IN PROGRESS".equals(status)) { try { getGPPPSManager().getGPPPS(ppsiuid); return; // avoid duplicate N_CREATE } catch (Exception e) { } pps.putSH(Tags.PPSID, "PPS" + ppsiuid.hashCode()); pps.putDA(Tags.PPSStartDate, now); pps.putTM(Tags.PPSStartTime, now); pps.putDA(Tags.PPSEndDate); pps.putTM(Tags.PPSEndTime); for (int i = 0; i < N_CREATE_TYPE2_ATTRS.length; i++) { pps.putXX(N_CREATE_TYPE2_ATTRS[i]); } pps.putAll(sps.subSet(N_CREATE_SPS_ATTRS)); copyCode( copyWorkitemCode, sps.getItem(Tags.ScheduledWorkitemCodeSeq), pps.putSQ(Tags.PerformedWorkitemCodeSeq)); copyCode( copyStationNameCode, sps.getItem(Tags.ScheduledStationNameCodeSeq), pps.putSQ(Tags.PerformedStationNameCodeSeq)); copyCode( copyStationClassCode, sps.getItem(Tags.ScheduledStationClassCodeSeq), pps.putSQ(Tags.PerformedStationClassCodeSeq)); copyCode( copyStationGeographicLocationCode, sps.getItem(Tags.ScheduledStationGeographicLocationCodeSeq), pps.putSQ(Tags.PerformedStationGeographicLocationCodeSeq)); copyCode( copyProcessingApplicationsCode, sps.getItem(Tags.ScheduledProcessingApplicationsCodeSeq), pps.putSQ(Tags.PerformedProcessingApplicationsCodeSeq)); } else if ("COMPLETED".equals(status) || "DISCONTINUED".equals(status)) { pps.putDA(Tags.PPSEndDate, now); pps.putTM(Tags.PPSEndTime, now); pps.putAll(gpwlmgr.getOutputInformation(spsuid)); } else { return; } } catch (Exception e) { log.error("Failed to access GP-SPS[" + spsuid + "]", e); return; } for (int i = 0; i < destAETs.length; i++) { PPSOrder order = new PPSOrder(pps, destAETs[i]); try { log.info("Scheduling " + order); jmsDelegate.queue(queueName, order, Message.DEFAULT_PRIORITY, 0L); } catch (Exception e) { log.error("Failed to schedule " + order, e); } } }
public static int maxDiffPixelData(File f1, File f2) throws IOException { int maxDiff = 0; InputStream in1 = new BufferedInputStream(new FileInputStream(f1)); try { InputStream in2 = new BufferedInputStream(new FileInputStream(f2)); try { DcmObjectFactory df = DcmObjectFactory.getInstance(); DcmParserFactory pf = DcmParserFactory.getInstance(); Dataset attrs1 = df.newDataset(); Dataset attrs2 = df.newDataset(); DcmParser p1 = pf.newDcmParser(in1); DcmParser p2 = pf.newDcmParser(in2); p1.setDcmHandler(attrs1.getDcmHandler()); p2.setDcmHandler(attrs2.getDcmHandler()); p1.parseDcmFile(FileFormat.DICOM_FILE, Tags.PixelData); p2.parseDcmFile(FileFormat.DICOM_FILE, Tags.PixelData); int samples = attrs1.getInt(Tags.SamplesPerPixel, 1); int frames = attrs1.getInt(Tags.NumberOfFrames, 1); int rows = attrs1.getInt(Tags.Rows, 1); int columns = attrs1.getInt(Tags.Columns, 1); int bitsAlloc = attrs1.getInt(Tags.BitsAllocated, 8); int bitsStored = attrs1.getInt(Tags.BitsStored, bitsAlloc); int bitsStored2 = attrs2.getInt(Tags.BitsStored, bitsAlloc); int pixelRepresentation1 = attrs1.getInt(Tags.PixelRepresentation, 0); int pixelRepresentation2 = attrs2.getInt(Tags.PixelRepresentation, 0); int frameLength = rows * columns * samples * bitsAlloc / 8; int pixelDataLength = frameLength * frames; if (pixelDataLength > p1.getReadLength() || pixelDataLength > p2.getReadLength()) { return Integer.MAX_VALUE; } byte[] b1 = new byte[BUFFER_SIZE]; byte[] b2 = new byte[BUFFER_SIZE]; byte lsb1 = 0, lsb2 = 0; int w1, w2, len, len2; int bitmask1 = 0xffff >>> (bitsAlloc - bitsStored); int bitmask2 = 0xffff >>> (bitsAlloc - bitsStored2); int signed1 = pixelRepresentation1 != 0 ? (-1 & ~bitmask1) >> 1 : 0; int signed2 = pixelRepresentation2 != 0 ? (-1 & ~bitmask2) >> 1 : 0; int pos = 0; while (pos < pixelDataLength) { len = in1.read(b1, 0, Math.min(pixelDataLength - pos, BUFFER_SIZE)); if (len < 0) // EOF return Integer.MAX_VALUE; int off = 0; while (off < len) { off += len2 = in2.read(b2, off, len - off); if (len2 < 0) // EOF return Integer.MAX_VALUE; } if (bitsAlloc == 8) for (int i = 0; i < len; i++, pos++) maxDiff = Math.max(maxDiff, Math.abs((b1[i] & 0xff) - (b2[i] & 0xff))); else { for (int i = 0; i < len; i++, pos++) // TODO assumes LE Byte Order if ((pos & 1) == 0) { lsb1 = b1[i]; lsb2 = b2[i]; } else { if (((w1 = ((b1[i] << 8) | (lsb1 & 0xff)) & bitmask1) & signed1) != 0) w1 |= signed1; if (((w2 = ((b2[i] << 8) | (lsb2 & 0xff)) & bitmask2) & signed2) != 0) w2 |= signed2; maxDiff = Math.max(maxDiff, Math.abs(w1 - w2)); } } } return maxDiff; } finally { in2.close(); } } finally { in1.close(); } }
/** * @author [email protected] * @version $Revision: 8056 $ $Date: 2008-11-12 14:31:15 +0100 (Wed, 12 Nov 2008) $ * @since 27.12.2005 * @ejb.bean name="PrivateManager" type="Stateless" view-type="remote" * jndi-name="ejb/PrivateManager" * @ejb.transaction-type type="Container" * @ejb.transaction type="Required" * @ejb.ejb-ref ejb-name="Patient" view-type="local" ref-name="ejb/Patient" * @ejb.ejb-ref ejb-name="Study" view-type="local" ref-name="ejb/Study" * @ejb.ejb-ref ejb-name="Series" view-type="local" ref-name="ejb/Series" * @ejb.ejb-ref ejb-name="Instance" view-type="local" ref-name="ejb/Instance" * @ejb.ejb-ref ejb-name="File" view-type="local" ref-name="ejb/File" * @ejb.ejb-ref ejb-name="PrivatePatient" view-type="local" ref-name="ejb/PrivatePatient" * @ejb.ejb-ref ejb-name="PrivateStudy" view-type="local" ref-name="ejb/PrivateStudy" * @ejb.ejb-ref ejb-name="PrivateSeries" view-type="local" ref-name="ejb/PrivateSeries" * @ejb.ejb-ref ejb-name="PrivateInstance" view-type="local" ref-name="ejb/PrivateInstance" * @ejb.ejb-ref ejb-name="PrivateFile" view-type="local" ref-name="ejb/PrivateFile" */ public abstract class PrivateManagerBean implements SessionBean { private static final int DELETED = 1; private PatientLocalHome patHome; private StudyLocalHome studyHome; private SeriesLocalHome seriesHome; private InstanceLocalHome instHome; private PrivatePatientLocalHome privPatHome; private PrivateStudyLocalHome privStudyHome; private PrivateSeriesLocalHome privSeriesHome; private PrivateInstanceLocalHome privInstHome; private PrivateFileLocalHome privFileHome; private static final DcmObjectFactory dof = DcmObjectFactory.getInstance(); private static Logger log = Logger.getLogger(PrivateManagerBean.class.getName()); public void setSessionContext(SessionContext arg0) throws EJBException, RemoteException { Context jndiCtx = null; try { jndiCtx = new InitialContext(); patHome = (PatientLocalHome) jndiCtx.lookup("java:comp/env/ejb/Patient"); studyHome = (StudyLocalHome) jndiCtx.lookup("java:comp/env/ejb/Study"); seriesHome = (SeriesLocalHome) jndiCtx.lookup("java:comp/env/ejb/Series"); instHome = (InstanceLocalHome) jndiCtx.lookup("java:comp/env/ejb/Instance"); privPatHome = (PrivatePatientLocalHome) jndiCtx.lookup("java:comp/env/ejb/PrivatePatient"); privStudyHome = (PrivateStudyLocalHome) jndiCtx.lookup("java:comp/env/ejb/PrivateStudy"); privSeriesHome = (PrivateSeriesLocalHome) jndiCtx.lookup("java:comp/env/ejb/PrivateSeries"); privInstHome = (PrivateInstanceLocalHome) jndiCtx.lookup("java:comp/env/ejb/PrivateInstance"); privFileHome = (PrivateFileLocalHome) jndiCtx.lookup("java:comp/env/ejb/PrivateFile"); } catch (NamingException e) { throw new EJBException(e); } finally { if (jndiCtx != null) { try { jndiCtx.close(); } catch (NamingException ignore) { } } } } public void unsetSessionContext() { patHome = null; studyHome = null; seriesHome = null; instHome = null; privPatHome = null; privStudyHome = null; privSeriesHome = null; privInstHome = null; privFileHome = null; } /** * @throws FinderException * @ejb.interface-method */ public void deletePrivateSeries(long series_pk) throws RemoteException, FinderException { try { PrivateSeriesLocal series = privSeriesHome.findByPrimaryKey(new Long(series_pk)); PrivateStudyLocal study = series.getStudy(); series.remove(); if (study.getSeries().isEmpty()) { PrivatePatientLocal pat = study.getPatient(); study.remove(); if (pat.getStudies().isEmpty()) { pat.remove(); } } } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** * @throws FinderException * @ejb.interface-method */ public Collection deletePrivateStudy(long study_pk) throws RemoteException, FinderException { try { PrivateStudyLocal study = privStudyHome.findByPrimaryKey(new Long(study_pk)); ArrayList files = null; PrivatePatientLocal pat = study.getPatient(); study.remove(); if (pat.getStudies().isEmpty()) { pat.remove(); } return files; } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public void deletePrivatePatient(long patient_pk) throws RemoteException { try { privPatHome.remove(new Long(patient_pk)); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** * @throws FinderException * @throws FinderException * @ejb.interface-method */ public void deletePrivateInstance(long instance_pk) throws RemoteException, FinderException { try { PrivateInstanceLocal instance = privInstHome.findByPrimaryKey(new Long(instance_pk)); PrivateSeriesLocal series = instance.getSeries(); instance.remove(); if (series.getInstances().isEmpty()) { PrivateStudyLocal study = series.getStudy(); series.remove(); if (study.getSeries().isEmpty()) { PrivatePatientLocal pat = study.getPatient(); study.remove(); if (pat.getStudies().isEmpty()) { pat.remove(); } } } } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** * @throws FinderException * @ejb.interface-method */ public void deletePrivateFile(long file_pk) throws RemoteException { try { privFileHome.remove(new Long(file_pk)); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** * @throws FinderException * @ejb.interface-method */ public void deletePrivateFiles(Collection fileDTOs) throws RemoteException { try { for (Iterator iter = fileDTOs.iterator(); iter.hasNext(); ) { privFileHome.remove(new Long(((FileDTO) iter.next()).getPk())); } } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public void deleteAll(int privateType) throws RemoteException { try { Collection c = privPatHome.findByPrivateType(privateType); for (Iterator iter = c.iterator(); iter.hasNext(); ) { privPatHome.remove(((PrivatePatientLocal) iter.next()).getPk()); } } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } } /** * Delete a list of instances, i.e., move them to trash bin * * @ejb.interface-method * @param iuids A list of instance uid * @param cascading True to delete the series/study if there's no instance/series * @return a collection of Dataset containing the actuall detetion information per study * @throws RemoteException */ public Collection moveInstancesToTrash(String[] iuids, boolean cascading) throws RemoteException { try { // These instances may belong to multiple studies, // although mostly they should be the same study Map mapStudies = new HashMap(); for (int i = 0; i < iuids.length; i++) { InstanceLocal instance = instHome.findBySopIuid(iuids[i]); SeriesLocal series = instance.getSeries(); StudyLocal study = series.getStudy(); if (!mapStudies.containsKey(study)) mapStudies.put(study, new HashMap()); Map mapSeries = (Map) mapStudies.get(study); if (!mapSeries.containsKey(series)) mapSeries.put(series, new ArrayList()); Collection colInstances = (Collection) mapSeries.get(series); colInstances.add(instance); } List dss = new ArrayList(); Iterator iter = mapStudies.keySet().iterator(); while (iter.hasNext()) { StudyLocal study = (StudyLocal) iter.next(); dss.add(getStudyMgtDataset(study, (Map) mapStudies.get(study))); Iterator iter2 = ((Map) mapStudies.get(study)).keySet().iterator(); while (iter2.hasNext()) { SeriesLocal series = (SeriesLocal) iter2.next(); List instances = (List) ((Map) mapStudies.get(study)).get(series); for (int i = 0; i < instances.size(); i++) { // Delete the instance now, i.e., move to trash bin, // becoming private instance getPrivateInstance((InstanceLocal) instances.get(i), DELETED, null); ((InstanceLocal) instances.get(i)).remove(); } if (series.getInstances().size() == 0 && cascading) { // Delete the series too since there's no instance left getPrivateSeries(series, DELETED, null, false); series.remove(); } else UpdateDerivedFieldsUtils.updateDerivedFieldsOf(series); } if (study.getSeries().size() == 0 && cascading) { // Delete the study too since there's no series left getPrivateStudy(study, DELETED, null, false); study.remove(); } else UpdateDerivedFieldsUtils.updateDerivedFieldsOf(study); } return dss; } catch (CreateException e) { throw new RemoteException(e.getMessage()); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public Dataset moveInstanceToTrash(long instance_pk) throws RemoteException { try { InstanceLocal instance = instHome.findByPrimaryKey(new Long(instance_pk)); Collection colInstance = new ArrayList(); colInstance.add(instance); SeriesLocal series = instance.getSeries(); Map mapSeries = new HashMap(); mapSeries.put(series, colInstance); Dataset ds = getStudyMgtDataset(series.getStudy(), mapSeries); getPrivateInstance(instance, DELETED, null); instance.remove(); UpdateDerivedFieldsUtils.updateDerivedFieldsOf(series); UpdateDerivedFieldsUtils.updateDerivedFieldsOf(series.getStudy()); return ds; } catch (CreateException e) { throw new RemoteException(e.getMessage()); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public Dataset moveSeriesToTrash(long series_pk) throws RemoteException { try { SeriesLocal series = seriesHome.findByPrimaryKey(new Long(series_pk)); StudyLocal study = series.getStudy(); Map mapSeries = new HashMap(); mapSeries.put(series, series.getInstances()); Dataset ds = getStudyMgtDataset(series.getStudy(), mapSeries); getPrivateSeries(series, DELETED, null, true); series.remove(); UpdateDerivedFieldsUtils.updateDerivedFieldsOf(study); return ds; } catch (CreateException e) { throw new RemoteException(e.getMessage()); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public Collection moveSeriesOfPPSToTrash(String ppsIUID, boolean removeEmptyParents) throws RemoteException { Collection result = new ArrayList(); // FIXME: NOT IN USE try { Object[] ppsSeries = seriesHome.findByPpsIuid(ppsIUID).toArray(); if (ppsSeries.length > 0) { SeriesLocal series = null; StudyLocal study = ((SeriesLocal) ppsSeries[0]).getStudy(); for (int i = 0; i < ppsSeries.length; i++) { series = (SeriesLocal) ppsSeries[i]; getPrivateSeries(series, DELETED, null, true); series.remove(); } if (removeEmptyParents && study.getSeries().isEmpty()) { study.remove(); } else { UpdateDerivedFieldsUtils.updateDerivedFieldsOf(study); } } } catch (FinderException ignore) { } catch (Exception e) { throw new RemoteException(e.getMessage()); } return result; } /** @ejb.interface-method */ public Dataset moveStudyToTrash(String iuid) throws RemoteException { try { StudyLocal study = studyHome.findByStudyIuid(iuid); if (study != null) return moveStudyToTrash(study.getPk().longValue()); else return null; } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public Dataset moveStudyToTrash(long study_pk) throws RemoteException { try { StudyLocal study = studyHome.findByPrimaryKey(new Long(study_pk)); Dataset ds = getStudyMgtDataset(study, null); getPrivateStudy(study, DELETED, null, true); study.remove(); return ds; } catch (CreateException e) { throw new RemoteException(e.getMessage()); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } /** @ejb.interface-method */ public Collection movePatientToTrash(long pat_pk) throws RemoteException { try { PatientLocal patient = patHome.findByPrimaryKey(new Long(pat_pk)); Collection col = patient.getStudies(); Collection result = new ArrayList(); for (Iterator iter = col.iterator(); iter.hasNext(); ) { result.add(getStudyMgtDataset((StudyLocal) iter.next(), null)); } Dataset ds = patient.getAttributes(true); getPrivatePatient(patient, DELETED, true); patient.remove(); if (result.isEmpty()) result.add(ds); return result; } catch (CreateException e) { throw new RemoteException(e.getMessage()); } catch (EJBException e) { throw new RemoteException(e.getMessage()); } catch (FinderException e) { throw new RemoteException(e.getMessage()); } catch (RemoveException e) { throw new RemoteException(e.getMessage()); } } private PrivateInstanceLocal getPrivateInstance( InstanceLocal instance, int type, PrivateSeriesLocal privSeries) throws FinderException, CreateException { Collection col = privInstHome.findBySopIuid(type, instance.getSopIuid()); PrivateInstanceLocal privInstance; if (col.isEmpty()) { if (privSeries == null) { privSeries = getPrivateSeries(instance.getSeries(), type, null, false); } privInstance = privInstHome.create(type, instance.getAttributes(true), privSeries); } else { privInstance = (PrivateInstanceLocal) col.iterator().next(); } Object[] files = instance.getFiles().toArray(); FileLocal file; for (int i = 0; i < files.length; i++) { file = (FileLocal) files[i]; privFileHome.create( file.getFilePath(), file.getFileTsuid(), file.getFileSize(), file.getFileMd5(), file.getFileStatus(), privInstance, file.getFileSystem()); try { file.remove(); } catch (Exception x) { log.warn("Can not remove File record:" + file, x); } } return privInstance; } private PrivateSeriesLocal getPrivateSeries( SeriesLocal series, int type, PrivateStudyLocal privStudy, boolean includeInstances) throws FinderException, CreateException { Collection col = privSeriesHome.findBySeriesIuid(type, series.getSeriesIuid()); PrivateSeriesLocal privSeries; if (col.isEmpty()) { if (privStudy == null) { privStudy = getPrivateStudy(series.getStudy(), type, null, false); } privSeries = privSeriesHome.create(type, series.getAttributes(true), privStudy); } else { privSeries = (PrivateSeriesLocal) col.iterator().next(); } if (includeInstances) { for (Iterator iter = series.getInstances().iterator(); iter.hasNext(); ) { getPrivateInstance( (InstanceLocal) iter.next(), type, privSeries); // move also all instances } } return privSeries; } private PrivateStudyLocal getPrivateStudy( StudyLocal study, int type, PrivatePatientLocal privPat, boolean includeSeries) throws FinderException, CreateException { Collection col = privStudyHome.findByStudyIuid(type, study.getStudyIuid()); PrivateStudyLocal privStudy; if (col.isEmpty()) { if (privPat == null) { privPat = getPrivatePatient(study.getPatient(), type, false); } privStudy = privStudyHome.create(type, study.getAttributes(true), privPat); } else { privStudy = (PrivateStudyLocal) col.iterator().next(); } if (includeSeries) { for (Iterator iter = study.getSeries().iterator(); iter.hasNext(); ) { getPrivateSeries( (SeriesLocal) iter.next(), type, privStudy, true); // move also all instances } } return privStudy; } private PrivatePatientLocal getPrivatePatient( PatientLocal patient, int type, boolean includeStudies) throws FinderException, CreateException { Collection col = privPatHome.findByPatientIdWithIssuer( type, patient.getPatientId(), patient.getIssuerOfPatientId()); PrivatePatientLocal privPat; if (col.isEmpty()) { privPat = privPatHome.create(type, patient.getAttributes(true)); } else { privPat = (PrivatePatientLocal) col.iterator().next(); } if (includeStudies) { for (Iterator iter = patient.getStudies().iterator(); iter.hasNext(); ) { getPrivateStudy((StudyLocal) iter.next(), type, privPat, true); // move // also // all // instances } } return privPat; } private Dataset getStudyMgtDataset(StudyLocal study, Map mapSeries) { Dataset ds = dof.newDataset(); ds.putUI(Tags.StudyInstanceUID, study.getStudyIuid()); ds.putOB(PrivateTags.StudyPk, Convert.toBytes(study.getPk().longValue())); ds.putSH(Tags.AccessionNumber, study.getAccessionNumber()); ds.putLO(Tags.PatientID, study.getPatient().getPatientId()); ds.putLO(Tags.IssuerOfPatientID, study.getPatient().getIssuerOfPatientId()); ds.putPN(Tags.PatientName, study.getPatient().getPatientName()); log.debug("getStudyMgtDataset: studyIUID:" + study.getStudyIuid()); DcmElement refSeriesSeq = ds.putSQ(Tags.RefSeriesSeq); Iterator iter = (mapSeries == null) ? study.getSeries().iterator() : mapSeries.keySet().iterator(); while (iter.hasNext()) { SeriesLocal sl = (SeriesLocal) iter.next(); Dataset dsSer = refSeriesSeq.addNewItem(); dsSer.putUI(Tags.SeriesInstanceUID, sl.getSeriesIuid()); Collection instances = (mapSeries == null) ? sl.getInstances() : (Collection) mapSeries.get(sl); Iterator iter2 = instances.iterator(); DcmElement refSopSeq = null; if (iter2.hasNext()) refSopSeq = dsSer.putSQ(Tags.RefSOPSeq); while (iter2.hasNext()) { InstanceLocal il = (InstanceLocal) iter2.next(); Dataset dsInst = refSopSeq.addNewItem(); dsInst.putUI(Tags.RefSOPClassUID, il.getSopCuid()); dsInst.putUI(Tags.RefSOPInstanceUID, il.getSopIuid()); dsInst.putAE(Tags.RetrieveAET, il.getRetrieveAETs()); } } if (log.isDebugEnabled()) { log.debug("return StgMgtDataset:"); log.debug(ds); } return ds; } }