Commit 894d6e1a authored by Piotr Gawron's avatar Piotr Gawron

modification residues are rendered in SBML

parent 61074fc5
......@@ -18,6 +18,8 @@ minerva (16.0.0~alpha.1) stable; urgency=medium
* Small improvement: styling of GENERIC search tab title improved (#1364)
* Small improvement: SBML render contains information about species shapes
(#1055)
* Small improvement: SBML render contains information about modification
residues (#1456)
* Bug fix: refreshing page after removing last project on the page redirect to
proper page (#1051)
* Bug fix: removing plugin that does not exist anymore does not raise an error
......
......@@ -27,6 +27,15 @@ public class NotesUtility {
static Logger logger = LogManager.getLogger();
static {
try {
// there is a bug in JSBML with initialization
new SBMLReader().readNotes(StringTools.toXMLNotesString(""));
} catch (XMLStreamException e) {
logger.error(e, e);
}
}
/**
* Extract notes from SBML node
*
......
......@@ -51,11 +51,9 @@ public abstract class SbmlElementExporter<T extends Element, S extends org.sbml.
textGlyph.setGraphicalObject(speciesGlyph.getId());
if (isExtensionEnabled(SbmlExtension.RENDER)) {
LocalRenderInformation renderInformation = getRenderInformation(getRenderPlugin());
LocalStyle style = new LocalStyle();
LocalStyle style = createStyle();
style.getIDList().add(textGlyph.getId());
RenderGroup group = new RenderGroup();
style.setGroup(group);
RenderGroup group = style.getGroup();
group.setVTextAnchor(VTextAnchor.MIDDLE);
group.setTextAnchor(HTextAnchor.MIDDLE);
switch (element.getNameHorizontalAlign()) {
......@@ -81,7 +79,6 @@ public abstract class SbmlElementExporter<T extends Element, S extends org.sbml.
break;
}
group.setStroke(getColorDefinition(element.getFontColor()).getId());
renderInformation.addLocalStyle(style);
}
textGlyph.setBoundingBox(createBoundingBox(x, y, element.getZ() + 1, width, height));
......@@ -89,7 +86,7 @@ public abstract class SbmlElementExporter<T extends Element, S extends org.sbml.
@Override
protected LocalStyle createStyle(T element) {
LocalStyle result = super.createStyle(element);
LocalStyle result = createStyle();
ColorDefinition color = getColorDefinition(element.getFillColor());
result.getGroup().setFill(color.getId());
result.getGroup().setFontSize(element.getFontSize().shortValue());
......
......@@ -51,7 +51,10 @@ public class SbmlExporter {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
SBMLWriter.write(doc, stream, "minerva", Configuration.getSystemVersion(null));
return stream.toString("UTF-8");
return stream.toString("UTF-8")
// COPASI does not accept double coordinates
.replace(".0\"", "\"")
.replace(".0%\"", "%\"");
} catch (UnsupportedEncodingException | XMLStreamException e) {
throw new InvalidStateException(e);
}
......
package lcsb.mapviewer.converter.model.sbml;
import java.awt.Color;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.ext.SBasePlugin;
import org.sbml.jsbml.ext.layout.*;
import org.sbml.jsbml.ext.render.*;
import lcsb.mapviewer.common.XmlParser;
import lcsb.mapviewer.model.graphics.ArrowType;
public class SbmlModelUtils {
private Logger logger = LogManager.getLogger();
private final Model sbmlModel;
private Layout layout;
public SbmlModelUtils(Model model) {
this.sbmlModel = model;
this.layout = computeLayout();
}
private Layout computeLayout() {
Layout layout = null;
if (sbmlModel.getExtensionCount() > 0) {
for (SBasePlugin plugin : sbmlModel.getExtensionPackages().values()) {
if (plugin.getClass().equals(org.sbml.jsbml.ext.layout.LayoutModelPlugin.class)) {
LayoutModelPlugin layoutPlugin = (LayoutModelPlugin) plugin;
if (layoutPlugin.getLayoutCount() == 0) {
logger.warn("Layout plugin available but no layouts defined");
} else if (layoutPlugin.getLayoutCount() > 1) {
logger.warn(layoutPlugin.getLayoutCount() + " layouts defined. Using first one.");
layout = layoutPlugin.getLayout(0);
} else {
layout = layoutPlugin.getLayout(0);
}
}
}
}
return layout;
}
public Layout getLayout() {
return layout;
}
public RenderLayoutPlugin getRenderPlugin() {
if (getLayout().getExtensionCount() > 0) {
return (RenderLayoutPlugin) getLayout().getExtension("render");
}
return null;
}
public ColorDefinition getColorDefinition(Color color) {
RenderLayoutPlugin renderPlugin = getRenderPlugin();
LocalRenderInformation renderInformation = getRenderInformation(renderPlugin);
for (ColorDefinition cd : renderInformation.getListOfColorDefinitions()) {
if (cd.getValue().equals(color)) {
return cd;
}
}
ColorDefinition colorDefinition = new ColorDefinition("color_" + XmlParser.colorToString(color), color);
renderInformation.addColorDefinition(colorDefinition);
return colorDefinition;
}
public LocalRenderInformation getRenderInformation() {
return getRenderInformation(getRenderPlugin());
}
public LocalRenderInformation getRenderInformation(RenderLayoutPlugin renderPlugin) {
LocalRenderInformation renderInformation = null;
for (LocalRenderInformation lri : renderPlugin.getListOfLocalRenderInformation()) {
if (lri.getId().equals("minerva_definitions")) {
renderInformation = lri;
}
}
if (renderInformation == null) {
renderInformation = new LocalRenderInformation("minerva_definitions");
renderPlugin.addLocalRenderInformation(renderInformation);
}
return renderInformation;
}
public LocalStyle createStyle() {
LocalRenderInformation renderInformation = getRenderInformation(getRenderPlugin());
LocalStyle style = new LocalStyle();
style.setGroup(new RenderGroup());
renderInformation.addLocalStyle(style);
return style;
}
protected LineEnding createLineEndingStyle(String endHead) {
String arrowTypeId = endHead.replaceAll("line_ending_", "");
LocalRenderInformation renderInformation = getRenderInformation();
LineEnding result = renderInformation.getListOfLineEndings().get("line_ending_" + arrowTypeId);
if (result != null) {
return result;
}
ArrowType arrowType = ArrowType.valueOf(arrowTypeId);
result = new LineEnding();
result.setId("line_ending_" + arrowTypeId);
result.setGroup(new RenderGroup());
renderInformation.getListOfLineEndings().add(result);
switch (arrowType) {
case FULL:
createFullLineEnding(result);
break;
case BLANK:
createBlankLineEnding(result);
break;
case FULL_CROSSBAR:
createFullCrossBarLineEnding(result);
break;
case BLANK_CROSSBAR:
createBlankCrossBarLineEnding(result);
break;
case CROSSBAR:
createCrossBarLineEnding(result);
break;
case DIAMOND:
createDiamondLineEnding(result);
break;
case OPEN:
createOpenLineEnding(result);
break;
case CIRCLE:
createCircleLineEnding(result);
break;
default:
logger.warn("Unknown arrow type: " + arrowType);
case NONE:
break;
}
return result;
}
private void createCircleLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(-2, 0, 0, 4, 4);
result.getGroup().setFill(getColorDefinition(Color.WHITE).getId());
result.setBoundingBox(boundingBox);
Ellipse ellipse = new Ellipse();
ellipse.setCx(new RelAbsVector(0, 0));
ellipse.setCy(new RelAbsVector(0, 0));
ellipse.setRx(new RelAbsVector(4, 0));
ellipse.setRy(new RelAbsVector(4, 0));
result.getGroup().addElement(ellipse);
}
private void createOpenLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(-12, -6, 0, 12, 12);
result.setBoundingBox(boundingBox);
Polygon polygon = new Polygon();
polygon.addElement(createRenderPoint(0, 0));
polygon.addElement(createRenderPoint(100, 50));
polygon.addElement(createRenderPoint(0, 100));
polygon.addElement(createRenderPoint(100, 50));
result.getGroup().addElement(polygon);
}
private void createDiamondLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(-18, -6, 0, 18, 12);
result.setBoundingBox(boundingBox);
result.getGroup().setFill(getColorDefinition(Color.WHITE).getId());
Polygon polygon = new Polygon();
polygon.addElement(createRenderPoint(50, 0));
polygon.addElement(createRenderPoint(100, 50));
polygon.addElement(createRenderPoint(50, 100));
polygon.addElement(createRenderPoint(0, 50));
polygon.addElement(createRenderPoint(50, 0));
result.getGroup().addElement(polygon);
}
private void createCrossBarLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(0, -6, 0, 1, 12);
result.setBoundingBox(boundingBox);
Polygon crossBar = new Polygon();
crossBar.addElement(createRenderPoint(0, 0));
crossBar.addElement(createRenderPoint(0, 100));
result.getGroup().addElement(crossBar);
}
private void createBlankLineEnding(LineEnding result) {
createFullLineEnding(result);
result.getGroup().setFill(getColorDefinition(Color.WHITE).getId());
}
private void createFullLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(-12, -6, 0, 12, 12);
result.setBoundingBox(boundingBox);
result.getGroup().setFill(getColorDefinition(Color.BLACK).getId());
Polygon polygon = new Polygon();
polygon.addElement(createRenderPoint(0, 0));
polygon.addElement(createRenderPoint(100, 50));
polygon.addElement(createRenderPoint(0, 100));
polygon.addElement(createRenderPoint(0, 0));
result.getGroup().addElement(polygon);
}
private void createBlankCrossBarLineEnding(LineEnding result) {
BoundingBox boundingBox = createBoundingBox(-18, -6, 0, 18, 12);
result.setBoundingBox(boundingBox);
result.getGroup().setFill(getColorDefinition(Color.WHITE).getId());
Polygon polygon = new Polygon();
polygon.addElement(createRenderPoint(33, 0));
polygon.addElement(createRenderPoint(100, 50));
polygon.addElement(createRenderPoint(33, 100));
polygon.addElement(createRenderPoint(33, 0));
result.getGroup().addElement(polygon);
Polygon crossBar = new Polygon();
crossBar.addElement(createRenderPoint(0, 0));
crossBar.addElement(createRenderPoint(0, 100));
result.getGroup().addElement(crossBar);
}
private void createFullCrossBarLineEnding(LineEnding result) {
createBlankCrossBarLineEnding(result);
result.getGroup().setFill(getColorDefinition(Color.BLACK).getId());
}
protected BoundingBox createBoundingBox(double x, double y, int z, double w, double h) {
BoundingBox boundingBox = new BoundingBox();
boundingBox.setPosition(new Point(x, y, z));
Dimensions dimensions = new Dimensions();
dimensions.setWidth(w);
dimensions.setHeight(h);
boundingBox.setDimensions(dimensions);
return boundingBox;
}
protected RenderPoint createRenderPoint(double percentX, int percentY) {
RenderPoint result = new RenderPoint();
result.setX(new RelAbsVector(0, Math.round(percentX)));
result.setY(new RelAbsVector(0, Math.round(percentY)));
return result;
}
}
......@@ -313,10 +313,7 @@ public class SbmlReactionExporter extends SbmlBioEntityExporter<Reaction, org.sb
}
private LocalStyle createStyle(PolylineData line) {
LocalRenderInformation renderInformation = getRenderInformation(getRenderPlugin());
LocalStyle style = new LocalStyle();
style.setGroup(new RenderGroup());
renderInformation.addLocalStyle(style);
LocalStyle style = createStyle();
ColorDefinition color = getColorDefinition(line.getColor());
style.getGroup().setStroke(color.getId());
style.getGroup().setFill(color.getId());
......
package lcsb.mapviewer.converter.model.sbml.species;
import java.awt.Color;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.ext.layout.*;
import org.sbml.jsbml.ext.render.*;
import lcsb.mapviewer.converter.model.sbml.SbmlModelUtils;
import lcsb.mapviewer.model.map.species.field.AbstractRegionModification;
import lcsb.mapviewer.model.map.species.field.ModificationResidue;
public class ModificationResidueExporter {
private SbmlModelUtils sbmlModelUtils;
public ModificationResidueExporter(Model sbmlModel) {
this.sbmlModelUtils = new SbmlModelUtils(sbmlModel);
}
protected LocalStyle createStyle(ModificationResidue element) {
LocalStyle style = sbmlModelUtils.createStyle();
ColorDefinition color = sbmlModelUtils.getColorDefinition(Color.BLACK);
ColorDefinition background = sbmlModelUtils.getColorDefinition(Color.WHITE);
style.getGroup().setStroke(color.getId());
style.getGroup().setStrokeWidth(1.0);
style.getGroup().setFill(background.getId());
style.getGroup().setFontSize((short) 10);
Ellipse ellipse = new Ellipse();
ellipse.setCx(new RelAbsVector(0, 50));
ellipse.setCy(new RelAbsVector(0, 50));
ellipse.setRx(new RelAbsVector(0, 50));
ellipse.setRy(new RelAbsVector(0, 50));
style.getGroup().addElement(ellipse);
return style;
}
public LocalStyle createTextStyle(ModificationResidue mr) {
LocalStyle style = sbmlModelUtils.createStyle();
ColorDefinition color = sbmlModelUtils.getColorDefinition(Color.BLACK);
ColorDefinition background = sbmlModelUtils.getColorDefinition(Color.WHITE);
style.getGroup().setStroke(color.getId());
style.getGroup().setStrokeWidth(1.0);
style.getGroup().setFill(background.getId());
style.getGroup().setFontSize((short) 10);
style.getGroup().setVTextAnchor(VTextAnchor.MIDDLE);
style.getGroup().setTextAnchor(HTextAnchor.MIDDLE);
return style;
}
public BoundingBox createBoundingBoxForModification(ModificationResidue mr) {
double width, height;
if (mr instanceof AbstractRegionModification) {
width = ((AbstractRegionModification) mr).getWidth();
height = ((AbstractRegionModification) mr).getHeight();
} else {
width = 12;
height = 12;
}
double x = mr.getPosition().getX() - width / 2;
double y = mr.getPosition().getY() - height / 2;
BoundingBox boundingBox = new BoundingBox();
boundingBox.setPosition(new Point(x, y, mr.getSpecies().getZ() + 1));
Dimensions dimensions = new Dimensions();
dimensions.setWidth(width);
dimensions.setHeight(height);
boundingBox.setDimensions(dimensions);
return boundingBox;
}
public BoundingBox createBoundingBoxForModificationText(ModificationResidue mr) {
BoundingBox result = createBoundingBoxForModification(mr);
result.getPosition().setZ(result.getPosition().getZ() + 1);
return result;
}
}
......@@ -43,12 +43,15 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
private int structuralStateCounter = 0;
private Map<String, String> modificationResidueIds = new HashMap<>();
private final ModificationResidueExporter modificationResidueExporter;
public SbmlSpeciesExporter(Model sbmlModel,
lcsb.mapviewer.model.map.model.Model minervaModel,
Collection<SbmlExtension> sbmlExtensions,
SbmlCompartmentExporter compartmentExporter) {
super(sbmlModel, minervaModel, sbmlExtensions);
this.compartmentExporter = compartmentExporter;
this.modificationResidueExporter = new ModificationResidueExporter(sbmlModel);
}
private void assignMultiExtensionData(Species element, org.sbml.jsbml.Species result) {
......@@ -738,8 +741,8 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
private void createRelativePoint(Polygon polygon, double x, double y) {
RenderPoint p1 = polygon.createRenderPoint();
p1.setX(new RelAbsVector(0, x));
p1.setY(new RelAbsVector(0, y));
p1.setX(new RelAbsVector(0, Math.round(x)));
p1.setY(new RelAbsVector(0, Math.round(y)));
}
private void createAbsolutePoint(Polygon polygon, double x, double y) {
......@@ -883,10 +886,32 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
modificationFeatureValue.getId());
ReferenceGlyph referenceGlyph = new ReferenceGlyph("modification_reference_" + (idCounter++));
referenceGlyph.setGlyph(speciesGlyph.getId());
referenceGlyph.setBoundingBox(createBoundingBoxForModification(mr));
referenceGlyph.setBoundingBox(modificationResidueExporter.createBoundingBoxForModification(mr));
modificationGlyph.addReferenceGlyph(referenceGlyph);
modificationGlyph.setBoundingBox(createBoundingBoxForModification(mr));
modificationGlyph.setBoundingBox(modificationResidueExporter.createBoundingBoxForModification(mr));
if (mr instanceof AbstractSiteModification) {
ModificationState state = ((AbstractSiteModification) mr).getState();
if (state != null) {
String str = state.getAbbreviation();
TextGlyph textGlyph = getLayout().createTextGlyph("modification_text_" + (idCounter++), str);
textGlyph.setGraphicalObject(modificationGlyph.getId());
if (isExtensionEnabled(SbmlExtension.RENDER)) {
LocalStyle style = modificationResidueExporter.createTextStyle(mr);
assignStyleToGlyph(textGlyph, style);
}
textGlyph.setBoundingBox(modificationResidueExporter.createBoundingBoxForModificationText(mr));
}
}
if (isExtensionEnabled(SbmlExtension.RENDER)) {
LocalStyle style = modificationResidueExporter.createStyle(mr);
assignStyleToGlyph(modificationGlyph, style);
}
}
private BoundingBox createBoundingBoxForStructuralState(StructuralState structuralState) {
......@@ -903,26 +928,6 @@ public class SbmlSpeciesExporter extends SbmlElementExporter<Species, org.sbml.j
return boundingBox;
}
private BoundingBox createBoundingBoxForModification(ModificationResidue mr) {
double width, height;
if (mr instanceof AbstractRegionModification) {
width = ((AbstractRegionModification) mr).getWidth();
height = ((AbstractRegionModification) mr).getHeight();
} else {
width = 12;
height = 12;
}
double x = mr.getPosition().getX() - width / 2;
double y = mr.getPosition().getY() - height / 2;
BoundingBox boundingBox = new BoundingBox();
boundingBox.setPosition(new Point(x, y, mr.getSpecies().getZ() + 1));
Dimensions dimensions = new Dimensions();
dimensions.setWidth(width);
dimensions.setHeight(height);
boundingBox.setDimensions(dimensions);
return boundingBox;
}
private String getModificationResidueUniqueId(ModificationResidue mr) {
SpeciesWithModificationResidue species = (SpeciesWithModificationResidue) mr.getSpecies();
String modificationId = "mod" + species.getModificationResidues().indexOf(mr);
......
......@@ -23,10 +23,10 @@ import lcsb.mapviewer.converter.model.sbml.species.AllSbmlSpeciesTests;
MultiParserTest.class,
ReactionPropertiesExportToMultiTest.class,
SbmlBioEntityParserTest.class,
SbmlBioEntityExporterTest.class,
SbmlElementParserTest.class,
SbmlExporterTest.class,
SbmlExporterFromCellDesignerTest.class,
SbmlModelUtilsTest.class,
SbmlPareserForInvalidReactionTest.class,
SbmlParserTest.class,
SbmlValidationTests.class,
......
......@@ -2,63 +2,76 @@ package lcsb.mapviewer.converter.model.sbml;
import static org.junit.Assert.*;
import java.awt.*;
import java.awt.Color;
import org.junit.Test;
import org.mockito.Mockito;
import org.sbml.jsbml.Model;
import org.sbml.jsbml.SBMLDocument;
import org.sbml.jsbml.ext.layout.Layout;
import org.sbml.jsbml.ext.render.ColorDefinition;
import org.sbml.jsbml.ext.layout.LayoutModelPlugin;
import org.sbml.jsbml.ext.render.*;
import lcsb.mapviewer.model.graphics.ArrowType;
import lcsb.mapviewer.model.map.model.ModelFullIndexed;
public class SbmlBioEntityExporterTest extends SbmlTestFunctions {
public class SbmlModelUtilsTest {
@Test
public void testEmptyGetRenderPlugin() {
@SuppressWarnings("rawtypes")
SbmlBioEntityExporter exporter = createMockExporter();
Mockito.when(exporter.getLayout()).thenReturn(new Layout());
SBMLDocument doc = new SBMLDocument(3, 1);
Model result = doc.createModel();
LayoutModelPlugin plugin = new LayoutModelPlugin(result);
result.addExtension("layout", plugin);
plugin.addLayout(new Layout());
SbmlModelUtils sbmlModelUtils = new SbmlModelUtils(result);
assertNull(exporter.getRenderPlugin());
assertNull(sbmlModelUtils.getRenderPlugin());
}
@Test
public void testGetRenderPluginWithLayout() {
SbmlBioEntityExporter<?, ?> exporter = createMockExporter();
assertNotNull(exporter.getRenderPlugin());
}
Model result = createModelWithLayoutAndRender();
@SuppressWarnings("rawtypes")
private SbmlBioEntityExporter createMockExporter() {
SbmlBioEntityExporter exporter = Mockito.mock(SbmlBioEntityExporter.class, Mockito.CALLS_REAL_METHODS);
SbmlModelUtils sbmlModelUtils = new SbmlModelUtils(result);
SBMLDocument doc = new SBMLDocument(3, 1);
Model result = doc.createModel();
SbmlExporter sbmlExporter = new SbmlExporter();
Layout layout = sbmlExporter.createSbmlLayout(new ModelFullIndexed(null), result);
Mockito.when(exporter.getLayout()).thenReturn(layout);
return exporter;
assertNotNull(sbmlModelUtils.getRenderPlugin());
}
@Test