/******************************************************************************* * 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.wado; import java.awt.event.KeyListener; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelListener; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import javax.swing.JProgressBar; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.ElementDictionary; import org.dcm4che3.data.Tag; import org.dcm4che3.io.DicomInputStream; import org.dcm4che3.io.DicomInputStream.IncludeBulkData; import org.dcm4che3.io.DicomOutputStream; import org.dcm4che3.util.SafeClose; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.explorer.ObservableEvent; import org.weasis.core.api.gui.util.AppProperties; import org.weasis.core.api.gui.util.GuiExecutor; import org.weasis.core.api.media.data.MediaElement; import org.weasis.core.api.media.data.MediaSeries; import org.weasis.core.api.media.data.MediaSeriesGroup; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.SeriesImporter; import org.weasis.core.api.media.data.SeriesThumbnail; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.media.data.TagW.TagType; import org.weasis.core.api.media.data.Thumbnail; import org.weasis.core.api.service.AuditLog; import org.weasis.core.api.util.FileUtil; import org.weasis.core.api.util.NetworkUtil; import org.weasis.core.api.util.StreamIOException; import org.weasis.core.api.util.StringUtil; import org.weasis.core.api.util.ThreadUtil; import org.weasis.core.ui.docking.UIManager; import org.weasis.core.ui.editor.SeriesViewerFactory; import org.weasis.core.ui.editor.ViewerPluginBuilder; import org.weasis.core.ui.editor.image.ImageViewerPlugin; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.editor.image.ViewerPlugin; import org.weasis.dicom.codec.DicomInstance; import org.weasis.dicom.codec.DicomMediaIO; import org.weasis.dicom.codec.DicomSpecialElement; import org.weasis.dicom.codec.TagD; import org.weasis.dicom.codec.TagD.Level; import org.weasis.dicom.codec.TransferSyntax; import org.weasis.dicom.codec.utils.DicomMediaUtils; import org.weasis.dicom.codec.wado.WadoParameters; import org.weasis.dicom.codec.wado.WadoParameters.HttpTag; import org.weasis.dicom.explorer.DicomModel; import org.weasis.dicom.explorer.ExplorerTask; import org.weasis.dicom.explorer.Messages; import org.weasis.dicom.explorer.MimeSystemAppFactory; import org.weasis.dicom.explorer.ThumbnailMouseAndKeyAdapter; public class LoadSeries extends ExplorerTask<Boolean, String> implements SeriesImporter { private static final Logger LOGGER = LoggerFactory.getLogger(LoadSeries.class); public static final String CONCURRENT_DOWNLOADS_IN_SERIES = "download.concurrent.series.images"; //$NON-NLS-1$ public static final File DICOM_TMP_DIR = AppProperties.buildAccessibleTempDirectory("downloading"); //$NON-NLS-1$ public static final TagW DOWNLOAD_START_TIME = new TagW("DownloadSartTime", TagType.TIME); //$NON-NLS-1$ public enum Status { DOWNLOADING, PAUSED, COMPLETE, CANCELLED, ERROR } public final int concurrentDownloads; private final DicomModel dicomModel; private final Series<?> dicomSeries; private final JProgressBar progressBar; private volatile DownloadPriority priority = null; private final boolean writeInCache; private volatile boolean hasError = false; public LoadSeries(Series<?> dicomSeries, DicomModel dicomModel, int concurrentDownloads, boolean writeInCache) { super(Messages.getString("DicomExplorer.loading"), writeInCache, null, true); //$NON-NLS-1$ if (dicomModel == null || dicomSeries == null) { throw new IllegalArgumentException("null parameters"); //$NON-NLS-1$ } this.dicomModel = dicomModel; this.dicomSeries = dicomSeries; this.writeInCache = writeInCache; this.progressBar = getBar(); if (!writeInCache) { progressBar.setVisible(false); } this.dicomSeries.setSeriesLoader(this); this.concurrentDownloads = concurrentDownloads; } public LoadSeries(Series<?> dicomSeries, DicomModel dicomModel, JProgressBar progressBar, int concurrentDownloads, boolean writeInCache) { super(Messages.getString("DicomExplorer.loading"), writeInCache, null, true); //$NON-NLS-1$ if (dicomModel == null || dicomSeries == null || progressBar == null) { throw new IllegalArgumentException("null parameters"); //$NON-NLS-1$ } this.dicomModel = dicomModel; this.dicomSeries = dicomSeries; this.progressBar = progressBar; this.writeInCache = writeInCache; this.dicomSeries.setSeriesLoader(this); this.concurrentDownloads = concurrentDownloads; } @Override protected Boolean doInBackground() { return startDownload(); } @Override public JProgressBar getProgressBar() { return progressBar; } @Override public boolean isStopped() { return isCancelled(); } @Override public boolean stop() { if (!isDone()) { boolean val = cancel(); dicomSeries.setSeriesLoader(this); return val; } return true; } @Override public void resume() { if (isStopped()) { this.getPriority().setPriority(DownloadPriority.COUNTER.getAndDecrement()); cancelAndReplace(this); } } @Override protected void done() { if (!isStopped()) { // Ensure to stop downloading and must be set before reusing LoadSeries to download again progressBar.setIndeterminate(false); this.dicomSeries.setSeriesLoader(null); DownloadManager.removeLoadSeries(this, dicomModel); LOGGER.info("{} type:{} seriesUID:{} modality:{} nbImages:{} size:{} {}", //$NON-NLS-1$ new Object[] {AuditLog.MARKER_PERF, getLoadType(), dicomSeries.getTagValue(dicomSeries.getTagID()), TagD.getTagValue(dicomSeries, Tag.Modality, String.class), getImageNumber(), (long) dicomSeries.getFileSize(), getDownloadTime() }); dicomSeries.removeTag(DOWNLOAD_START_TIME); final SeriesThumbnail thumbnail = (SeriesThumbnail) dicomSeries.getTagValue(TagW.Thumbnail); if (thumbnail != null) { thumbnail.setProgressBar(null); if (thumbnail.getThumbnailPath() == null || dicomSeries.getTagValue(TagW.DirectDownloadThumbnail) != null) { thumbnail.reBuildThumbnail(MediaSeries.MEDIA_POSITION.MIDDLE); } else { thumbnail.repaint(); } } if (DicomModel.isSpecialModality(dicomSeries)) { dicomModel.addSpecialModality(dicomSeries); List<DicomSpecialElement> list = (List<DicomSpecialElement>) dicomSeries.getTagValue(TagW.DicomSpecialElementList); if (list != null) { list.stream().filter(DicomSpecialElement.class::isInstance).map(DicomSpecialElement.class::cast) .findFirst().ifPresent(d -> dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.UPDATE, dicomModel, null, d))); } } Integer splitNb = (Integer) dicomSeries.getTagValue(TagW.SplitSeriesNumber); if (splitNb != null) { dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.UPDATE, dicomModel, null, dicomSeries)); } else if (dicomSeries.size(null) == 0 && dicomSeries.getTagValue(TagW.DicomSpecialElementList) == null && !hasDownloadFailed()) { // Remove in case of split Series and all the SopInstanceUIDs already exist dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.REMOVE, dicomModel, null, dicomSeries)); } } } public boolean hasDownloadFailed() { return hasError; } private String getLoadType() { final WadoParameters wado = (WadoParameters) dicomSeries.getTagValue(TagW.WadoParameters); if (wado == null || !StringUtil.hasText(wado.getWadoURL())) { if (wado != null) { return wado.isRequireOnlySOPInstanceUID() ? "DICOMDIR" : "URL"; //$NON-NLS-1$ //$NON-NLS-2$ } return "local"; //$NON-NLS-1$ } else { final List<DicomInstance> sopList = (List<DicomInstance>) dicomSeries.getTagValue(TagW.WadoInstanceReferenceList); if (sopList != null && !sopList.isEmpty() && sopList.get(0).getDirectDownloadFile() != null) { return "URL"; //$NON-NLS-1$ } return "WADO"; //$NON-NLS-1$ } } private int getImageNumber() { int val = dicomSeries.size(null); Integer splitNb = (Integer) dicomSeries.getTagValue(TagW.SplitSeriesNumber); if (splitNb != null) { MediaSeriesGroup study = dicomModel.getParent(dicomSeries, DicomModel.study); if (study != null) { String uid = TagD.getTagValue(dicomSeries, Tag.SeriesInstanceUID, String.class); if (uid != null) { Collection<MediaSeriesGroup> list = dicomModel.getChildren(study); list.remove(dicomSeries); for (MediaSeriesGroup s : list) { if (s instanceof Series && uid.equals(TagD.getTagValue(s, Tag.SeriesInstanceUID))) { val += ((Series<?>) s).size(null); } } } } } return val; } private String getDownloadTime() { Long val = (Long) dicomSeries.getTagValue(DOWNLOAD_START_TIME); long time = val == null ? 0 : System.currentTimeMillis() - val; StringBuilder buf = new StringBuilder(); buf.append("time:"); //$NON-NLS-1$ buf.append(time); buf.append(" rate:"); //$NON-NLS-1$ // rate in kB/s or B/ms DecimalFormat format = new DecimalFormat("#.##"); //$NON-NLS-1$ buf.append(val == null ? 0 : format.format(dicomSeries.getFileSize() / time)); return buf.toString(); } private boolean isSOPInstanceUIDExist(MediaSeriesGroup study, Series<?> dicomSeries, String sopUID) { TagW sopTag = TagD.getUID(Level.INSTANCE); if (dicomSeries.hasMediaContains(sopTag, sopUID)) { return true; } // Search in split Series, cannot use "has this series a SplitNumber" because splitting can be executed later // for Dicom Video and other special Dicom String uid = TagD.getTagValue(dicomSeries, Tag.SeriesInstanceUID, String.class); if (study != null && uid != null) { for (MediaSeriesGroup group : dicomModel.getChildren(study)) { if (dicomSeries != group && group instanceof Series) { Series s = (Series) group; if (uid.equals(TagD.getTagValue(group, Tag.SeriesInstanceUID)) && s.hasMediaContains(sopTag, sopUID)) { return true; } } } } return false; } private void incrementProgressBarValue() { GuiExecutor.instance().execute(() -> progressBar.setValue(progressBar.getValue() + 1)); } private Boolean startDownload() { MediaSeriesGroup patient = dicomModel.getParent(dicomSeries, DicomModel.patient); MediaSeriesGroup study = dicomModel.getParent(dicomSeries, DicomModel.study); LOGGER.info("Downloading series of {} [{}]", patient, dicomSeries); //$NON-NLS-1$ final List<DicomInstance> sopList = (List<DicomInstance>) dicomSeries.getTagValue(TagW.WadoInstanceReferenceList); final WadoParameters wado = (WadoParameters) dicomSeries.getTagValue(TagW.WadoParameters); if (wado == null) { return false; } ExecutorService imageDownloader = ThreadUtil.buildNewFixedThreadExecutor(concurrentDownloads, "Image Downloader"); //$NON-NLS-1$ ArrayList<Callable<Boolean>> tasks = new ArrayList<>(sopList.size()); int[] dindex = generateDownladOrder(sopList.size()); GuiExecutor.instance().execute(() -> { progressBar.setMaximum(sopList.size()); progressBar.setValue(0); }); for (int k = 0; k < sopList.size(); k++) { DicomInstance instance = sopList.get(dindex[k]); if (isCancelled()) { return true; } // Test if SOPInstanceUID already exists if (isSOPInstanceUIDExist(study, dicomSeries, instance.getSopInstanceUID())) { incrementProgressBarValue(); LOGGER.debug("DICOM instance {} already exists, skip.", instance.getSopInstanceUID()); //$NON-NLS-1$ continue; } URLConnection urlConnection = null; try { String studyUID = ""; //$NON-NLS-1$ String seriesUID = ""; //$NON-NLS-1$ if (!wado.isRequireOnlySOPInstanceUID()) { studyUID = TagD.getTagValue(study, Tag.StudyInstanceUID, String.class); seriesUID = TagD.getTagValue(dicomSeries, Tag.SeriesInstanceUID, String.class); } StringBuilder request = new StringBuilder(wado.getWadoURL()); if (instance.getDirectDownloadFile() == null) { request.append("?requestType=WADO&studyUID="); //$NON-NLS-1$ request.append(studyUID); request.append("&seriesUID="); //$NON-NLS-1$ request.append(seriesUID); request.append("&objectUID="); //$NON-NLS-1$ request.append(instance.getSopInstanceUID()); request.append("&contentType=application%2Fdicom"); //$NON-NLS-1$ TransferSyntax transcoding = DicomManager.getInstance().getWadoTSUID(); if (transcoding.getTransferSyntaxUID() != null) { dicomSeries.setTag(TagW.WadoTransferSyntaxUID, transcoding.getTransferSyntaxUID()); } // for dcm4chee: it gets original DICOM files when no TransferSyntax is specified String wadoTsuid = (String) dicomSeries.getTagValue(TagW.WadoTransferSyntaxUID); if (StringUtil.hasText(wadoTsuid)) { // Ensure the client has the decoder. Otherwise ask uncompressed syntax if (!DicomManager.getInstance().containsImageioCodec(wadoTsuid)) { wadoTsuid = TransferSyntax.EXPLICIT_VR_LE.getTransferSyntaxUID(); } request.append("&transferSyntax="); //$NON-NLS-1$ request.append(wadoTsuid); if (transcoding.getTransferSyntaxUID() != null) { dicomSeries.setTag(TagW.WadoCompressionRate, transcoding.getCompression()); } Integer rate = (Integer) dicomSeries.getTagValue(TagW.WadoCompressionRate); if (rate != null && rate > 0) { request.append("&imageQuality="); //$NON-NLS-1$ request.append(rate); } } } else { request.append(instance.getDirectDownloadFile()); } request.append(wado.getAdditionnalParameters()); urlConnection = initConnection(new URL(request.toString()), wado); } catch (MalformedURLException e) { LOGGER.error("Invalid URL", e); //$NON-NLS-1$ continue; } catch (IOException e) { hasError = true; LOGGER.error("Cannot open URL", e); //$NON-NLS-1$ continue; } LOGGER.debug("Download DICOM instance {} index {}.", urlConnection, k); //$NON-NLS-1$ Download ref = new Download(urlConnection); tasks.add(ref); } try { dicomSeries.setTag(DOWNLOAD_START_TIME, System.currentTimeMillis()); imageDownloader.invokeAll(tasks); } catch (InterruptedException e) { } imageDownloader.shutdown(); return true; } private static URLConnection initConnection(URL url, WadoParameters wadoParameters) throws IOException { // If there is a proxy, it should be already configured URLConnection urlConnection = url.openConnection(); // Set http login (no protection, only convert in base64) if (wadoParameters.getWebLogin() != null) { urlConnection.setRequestProperty("Authorization", "Basic " + wadoParameters.getWebLogin()); //$NON-NLS-1$ //$NON-NLS-2$ } if (!wadoParameters.getHttpTaglist().isEmpty()) { for (HttpTag tag : wadoParameters.getHttpTaglist()) { urlConnection.setRequestProperty(tag.getKey(), tag.getValue()); } } return urlConnection; } public void startDownloadImageReference(final WadoParameters wadoParameters) { final List<DicomInstance> sopList = (List<DicomInstance>) dicomSeries.getTagValue(TagW.WadoInstanceReferenceList); if (!sopList.isEmpty()) { // Sort the UIDs for building the thumbnail that is in the middle of the Series Collections.sort(sopList); final DicomInstance instance = sopList.get(sopList.size() / 2); GuiExecutor.instance().execute(() -> { SeriesThumbnail thumbnail = (SeriesThumbnail) dicomSeries.getTagValue(TagW.Thumbnail); if (thumbnail == null) { thumbnail = new SeriesThumbnail(dicomSeries, Thumbnail.DEFAULT_SIZE); } // In case series is downloaded or canceled thumbnail.setProgressBar(LoadSeries.this.isDone() ? null : progressBar); thumbnail.registerListeners(); addListenerToThumbnail(thumbnail, LoadSeries.this, dicomModel); dicomSeries.setTag(TagW.Thumbnail, thumbnail); dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.ADD, dicomModel, null, dicomSeries)); }); loadThumbnail(instance, wadoParameters); } } public void loadThumbnail(DicomInstance instance, WadoParameters wadoParameters) { File file = null; if (instance.getDirectDownloadFile() == null) { String studyUID = ""; //$NON-NLS-1$ String seriesUID = ""; //$NON-NLS-1$ if (!wadoParameters.isRequireOnlySOPInstanceUID()) { MediaSeriesGroup study = dicomModel.getParent(dicomSeries, DicomModel.study); studyUID = TagD.getTagValue(study, Tag.StudyInstanceUID, String.class); seriesUID = TagD.getTagValue(dicomSeries, Tag.SeriesInstanceUID, String.class); } try { file = getJPEGThumnails(wadoParameters, studyUID, seriesUID, instance.getSopInstanceUID()); } catch (Exception e) { LOGGER.error("Downloading thbumbnail", e); //$NON-NLS-1$ } } else { String thumURL = (String) dicomSeries.getTagValue(TagW.DirectDownloadThumbnail); if (thumURL != null) { if (thumURL.startsWith(Thumbnail.THUMBNAIL_CACHE_DIR.getPath())) { file = new File(thumURL); } else { try { File outFile = File.createTempFile("tumb_", FileUtil.getExtension(thumURL), //$NON-NLS-1$ Thumbnail.THUMBNAIL_CACHE_DIR); FileUtil.writeStreamWithIOException( new URL(wadoParameters.getWadoURL() + thumURL).openConnection(), outFile); file = outFile; } catch (Exception e) { LOGGER.error("Downloading thbumbnail", e); //$NON-NLS-1$ } } } } if (file != null) { final File finalfile = file; GuiExecutor.instance().execute(() -> { SeriesThumbnail thumbnail = (SeriesThumbnail) dicomSeries.getTagValue(TagW.Thumbnail); if (thumbnail != null) { thumbnail.reBuildThumbnail(finalfile, MediaSeries.MEDIA_POSITION.MIDDLE); } }); } } public static void removeThumbnailMouseAndKeyAdapter(Thumbnail tumbnail) { MouseListener[] listener = tumbnail.getMouseListeners(); MouseMotionListener[] motionListeners = tumbnail.getMouseMotionListeners(); KeyListener[] keyListeners = tumbnail.getKeyListeners(); MouseWheelListener[] wheelListeners = tumbnail.getMouseWheelListeners(); for (int i = 0; i < listener.length; i++) { if (listener[i] instanceof ThumbnailMouseAndKeyAdapter) { tumbnail.removeMouseListener(listener[i]); } } for (int i = 0; i < motionListeners.length; i++) { if (motionListeners[i] instanceof ThumbnailMouseAndKeyAdapter) { tumbnail.removeMouseMotionListener(motionListeners[i]); } } for (int i = 0; i < wheelListeners.length; i++) { if (wheelListeners[i] instanceof ThumbnailMouseAndKeyAdapter) { tumbnail.removeMouseWheelListener(wheelListeners[i]); } } for (int i = 0; i < keyListeners.length; i++) { if (keyListeners[i] instanceof ThumbnailMouseAndKeyAdapter) { tumbnail.removeKeyListener(keyListeners[i]); } } } private static void addListenerToThumbnail(final Thumbnail thumbnail, final LoadSeries loadSeries, final DicomModel dicomModel) { ThumbnailMouseAndKeyAdapter thumbAdapter = new ThumbnailMouseAndKeyAdapter(loadSeries.getDicomSeries(), dicomModel, loadSeries); thumbnail.addMouseListener(thumbAdapter); thumbnail.addKeyListener(thumbAdapter); if (thumbnail instanceof SeriesThumbnail) { ((SeriesThumbnail) thumbnail).setProgressBar(loadSeries.getProgressBar()); } } public Series<?> getDicomSeries() { return dicomSeries; } public File getJPEGThumnails(WadoParameters wadoParameters, String StudyUID, String SeriesUID, String SOPInstanceUID) throws IOException { // TODO set quality as a preference URL url = new URL(wadoParameters.getWadoURL() + "?requestType=WADO&studyUID=" + StudyUID + "&seriesUID=" + SeriesUID //$NON-NLS-1$ //$NON-NLS-2$ + "&objectUID=" + SOPInstanceUID + "&contentType=image/jpeg&imageQuality=70" + "&rows=" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + Thumbnail.MAX_SIZE + "&columns=" + Thumbnail.MAX_SIZE + wadoParameters.getAdditionnalParameters()); //$NON-NLS-1$ URLConnection httpCon = initConnection(url, wadoParameters); File outFile = File.createTempFile("tumb_", ".jpg", Thumbnail.THUMBNAIL_CACHE_DIR); //$NON-NLS-1$ //$NON-NLS-2$ LOGGER.debug("Start to download JPEG thbumbnail {} to {}.", url, outFile.getName()); //$NON-NLS-1$ FileUtil.writeStreamWithIOException(httpCon, outFile); return outFile; } private int[] generateDownladOrder(final int size) { int[] dindex = new int[size]; if (size < 4) { for (int i = 0; i < dindex.length; i++) { dindex[i] = i; } return dindex; } boolean[] map = new boolean[size]; int pos = 0; dindex[pos++] = 0; map[0] = true; dindex[pos++] = size - 1; map[size - 1] = true; int k = (size - 1) / 2; dindex[pos++] = k; map[k] = true; while (k > 0) { int i = 1; int start = 0; while (i < map.length) { if (map[i]) { if (!map[i - 1]) { int mid = start + (i - start) / 2; map[mid] = true; dindex[pos++] = mid; } start = i; } i++; } k /= 2; } return dindex; } class Download implements Callable<Boolean> { private final URLConnection urlConnection; // download URL private Status status; // current status of download public Download(URLConnection urlConnection) { this.urlConnection = urlConnection; status = Status.DOWNLOADING; } public String getUrl() { return urlConnection.getURL().toExternalForm(); } public void pause() { status = Status.PAUSED; } public void resume() { status = Status.DOWNLOADING; } public void cancel() { status = Status.CANCELLED; } private void error() { status = Status.ERROR; } private InputStream replaceToDefaultTSUID() throws IOException { String old = getUrl(); StringBuilder buffer = new StringBuilder(); int start = old.indexOf("&transferSyntax="); //$NON-NLS-1$ if (start != -1) { int end = old.indexOf("&", start + 16); //$NON-NLS-1$ buffer.append(old.substring(0, start + 16)); buffer.append(TransferSyntax.EXPLICIT_VR_LE.getTransferSyntaxUID()); if (end != -1) { buffer.append(old.substring(end)); } } else { buffer.append(old); buffer.append("&transferSyntax="); //$NON-NLS-1$ buffer.append(TransferSyntax.EXPLICIT_VR_LE.getTransferSyntaxUID()); } final WadoParameters wado = (WadoParameters) dicomSeries.getTagValue(TagW.WadoParameters); return NetworkUtil.getUrlInputStream(initConnection(new URL(buffer.toString()), wado)); } @Override public Boolean call() throws Exception { try { process(); } catch (StreamIOException es) { hasError = true; // network issue (allow to retry) error(); LOGGER.error("Downloading", es); //$NON-NLS-1$ } catch (IOException | URISyntaxException e) { error(); LOGGER.error("Downloading", e); //$NON-NLS-1$ } return Boolean.TRUE; } /** * Download file. * * @return * @throws IOException * @throws URISyntaxException */ private boolean process() throws IOException, URISyntaxException { File tempFile = null; InputStream stream = NetworkUtil.getUrlInputStream(urlConnection); boolean cache = true; if (!writeInCache && getUrl().startsWith("file:")) { //$NON-NLS-1$ cache = false; } if (cache) { tempFile = File.createTempFile("image_", ".dcm", DICOM_TMP_DIR); //$NON-NLS-1$ //$NON-NLS-2$ } // Cannot resume with WADO because the stream is modified on the fly by the wado server. In dcm4chee, see // http://www.dcm4che.org/jira/browse/DCMEE-421 progressBar.setIndeterminate(progressBar.getMaximum() < 3); DicomMediaIO dicomReader = null; if (dicomSeries != null) { if (cache) { LOGGER.debug("Start to download DICOM instance {} to {}.", getUrl(), tempFile.getName()); //$NON-NLS-1$ int bytesTransferred = downloadInFileCache(stream, tempFile); if (bytesTransferred == -1) { LOGGER.info("End of downloading {} ", getUrl()); //$NON-NLS-1$ } else if (bytesTransferred >= 0) { return false; } File renameFile = new File(DicomMediaIO.DICOM_EXPORT_DIR, tempFile.getName()); if (tempFile.renameTo(renameFile)) { tempFile = renameFile; } } else { tempFile = new File(urlConnection.getURL().toURI()); } // Ensure the stream is closed if image is not written in cache FileUtil.safeClose(stream); dicomReader = new DicomMediaIO(tempFile); if (dicomReader.isReadableDicom() && dicomSeries.size(null) == 0) { // Override the group (patient, study and series) by the dicom fields except the UID of the group MediaSeriesGroup patient = dicomModel.getParent(dicomSeries, DicomModel.patient); dicomReader.writeMetaData(patient); MediaSeriesGroup study = dicomModel.getParent(dicomSeries, DicomModel.study); dicomReader.writeMetaData(study); dicomReader.writeMetaData(dicomSeries); GuiExecutor.instance().invokeAndWait(() -> { Thumbnail thumb = (Thumbnail) dicomSeries.getTagValue(TagW.Thumbnail); if (thumb != null) { thumb.repaint(); } dicomModel.firePropertyChange(new ObservableEvent(ObservableEvent.BasicAction.UDPATE_PARENT, dicomModel, null, dicomSeries)); }); } } // Change status to complete if this point was reached because downloading has finished. if (status == Status.DOWNLOADING) { status = Status.COMPLETE; if (tempFile != null) { if (dicomSeries != null && dicomReader.isReadableDicom()) { if (cache) { dicomReader.getFileCache().setOriginalTempFile(tempFile); } final DicomMediaIO reader = dicomReader; // Necessary to wait the runnable because the dicomSeries must be added to the dicomModel // before reaching done() of SwingWorker GuiExecutor.instance().invokeAndWait(() -> updateUI(reader)); } } } // Increment progress bar in EDT and repaint when downloaded incrementProgressBarValue(); return true; } private int downloadInFileCache(InputStream stream, File tempFile) throws IOException { final WadoParameters wado = (WadoParameters) dicomSeries.getTagValue(TagW.WadoParameters); int[] overrideList = Optional.ofNullable(wado).map(p -> p.getOverrideDicomTagIDList()).orElse(null); boolean readTsuid = DicomManager.getInstance().hasAllImageCodecs() ? false : getUrl().contains("?requestType=WADO"); //$NON-NLS-1$ int bytesTransferred; if (overrideList == null) { bytesTransferred = FileUtil.writeStream(new DicomSeriesProgressMonitor(dicomSeries, stream, readTsuid), tempFile); } else { bytesTransferred = writFile(new DicomSeriesProgressMonitor(dicomSeries, stream, readTsuid), tempFile, overrideList); } if (bytesTransferred == Integer.MIN_VALUE) { LOGGER.warn("Stop downloading unsupported TSUID, retry to download non compressed TSUID"); //$NON-NLS-1$ InputStream stream2 = replaceToDefaultTSUID(); if (overrideList == null) { bytesTransferred = FileUtil.writeStream(new DicomSeriesProgressMonitor(dicomSeries, stream2, false), tempFile); } else { bytesTransferred = writFile(new DicomSeriesProgressMonitor(dicomSeries, stream2, false), tempFile, overrideList); } } return bytesTransferred; } /** * @param in * @param tempFile * @param overrideList * @return bytes transferred. O = error, -1 = all bytes has been transferred, other = bytes transferred before * interruption * @throws StreamIOException */ public int writFile(InputStream in, File tempFile, int[] overrideList) throws StreamIOException { if (in == null && tempFile == null) { return 0; } DicomInputStream dis = null; DicomOutputStream dos = null; try { String tsuid = null; Attributes dataset; dis = new DicomInputStream(in); try { dis.setIncludeBulkData(IncludeBulkData.URI); dataset = dis.readDataset(-1, -1); tsuid = dis.getTransferSyntax(); } finally { dis.close(); } dos = new DicomOutputStream(tempFile); if (overrideList != null) { MediaSeriesGroup study = dicomModel.getParent(dicomSeries, DicomModel.study); MediaSeriesGroup patient = dicomModel.getParent(dicomSeries, DicomModel.patient); ElementDictionary dic = ElementDictionary.getStandardElementDictionary(); for (int tag : overrideList) { TagW tagElement = patient.getTagElement(tag); Object value = null; if (tagElement == null) { tagElement = study.getTagElement(tag); value = study.getTagValue(tagElement); } else { value = patient.getTagValue(tagElement); } DicomMediaUtils.fillAttributes(dataset, tagElement, value, dic); } } dos.writeDataset(dataset.createFileMetaInformation(tsuid), dataset); dos.finish(); dos.flush(); return -1; } catch (InterruptedIOException e) { FileUtil.delete(tempFile); LOGGER.error("Interruption when writing file: {}", e.getMessage()); //$NON-NLS-1$ return e.bytesTransferred; } catch (IOException e) { FileUtil.delete(tempFile); throw new StreamIOException(e); } catch (Exception e) { FileUtil.delete(tempFile); LOGGER.error("Writing DICOM temp file", e); //$NON-NLS-1$ return 0; } finally { SafeClose.close(dos); if (dis != null) { List<File> blkFiles = dis.getBulkDataFiles(); if (blkFiles != null) { for (File file : blkFiles) { file.delete(); } } } } } private void updateUI(final DicomMediaIO reader) { boolean firstImageToDisplay = false; MediaElement[] medias = reader.getMediaElement(); if (medias != null) { firstImageToDisplay = dicomSeries.size(null) == 0; if (firstImageToDisplay) { MediaSeriesGroup patient = dicomModel.getParent(dicomSeries, DicomModel.patient); if (patient != null) { String dicomPtUID = (String) reader.getTagValue(TagW.PatientPseudoUID); if (!patient.getTagValue(TagW.PatientPseudoUID).equals(dicomPtUID)) { // Fix when patientUID in xml have different patient name dicomModel.replacePatientUID((String) patient.getTagValue(TagW.PatientPseudoUID), dicomPtUID); } } } for (MediaElement media : medias) { dicomModel.applySplittingRules(dicomSeries, media); } if (firstImageToDisplay && dicomSeries.size(null) == 0) { firstImageToDisplay = false; } } Thumbnail thumb = (Thumbnail) dicomSeries.getTagValue(TagW.Thumbnail); if (thumb != null) { thumb.repaint(); } if (firstImageToDisplay) { boolean openNewTab = true; MediaSeriesGroup entry1 = dicomModel.getParent(dicomSeries, DicomModel.patient); if (entry1 != null) { synchronized (UIManager.VIEWER_PLUGINS) { for (final ViewerPlugin p : UIManager.VIEWER_PLUGINS) { if (entry1.equals(p.getGroupID())) { if (p instanceof ImageViewerPlugin) { ViewCanvas pane = ((ImageViewerPlugin) p).getSelectedImagePane(); if (pane != null && pane.getImageLayer() != null && pane.getImageLayer().getSourceImage() == null) { // When the selected view has no image send, open in it. break; } } openNewTab = false; break; } } } } if (openNewTab) { SeriesViewerFactory plugin = UIManager.getViewerFactory(dicomSeries.getMimeType()); if (plugin != null && !(plugin instanceof MimeSystemAppFactory)) { ViewerPluginBuilder.openSequenceInPlugin(plugin, dicomSeries, dicomModel, true, true); } else if (plugin != null) { // Send event to select the related patient in Dicom Explorer. dicomModel.firePropertyChange( new ObservableEvent(ObservableEvent.BasicAction.SELECT, dicomModel, null, dicomSeries)); } } } } } public synchronized DownloadPriority getPriority() { return priority; } public synchronized void setPriority(DownloadPriority priority) { this.priority = priority; } @Override public void setPriority() { DownloadPriority p = getPriority(); if (p != null && StateValue.PENDING.equals(getState())) { boolean change = DownloadManager.removeSeriesInQueue(this); if (change) { // Set the priority to the current loadingSeries and stop a task. p.setPriority(DownloadPriority.COUNTER.getAndDecrement()); DownloadManager.offerSeriesInQueue(this); synchronized (DownloadManager.TASKS) { for (LoadSeries s : DownloadManager.TASKS) { if (s != this && StateValue.STARTED.equals(s.getState())) { cancelAndReplace(s); break; } } } } } } public LoadSeries cancelAndReplace(LoadSeries s) { LoadSeries taskResume = new LoadSeries(s.getDicomSeries(), dicomModel, s.getProgressBar(), s.getConcurrentDownloads(), s.writeInCache); s.cancel(); taskResume.setPriority(s.getPriority()); Thumbnail thumbnail = (Thumbnail) s.getDicomSeries().getTagValue(TagW.Thumbnail); if (thumbnail != null) { LoadSeries.removeThumbnailMouseAndKeyAdapter(thumbnail); addListenerToThumbnail(thumbnail, taskResume, dicomModel); } DownloadManager.addLoadSeries(taskResume, dicomModel, true); DownloadManager.removeLoadSeries(s, dicomModel); return taskResume; } public int getConcurrentDownloads() { return concurrentDownloads; } }