/*******************************************************************************
* 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.sr;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.border.EmptyBorder;
import javax.swing.event.HyperlinkEvent;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.weasis.core.api.explorer.DataExplorerView;
import org.weasis.core.api.explorer.ObservableEvent;
import org.weasis.core.api.gui.util.JMVUtils;
import org.weasis.core.api.media.data.ImageElement;
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.TagW;
import org.weasis.core.ui.docking.UIManager;
import org.weasis.core.ui.editor.SeriesViewerEvent;
import org.weasis.core.ui.editor.SeriesViewerEvent.EVENT;
import org.weasis.core.ui.editor.SeriesViewerFactory;
import org.weasis.core.ui.editor.SeriesViewerListener;
import org.weasis.core.ui.editor.ViewerPluginBuilder;
import org.weasis.core.ui.editor.image.ViewerPlugin;
import org.weasis.core.ui.model.GraphicModel;
import org.weasis.core.ui.model.graphic.Graphic;
import org.weasis.core.ui.model.imp.XmlGraphicModel;
import org.weasis.core.ui.model.layer.GraphicLayer;
import org.weasis.core.ui.model.layer.LayerType;
import org.weasis.core.ui.model.layer.imp.DefaultLayer;
import org.weasis.dicom.codec.DcmMediaReader;
import org.weasis.dicom.codec.DicomImageElement;
import org.weasis.dicom.codec.DicomMediaIO;
import org.weasis.dicom.codec.DicomSeries;
import org.weasis.dicom.codec.DicomSpecialElement;
import org.weasis.dicom.codec.KOSpecialElement;
import org.weasis.dicom.codec.TagD;
import org.weasis.dicom.codec.TagD.Level;
import org.weasis.dicom.codec.macro.SOPInstanceReference;
import org.weasis.dicom.codec.utils.DicomMediaUtils;
import org.weasis.dicom.explorer.DicomExplorer;
import org.weasis.dicom.explorer.DicomModel;
import org.weasis.dicom.explorer.LoadDicomObjects;
import org.weasis.dicom.explorer.MimeSystemAppFactory;
public class SRView extends JScrollPane implements SeriesViewerListener {
private final JTextPane htmlPanel = new JTextPane();
private final Map<String, SRImageReference> map = new HashMap<>();
private Series<?> series;
private KOSpecialElement keyReferences;
public SRView() {
this(null);
}
public SRView(Series<?> series) {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
htmlPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
htmlPanel.setEditorKit(JMVUtils.buildHTMLEditorKit(htmlPanel));
htmlPanel.setContentType("text/html"); //$NON-NLS-1$
htmlPanel.setEditable(false);
htmlPanel.addHyperlinkListener(e -> {
JTextPane pane = (JTextPane) e.getSource();
if (e.getEventType() == HyperlinkEvent.EventType.ENTERED) {
pane.setToolTipText(e.getDescription());
} else if (e.getEventType() == HyperlinkEvent.EventType.EXITED) {
pane.setToolTipText(null);
} else if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
String desc = e.getDescription();
URL url = e.getURL();
if (url == null && desc != null && desc.startsWith("#")) { //$NON-NLS-1$
htmlPanel.scrollToReference(desc.substring(1));
} else {
openRelatedSeries(e.getURL().getHost());
}
}
});
setPreferredSize(new Dimension(1024, 1024));
setSeries(series);
}
public JTextPane getHtmlPanel() {
return htmlPanel;
}
public synchronized Series<?> getSeries() {
return series;
}
public synchronized void setSeries(Series<?> newSeries) {
MediaSeries<?> oldsequence = this.series;
this.series = newSeries;
if (oldsequence == null && newSeries == null) {
return;
}
if (oldsequence != null && oldsequence.equals(newSeries)) {
return;
}
closingSeries(oldsequence);
if (series != null) {
// Should have only one object by series (if more, they are split in several sub-series in dicomModel)
DicomSpecialElement s = DicomModel.getFirstSpecialElement(series, DicomSpecialElement.class);
displayLimitedDicomInfo(s);
series.setOpen(true);
series.setFocused(true);
series.setSelected(true, null);
}
}
private void closingSeries(MediaSeries<?> mediaSeries) {
if (mediaSeries == null) {
return;
}
boolean open = false;
synchronized (UIManager.VIEWER_PLUGINS) {
List<ViewerPlugin<?>> plugins = UIManager.VIEWER_PLUGINS;
pluginList: for (final ViewerPlugin<?> plugin : plugins) {
List<? extends MediaSeries<?>> openSeries = plugin.getOpenSeries();
if (openSeries != null) {
for (MediaSeries<?> s : openSeries) {
if (mediaSeries == s) {
// The sequence is still open in another view or plugin
open = true;
break pluginList;
}
}
}
}
}
mediaSeries.setOpen(open);
// TODO setSelected and setFocused must be global to all view as open
mediaSeries.setSelected(false, null);
mediaSeries.setFocused(false);
}
public void dispose() {
if (series != null) {
closingSeries(series);
series = null;
}
}
@Override
public void changingViewContentEvent(SeriesViewerEvent event) {
EVENT type = event.getEventType();
if (EVENT.LAYOUT.equals(type) && event.getSeries() instanceof Series) {
setSeries((Series<?>) event.getSeries());
}
}
private void displayLimitedDicomInfo(DicomSpecialElement media) {
StringBuilder html = new StringBuilder();
if (media != null) {
SRReader reader = new SRReader(series, media);
map.clear();
reader.readDocumentGeneralModule(html, map);
}
htmlPanel.setText(html.toString());
this.setViewportView(htmlPanel);
}
private void openRelatedSeries(String reference) {
SRImageReference imgRef = map.get(reference);
if (imgRef != null) {
SOPInstanceReference ref = imgRef.getSopInstanceReference();
if (ref != null) {
DataExplorerView dicomView = org.weasis.core.ui.docking.UIManager.getExplorerplugin(DicomExplorer.NAME);
DicomModel model = null;
if (dicomView != null) {
model = (DicomModel) dicomView.getDataExplorerModel();
}
if (model != null) {
MediaSeriesGroup study = model.getParent(series, DicomModel.study);
MediaSeriesGroup patient = model.getParent(series, DicomModel.patient);
Series<?> s = findSOPInstanceReference(model, patient, study, ref.getReferencedSOPInstanceUID());
if (s instanceof DicomSeries) {
if (keyReferences == null) {
keyReferences = buildKO(model, (DicomSeries) s);
}
if (keyReferences != null) {
// TODO Handle multiframe and select the current frame or SOPInstanceUID
// int[] frames = ref.getReferencedFrameNumber();
keyReferences.addKeyObject(TagD.getTagValue(s, Tag.StudyInstanceUID, String.class),
TagD.getTagValue(s, Tag.SeriesInstanceUID, String.class),
ref.getReferencedSOPInstanceUID(), ref.getReferencedSOPClassUID());
SeriesViewerFactory plugin = UIManager.getViewerFactory(DicomMediaIO.SERIES_MIMETYPE);
if (plugin != null && !(plugin instanceof MimeSystemAppFactory)) {
addGraphicstoView(s.getMedia(0, null, null), imgRef);
String uid = UUID.randomUUID().toString();
Map<String, Object> props = Collections.synchronizedMap(new HashMap<String, Object>());
props.put(ViewerPluginBuilder.CMP_ENTRY_BUILD_NEW_VIEWER, false);
props.put(ViewerPluginBuilder.BEST_DEF_LAYOUT, false);
props.put(ViewerPluginBuilder.ICON,
new ImageIcon(model.getClass().getResource("/icon/16x16/key-images.png"))); //$NON-NLS-1$
props.put(ViewerPluginBuilder.UID, uid);
List<DicomSeries> seriesList = new ArrayList<>();
seriesList.add((DicomSeries) s);
ViewerPluginBuilder builder = new ViewerPluginBuilder(plugin, seriesList, model, props);
ViewerPluginBuilder.openSequenceInPlugin(builder);
model.firePropertyChange(
new ObservableEvent(ObservableEvent.BasicAction.SELECT, uid, null, keyReferences));
}
}
} else {
// TODO try to download if IHE IID has been configured
JOptionPane.showMessageDialog(this, Messages.getString("SRView.msg"), //$NON-NLS-1$
Messages.getString("SRView.open"), //$NON-NLS-1$
JOptionPane.WARNING_MESSAGE);
}
}
}
}
}
private void addGraphicstoView(MediaElement mediaElement, SRImageReference imgRef) {
if (mediaElement instanceof ImageElement && imgRef.getGraphics() != null && !imgRef.getGraphics().isEmpty()) {
GraphicModel modelList = (GraphicModel) mediaElement.getTagValue(TagW.PresentationModel);
// After getting a new image iterator, update the measurements
if (modelList == null) {
modelList = new XmlGraphicModel((ImageElement) mediaElement);
mediaElement.setTag(TagW.PresentationModel, modelList);
}
String layerName = "SCOORD [DICOM]";//$NON-NLS-1$
GraphicLayer layer = null;
for (GraphicLayer l : modelList.getLayers()) {
if (layerName.equals(l.getName())) {
layer = l;
break;
}
}
boolean addLayer = layer == null;
if (addLayer) {
layer = new DefaultLayer(LayerType.DICOM_SR);
layer.setName(layerName);
layer.setSerializable(false);
layer.setLocked(true);
layer.setSelectable(false);
layer.setLevel(305);
}
if (!addLayer) {
List<Graphic> models = modelList.getModels();
int size = 0;
synchronized (models ) {
for (Graphic g : models) {
boolean sr = layer.equals(g.getLayer());
if (sr) {
size ++;
}
}
}
if(imgRef.getGraphics().size() == size) {
return;
}
if(size > 0) {
modelList.deleteByLayer(layer);
}
}
for (Graphic graphic : imgRef.getGraphics()) {
graphic.setLayer(layer);
for (PropertyChangeListener listener : modelList.getGraphicsListeners()) {
graphic.addPropertyChangeListener(listener);
}
modelList.addGraphic(graphic);
}
}
}
private static Series<?> findSOPInstanceReference(DicomModel model, MediaSeriesGroup patient,
MediaSeriesGroup study, String sopUID) {
if (model != null && patient != null && sopUID != null) {
Series<?> s = null;
if (study != null) {
s = findSOPInstanceReference(model, study, sopUID);
if (s != null) {
return s;
}
}
synchronized (model) {
for (MediaSeriesGroup st : model.getChildren(patient)) {
if (st != study) {
s = findSOPInstanceReference(model, st, sopUID);
}
if (s != null) {
return s;
}
}
}
}
return null;
}
private KOSpecialElement buildKO(DicomModel model, DicomSeries s) {
DicomSpecialElement dcmElement = DicomModel.getFirstSpecialElement(series, DicomSpecialElement.class);
if (dcmElement != null) {
DicomImageElement dcm = s.getMedia(MediaSeries.MEDIA_POSITION.FIRST, null, null);
if (dcm != null && dcm.getMediaReader() instanceof DcmMediaReader) {
Attributes dicomSourceAttribute = ((DcmMediaReader) dcm.getMediaReader()).getDicomObject();
Attributes attributes =
DicomMediaUtils.createDicomKeyObject(dicomSourceAttribute, dcmElement.getShortLabel(), null);
new LoadDicomObjects(model, attributes).addSelectionAndnotify(); // must be executed in the EDT
for (KOSpecialElement koElement : DicomModel.getKoSpecialElements(s)) {
if (koElement.getMediaReader().getDicomObject().equals(attributes)) {
return koElement;
}
}
}
}
return null;
}
private static Series<?> findSOPInstanceReference(DicomModel model, MediaSeriesGroup study, String sopUID) {
if (model != null && study != null) {
TagW sopTag = TagD.getUID(Level.INSTANCE);
synchronized (model) {
for (MediaSeriesGroup seq : model.getChildren(study)) {
if (seq instanceof Series) {
Series<?> s = (Series<?>) seq;
if (s.hasMediaContains(sopTag, sopUID)) {
return s;
}
}
}
}
}
return null;
}
}