/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.dicom.explorer; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Graphics2D; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Properties; import javax.media.jai.PlanarImage; import javax.media.jai.operator.SubsampleAverageDescriptor; import javax.swing.BoxLayout; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JSlider; import javax.swing.border.TitledBorder; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.data.VR; import org.dcm4che3.media.DicomDirWriter; import org.dcm4che3.media.RecordType; import org.dcm4che3.util.UIDUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.explorer.ObservableEvent; import org.weasis.core.api.gui.util.AbstractItemDialogPage; import org.weasis.core.api.gui.util.AppProperties; import org.weasis.core.api.gui.util.FileFormatFilter; import org.weasis.core.api.gui.util.JMVUtils; import org.weasis.core.api.image.util.ImageFiler; import org.weasis.core.api.media.data.MediaElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.media.data.Thumbnail; import org.weasis.core.api.util.FileUtil; import org.weasis.core.api.util.StringUtil; import org.weasis.core.api.util.StringUtil.Suffix; import org.weasis.core.ui.model.GraphicModel; import org.weasis.core.ui.serialize.XmlSerializer; import org.weasis.dicom.codec.DcmMediaReader; import org.weasis.dicom.codec.DicomImageElement; import org.weasis.dicom.codec.DicomSeries; import org.weasis.dicom.codec.FileExtractor; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.explorer.internal.Activator; import org.weasis.dicom.explorer.pr.PrSerializer; @SuppressWarnings("serial") public class LocalExport extends AbstractItemDialogPage implements ExportDicom { private static final Logger LOGGER = LoggerFactory.getLogger(LocalExport.class); public static final String LAST_DIR = "lastExportDir";//$NON-NLS-1$ public static final String INC_DICOMDIR = "exp.include.dicomdir";//$NON-NLS-1$ public static final String KEEP_INFO_DIR = "exp.keep.dir.name";//$NON-NLS-1$ public static final String IMG_QUALITY = "exp.img.quality";//$NON-NLS-1$ public static final String HEIGHT_BITS = "exp.8bis";//$NON-NLS-1$ public static final String CD_COMPATIBLE = "exp.cd";//$NON-NLS-1$ public static final String[] EXPORT_FORMAT = { "DICOM", "DICOM ZIP", "JPEG", "PNG", "TIFF" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ private final DicomModel dicomModel; private JLabel lblImportAFolder; private File outputFolder; private JPanel panel; private final ExportTree exportTree; private JComboBox<String> comboBoxImgFormat; private JButton btnNewButton; public LocalExport(DicomModel dicomModel, CheckTreeModel treeModel) { super(Messages.getString("LocalExport.local_dev")); //$NON-NLS-1$ this.dicomModel = dicomModel; this.exportTree = new ExportTree(treeModel); setComponentPosition(0); initGUI(); } public void initGUI() { setLayout(new BorderLayout()); panel = new JPanel(); FlowLayout flowLayout = (FlowLayout) panel.getLayout(); flowLayout.setAlignment(FlowLayout.LEFT); lblImportAFolder = new JLabel(Messages.getString("LocalExport.exp") + StringUtil.COLON); //$NON-NLS-1$ panel.add(lblImportAFolder); comboBoxImgFormat = new JComboBox<>(new DefaultComboBoxModel<>(EXPORT_FORMAT)); panel.add(comboBoxImgFormat); add(panel, BorderLayout.NORTH); btnNewButton = new JButton(Messages.getString("LocalExport.options")); //$NON-NLS-1$ btnNewButton.addActionListener(e -> showExportingOptions()); panel.add(btnNewButton); add(exportTree, BorderLayout.CENTER); } protected void showExportingOptions() { Properties pref = Activator.IMPORT_EXPORT_PERSISTENCE; final JCheckBox boxKeepNames = new JCheckBox(Messages.getString("LocalExport.keep_dir"), //$NON-NLS-1$ Boolean.valueOf(pref.getProperty(KEEP_INFO_DIR, "true"))); //$NON-NLS-1$ Object seltected = comboBoxImgFormat.getSelectedItem(); if (EXPORT_FORMAT[0].equals(seltected)) { final JCheckBox box1 = new JCheckBox(Messages.getString("LocalExport.inc_dicomdir"), //$NON-NLS-1$ Boolean.valueOf(pref.getProperty(INC_DICOMDIR, Boolean.TRUE.toString()))); final JCheckBox box2 = new JCheckBox(Messages.getString("LocalExport.cd_folders"), //$NON-NLS-1$ Boolean.valueOf(pref.getProperty(CD_COMPATIBLE, Boolean.FALSE.toString()))); box2.setEnabled(box1.isSelected()); boxKeepNames.setEnabled(!box1.isSelected()); box1.addActionListener(e -> { boxKeepNames.setEnabled(!box1.isSelected()); box2.setEnabled(box1.isSelected()); }); Object[] options = { box1, box2, boxKeepNames }; int response = JOptionPane.showOptionDialog(this, options, Messages.getString("LocalExport.export_message"), //$NON-NLS-1$ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null); if (response == JOptionPane.OK_OPTION) { pref.setProperty(INC_DICOMDIR, String.valueOf(box1.isSelected())); pref.setProperty(KEEP_INFO_DIR, String.valueOf(boxKeepNames.isSelected())); pref.setProperty(CD_COMPATIBLE, String.valueOf(box2.isSelected())); } } else if (EXPORT_FORMAT[1].equals(seltected)) { // No option } else if (EXPORT_FORMAT[2].equals(seltected)) { final JSlider slider = new JSlider(0, 100, StringUtil.getInteger(pref.getProperty(IMG_QUALITY, null), 80)); final JPanel palenSlider1 = new JPanel(); palenSlider1.setLayout(new BoxLayout(palenSlider1, BoxLayout.Y_AXIS)); palenSlider1.setBorder(new TitledBorder( Messages.getString("LocalExport.jpeg_quality") + StringUtil.COLON_AND_SPACE + slider.getValue())); //$NON-NLS-1$ slider.setPaintTicks(true); slider.setSnapToTicks(false); slider.setMajorTickSpacing(10); JMVUtils.setPreferredWidth(slider, 145, 145); palenSlider1.add(slider); slider.addChangeListener(e -> { JSlider source = (JSlider) e.getSource(); ((TitledBorder) palenSlider1.getBorder()) .setTitle(Messages.getString("LocalExport.jpeg_quality") + source.getValue()); //$NON-NLS-1$ palenSlider1.repaint(); }); Object[] options = { palenSlider1, boxKeepNames }; int response = JOptionPane.showOptionDialog(this, options, Messages.getString("LocalExport.export_message"), //$NON-NLS-1$ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null); if (response == JOptionPane.OK_OPTION) { pref.setProperty(IMG_QUALITY, String.valueOf(slider.getValue())); pref.setProperty(KEEP_INFO_DIR, String.valueOf(boxKeepNames.isSelected())); } } else if (EXPORT_FORMAT[3].equals(seltected)) { Object[] options = { boxKeepNames }; int response = JOptionPane.showOptionDialog(this, options, Messages.getString("LocalExport.export_message"), //$NON-NLS-1$ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null); if (response == JOptionPane.OK_OPTION) { pref.setProperty(KEEP_INFO_DIR, String.valueOf(boxKeepNames.isSelected())); } } else if (EXPORT_FORMAT[4].equals(seltected)) { final JCheckBox box1 = new JCheckBox(Messages.getString("LocalExport.tiff_sup_8bits"), //$NON-NLS-1$ Boolean.valueOf(pref.getProperty(HEIGHT_BITS, "false"))); //$NON-NLS-1$ Object[] options = { box1, boxKeepNames }; int response = JOptionPane.showOptionDialog(this, options, Messages.getString("LocalExport.export_message"), //$NON-NLS-1$ JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null); if (response == JOptionPane.OK_OPTION) { pref.setProperty(HEIGHT_BITS, String.valueOf(box1.isSelected())); pref.setProperty(KEEP_INFO_DIR, String.valueOf(boxKeepNames.isSelected())); } } } public void browseImgFile(String format) { String targetDirectoryPath = Activator.IMPORT_EXPORT_PERSISTENCE.getProperty(LAST_DIR, "");//$NON-NLS-1$ boolean isSaveFileMode = EXPORT_FORMAT[1].equals(format); JFileChooser fileChooser = new JFileChooser(targetDirectoryPath); if (isSaveFileMode) { fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY); fileChooser.setAcceptAllFileFilterUsed(false); FileFormatFilter filter = new FileFormatFilter("zip", "ZIP"); //$NON-NLS-1$ //$NON-NLS-2$ fileChooser.addChoosableFileFilter(filter); fileChooser.setFileFilter(filter); } else { /** * Idea is to show all the files in the directories to give the user some context, but only directories * should be accepted as selections. As the effect is L&F dependent, consider using DIRECTORIES_ONLY on * platforms that already meet your UI requirements. Empirically, it's platform-dependent, with files * appearing gray in all supported L&Fs on Mac OS X. <br> * Disabling file selection may be annoying. A solution is just to allow the user to select either a file or * a directory and if the user select a file just use the directory where that file is located. */ if (System.getProperty("os.name").startsWith("Mac OS X")) { //$NON-NLS-1$ //$NON-NLS-2$ fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); } else { fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); } } fileChooser.setMultiSelectionEnabled(false); // Set default selection name to enable save button if (StringUtil.hasText(targetDirectoryPath)) { File targetFile = new File(targetDirectoryPath); if (targetFile.exists()) { if (targetFile.isFile()) { fileChooser.setSelectedFile(targetFile); } else if (targetFile.isDirectory()) { String newExportSelectionName = Messages.getString("LocalExport.newExportSelectionName"); //$NON-NLS-1$ fileChooser.setSelectedFile(new File(newExportSelectionName)); } } } File selectedFile; if (fileChooser.showSaveDialog(this) != JFileChooser.APPROVE_OPTION || (selectedFile = fileChooser.getSelectedFile()) == null) { outputFolder = null; return; } else { if (isSaveFileMode) { outputFolder = ".zip".equals(FileUtil.getExtension(selectedFile.getName())) ? selectedFile //$NON-NLS-1$ : new File(selectedFile + ".zip"); //$NON-NLS-1$ } else { outputFolder = selectedFile.isDirectory() ? selectedFile : selectedFile.getParentFile(); } Activator.IMPORT_EXPORT_PERSISTENCE.setProperty(LAST_DIR, outputFolder.isDirectory() ? outputFolder.getPath() : outputFolder.getParent()); } } @Override public void closeAdditionalWindow() { // Do nothing } @Override public void resetoDefaultValues() { // Do nothing } @Override public void exportDICOM(final CheckTreeModel model, JProgressBar info) throws IOException { final String format = (String) comboBoxImgFormat.getSelectedItem(); browseImgFile(format); if (outputFolder != null) { final File exportDir = outputFolder.getCanonicalFile(); final ExplorerTask<Boolean, String> task = new ExplorerTask<Boolean, String>(Messages.getString("LocalExport.exporting"), false) { //$NON-NLS-1$ @Override protected Boolean doInBackground() throws Exception { dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.LOADING_START, dicomModel, null, this)); if (EXPORT_FORMAT[0].equals(format)) { writeDicom(this, exportDir, model, false); } else if (EXPORT_FORMAT[1].equals(format)) { writeDicom(this, exportDir, model, true); } else { writeOther(this, exportDir, model, format); } return true; } @Override protected void done() { dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.LOADING_STOP, dicomModel, null, this)); } }; task.execute(); } } private static String getinstanceFileName(MediaElement img) { Integer instance = TagD.getTagValue(img, Tag.InstanceNumber, Integer.class); if (instance != null) { String val = instance.toString(); if (val.length() < 5) { char[] chars = new char[5 - val.length()]; for (int i = 0; i < chars.length; i++) { chars[i] = '0'; } return new String(chars) + val; } else { return val; } } return TagD.getTagValue(img, Tag.SOPInstanceUID, String.class); } private void writeOther(ExplorerTask task, File exportDir, CheckTreeModel model, String format) { Properties pref = Activator.IMPORT_EXPORT_PERSISTENCE; boolean keepNames = Boolean.parseBoolean(pref.getProperty(KEEP_INFO_DIR, Boolean.TRUE.toString())); int jpegQuality = StringUtil.getInteger(pref.getProperty(IMG_QUALITY, null), 80); boolean more8bits = Boolean.parseBoolean(pref.getProperty(HEIGHT_BITS, Boolean.FALSE.toString())); try { synchronized (model) { ArrayList<String> seriesGph = new ArrayList<>(); TreePath[] paths = model.getCheckingPaths(); for (TreePath treePath : paths) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); if (node.getUserObject() instanceof Series) { MediaSeries<?> s = (MediaSeries<?>) node.getUserObject(); if (JMVUtils.getNULLtoFalse(s.getTagValue(TagW.ObjectToSave))) { Series<?> series = (Series<?>) s.getTagValue(CheckTreeModel.SourceSeriesForPR); if (series != null) { seriesGph.add((String) series.getTagValue(TagD.get(Tag.SeriesInstanceUID))); } } } } for (TreePath treePath : paths) { if (task.isCancelled()) { return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); if (node.getUserObject() instanceof DicomImageElement) { DicomImageElement img = (DicomImageElement) node.getUserObject(); // Get instance number instead SOPInstanceUID to handle multiframe String instance = getinstanceFileName(img); if (!keepNames) { instance = makeFileIDs(instance); } String path = buildPath(img, keepNames, node); File destinationDir = new File(exportDir, path); destinationDir.mkdirs(); RenderedImage image = img.getImage(null); if (image != null && !more8bits) { image = img.getRenderedImage(image); } if (image != null) { File destinationFile = new File(destinationDir, instance + getExtension(format)); if (EXPORT_FORMAT[3].equals(format)) { ImageFiler.writePNG(destinationFile, image); } else if (EXPORT_FORMAT[4].equals(format)) { ImageFiler.writeTIFF(destinationFile, image, false, false, false); } else { ImageFiler.writeJPG(destinationFile, image, jpegQuality / 100.0f); } if (seriesGph.contains(img.getTagValue(TagD.get(Tag.SeriesInstanceUID)))) { XmlSerializer.writePresentation(img, destinationFile); } } else { LOGGER.error("Cannot export DICOM file to {}: {}", format, //$NON-NLS-1$ img.getFileCache().getOriginalFile()); } // Prevent to many files open on Linux (Ubuntu => 1024) and close image stream img.removeImageFromCache(); } else if (node.getUserObject() instanceof MediaElement && node.getUserObject() instanceof FileExtractor) { MediaElement dcm = (MediaElement) node.getUserObject(); File fileSrc = ((FileExtractor) dcm).getExtractFile(); if (fileSrc != null) { // Get instance number instead SOPInstanceUID to handle multiframe String instance = getinstanceFileName(dcm); if (!keepNames) { instance = makeFileIDs(instance); } String path = buildPath(dcm, keepNames, node); File destinationDir = new File(exportDir, path); destinationDir.mkdirs(); File destinationFile = new File(destinationDir, instance + FileUtil.getExtension(fileSrc.getName())); FileUtil.nioCopyFile(fileSrc, destinationFile); } } } } } catch (Exception e) { LOGGER.error("Cannot extract media from DICOM", e); //$NON-NLS-1$ } } private static String getExtension(String format) { if (EXPORT_FORMAT[3].equals(format)) { return ".png"; //$NON-NLS-1$ } else if (EXPORT_FORMAT[4].equals(format)) { return ".tif"; //$NON-NLS-1$ } return ".jpg"; //$NON-NLS-1$ } private void writeDicom(ExplorerTask task, File exportDir, CheckTreeModel model, boolean zipFile) throws IOException { boolean keepNames; boolean writeDicomdir; boolean cdCompatible; File writeDir; if (zipFile) { keepNames = false; writeDicomdir = true; cdCompatible = true; writeDir = FileUtil.createTempDir(AppProperties.buildAccessibleTempDirectory("tmp", "zip")); //$NON-NLS-1$ //$NON-NLS-2$ } else { Properties pref = Activator.IMPORT_EXPORT_PERSISTENCE; writeDicomdir = Boolean.valueOf(pref.getProperty(INC_DICOMDIR, "true"));//$NON-NLS-1$ keepNames = writeDicomdir ? false : Boolean.valueOf(pref.getProperty(KEEP_INFO_DIR, "true"));//$NON-NLS-1$ cdCompatible = Boolean.valueOf(pref.getProperty(CD_COMPATIBLE, "false"));//$NON-NLS-1$ writeDir = exportDir; } DicomDirWriter writer = null; try { if (writeDicomdir) { File dcmdirFile = new File(writeDir, "DICOMDIR"); //$NON-NLS-1$ writer = DicomDirLoader.open(dcmdirFile); } synchronized (model) { ArrayList<String> uids = new ArrayList<>(); TreePath[] paths = model.getCheckingPaths(); for (TreePath treePath : paths) { if (task.isCancelled()) { return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent(); if (node.getUserObject() instanceof DicomImageElement) { DicomImageElement img = (DicomImageElement) node.getUserObject(); String iuid = TagD.getTagValue(img, Tag.SOPInstanceUID, String.class); int index = uids.indexOf(iuid); if (index == -1) { uids.add(iuid); } else { // Write only once the file for multiframe continue; } if (!keepNames) { iuid = makeFileIDs(iuid); } String path = buildPath(img, keepNames, writeDicomdir, cdCompatible, node); File destinationDir = new File(writeDir, path); destinationDir.mkdirs(); File destinationFile = new File(destinationDir, iuid); if (img.saveToFile(destinationFile)) { writeInDicomDir(writer, img, node, iuid, destinationFile); } else { LOGGER.error("Cannot export DICOM file: {}", img.getFileCache().getOriginalFile()); //$NON-NLS-1$ } } else if (node.getUserObject() instanceof MediaElement) { MediaElement dcm = (MediaElement) node.getUserObject(); String iuid = TagD.getTagValue(dcm, Tag.SOPInstanceUID, String.class); if (!keepNames) { iuid = makeFileIDs(iuid); } String path = buildPath(dcm, keepNames, writeDicomdir, cdCompatible, node); File destinationDir = new File(writeDir, path); destinationDir.mkdirs(); File destinationFile = new File(destinationDir, iuid); if (dcm.saveToFile(destinationFile)) { writeInDicomDir(writer, dcm, node, iuid, destinationFile); } } else if (node.getUserObject() instanceof Series) { MediaSeries<?> s = (MediaSeries<?>) node.getUserObject(); if (JMVUtils.getNULLtoFalse(s.getTagValue(TagW.ObjectToSave))) { Series<?> series = (Series<?>) s.getTagValue(CheckTreeModel.SourceSeriesForPR); if (series != null) { String seriesInstanceUID = UIDUtils.createUID(); for (MediaElement dcm : series.getMedias(null, null)) { GraphicModel grModel = (GraphicModel) dcm.getTagValue(TagW.PresentationModel); if (grModel != null && grModel.hasSerializableGraphics()) { String path = buildPath(dcm, keepNames, writeDicomdir, cdCompatible, node); buildAndWritePR(dcm, keepNames, new File(writeDir, path), writer, node, seriesInstanceUID); } } } } } } } } catch (IOException e) { throw e; } catch (Exception e) { LOGGER.error("Cannot export DICOM", e); //$NON-NLS-1$ } finally { if (writer != null) { // Commit DICOMDIR changes and close the file writer.close(); } } if (zipFile) { try { FileUtil.zip(writeDir, exportDir); } catch (Exception e) { LOGGER.error("Cannot export DICOM ZIP file: {}", exportDir, e); //$NON-NLS-1$ } finally { FileUtil.recursiveDelete(writeDir); } } } public static Attributes buildAndWritePR(MediaElement img, boolean keepNames, File destinationDir, DicomDirWriter writer, DefaultMutableTreeNode node, String seriesInstanceUID) { Attributes imgAttributes = img.getMediaReader() instanceof DcmMediaReader ? ((DcmMediaReader) img.getMediaReader()).getDicomObject() : null; if (imgAttributes != null) { GraphicModel grModel = (GraphicModel) img.getTagValue(TagW.PresentationModel); if (grModel != null && grModel.hasSerializableGraphics()) { String prUid = UIDUtils.createUID(); File outputFile = new File(destinationDir, keepNames ? prUid : makeFileIDs(prUid)); destinationDir.mkdirs(); Attributes prAttributes = PrSerializer.writePresentation(grModel, imgAttributes, outputFile, seriesInstanceUID, prUid); if (prAttributes != null) { try { writeInDicomDir(writer, prAttributes, node, outputFile.getName(), outputFile); } catch (IOException e) { LOGGER.error("Writing DICOMDIR", e); //$NON-NLS-1$ } } } } return imgAttributes; } public static String buildPath(MediaElement img, boolean keepNames, boolean writeDicomdir, boolean cdCompatible, DefaultMutableTreeNode node) { StringBuilder buffer = new StringBuilder(); // Cannot keep folders names with DICOMDIR (could be not valid) if (keepNames && !writeDicomdir) { TreeNode[] objects = node.getPath(); if (objects.length > 2) { for (int i = 1; i < objects.length - 1; i++) { buffer.append(buildFolderName(objects[i].toString(), 30)); buffer.append(File.separator); } } } else { if (cdCompatible) { buffer.append("DICOM"); //$NON-NLS-1$ buffer.append(File.separator); } buffer.append(makeFileIDs((String) img.getTagValue(TagW.PatientPseudoUID))); buffer.append(File.separator); buffer.append(makeFileIDs(TagD.getTagValue(img, Tag.StudyInstanceUID, String.class))); buffer.append(File.separator); buffer.append(makeFileIDs(TagD.getTagValue(img, Tag.SeriesInstanceUID, String.class))); } return buffer.toString(); } public static String buildPath(MediaElement img, boolean keepNames, DefaultMutableTreeNode node) { StringBuilder buffer = new StringBuilder(); if (keepNames) { TreeNode[] objects = node.getPath(); if (objects.length > 3) { buffer.append(buildFolderName(objects[1].toString(), 30)); buffer.append(File.separator); buffer.append(buildFolderName(objects[2].toString(), 30)); buffer.append(File.separator); buffer.append(buildFolderName(objects[3].toString(), 25)); buffer.append('-'); // Hash of UID to guaranty the unique behavior of the name. buffer.append(makeFileIDs(TagD.getTagValue(img, Tag.SeriesInstanceUID, String.class))); } } else { buffer.append(makeFileIDs((String) img.getTagValue(TagW.PatientPseudoUID))); buffer.append(File.separator); buffer.append(makeFileIDs(TagD.getTagValue(img, Tag.StudyInstanceUID, String.class))); buffer.append(File.separator); buffer.append(makeFileIDs(TagD.getTagValue(img, Tag.SeriesInstanceUID, String.class))); } return buffer.toString(); } private static String buildFolderName(String str, int length) { String value = FileUtil.getValidFileNameWithoutHTML(str); return StringUtil.getTruncatedString(value, length, Suffix.NO); } private static boolean writeInDicomDir(DicomDirWriter writer, MediaElement img, DefaultMutableTreeNode node, String iuid, File destinationFile) throws IOException { if (writer != null) { if (!(img.getMediaReader() instanceof DcmMediaReader) || ((DcmMediaReader) img.getMediaReader()).getDicomObject() == null) { LOGGER.error("Cannot export DICOM file: ", img.getFileCache().getOriginalFile()); //$NON-NLS-1$ return false; } return writeInDicomDir(writer, ((DcmMediaReader) img.getMediaReader()).getDicomObject(), node, iuid, destinationFile); } return false; } private static boolean writeInDicomDir(DicomDirWriter writer, Attributes dataset, DefaultMutableTreeNode node, String iuid, File destinationFile) throws IOException { if (writer != null && dataset != null) { Attributes fmi = dataset.createFileMetaInformation(UID.ImplicitVRLittleEndian); String miuid = fmi.getString(Tag.MediaStorageSOPInstanceUID, null); String pid = dataset.getString(Tag.PatientID, null); String styuid = dataset.getString(Tag.StudyInstanceUID, null); String seruid = dataset.getString(Tag.SeriesInstanceUID, null); if (styuid != null && seruid != null) { if (pid == null) { pid = styuid; dataset.setString(Tag.PatientID, VR.LO, pid); } Attributes patRec = writer.findPatientRecord(pid); if (patRec == null) { patRec = DicomDirLoader.RecordFactory.createRecord(RecordType.PATIENT, null, dataset, null, null); writer.addRootDirectoryRecord(patRec); } Attributes studyRec = writer.findStudyRecord(patRec, styuid); if (studyRec == null) { studyRec = DicomDirLoader.RecordFactory.createRecord(RecordType.STUDY, null, dataset, null, null); writer.addLowerDirectoryRecord(patRec, studyRec); } Attributes seriesRec = writer.findSeriesRecord(studyRec, seruid); if (seriesRec == null) { seriesRec = DicomDirLoader.RecordFactory.createRecord(RecordType.SERIES, null, dataset, null, null); /* * Icon Image Sequence (0088,0200).This Icon Image is representative of the Series. It may or may * not correspond to one of the images of the Series. */ if (seriesRec != null && node.getParent() instanceof DefaultMutableTreeNode) { Object userObject = ((DefaultMutableTreeNode) node.getParent()).getUserObject(); if (userObject instanceof DicomSeries) { DicomImageElement midImage = ((DicomSeries) userObject).getMedia(MediaSeries.MEDIA_POSITION.MIDDLE, null, null); Attributes iconItem = mkIconItem(midImage); if (iconItem != null) { seriesRec.newSequence(Tag.IconImageSequence, 1).add(iconItem); } } } writer.addLowerDirectoryRecord(studyRec, seriesRec); } Attributes instRec; if (writer.findLowerInstanceRecord(seriesRec, false, iuid) == null) { instRec = DicomDirLoader.RecordFactory.createRecord(dataset, fmi, writer.toFileIDs(destinationFile)); writer.addLowerDirectoryRecord(seriesRec, instRec); } } else { if (writer.findRootInstanceRecord(false, miuid) == null) { Attributes instRec = DicomDirLoader.RecordFactory.createRecord(dataset, fmi, writer.toFileIDs(destinationFile)); writer.addRootDirectoryRecord(instRec); } } } return true; } public static String makeFileIDs(String uid) { if (uid != null) { return Integer.toHexString(uid.hashCode()); } return null; } public static Attributes mkIconItem(DicomImageElement image) { if (image == null) { return null; } BufferedImage thumbnail = null; PlanarImage imgPl = image.getImage(null); if (imgPl != null) { RenderedImage img = image.getRenderedImage(imgPl); final double scale = Math.min(128 / (double) img.getHeight(), 128 / (double) img.getWidth()); final PlanarImage thumb = scale < 1.0 ? SubsampleAverageDescriptor.create(img, scale, scale, Thumbnail.DownScaleQualityHints).getRendering() : PlanarImage.wrapRenderedImage(img); thumbnail = thumb.getAsBufferedImage(); } // Prevent to many files open on Linux (Ubuntu => 1024) and close image stream image.removeImageFromCache(); if (thumbnail == null) { return null; } int w = thumbnail.getWidth(); int h = thumbnail.getHeight(); String pmi = TagD.getTagValue(image, Tag.PhotometricInterpretation, String.class); BufferedImage bi = thumbnail; if (thumbnail.getColorModel().getColorSpace().getType() != ColorSpace.TYPE_GRAY) { bi = convertBI(thumbnail, BufferedImage.TYPE_BYTE_INDEXED); pmi = "PALETTE COLOR"; //$NON-NLS-1$ } byte[] iconPixelData = new byte[w * h]; Attributes iconItem = new Attributes(); if ("PALETTE COLOR".equals(pmi)) { //$NON-NLS-1$ IndexColorModel cm = (IndexColorModel) bi.getColorModel(); int[] lutDesc = { cm.getMapSize(), 0, 8 }; byte[] r = new byte[lutDesc[0]]; byte[] g = new byte[lutDesc[0]]; byte[] b = new byte[lutDesc[0]]; cm.getReds(r); cm.getGreens(g); cm.getBlues(b); iconItem.setInt(Tag.RedPaletteColorLookupTableDescriptor, VR.US, lutDesc); iconItem.setInt(Tag.GreenPaletteColorLookupTableDescriptor, VR.US, lutDesc); iconItem.setInt(Tag.BluePaletteColorLookupTableDescriptor, VR.US, lutDesc); iconItem.setBytes(Tag.RedPaletteColorLookupTableData, VR.OW, r); iconItem.setBytes(Tag.GreenPaletteColorLookupTableData, VR.OW, g); iconItem.setBytes(Tag.BluePaletteColorLookupTableData, VR.OW, b); Raster raster = bi.getRaster(); for (int y = 0, i = 0; y < h; ++y) { for (int x = 0; x < w; ++x, ++i) { iconPixelData[i] = (byte) raster.getSample(x, y, 0); } } } else { pmi = "MONOCHROME2"; //$NON-NLS-1$ for (int y = 0, i = 0; y < h; ++y) { for (int x = 0; x < w; ++x, ++i) { iconPixelData[i] = (byte) bi.getRGB(x, y); } } } iconItem.setString(Tag.PhotometricInterpretation, VR.CS, pmi); iconItem.setInt(Tag.Rows, VR.US, h); iconItem.setInt(Tag.Columns, VR.US, w); iconItem.setInt(Tag.SamplesPerPixel, VR.US, 1); iconItem.setInt(Tag.BitsAllocated, VR.US, 8); iconItem.setInt(Tag.BitsStored, VR.US, 8); iconItem.setInt(Tag.HighBit, VR.US, 7); iconItem.setBytes(Tag.PixelData, VR.OW, iconPixelData); return iconItem; } private static BufferedImage convertBI(BufferedImage src, int imageType) { BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), imageType); Graphics2D big = dst.createGraphics(); try { big.drawImage(src, 0, 0, null); } finally { big.dispose(); } return dst; } }