Commit d98c5da3 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '1455-reactions-overlap-structural-modification-bubbles' into 'master'

Resolve "Reactions overlap structural modification bubbles"

See merge request !1348
parents e9839c04 a3061229
Pipeline #47238 passed with stage
in 37 minutes and 35 seconds
minerva (16.1.0~alpha.0) stable; urgency=medium
* Small improvement: API provides information about modifier type (#1085)
* Small improvement: map file for JavaScript is properly referenced (#1545)
* Small improvement: z-index for modification residues and structural states
is imported from SBML file if available
* Bug fix: api endpoints were exposed without 'api' prefix
* Bug fix: anonymous user could upload file using API
* Bug fix: when passing JSON in patch/post methods contentType was not
......
......@@ -337,11 +337,11 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
RestAnnotationParser rap = new RestAnnotationParser();
rap.processNotes(
"Symbol: ROS1\r\nName: c-ros oncogene 1 , receptor tyrosine kinase\r\n"
+ "Description: RecName: Full=Proto-oncogene tyrosine-protein kinase ROS; EC=2.7.10.1; "
+ "AltName: Full=Proto-oncogene c-Ros; AltName: Full=Proto-oncogene c-Ros-1; "
+ "AltName: Full=Receptor tyrosine kinase c-ros oncogene 1; "
+ "AltName: Full=c-Ros receptor tyrosine kinase; Flags: Precursor;\r\n"
+ "Previous Symbols:\r\nSynonyms: ROS, MCF3",
+ "Description: RecName: Full=Proto-oncogene tyrosine-protein kinase ROS; EC=2.7.10.1; "
+ "AltName: Full=Proto-oncogene c-Ros; AltName: Full=Proto-oncogene c-Ros-1; "
+ "AltName: Full=Receptor tyrosine kinase c-ros oncogene 1; "
+ "AltName: Full=c-Ros receptor tyrosine kinase; Flags: Precursor;\r\n"
+ "Previous Symbols:\r\nSynonyms: ROS, MCF3",
speciesAlias);
model.addElement(speciesAlias);
......@@ -353,10 +353,10 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
speciesAlias2.setName("PDK1");
rap.processNotes(
"Symbol: ROS1\r\nName: c-ros oncogene 1 , receptor tyrosine kinase\r\n"
+ "Description: RecName: Full=Proto-oncogene tyrosine-protein kinase ROS; EC=2.7.10.1; "
+ "AltName: Full=Proto-oncogene c-Ros; AltName: Full=Proto-oncogene c-Ros-1; "
+ "AltName: Full=Receptor tyrosine kinase c-ros oncogene 1; AltName: Full=c-Ros receptor tyrosine kinase; Flags: Precursor;\r\n"
+ "Previous Symbols:\r\nSynonyms: ROS, MCF3",
+ "Description: RecName: Full=Proto-oncogene tyrosine-protein kinase ROS; EC=2.7.10.1; "
+ "AltName: Full=Proto-oncogene c-Ros; AltName: Full=Proto-oncogene c-Ros-1; "
+ "AltName: Full=Receptor tyrosine kinase c-ros oncogene 1; AltName: Full=c-Ros receptor tyrosine kinase; Flags: Precursor;\r\n"
+ "Previous Symbols:\r\nSynonyms: ROS, MCF3",
speciesAlias2);
model.addElement(speciesAlias2);
......@@ -931,7 +931,8 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
Reactant newReactant = reaction2.getReactants().get(0);
// center part of the line shouldn't change - edges should be aligned to touch
// center part of the line shouldn't change - edges should be aligned to
// touch
// species
PointComparator pc = new PointComparator(Configuration.EPSILON);
assertEquals(0,
......@@ -975,7 +976,7 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
Model model = parser.createModel(new ConverterParams().filename("testFiles/orthogonal_reaction_to_phenotype.xml"));
Reaction r = model.getReactionByReactionId("re2");
assertEquals(r.getReactants().get(0).getLine().getStartPoint().getY(),
assertEquals(r.getReactants().get(0).getLine().getStartPoint().getY(),
r.getProducts().get(0).getLine().getStartPoint().getY(), Configuration.EPSILON);
}
......@@ -984,9 +985,8 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
Model model = parser.createModel(new ConverterParams().filename("testFiles/orthogonal_reaction_to_drug.xml"));
Reaction r = model.getReactionByReactionId("re3");
assertEquals(r.getReactants().get(0).getLine().getStartPoint().getY(),
assertEquals(r.getReactants().get(0).getLine().getStartPoint().getY(),
r.getProducts().get(0).getLine().getStartPoint().getY(), Configuration.EPSILON);
}
}
......@@ -46,7 +46,7 @@ import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.geometry.ColorParser;
import lcsb.mapviewer.converter.ConverterException;
import lcsb.mapviewer.converter.annotation.XmlAnnotationParser;
import lcsb.mapviewer.converter.graphics.bioentity.element.species.SpeciesConverter;
import lcsb.mapviewer.converter.graphics.bioentity.element.species.ModificationResidueConverter;
import lcsb.mapviewer.converter.graphics.bioentity.reaction.ReactionConverter;
import lcsb.mapviewer.converter.model.celldesigner.CommonXmlParser;
import lcsb.mapviewer.model.LogMarker;
......@@ -586,8 +586,8 @@ public class SbgnmlXmlExporter {
glyph.setId(mr.getIdModificationResidue());
glyph.setClazz(GlyphClazz.STATE_VARIABLE.getClazz());
double width = SpeciesConverter.DEFAULT_MODIFICATION_DIAMETER;
double height = SpeciesConverter.DEFAULT_MODIFICATION_DIAMETER;
double width = ModificationResidueConverter.DEFAULT_MODIFICATION_DIAMETER;
double height = ModificationResidueConverter.DEFAULT_MODIFICATION_DIAMETER;
if (mr instanceof AbstractSiteModification) {
AbstractSiteModification modification = (AbstractSiteModification) mr;
Glyph.State state = new Glyph.State();
......
......@@ -868,7 +868,6 @@ public class SbgnmlXmlParser {
Residue residue = stateVariableToResidue(child);
if (residue != null && (!canAssignStructuralState || residue.getState() != null
|| (child.getState() == null || child.getState().getValue() == null))) {
// if (residue != null) {
if (newSpecies instanceof Protein) {
((Protein) newSpecies).addResidue(residue);
} else if (newSpecies instanceof Complex) {
......@@ -1232,35 +1231,35 @@ public class SbgnmlXmlParser {
/**
* Method used to parse state variable.
*
* @param unitOfInformationGlyph
* @param stateVariable
* unit of information glyph from sbgn-ml file
* @throws Exception
* Exception is thrown if state variable is parsed for species other
* than Protein
*/
private Residue stateVariableToResidue(final Glyph unitOfInformationGlyph) {
if (unitOfInformationGlyph.getState() != null && (unitOfInformationGlyph.getState().getVariable() == null
|| unitOfInformationGlyph.getState().getVariable().trim().isEmpty())) {
private Residue stateVariableToResidue(final Glyph stateVariable) {
if (stateVariable.getState() != null && (stateVariable.getState().getVariable() == null
|| stateVariable.getState().getVariable().trim().isEmpty())) {
return null;
}
Residue mr = new Residue();
mr.setIdModificationResidue(unitOfInformationGlyph.getId());
if (unitOfInformationGlyph.getState() != null) {
mr.setIdModificationResidue(stateVariable.getId());
if (stateVariable.getState() != null) {
// If State variable consists of value and variable
mr.setName(unitOfInformationGlyph.getState().getVariable());
mr.setName(stateVariable.getState().getVariable());
for (final ModificationState ms : ModificationState.values()) {
if (ms.getAbbreviation().equals(unitOfInformationGlyph.getState().getValue())) {
if (ms.getAbbreviation().equals(stateVariable.getState().getValue())) {
mr.setState(ms);
}
}
}
// Compute the angle from coordinates and dimensions
double x = unitOfInformationGlyph.getBbox().getX() + unitOfInformationGlyph.getBbox().getW() / 2;
double y = unitOfInformationGlyph.getBbox().getY() + unitOfInformationGlyph.getBbox().getH() / 2;
double x = stateVariable.getBbox().getX() + stateVariable.getBbox().getW() / 2;
double y = stateVariable.getBbox().getY() + stateVariable.getBbox().getH() / 2;
mr.setPosition(new Point2D.Double(x, y));
......
......@@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
......@@ -128,10 +129,10 @@ public class CellDesignerToSbgnTest extends SbgnmlTestFunctions {
Model model = converter.createModel(new ConverterParams().filename("testFiles/cellDesigner/modifications.xml"));
String output = File.createTempFile("temp-sbgn-output", ".sbgn").getAbsolutePath();
converter2.model2File(model, output);
String content = converter2.model2String(model);
Model model2 = converter2.createModel(new ConverterParams().filename(output).sizeAutoAdjust(false));
InputStream is = new ByteArrayInputStream(content.getBytes());
Model model2 = converter2.createModel(new ConverterParams().inputStream(is).sizeAutoAdjust(false));
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
......@@ -143,12 +144,10 @@ public class CellDesignerToSbgnTest extends SbgnmlTestFunctions {
for (final ModificationResidue region : ((Protein) model2.getElementByElementId("sa3988")).getModificationResidues()) {
set2.add(region.toString());
}
SetComparator<String> comparator = new SetComparator<>(new StringComparator());
assertEquals(0, comparator.compare(set1, set2));
new File(output).delete();
}
@Test
......@@ -165,5 +164,4 @@ public class CellDesignerToSbgnTest extends SbgnmlTestFunctions {
assertTrue(model2.getElements().iterator().next() instanceof AntisenseRna);
}
}
......@@ -121,7 +121,7 @@
<celldesigner:protein id="p_s_id_sa3989" name="NFKB1" type="GENERIC">
</celldesigner:protein>
<celldesigner:protein id="p_s_id_sa3987" name="NFKBIA" type="GENERIC">
<celldesigner:listOfModificationResidues><celldesigner:modificationResidue id="rs1" name="S32" angle="3.87729847927592"></celldesigner:modificationResidue><celldesigner:modificationResidue id="rs2" name="S36" angle="2.4376765858046574"></celldesigner:modificationResidue><celldesigner:modificationResidue id="rs4" angle="-1.5001322409935126E-12"></celldesigner:modificationResidue></celldesigner:listOfModificationResidues>
<celldesigner:listOfModificationResidues><celldesigner:modificationResidue id="rs1" name="S32" angle="3.87729847927592"></celldesigner:modificationResidue><celldesigner:modificationResidue id="rs2" name="S36" angle="2.4376765858046574"></celldesigner:modificationResidue></celldesigner:listOfModificationResidues>
</celldesigner:protein>
</celldesigner:listOfProteins>
<celldesigner:listOfGenes></celldesigner:listOfGenes>
......@@ -129,9 +129,6 @@
<celldesigner:listOfAntisenseRNAs></celldesigner:listOfAntisenseRNAs>
<celldesigner:listOfLayers><celldesigner:layer id ="1" name ="Layout annotation" locked ="false" visible ="false">
<celldesigner:listOfTexts>
<celldesigner:layerSpeciesAlias><celldesigner:layerNotes>
Inflammation Signalling
</celldesigner:layerNotes><celldesigner:paint color="FF000000"/><celldesigner:bounds x="207.16666666666606" y="142.0" w="1825.0" h="985.0"/><celldesigner:font size="11"/></celldesigner:layerSpeciesAlias>
</celldesigner:listOfTexts>
<celldesigner:listOfSquares>
</celldesigner:listOfSquares>
......
......@@ -52,10 +52,6 @@
<celldesigner:listOfProteins>
<celldesigner:protein id="pr1" name="s1" type="GENERIC">
<celldesigner:listOfModificationResidues>
<celldesigner:modificationResidue angle="3.141592653589793" id="rs1" side="none"/>
<celldesigner:modificationResidue angle="3.04" id="rs2" name="x" side="none"/>
<celldesigner:modificationResidue angle="2.94" id="rs3" name="a" side="none"/>
<celldesigner:modificationResidue angle="5.21" id="rs4" side="none"/>
</celldesigner:listOfModificationResidues>
</celldesigner:protein>
</celldesigner:listOfProteins>
......
......@@ -18,6 +18,9 @@ import lcsb.mapviewer.commands.SemanticZoomLevelMatcher;
import lcsb.mapviewer.common.MimeType;
import lcsb.mapviewer.common.Pair;
import lcsb.mapviewer.converter.graphics.bioentity.BioEntityConverterImpl;
import lcsb.mapviewer.converter.graphics.bioentity.element.species.ModificationResidueConverter;
import lcsb.mapviewer.converter.graphics.bioentity.element.species.StructuralStateConverter;
import lcsb.mapviewer.converter.graphics.bioentity.element.species.StructuralStateSbgnConverter;
import lcsb.mapviewer.converter.graphics.layer.LayerLineConverter;
import lcsb.mapviewer.converter.graphics.layer.LayerOvalConverter;
import lcsb.mapviewer.converter.graphics.layer.LayerRectConverter;
......@@ -27,7 +30,6 @@ import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.Drawable;
import lcsb.mapviewer.model.map.compartment.Compartment;
import lcsb.mapviewer.model.map.compartment.PathwayCompartment;
import lcsb.mapviewer.model.map.layout.graphics.Layer;
import lcsb.mapviewer.model.map.layout.graphics.LayerOval;
import lcsb.mapviewer.model.map.layout.graphics.LayerRect;
import lcsb.mapviewer.model.map.layout.graphics.LayerText;
......@@ -36,6 +38,8 @@ import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.species.Complex;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Species;
import lcsb.mapviewer.model.map.species.field.ModificationResidue;
import lcsb.mapviewer.model.map.species.field.StructuralState;
import lcsb.mapviewer.model.overlay.DataOverlayEntry;
/**
......@@ -210,17 +214,11 @@ public abstract class AbstractImageGenerator {
// Get the SBGN display format option from the model
this.sbgnFormat = params.isSbgn();
List<Drawable> bioEntities = new ArrayList<>();
bioEntities.addAll(params.getModel().getBioEntities());
for (final Layer layer : params.getModel().getLayers()) {
if (layer.isVisible()) {
bioEntities.addAll(layer.getDrawables());
}
}
bioEntities.sort(BioEntity.Z_INDEX_COMPARATOR);
List<Drawable> drawables = new ArrayList<>(params.getModel().getDrawables(true));
drawables.sort(BioEntity.Z_INDEX_COMPARATOR);
// draw all elements
for (final Drawable element : bioEntities) {
for (final Drawable element : drawables) {
if (element instanceof Species) {
drawSpecies((Species) element);
} else if (element instanceof Reaction) {
......@@ -235,6 +233,10 @@ public abstract class AbstractImageGenerator {
drawRect((LayerRect) element);
} else if (element instanceof PolylineData) {
drawLine((PolylineData) element);
} else if (element instanceof StructuralState) {
drawStructuralState((StructuralState) element);
} else if (element instanceof ModificationResidue) {
drawModificationResidue((ModificationResidue) element);
} else {
throw new DrawingException("Unknown class type: " + element);
}
......@@ -243,6 +245,7 @@ public abstract class AbstractImageGenerator {
setDrawn(true);
}
private void drawText(final LayerText element) {
new LayerTextConverter().draw(element, graphics);
}
......@@ -259,6 +262,25 @@ public abstract class AbstractImageGenerator {
new LayerLineConverter().draw(element, graphics);
}
private void drawStructuralState(final StructuralState structuralState) {
ConverterParams stateParams = new ConverterParams().level(level).nested(params.nested);
if (this.sbgnFormat) {
new StructuralStateSbgnConverter().draw(structuralState, graphics, stateParams);
} else {
new StructuralStateConverter().draw(structuralState, graphics, stateParams);
}
}
protected void drawModificationResidue(final ModificationResidue structuralState) {
ConverterParams stateParams = new ConverterParams().level(level).nested(params.nested);
if (this.sbgnFormat) {
new ModificationResidueConverter().drawModification(structuralState, graphics, stateParams);
} else {
new ModificationResidueConverter().drawModification(structuralState, graphics, stateParams);
}
}
/**
* Method called after drawing. It should close drawing canvas properly.
*/
......
package lcsb.mapviewer.converter.graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.List;
import lcsb.mapviewer.commands.SemanticZoomLevelMatcher;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.common.exception.InvalidStateException;
import lcsb.mapviewer.model.graphics.HorizontalAlign;
import lcsb.mapviewer.model.graphics.VerticalAlign;
import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.Drawable;
import lcsb.mapviewer.model.map.compartment.Compartment;
import lcsb.mapviewer.model.map.reaction.AbstractNode;
import lcsb.mapviewer.model.map.reaction.NodeOperator;
import lcsb.mapviewer.model.map.reaction.Product;
import lcsb.mapviewer.model.map.reaction.Reactant;
import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.reaction.ReactionNode;
import lcsb.mapviewer.model.map.species.Complex;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Species;
public abstract class DrawableConverter<T extends Drawable> {
/**
* Class that allows to check if element is visible (or transparent) when
* drawing. It's used to filter out invisible elements when drawing
* semantic/hierarchy view.
*/
private SemanticZoomLevelMatcher zoomLevelMatcher = new SemanticZoomLevelMatcher();
/**
* This method draws a string on graphics using current font. The coordinates
* of the text is given as a point. Both parameters centered described if text
* should be automatically centered horizontally and vertically.
*
* @param border
* where the text should be drawn
* @param text
* text to draw
* @param graphics
* where we want to draw the object
* @param horizontalCentered
* should the text be horizontally centered
* @param verticalCentered
* should the text be vertically centered
*/
protected void drawText(final Rectangle2D border, final String text, final Graphics2D graphics,
final HorizontalAlign horizontalAlign, final VerticalAlign verticalAlign) {
double textHeight = getTextHeight(text, graphics);
double y = border.getY();
String[] lines = text.split("\n");
double lineHeight = graphics.getFontMetrics().getHeight();
switch (verticalAlign) {
case TOP:
y = border.getMinY() + graphics.getFontMetrics().getAscent();
break;
case MIDDLE:
y = border.getCenterY() - (textHeight / 2 - graphics.getFontMetrics().getAscent());
break;
case BOTTOM:
y = border.getMaxY() - (textHeight - graphics.getFontMetrics().getAscent());
break;
default:
throw new InvalidArgumentException("Don't know how to align text with: " + verticalAlign);
}
for (final String string : lines) {
double currX;
switch (horizontalAlign) {
case LEFT:
currX = border.getX();
break;
case CENTER:
currX = border.getCenterX() - getTextWidth(string, graphics) / 2;
break;
case RIGTH:
currX = border.getMaxX() - getTextWidth(string, graphics);
break;
default:
throw new InvalidArgumentException("Don't know how to align text with: " + horizontalAlign);
}
graphics.drawString(string, (int) currX, (int) y);
y += lineHeight;
}
}
/**
* Return width of the text.
*
* @param text
* width of this text will be computed
* @param graphics
* the width will be computed assuming using this graphics
* @return width of the text
*/
protected double getTextWidth(final String text, final Graphics2D graphics) {
if (text == null) {
return 0;
}
if (text.equals("")) {
return 0;
}
double result = 0;
String[] lines = text.split("\n");
for (final String string : lines) {
result = Math.max(result, graphics.getFontMetrics().stringWidth(string));
}
return result;
}
/**
* Returns text height.
*
* @param text
* height of this text will be computed
* @param graphics
* the height will be computed assuming using this graphics
* @return height of the text
*/
protected double getTextHeight(final String text, final Graphics2D graphics) {
if (text == null) {
return 0;
}
if (text.equals("")) {
return 0;
}
double result = 0;
int lines = text.split("\n").length;
result = graphics.getFontMetrics().getHeight() * lines;
return result;
}
/**
* Checks if {@link AbstractNode} is visible according to visualization given
* in params.
*
* @param node
* visibility of this object will be checked
* @param params
* visualization params
* @return true if object is visible
*/
protected boolean isVisible(final AbstractNode node, final ConverterParams params) {
if (node instanceof NodeOperator) {
return isVisible((NodeOperator) node, params);
} else if (node instanceof ReactionNode) {
return isVisible(((ReactionNode) node).getElement(), params);
} else {
throw new InvalidArgumentException("Unknown class type: " + node.getClass());
}
}
/**
* Checks if {@link NodeOperator} is visible according to visualization given
* in params.
*
* @param operator
* visibility of this object will be checked
* @param params
* visualization params
* @return true if object is visible
*/
protected boolean isVisible(final NodeOperator operator, final ConverterParams params) {
boolean result = false;
if (operator.isModifierOperator() || operator.isReactantOperator()) {
for (final AbstractNode input : operator.getInputs()) {
result |= isVisible(input, params);
}
} else if (operator.isProductOperator()) {
for (final AbstractNode output : operator.getOutputs()) {
result |= isVisible(output, params);
}
} else {
throw new InvalidStateException("Unknown class state: " + operator);
}
return result;
}
/**
* Checks if {@link BioEntity} is visible according to visualization given in
* params.
*
* @param bioEntity
* visibility of this object will be checked
* @param params
* visualization params
* @return true if object is visible
*/
protected boolean isVisible(final BioEntity bioEntity, final ConverterParams params) {
if (params.isNested()) {
boolean result = zoomLevelMatcher.isVisible(params.getLevel(), bioEntity.getVisibilityLevel());
if (bioEntity instanceof Element) {
Compartment compartment = ((Element) bioEntity).getCompartment();
if (compartment != null) {
result &= isVisible(compartment, params);
}
if (bioEntity instanceof Species) {
Complex complex = ((Species) bioEntity).getComplex();
if (complex != null) {
result &= isVisible(complex, params);
}
}
} else if (bioEntity instanceof Reaction) {
if (!isAnyProductVisible(((Reaction) bioEntity).getProducts(), params)) {
result = false;
} else if (!isAnyReactantVisible(((Reaction) bioEntity).getReactants(), params)) {
result = false;
}
} else {
throw new InvalidArgumentException("Unknown class type: " + bioEntity.getClass());
}
return result;
}
return true;
}
/**
* Checks if {@link Element} given in the argument is transparent according to
* the level given in the {@link ConverterParams}.
*
* @param bioEntity
* {@link BioEntity} to be checked
* @param params
* params against which check is run
* @return <code>true</code> if object is transparent, <code>false</code>
* otherwise
*/
protected boolean isTransparent(final Element bioEntity, final ConverterParams params) {
return zoomLevelMatcher.isTransparent(params.getLevel(), bioEntity.getTransparencyLevel()) || !params.isNested();
}
/**
* Checks if at least one reactant is visible.
*
* @param reactants
* list of reactants
* @param params
* params against which check is run
* @return true if at least one reactant is visible
*/
private boolean isAnyReactantVisible(final List<Reactant> reactants, final ConverterParams params) {
boolean result = false;