/******************************************************************************* * 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.codec; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import javax.media.jai.PlanarImage; import org.dcm4che3.data.Tag; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.api.explorer.ObservableEvent; import org.weasis.core.api.explorer.model.DataExplorerModel; import org.weasis.core.api.gui.util.Filter; import org.weasis.core.api.gui.util.MathUtil; import org.weasis.core.api.media.data.Series; import org.weasis.core.api.media.data.SeriesEvent; import org.weasis.core.api.media.data.TagView; import org.weasis.core.api.media.data.TagW; import org.weasis.core.api.util.FileUtil; import org.weasis.core.api.util.StringUtil; import org.weasis.dicom.codec.TagD.Level; public class DicomSeries extends Series<DicomImageElement> { private static final Logger LOGGER = LoggerFactory.getLogger(DicomSeries.class); static final TagView defaultTagView = new TagView(TagD.getTagFromIDs(Tag.SeriesDescription, Tag.SeriesNumber, Tag.SeriesTime)); private static volatile PreloadingTask preloadingTask; public DicomSeries(String subseriesInstanceUID) { this(subseriesInstanceUID, null, defaultTagView); } public DicomSeries(String subseriesInstanceUID, List<DicomImageElement> c, TagView displayTag) { super(TagD.getUID(Level.SERIES), subseriesInstanceUID, displayTag, c, SortSeriesStack.instanceNumber); } public boolean[] getImageInMemoryList() { boolean[] list; synchronized (this) { list = new boolean[medias.size()]; for (int i = 0; i < medias.size(); i++) { if (medias.get(i).isImageInCache()) { list[i] = true; } } } return list; } @Override public void addMedia(DicomImageElement media) { if (media != null && media.getMediaReader() instanceof DcmMediaReader) { int insertIndex; synchronized (this) { // add image or multi-frame sorted by Instance Number (0020,0013) order int index = Collections.binarySearch(medias, media, SortSeriesStack.instanceNumber); if (index < 0) { insertIndex = -(index + 1); } else { // Should not happen because the instance number must be unique insertIndex = index + 1; } if (insertIndex < 0 || insertIndex > medias.size()) { insertIndex = medias.size(); } add(insertIndex, media); } DataExplorerModel model = (DataExplorerModel) getTagValue(TagW.ExplorerModel); if (model != null) { model.firePropertyChange(new ObservableEvent(ObservableEvent.BasicAction.ADD, model, null, new SeriesEvent(SeriesEvent.Action.ADD_IMAGE, this, media))); } } } @Override public String getToolTips() { StringBuilder toolTips = new StringBuilder("<html>"); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.pat"), TagD.get(Tag.PatientName)); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.mod"), TagD.get(Tag.Modality)); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.series_nb"), TagD.get(Tag.SeriesNumber)); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.study"), TagD.get(Tag.StudyDescription)); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.series"), TagD.get(Tag.SeriesDescription)); //$NON-NLS-1$ addToolTipsElement(toolTips, Messages.getString("DicomSeries.date"), TagD.get(Tag.SeriesDate)); //$NON-NLS-1$ if (getFileSize() > 0.0) { toolTips.append(Messages.getString("DicomSeries.size")); //$NON-NLS-1$ toolTips.append(StringUtil.COLON_AND_SPACE); toolTips.append(FileUtil.formatSize(getFileSize())); toolTips.append("<br>"); //$NON-NLS-1$ } toolTips.append("</html>"); //$NON-NLS-1$ return toolTips.toString(); } @Override public String getSeriesNumber() { Integer splitNb = (Integer) getTagValue(TagW.SplitSeriesNumber); Integer val = TagD.getTagValue(this, Tag.SeriesNumber, Integer.class); String result = val == null ? "" : val.toString(); //$NON-NLS-1$ return splitNb == null ? result : result + "-" + splitNb.toString(); //$NON-NLS-1$ } @Override public String getMimeType() { String modality = TagD.getTagValue(this, Tag.Modality, String.class); DicomSpecialElementFactory factory = DicomMediaIO.DCM_ELEMENT_FACTORIES.get(modality); if (factory != null) { return factory.getSeriesMimeType(); } // Type for the default 2D viewer return DicomMediaIO.SERIES_MIMETYPE; } @Override public void dispose() { stopPreloading(this); super.dispose(); } @Override public DicomImageElement getNearestImage(double location, int offset, Filter<DicomImageElement> filter, Comparator<DicomImageElement> sort) { Iterable<DicomImageElement> mediaList = getMedias(filter, sort); DicomImageElement nearest = null; int index = 0; int bestIndex = -1; synchronized (this) { double bestDiff = Double.MAX_VALUE; for (Iterator<DicomImageElement> iter = mediaList.iterator(); iter.hasNext();) { DicomImageElement dcm = iter.next(); double[] val = (double[]) dcm.getTagValue(TagW.SlicePosition); if (val != null) { double diff = Math.abs(location - (val[0] + val[1] + val[2])); if (diff < bestDiff) { bestDiff = diff; nearest = dcm; bestIndex = index; if (MathUtil.isEqualToZero(diff)) { break; } } } index++; } } if (offset > 0) { return getMedia(bestIndex + offset, filter, sort); } return nearest; } @Override public int getNearestImageIndex(double location, int offset, Filter<DicomImageElement> filter, Comparator<DicomImageElement> sort) { Iterable<DicomImageElement> mediaList = getMedias(filter, sort); int index = 0; int bestIndex = -1; synchronized (this) { double bestDiff = Double.MAX_VALUE; for (Iterator<DicomImageElement> iter = mediaList.iterator(); iter.hasNext();) { DicomImageElement dcm = iter.next(); double[] val = (double[]) dcm.getTagValue(TagW.SlicePosition); if (val != null) { double diff = Math.abs(location - (val[0] + val[1] + val[2])); if (diff < bestDiff) { bestDiff = diff; bestIndex = index; if (MathUtil.isEqualToZero(diff)) { break; } } } index++; } } return (offset > 0) ? (bestIndex + offset) : bestIndex; } public static synchronized void startPreloading(DicomSeries series, List<DicomImageElement> imageList, int currentIndex) { if (series != null && imageList != null) { if (preloadingTask != null) { if (preloadingTask.getSeries() == series) { return; } stopPreloading(preloadingTask.getSeries()); } preloadingTask = new PreloadingTask(series, imageList, currentIndex); preloadingTask.start(); } } public static synchronized void stopPreloading(DicomSeries series) { if (preloadingTask != null && preloadingTask.getSeries() == series) { PreloadingTask moribund = preloadingTask; preloadingTask = null; if (moribund != null) { moribund.setPreloading(false); moribund.interrupt(); } } } static class PreloadingTask extends Thread { private volatile boolean preloading = true; private final int index; private final List<DicomImageElement> imageList; private final DicomSeries series; public PreloadingTask(DicomSeries series, List<DicomImageElement> imageList, int currentIndex) { this.series = series; this.imageList = imageList; this.index = currentIndex; } public synchronized boolean isPreloading() { return preloading; } public DicomSeries getSeries() { return series; } public List<DicomImageElement> getImageList() { return imageList; } public synchronized void setPreloading(boolean preloading) { this.preloading = preloading; } private static void freeMemory() { System.gc(); try { Thread.sleep(50); } catch (InterruptedException ex) { } } private static long evaluateImageSize(DicomImageElement image) { Integer allocated = TagD.getTagValue(image, Tag.BitsAllocated, Integer.class); Integer sample = TagD.getTagValue(image, Tag.SamplesPerPixel, Integer.class); Integer rows = TagD.getTagValue(image, Tag.Rows, Integer.class); Integer columns = TagD.getTagValue(image, Tag.Columns, Integer.class); if (allocated != null && sample != null && rows != null && columns != null) { return (rows * columns * sample * allocated) / 8L; } return 0L; } private void loadArrays(DicomImageElement img, DataExplorerModel model) { // Do not load an image if another process already loading it if (preloading && !img.isLoading()) { Boolean cache = (Boolean) img.getTagValue(TagW.ImageCache); if (cache == null || !cache) { long start = System.currentTimeMillis(); PlanarImage i = img.getImage(); if (i != null) { int tymin = i.getMinTileY(); int tymax = i.getMaxTileY(); int txmin = i.getMinTileX(); int txmax = i.getMaxTileX(); for (int tj = tymin; tj <= tymax; tj++) { for (int ti = txmin; ti <= txmax; ti++) { try { i.getTile(ti, tj); } catch (OutOfMemoryError e) { LOGGER.error("Out of memory when loading image: {}", img, e); //$NON-NLS-1$ freeMemory(); return; } } } } long stop = System.currentTimeMillis(); LOGGER.debug("Reading time: {} ms of image: {}", stop - start, img); //$NON-NLS-1$ if (model != null) { model.firePropertyChange(new ObservableEvent(ObservableEvent.BasicAction.ADD, model, null, new SeriesEvent(SeriesEvent.Action.PRELOADING, series, img))); } } } } @Override public void run() { if (imageList != null) { DataExplorerModel model = (DataExplorerModel) series.getTagValue(TagW.ExplorerModel); int size = imageList.size(); if (model == null || index < 0 || index >= size) { return; } long imgSize = evaluateImageSize(imageList.get(index)) * size + 5000; long heapSize = Runtime.getRuntime().totalMemory(); long heapFreeSize = Runtime.getRuntime().freeMemory(); if (imgSize > heapSize / 3) { if (imgSize > heapFreeSize) { freeMemory(); } double val = (double) heapFreeSize / imgSize; int ajustSize = (int) (size * val) / 2; int start = index - ajustSize; if (start < 0) { ajustSize -= start; start = 0; } if (ajustSize > size) { ajustSize = size; } for (int i = start; i < ajustSize; i++) { loadArrays(imageList.get(i), model); } } else { if (imgSize > heapFreeSize) { freeMemory(); } for (DicomImageElement img : imageList) { loadArrays(img, model); } } } } } }