public void doTrackStyle() { if (null == stylist) { return; } gui.logger.setStatus("Setting style."); graph.getModel().beginUpdate(); try { // Collect edges Set<Integer> trackIDs = model.getTrackModel().trackIDs(true); HashMap<Integer, Set<mxCell>> edgeMap = new HashMap<Integer, Set<mxCell>>(trackIDs.size()); for (Integer trackID : trackIDs) { Set<DefaultWeightedEdge> edges = model.getTrackModel().trackEdges(trackID); HashSet<mxCell> set = new HashSet<mxCell>(edges.size()); for (DefaultWeightedEdge edge : edges) { set.add(graph.getCellFor(edge)); } edgeMap.put(trackID, set); } // Give them style Set<mxICell> verticesUpdated = stylist.execute(edgeMap); // Take care of vertices HashSet<mxCell> missedVertices = new HashSet<mxCell>(graph.getVertexCells()); missedVertices.removeAll(verticesUpdated); stylist.updateVertexStyle(missedVertices); } finally { graph.getModel().endUpdate(); } }
/** * Import a whole track from the {@link Model} and make it visible * * @param trackIndex the index of the track to show in TrackScheme */ private void importTrack(int trackIndex) { model.beginUpdate(); graph.getModel().beginUpdate(); try { // Flag original track as visible model.setTrackVisibility(trackIndex, true); // Find adequate column int targetColumn = getUnlaidSpotColumn(); // Create cells for track Set<Spot> trackSpots = model.getTrackModel().trackSpots(trackIndex); for (Spot trackSpot : trackSpots) { int frame = trackSpot.getFeature(Spot.FRAME).intValue(); int column = Math.max(targetColumn, getNextFreeColumn(frame)); insertSpotInGraph(trackSpot, column); rowLengths.put(frame, column); } Set<DefaultWeightedEdge> trackEdges = model.getTrackModel().trackEdges(trackIndex); for (DefaultWeightedEdge trackEdge : trackEdges) { graph.addJGraphTEdge(trackEdge); } } finally { model.endUpdate(); graph.getModel().endUpdate(); } }
/** * Updates or creates a cell for the target spot. Is called after the user modified a spot * (location, radius, ...) somewhere else. * * @param spot the spot that was modified. */ private mxICell updateCellOf(final Spot spot) { mxICell cell = graph.getCellFor(spot); graph.getModel().beginUpdate(); try { if (DEBUG) System.out.println("[TrackScheme] modelChanged: updating cell for spot " + spot); if (null == cell) { // mxCell not present in graph. Most likely because the corresponding spot belonged // to an invisible track, and a cell was not created for it when TrackScheme was // launched. So we create one on the fly now. int row = getUnlaidSpotColumn(); cell = insertSpotInGraph(spot, row); int frame = spot.getFeature(Spot.FRAME).intValue(); rowLengths.put(frame, row + 1); } // Update cell look if (spotImageUpdater != null && doThumbnailCapture) { String style = cell.getStyle(); String imageStr = spotImageUpdater.getImageString(spot); style = mxStyleUtils.setStyle(style, mxConstants.STYLE_IMAGE, "data:image/base64," + imageStr); graph.getModel().setStyle(cell, style); } } finally { graph.getModel().endUpdate(); } return cell; }
public void selectTrack( final Collection<mxCell> vertices, final Collection<mxCell> edges, final int direction) { // Look for spot and edges matching given mxCells HashSet<Spot> inspectionSpots = new HashSet<Spot>(vertices.size()); for (mxCell cell : vertices) { Spot spot = graph.getSpotFor(cell); if (null == spot) { if (DEBUG) { System.out.println( "[TrackScheme] selectWholeTrack: tried to retrieve cell " + cell + ", unknown to spot map."); } continue; } inspectionSpots.add(spot); } HashSet<DefaultWeightedEdge> inspectionEdges = new HashSet<DefaultWeightedEdge>(edges.size()); for (mxCell cell : edges) { DefaultWeightedEdge dwe = graph.getEdgeFor(cell); if (null == dwe) { if (DEBUG) { System.out.println( "[TrackScheme] select whole track: tried to retrieve cell " + cell + ", unknown to edge map."); } continue; } inspectionEdges.add(dwe); } // Forward to selection model selectionModel.selectTrack(inspectionSpots, inspectionEdges, direction); }
/** Removes the cell selected by the user in the GUI. */ public void removeSelectedCells() { graph.getModel().beginUpdate(); try { graph.removeCells(graph.getSelectionCells()); // Will be caught by the graph listeners } finally { graph.getModel().endUpdate(); } }
@Override public void selectionChanged(SelectionChangeEvent event) { if (DEBUG_SELECTION) System.out.println( "[TrackSchemeFrame] selectionChanged: received event " + event.hashCode() + " from " + event.getSource() + ". Fire flag is " + doFireSelectionChangeEvent); if (!doFireSelectionChangeEvent) return; doFireSelectionChangeEvent = false; { ArrayList<Object> newSelection = new ArrayList<Object>( selectionModel.getSpotSelection().size() + selectionModel.getEdgeSelection().size()); Iterator<DefaultWeightedEdge> edgeIt = selectionModel.getEdgeSelection().iterator(); while (edgeIt.hasNext()) { mxICell cell = graph.getCellFor(edgeIt.next()); if (null != cell) { newSelection.add(cell); } } Iterator<Spot> spotIt = selectionModel.getSpotSelection().iterator(); while (spotIt.hasNext()) { mxICell cell = graph.getCellFor(spotIt.next()); if (null != cell) { newSelection.add(cell); } } mxGraphSelectionModel mGSmodel = graph.getSelectionModel(); mGSmodel.setCells(newSelection.toArray()); } // Center on selection if we added one spot exactly Map<Spot, Boolean> spotsAdded = event.getSpots(); if (spotsAdded != null && spotsAdded.size() == 1) { boolean added = spotsAdded.values().iterator().next(); if (added) { Spot spot = spotsAdded.keySet().iterator().next(); centerViewOn(spot); } } doFireSelectionChangeEvent = true; }
/** * Captures and stores the thumbnail image that will be displayed in each spot cell, when using * styles that can display images. */ private void createThumbnails() { // Group spots per frame Set<Integer> frames = model.getSpots().keySet(); final HashMap<Integer, HashSet<Spot>> spotPerFrame = new HashMap<Integer, HashSet<Spot>>(frames.size()); for (Integer frame : frames) { spotPerFrame.put( frame, new HashSet<Spot>(model.getSpots().getNSpots(frame, true))); // max size } for (Integer trackID : model.getTrackModel().trackIDs(true)) { for (Spot spot : model.getTrackModel().trackSpots(trackID)) { int frame = spot.getFeature(Spot.FRAME).intValue(); spotPerFrame.get(frame).add(spot); } } // Set spot image to cell style if (null != spotImageUpdater) { gui.logger.setStatus("Collecting spot thumbnails."); int index = 0; try { graph.getModel().beginUpdate(); // Iterate per frame for (Integer frame : frames) { for (Spot spot : spotPerFrame.get(frame)) { mxICell cell = graph.getCellFor(spot); String imageStr = spotImageUpdater.getImageString(spot); String style = cell.getStyle(); style = mxStyleUtils.setStyle( style, mxConstants.STYLE_IMAGE, "data:image/base64," + imageStr); graph.getModel().setStyle(cell, style); } gui.logger.setProgress((double) index++ / frames.size()); } } finally { graph.getModel().endUpdate(); gui.logger.setProgress(0d); gui.logger.setStatus(""); thumbnailCaptured = true; // After that they will be kept in synch thanks to #modelChanged } } }
/** * Insert a spot in the {@link TrackSchemeFrame}, by creating a {@link mxCell} in the graph model * of this frame and position it according to its feature. */ private mxICell insertSpotInGraph(Spot spot, int targetColumn) { mxICell cellAdded = graph.getCellFor(spot); if (cellAdded != null) { // cell for spot already exist, do nothing and return original spot return cellAdded; } // Instantiate JGraphX cell cellAdded = graph.addJGraphTVertex(spot); // Position it int row = spot.getFeature(Spot.FRAME).intValue() + 1; double x = (targetColumn - 1) * X_COLUMN_SIZE - DEFAULT_CELL_WIDTH / 2; double y = (0.5 + row) * Y_COLUMN_SIZE - DEFAULT_CELL_HEIGHT / 2; mxGeometry geometry = new mxGeometry(x, y, DEFAULT_CELL_WIDTH, DEFAULT_CELL_HEIGHT); cellAdded.setGeometry(geometry); // Set its style if (null != spotImageUpdater && doThumbnailCapture) { String imageStr = spotImageUpdater.getImageString(spot); graph .getModel() .setStyle(cellAdded, mxConstants.STYLE_IMAGE + "=" + "data:image/base64," + imageStr); } return cellAdded; }
/** * Called when the user makes a selection change in the graph. Used to forward this event to the * {@link InfoPane} and to other {@link SelectionChangeListener}s. * * @param model the selection model * @param added the cells <b>removed</b> from selection (careful, inverted) * @param removed the cells <b>added</b> to selection (careful, inverted) */ private void userChangedSelection( mxGraphSelectionModel mxGSmodel, Collection<Object> added, Collection<Object> removed) { // Seems to be inverted if (!doFireSelectionChangeEvent) return; Collection<Spot> spotsToAdd = new ArrayList<Spot>(); Collection<Spot> spotsToRemove = new ArrayList<Spot>(); Collection<DefaultWeightedEdge> edgesToAdd = new ArrayList<DefaultWeightedEdge>(); Collection<DefaultWeightedEdge> edgesToRemove = new ArrayList<DefaultWeightedEdge>(); if (null != added) { for (Object obj : added) { mxCell cell = (mxCell) obj; if (cell.getChildCount() > 0) { for (int i = 0; i < cell.getChildCount(); i++) { mxICell child = cell.getChildAt(i); if (child.isVertex()) { Spot spot = graph.getSpotFor(child); spotsToRemove.add(spot); } else { DefaultWeightedEdge edge = graph.getEdgeFor(child); edgesToRemove.add(edge); } } } else { if (cell.isVertex()) { Spot spot = graph.getSpotFor(cell); spotsToRemove.add(spot); } else { DefaultWeightedEdge edge = graph.getEdgeFor(cell); edgesToRemove.add(edge); } } } } if (null != removed) { for (Object obj : removed) { mxCell cell = (mxCell) obj; if (cell.getChildCount() > 0) { for (int i = 0; i < cell.getChildCount(); i++) { mxICell child = cell.getChildAt(i); if (child.isVertex()) { Spot spot = graph.getSpotFor(child); spotsToAdd.add(spot); } else { DefaultWeightedEdge edge = graph.getEdgeFor(child); edgesToAdd.add(edge); } } } else { if (cell.isVertex()) { Spot spot = graph.getSpotFor(cell); spotsToAdd.add(spot); } else { DefaultWeightedEdge edge = graph.getEdgeFor(cell); edgesToAdd.add(edge); } } } } if (DEBUG_SELECTION) System.out.println("[TrackScheme] userChangeSelection: sending selection change to model."); doFireSelectionChangeEvent = false; if (!edgesToAdd.isEmpty()) selectionModel.addEdgeToSelection(edgesToAdd); if (!spotsToAdd.isEmpty()) selectionModel.addSpotToSelection(spotsToAdd); if (!edgesToRemove.isEmpty()) selectionModel.removeEdgeFromSelection(edgesToRemove); if (!spotsToRemove.isEmpty()) selectionModel.removeSpotFromSelection(spotsToRemove); doFireSelectionChangeEvent = true; }
/** * Used to catch spot creation events that occurred elsewhere, for instance by manual editing in * the {@link AbstractTrackMateModelView}. * * <p>We have to deal with the graph modification ourselves here, because the {@link Model} model * holds a non-listenable JGraphT instance. A modification made to the model would not be * reflected on the graph here. */ @Override public void modelChanged(final ModelChangeEvent event) { // Only catch model changes if (event.getEventID() != ModelChangeEvent.MODEL_MODIFIED) return; graph.getModel().beginUpdate(); try { ArrayList<mxICell> cellsToRemove = new ArrayList<mxICell>(); final int targetColumn = getUnlaidSpotColumn(); // Deal with spots if (!event.getSpots().isEmpty()) { Collection<mxCell> spotsWithStyleToUpdate = new HashSet<mxCell>(); for (Spot spot : event.getSpots()) { if (event.getSpotFlag(spot) == ModelChangeEvent.FLAG_SPOT_ADDED) { int frame = spot.getFeature(Spot.FRAME).intValue(); // Put in the graph int column = Math.max(targetColumn, getNextFreeColumn(frame)); mxICell newCell = insertSpotInGraph(spot, column); // move in right+1 free column rowLengths.put(frame, column); spotsWithStyleToUpdate.add((mxCell) newCell); } else if (event.getSpotFlag(spot) == ModelChangeEvent.FLAG_SPOT_MODIFIED) { // Change the look of the cell mxICell cell = updateCellOf(spot); spotsWithStyleToUpdate.add((mxCell) cell); } else if (event.getSpotFlag(spot) == ModelChangeEvent.FLAG_SPOT_REMOVED) { mxICell cell = graph.getCellFor(spot); cellsToRemove.add(cell); } } graph.removeCells(cellsToRemove.toArray(), true); stylist.updateVertexStyle(spotsWithStyleToUpdate); } } finally { graph.getModel().endUpdate(); } // Deal with edges if (!event.getEdges().isEmpty()) { graph.getModel().beginUpdate(); try { if (event.getEdges().size() > 0) { Map<Integer, Set<mxCell>> edgesToUpdate = new HashMap<Integer, Set<mxCell>>(); for (DefaultWeightedEdge edge : event.getEdges()) { if (event.getEdgeFlag(edge) == ModelChangeEvent.FLAG_EDGE_ADDED) { mxCell edgeCell = graph.getCellFor(edge); if (null == edgeCell) { // Make sure target & source cells exist Spot source = model.getTrackModel().getEdgeSource(edge); mxCell sourceCell = graph.getCellFor(source); if (sourceCell == null) { int frame = source.getFeature(Spot.FRAME).intValue(); // Put in the graph int targetColumn = getUnlaidSpotColumn(); int column = Math.max(targetColumn, getNextFreeColumn(frame)); insertSpotInGraph(source, column); // move in right+1 free column rowLengths.put(frame, column); } Spot target = model.getTrackModel().getEdgeTarget(edge); mxCell targetCell = graph.getCellFor(target); if (targetCell == null) { int frame = target.getFeature(Spot.FRAME).intValue(); // Put in the graph int targetColumn = getUnlaidSpotColumn(); int column = Math.max(targetColumn, getNextFreeColumn(frame)); insertSpotInGraph(target, column); // move in right+1 free column rowLengths.put(frame, column); } // And finally create the edge cell edgeCell = graph.addJGraphTEdge(edge); } graph.getModel().add(graph.getDefaultParent(), edgeCell, 0); // Add it to the map of cells to recolor Integer trackID = model.getTrackModel().trackIDOf(edge); Set<mxCell> edgeSet = edgesToUpdate.get(trackID); if (edgesToUpdate.get(trackID) == null) { edgeSet = new HashSet<mxCell>(); edgesToUpdate.put(trackID, edgeSet); } edgeSet.add(edgeCell); } else if (event.getEdgeFlag(edge) == ModelChangeEvent.FLAG_EDGE_MODIFIED) { // Add it to the map of cells to recolor Integer trackID = model.getTrackModel().trackIDOf(edge); Set<mxCell> edgeSet = edgesToUpdate.get(trackID); if (edgesToUpdate.get(trackID) == null) { edgeSet = new HashSet<mxCell>(); edgesToUpdate.put(trackID, edgeSet); } edgeSet.add(graph.getCellFor(edge)); } else if (event.getEdgeFlag(edge) == ModelChangeEvent.FLAG_EDGE_REMOVED) { mxCell cell = graph.getCellFor(edge); graph.removeCells(new Object[] {cell}); } } stylist.execute(edgesToUpdate); SwingUtilities.invokeLater( new Runnable() { public void run() { gui.graphComponent.refresh(); gui.graphComponent.repaint(); } }); } } finally { graph.getModel().endUpdate(); } } }
@Override public void centerViewOn(Spot spot) { gui.centerViewOn(graph.getCellFor(spot)); }
/** * This method is called when the user has created manually an edge in the graph, by dragging a * link between two spot cells. It checks whether the matching edge in the model exists, and tune * what should be done accordingly. * * @param cell the mxCell of the edge that has been manually created. */ protected void addEdgeManually(mxCell cell) { if (cell.isEdge()) { final mxIGraphModel graphModel = graph.getModel(); cell.setValue("New"); model.beginUpdate(); graphModel.beginUpdate(); try { Spot source = graph.getSpotFor(cell.getSource()); Spot target = graph.getSpotFor(cell.getTarget()); if (DEBUG) { System.out.println( "[TrackScheme] #addEdgeManually: edge is between 2 spots belonging to the same frame. Removing it."); System.out.println( "[TrackScheme] #addEdgeManually: adding edge between source " + source + " at frame " + source.getFeature(Spot.FRAME).intValue() + " and target " + target + " at frame " + target.getFeature(Spot.FRAME).intValue()); } if (Spot.frameComparator.compare(source, target) == 0) { // Prevent adding edges between spots that belong to the same frame if (DEBUG) { System.out.println( "[TrackScheme] addEdgeManually: edge is between 2 spots belonging to the same frame. Removing it."); } graph.removeCells(new Object[] {cell}); } else { // We can add it to the model // Put them right in order: since we use a oriented graph, // we want the source spot to precede in time. if (Spot.frameComparator.compare(source, target) > 0) { if (DEBUG) { System.out.println( "[TrackScheme] #addEdgeManually: Source " + source + " succeed target " + target + ". Inverting edge direction."); } Spot tmp = source; source = target; target = tmp; } // We add a new jGraphT edge to the underlying model, if it does not exist yet. DefaultWeightedEdge edge = model.getTrackModel().getEdge(source, target); if (null == edge) { edge = model.addEdge(source, target, -1); if (DEBUG) { System.out.println( "[TrackScheme] #addEdgeManually: Creating new edge: " + edge + "."); } } else { // Ah. There was an existing edge in the model we were trying to re-add there, from the // graph. // We remove the graph edge we have added, if (DEBUG) { System.out.println("[TrackScheme] #addEdgeManually: Edge pre-existed. Retrieve it."); } graph.removeCells(new Object[] {cell}); // And re-create a graph edge from the model edge. cell = graph.addJGraphTEdge(edge); cell.setValue(String.format("%.1f", model.getTrackModel().getEdgeWeight(edge))); // We also need now to check if the edge belonged to a visible track. If not, // we make it visible. int ID = model.getTrackModel().trackIDOf(edge); // This will work, because track indices will be reprocessed only after the // graphModel.endUpdate() // reaches 0. So now, it's like we are dealing with the track indices priori to // modification. if (model.getTrackModel().isVisible(ID)) { if (DEBUG) { System.out.println( "[TrackScheme] #addEdgeManually: Track was visible. Do nothing."); } } else { if (DEBUG) { System.out.println( "[TrackScheme] #addEdgeManually: Track was invisible. Make it visible."); } importTrack(ID); } } graph.mapEdgeToCell(edge, cell); } } finally { graphModel.endUpdate(); model.endUpdate(); selectionModel.clearEdgeSelection(); } } }
/** Used to instantiate and configure the {@link JGraphXAdapter} that will be used for display. */ private JGraphXAdapter createGraph() { gui.logger.setStatus("Creating graph adapter."); final JGraphXAdapter graph = new JGraphXAdapter(model); graph.setAllowLoops(false); graph.setAllowDanglingEdges(false); graph.setCellsCloneable(false); graph.setCellsSelectable(true); graph.setCellsDisconnectable(false); graph.setCellsMovable(true); graph.setGridEnabled(false); graph.setLabelsVisible(true); graph.setDropEnabled(false); // Cells removed from JGraphX graph.addListener(mxEvent.CELLS_REMOVED, new CellRemovalListener()); // Cell selection change graph.getSelectionModel().addListener(mxEvent.CHANGE, new SelectionChangeListener()); // Return graph return graph; }
/** * Create links between all the spots currently in the {@link Model} selection. We update * simultaneously the {@link Model} and the {@link JGraphXAdapter}. */ public void linkSpots() { // Sort spots by time TreeMap<Integer, Spot> spotsInTime = new TreeMap<Integer, Spot>(); for (Spot spot : selectionModel.getSpotSelection()) { spotsInTime.put(spot.getFeature(Spot.FRAME).intValue(), spot); } // Find adequate column int targetColumn = getUnlaidSpotColumn(); // Then link them in this order model.beginUpdate(); graph.getModel().beginUpdate(); try { Iterator<Integer> it = spotsInTime.keySet().iterator(); Integer previousTime = it.next(); Spot previousSpot = spotsInTime.get(previousTime); // If this spot belong to an invisible track, we make it visible Integer ID = model.getTrackModel().trackIDOf(previousSpot); if (ID != null && !model.getTrackModel().isVisible(ID)) { importTrack(ID); } while (it.hasNext()) { Integer currentTime = it.next(); Spot currentSpot = spotsInTime.get(currentTime); // If this spot belong to an invisible track, we make it visible ID = model.getTrackModel().trackIDOf(currentSpot); if (ID != null && !model.getTrackModel().isVisible(ID)) { importTrack(ID); } // Check that the cells matching the 2 spots exist in the graph mxICell currentCell = graph.getCellFor(currentSpot); if (null == currentCell) { currentCell = insertSpotInGraph(currentSpot, targetColumn); if (DEBUG) { System.out.println( "[TrackScheme] linkSpots: creating cell " + currentCell + " for spot " + currentSpot); } } mxICell previousCell = graph.getCellFor(previousSpot); if (null == previousCell) { int frame = previousSpot.getFeature(Spot.FRAME).intValue(); int column = Math.max(targetColumn, getNextFreeColumn(frame)); rowLengths.put(frame, column); previousCell = insertSpotInGraph(previousSpot, column); if (DEBUG) { System.out.println( "[TrackScheme] linkSpots: creating cell " + previousCell + " for spot " + previousSpot); } } // Check if the model does not have already a edge for these 2 spots (that is // the case if the 2 spot are in an invisible track, which track scheme does not // know of). DefaultWeightedEdge edge = model.getTrackModel().getEdge(previousSpot, currentSpot); if (null == edge) { // We create a new edge between 2 spots, and pair it with a new cell edge. edge = model.addEdge(previousSpot, currentSpot, -1); mxCell cell = (mxCell) graph.addJGraphTEdge(edge); cell.setValue("New"); } else { // We retrieve the edge, and pair it with a new cell edge. mxCell cell = (mxCell) graph.addJGraphTEdge(edge); cell.setValue(String.format("%.1f", model.getTrackModel().getEdgeWeight(edge))); // Also, if the existing edge belonged to an existing invisible track, we make it visible. ID = model.getTrackModel().trackIDOf(edge); if (ID != null && !model.getTrackModel().isVisible(ID)) { importTrack(ID); } } previousSpot = currentSpot; } } finally { graph.getModel().endUpdate(); model.endUpdate(); } }