Commit 3fb09925 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch '487-custom-images-as-overlay-levels-or-background' into 'master'

Resolve "Custom images as overlay levels  or background"

Closes #487

See merge request !768
parents 4c305a38 cd1ab9fd
......@@ -3,6 +3,9 @@ minerva (13.1.0~alpha.0) unstable; urgency=low
outputs used by annotator (#617)
* Feature: changes in admin panel doesn't require saving - they are saved
automatically (#676)
* Feature: elements can have custom glyphs used for visualization (#487)
* Small improvement: all bio entities have z-index associated with them
(#487)
* Small improvement: validation of the organism and disease id on map upload
added (#618)
* Small improvement: added current username next to logout button in admin
......@@ -48,6 +51,8 @@ minerva (13.1.0~alpha.0) unstable; urgency=low
* Small improvement: allow admin to disable CORS check (#802)
* Small improvement: TransparencyZoomLevelVisibility parameter renamed to
SemanticZoomLevelTransparency (#801)
* Small improvement: export/import from SBML support z-index in LAYOUT
extension
* Bug fix: progress bar of gene genome mapping upload is refreshing properly
(#728)
* Bug fix: when editing project Disease and Organism could not be removed
......
......@@ -8,7 +8,10 @@ import org.apache.log4j.Logger;
import lcsb.mapviewer.common.exception.NotImplementedException;
public abstract class Comparator<T extends Object> implements java.util.Comparator<T> {
Logger logger = Logger.getLogger(Comparator.class);
/**
* Default class logger.
*/
private static Logger logger = Logger.getLogger(Comparator.class);
private Class<T> comparatorClazz;
private boolean exactClassMatch;
private List<Comparator<? extends T>> subClassComparatorList = new ArrayList<>();
......
......@@ -22,6 +22,7 @@ import lcsb.mapviewer.common.exception.InvalidXmlSchemaException;
import lcsb.mapviewer.converter.Converter;
import lcsb.mapviewer.converter.ConverterParams;
import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.converter.ZIndexPopulator;
import lcsb.mapviewer.converter.annotation.XmlAnnotationParser;
import lcsb.mapviewer.converter.model.celldesigner.alias.AliasCollectionXmlParser;
import lcsb.mapviewer.converter.model.celldesigner.annotation.RestAnnotationParser;
......@@ -144,7 +145,10 @@ public class CellDesignerXmlParser extends Converter {
}
// we ignore metaid - it's useless and obstruct data model
// model.setMetaId(XmlParser.getNodeAttr("metaId", modelNode));
model.setIdModel(XmlParser.getNodeAttr("id", modelNode));
String modelId = XmlParser.getNodeAttr("id", modelNode);
if (modelId != null && !modelId.isEmpty()) {
model.setIdModel(modelId);
}
Node compartmentNode = XmlParser.getNode("listOfCompartments", modelNode.getChildNodes());
if (compartmentNode != null) {
......@@ -236,6 +240,7 @@ public class CellDesignerXmlParser extends Converter {
model.setWidth(width);
model.setHeight(height);
}
new ZIndexPopulator().populateZIndex(model);
} catch (InvalidXmlSchemaException e) {
throw new InvalidInputDataExecption(e);
} catch (CellDesignerParserException e) {
......@@ -610,7 +615,11 @@ public class CellDesignerXmlParser extends Converter {
+ "xmlns:celldesigner=\"http://www.sbml.org/2001/ns/celldesigner\" level=\"2\" version=\"4\">\n");
// metaid is a string cell designer id, usually it's model id and as far as
// we can tell it's not used at all
result.append("<model metaid=\"" + model.getIdModel() + "\" id=\"" + model.getIdModel() + "\">\n");
result.append("<model ");
if (model.getIdModel() != null) {
result.append("metaid=\"" + model.getIdModel() + "\" id=\"" + model.getIdModel() + "\"");
}
result.append(">\n");
result.append(unitCollectionXmlParser.toXml(model.getUnits()));
result.append(functionCollectionXmlParser.toXml(model.getFunctions()));
......@@ -697,7 +706,8 @@ public class CellDesignerXmlParser extends Converter {
result.append("</celldesigner:extension>\n");
XmlAnnotationParser xmlAnnotationParser = new XmlAnnotationParser();
result.append(xmlAnnotationParser.dataSetToXmlString(model.getMiriamData(), model.getAuthors(), model.getCreationDate(), model.getModificationDates()));
result.append(xmlAnnotationParser.dataSetToXmlString(model.getMiriamData(), model.getAuthors(),
model.getCreationDate(), model.getModificationDates()));
result.append("</annotation>\n");
return result.toString();
}
......
......@@ -3,7 +3,7 @@ package lcsb.mapviewer.converter.model.celldesigner;
import lcsb.mapviewer.common.exception.InvalidXmlSchemaException;
/**
* Exception that shold be thrown when the group in xml schema is invalid.
* Exception that should be thrown when the group in xml schema is invalid.
*
* @author Piotr Gawron
*
......@@ -20,7 +20,7 @@ public class InvalidGroupException extends InvalidXmlSchemaException {
*/
public InvalidGroupException() {
super(); // call superclass constructor
super();
}
/**
......
......@@ -13,6 +13,7 @@ import org.w3c.dom.NodeList;
import lcsb.mapviewer.common.XmlParser;
import lcsb.mapviewer.common.exception.InvalidXmlSchemaException;
import lcsb.mapviewer.common.geometry.ColorParser;
import lcsb.mapviewer.converter.model.celldesigner.annotation.RestAnnotationParser;
import lcsb.mapviewer.model.graphics.ArrowType;
import lcsb.mapviewer.model.graphics.LineType;
import lcsb.mapviewer.model.graphics.PolylineData;
......@@ -522,6 +523,7 @@ public class LayerXmlParser {
}
}
}
new RestAnnotationParser().processNotes(result.getNotes(), result);
return result;
}
......
package lcsb.mapviewer.converter.model.celldesigner.annotation;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(FIELD)
public @interface ImportOnly {
}
package lcsb.mapviewer.converter.model.celldesigner.annotation;
import lcsb.mapviewer.converter.model.celldesigner.structure.CellDesignerElement;
import lcsb.mapviewer.converter.model.celldesigner.structure.CellDesignerSpecies;
import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.Drawable;
import lcsb.mapviewer.model.map.MiriamType;
import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.species.Element;
......@@ -53,12 +52,12 @@ public enum NoteField {
/**
* {@link Element#symbol}.
*/
SYMBOL("Symbol", CellDesignerElement.class, null),
SYMBOL("Symbol", BioEntity.class, null),
/**
* {@link Element#fullName}.
*/
NAME("Name", CellDesignerElement.class, null),
NAME("Name", BioEntity.class, null),
/**
* {@link Element#notes} or {@link Reaction#notes} .
......@@ -68,7 +67,7 @@ public enum NoteField {
/**
* {@link Element#formerSymbols}.
*/
PREVIOUS_SYMBOLS("Previous Symbols", CellDesignerElement.class, null),
PREVIOUS_SYMBOLS("Previous Symbols", Element.class, null),
/**
* {@link Element#synonyms} or {@link Reaction#synonyms}.
......@@ -113,12 +112,12 @@ public enum NoteField {
/**
* {@link Element#formula}.
*/
CHARGED_FORMULA("ChargedFormula", CellDesignerElement.class, null),
CHARGED_FORMULA("ChargedFormula", Element.class, null),
/**
* {@link Species#charge}.
*/
CHARGE("Charge", CellDesignerSpecies.class, null),
CHARGE("Charge", Species.class, null),
/**
* {@link Element#getSemanticZoomLevelVisibility()}.
......@@ -135,6 +134,10 @@ public enum NoteField {
*/
@Deprecated
TRANSPARENCY_ZOOM_LEVEL_VISIBILITY_OLD("TransparencyZoomLevelVisibility", Element.class, null),
@ImportOnly
Z_INDEX("Z-Index", Drawable.class, null),
;
/**
......@@ -145,7 +148,7 @@ public enum NoteField {
/**
* What object class can have this field.
*/
private Class<?> clazz;
private Class<? extends Drawable> clazz;
/**
* What {@link MiriamType} is associated with the field.
......@@ -160,7 +163,7 @@ public enum NoteField {
* @param clazz
* {@link #clazz}
*/
NoteField(String name, Class<?> clazz) {
NoteField(String name, Class<? extends Drawable> clazz) {
this.commonName = name;
this.clazz = clazz;
}
......@@ -175,7 +178,7 @@ public enum NoteField {
* @param type
* {@link #miriamType}
*/
NoteField(String name, Class<?> clazz, MiriamType type) {
NoteField(String name, Class<? extends Drawable> clazz, MiriamType type) {
this(name, clazz);
this.miriamType = type;
}
......
......@@ -23,9 +23,11 @@ import lcsb.mapviewer.common.exception.InvalidXmlSchemaException;
import lcsb.mapviewer.common.exception.NotImplementedException;
import lcsb.mapviewer.converter.annotation.XmlAnnotationParser;
import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.Drawable;
import lcsb.mapviewer.model.map.MiriamData;
import lcsb.mapviewer.model.map.MiriamRelationType;
import lcsb.mapviewer.model.map.MiriamType;
import lcsb.mapviewer.model.map.layout.graphics.LayerText;
import lcsb.mapviewer.model.map.reaction.Reaction;
import lcsb.mapviewer.model.map.species.Element;
import lcsb.mapviewer.model.map.species.Species;
......@@ -702,6 +704,24 @@ public class RestAnnotationParser {
}
}
private void setZIndex(Drawable element, String annotationString) {
String zIndex = getParamByPrefix(annotationString,
NoteField.Z_INDEX.getCommonName() + ":");
if (zIndex != null) {
try {
Integer z = Integer.valueOf(zIndex);
if (element.getZ() == null) {
element.setZ(z);
} else if (!element.getZ().equals(z)) {
logger.warn(elementUtils.getElementTag(element) + " New " + NoteField.Z_INDEX.getCommonName()
+ " different than default [" + zIndex + "][" + element.getZ() + "]. Ignoring.");
}
} catch (NumberFormatException e) {
logger.warn("Invalid e index", e);
}
}
}
/**
* Assigns notes to the element from notes string. This might look strange. The
* idea is that sometimes we have notes from more then one source. And the data
......@@ -739,7 +759,7 @@ public class RestAnnotationParser {
* @param object
* where the structural data should be put
*/
public void processNotes(String notes, BioEntity object) {
public void processNotes(String notes, Drawable object) {
StringBuilder annotations = new StringBuilder();
String[] string = notes.split("\n");
......@@ -748,7 +768,7 @@ public class RestAnnotationParser {
boolean remove = false;
for (NoteField field : NoteField.values()) {
if (string2.startsWith(field.getCommonName() + ":")) {
if (string2.startsWith(field.getCommonName() + ":")&& field.getClazz().isAssignableFrom(object.getClass())) {
remove = true;
}
}
......@@ -758,15 +778,21 @@ public class RestAnnotationParser {
newNotes.append(string2 + "\n");
}
}
object.setNotes(newNotes.toString().trim());
String ann = annotations.toString();
setNotes(object, ann);
setSymbol(object, ann);
setSynonyms(object, ann);
setSemanticZoomLevelVisibility(object, ann);
setAbbreviation(object, ann);
setFormula(object, ann);
setZIndex(object, ann);
if (object instanceof LayerText) {
((LayerText) object).setNotes(newNotes.toString().trim());
}
if (object instanceof BioEntity) {
BioEntity bioEntity = (BioEntity) object;
bioEntity.setNotes(newNotes.toString().trim());
setNotes(bioEntity, ann);
setSymbol(bioEntity, ann);
setSynonyms(bioEntity, ann);
setSemanticZoomLevelVisibility(bioEntity, ann);
setAbbreviation(bioEntity, ann);
setFormula(bioEntity, ann);
if (object instanceof Reaction) {
Reaction reaction = (Reaction) object;
setMechanicalConfidenceScoreToReaction(reaction, ann);
......@@ -786,12 +812,13 @@ public class RestAnnotationParser {
}
try {
processRdfDescription(object);
processRdfDescription(bioEntity);
} catch (InvalidXmlSchemaException e) {
String warning = elementUtils.getElementTag(object) + " Problem with processing notes. Invalid RDF node.";
logger.warn(warning);
}
}
}
/**
* Process rdf description from notes, removes it from the description and adds
......
......@@ -44,6 +44,7 @@ import lcsb.mapviewer.common.EventStorageLoggerAppender;
import lcsb.mapviewer.common.exception.InvalidXmlSchemaException;
import lcsb.mapviewer.converter.ConverterParams;
import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.InconsistentModelException;
import lcsb.mapviewer.model.map.model.Model;
import lcsb.mapviewer.model.map.model.ModelComparator;
......@@ -234,14 +235,30 @@ public abstract class CellDesignerTestFunctions {
protected void testXmlSerialization(Model model)
throws InconsistentModelException, UnsupportedEncodingException, InvalidInputDataExecption {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String xml = parser.model2String(model);
InputStream is = new ByteArrayInputStream(xml.getBytes("UTF-8"));
Model model2 = parser.createModel(new ConverterParams().inputStream(is).sizeAutoAdjust(false));
for (BioEntity bioEntity : model.getBioEntities()) {
bioEntity.setZ(null);
}
Model model2 = serializeModel(model);
model.setName(null);
ModelComparator comparator = new ModelComparator();
assertEquals(0, comparator.compare(model, model2));
}
protected Model serializeModel(Model model)
throws InconsistentModelException, UnsupportedEncodingException, InvalidInputDataExecption {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String xmlString = parser.model2String(model);
InputStream is = new ByteArrayInputStream(xmlString.getBytes("UTF-8"));
Model model2 = parser.createModel(new ConverterParams().inputStream(is).sizeAutoAdjust(false));
for (BioEntity bioEntity : model2.getBioEntities()) {
bioEntity.setZ(null);
}
return model2;
}
}
......@@ -36,6 +36,7 @@ import lcsb.mapviewer.converter.ConverterParams;
import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.converter.model.celldesigner.annotation.RestAnnotationParser;
import lcsb.mapviewer.model.graphics.PolylineData;
import lcsb.mapviewer.model.map.BioEntity;
import lcsb.mapviewer.model.map.compartment.Compartment;
import lcsb.mapviewer.model.map.layout.graphics.Layer;
import lcsb.mapviewer.model.map.layout.graphics.LayerOval;
......@@ -82,6 +83,20 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
}
}
@Test
public void testZIndexAvailableForBioEntities() throws Exception {
try {
CellDesignerXmlParser parser = new CellDesignerXmlParser();
Model model = parser.createModel(new ConverterParams().filename("testFiles/sample.xml"));
for (BioEntity bioEntity : model.getBioEntities()) {
assertNotNull(bioEntity.getZ());
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@Test
public void testOpenFromFile() throws Exception {
try {
......@@ -455,11 +470,7 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
alias2.setName("PDK2");
model.addElement(alias2);
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String xmlString = parser.model2String(model);
InputStream is = new ByteArrayInputStream(xmlString.getBytes());
Model model2 = parser.createModel(new ConverterParams().inputStream(is).sizeAutoAdjust(false));
Model model2 = serializeModel(model);
assertEquals(0, modelComparator.compare(model, model2));
......@@ -1063,11 +1074,7 @@ public class CellDesignerXmlParserTest extends CellDesignerTestFunctions {
protein.addSynonym("&");
model.addElement(protein);
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String xmlString = parser.model2String(model);
InputStream is = new ByteArrayInputStream(xmlString.getBytes("UTF-8"));
Model model2 = parser.createModel(new ConverterParams().inputStream(is));
Model model2 = serializeModel(model);
assertEquals(0, modelComparator.compare(model, model2));
......
......@@ -7,8 +7,6 @@ import static org.junit.Assert.assertTrue;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
......@@ -98,7 +96,6 @@ public class ComplexParserTests extends CellDesignerTestFunctions {
}
}
@Test
public void testParseTextWithBackground() throws Exception {
try {
......@@ -413,14 +410,7 @@ public class ComplexParserTests extends CellDesignerTestFunctions {
alias.setName("name & no-name");
model.addElement(alias);
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String string = parser.model2String(model);
InputStream stream = new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8));
ConverterParams params = new ConverterParams().inputStream(stream).sizeAutoAdjust(false);
Model model2 = parser.createModel(params);
Model model2 = serializeModel(model);
ModelComparator comparator = new ModelComparator();
......@@ -443,14 +433,7 @@ public class ComplexParserTests extends CellDesignerTestFunctions {
alias.setName("name\rno-name");
model.addElement(alias);
CellDesignerXmlParser parser = new CellDesignerXmlParser();
String string = parser.model2String(model);
InputStream stream = new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8));
ConverterParams params = new ConverterParams().inputStream(stream).sizeAutoAdjust(false);
Model model2 = parser.createModel(params);
Model model2 = serializeModel(model);
ModelComparator comparator = new ModelComparator();
......@@ -485,5 +468,4 @@ public class ComplexParserTests extends CellDesignerTestFunctions {
}
}
}
......@@ -391,6 +391,19 @@ public class LayerXmlParserTest extends CellDesignerTestFunctions {
}
}
@Test
public void testGetLayerTextWithZIndex() throws Exception {
try {
String xmlString = readFile("testFiles/xmlNodeTestExamples/layer_text_with_z_index.xml");
Node node = getNodeFromXmlString(xmlString);
LayerText layer = parser.getLayerText(node);
assertEquals((Integer) 19, layer.getZ());
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
@Test
public void testParseInvalidLayerText() throws Exception {
try {
......
......@@ -295,17 +295,19 @@ public class RestAnnotationParserTest extends CellDesignerTestFunctions {
for (NoteField field : NoteField.values()) {
boolean deprecated = false;
boolean importOnly = false;
try {
Field f = NoteField.class.getField(field.name());
if (f.isAnnotationPresent(Deprecated.class))
deprecated = true;
if (f.isAnnotationPresent(ImportOnly.class))
importOnly = true;
} catch (NoSuchFieldException | SecurityException e) {
}
if (!deprecated) {
if (!deprecated && !importOnly) {
if (field.getClazz().isAssignableFrom(element.getClass())
|| CellDesignerElement.class.isAssignableFrom(field.getClazz())) {
assertTrue("Export string doesn't contain info about: " + field.getCommonName(),
str.indexOf(field.getCommonName()) >= 0);
......
<celldesigner:layerSpeciesAlias x="0.0" y="0.0">
<celldesigner:layerNotes>
text node
Z-Index: 19
</celldesigner:layerNotes>
<celldesigner:bounds x="55.0" y="37.0" w="152.0" h="105.0"/>
<celldesigner:paint color="ff000000"/>
<celldesigner:font size="11"/>
</celldesigner:layerSpeciesAlias>
......@@ -24,6 +24,7 @@ import org.sbgn.bindings.Sbgn;
import lcsb.mapviewer.common.comparator.DoubleComparator;
import lcsb.mapviewer.common.exception.InvalidArgumentException;
import lcsb.mapviewer.converter.InvalidInputDataExecption;
import lcsb.mapviewer.converter.ZIndexPopulator;
import lcsb.mapviewer.converter.graphics.bioEntity.element.species.SpeciesConverter;
import lcsb.mapviewer.converter.model.celldesigner.geometry.CellDesignerAliasConverter;
import lcsb.mapviewer.converter.model.celldesigner.types.ModifierType;
......@@ -186,6 +187,8 @@ public class SbgnmlXmlParser {
}
}
new ZIndexPopulator().populateZIndex(model);
return model;
}
......
......@@ -46,11 +46,12 @@ public class SbgnmlXmlParserTest2 {
Converter converter = new SbgnmlXmlConverter();
Model model = converter
.createModel(new ConverterParams().filename("testFiles/sbgnmlParserTestFiles/sbgnmlFiles/elements_inside_compartment.xml"));
.createModel(new ConverterParams()
.filename("testFiles/sbgnmlParserTestFiles/sbgnmlFiles/elements_inside_compartment.xml"));
Complex complexInsideCompartment = model.getElementByElementId("csa1830");
assertNotNull("Complex inside compartment has undefined compartment", complexInsideCompartment.getCompartment());
Complex complexOutsideCompartment = model.getElementByElementId("csa1831");
assertNull("Complex outside compartment has not null compartment",complexOutsideCompartment.getCompartment());
assertNull("Complex outside compartment has not null compartment", complexOutsideCompartment.getCompartment());
Compartment compartment = model.getElementByElementId("ca107");
assertNull("Top compartment has not null compartment", compartment.getCompartment());
} catch (Exception e) {
......@@ -59,4 +60,20 @@ public class SbgnmlXmlParserTest2 {
}
}