EVS/ALISA

From FarsightWiki
Jump to: navigation, search

Contents

Overview

The current linkage between views in Farsight (ALISA) that enables the PACE technology is implemented using QT's Model/View Architecture. The two model classes: QStandardItemModel and QItemSelectionModel are all that is required for the views to work.

The two types of views available (scatter plot and table) can be found in the ftkGUI library. To see an example of how these views can be used in an editor program, check out the NucleusEditor tool. Within this library a help class - segmentationModel has been created to enclose the qstandarditemmodel and qitemselectionmodel, handle editing triggers, and keep track of visualization preferences. This Segmentation Model can be created from Nuclear Segmentation Result. The NuclearSegmentation class handles reading/writing of object data from an XML file.

ALISA2.PNG

Location of Files

The relevant classes/files can be found in these locations:

  • ftkObject - farsight/trunk/SegmentationCommon
  • ftkNuclearSegmentation - farsight/trunk/NuclearSegmentation
  • SegmentationModel, SegmentationView - farsight/trunk/NuclearSegmentation/NucleusEditor
  • TableWindow, PlotWindow - farsight/trunk/ftkGUI

Nucleus Example

The actual construction of the Segmentation Model - and setup of the Views is done in NucleusEditor. Refer to the loadResult method for an example of how this is done. This is the process:

1. Build NuclearSegmentation

  • Here an instance of NuclearSegmentation is created and the object information is loaded from an XML file

2. Create SegmentationModel

  • The SegmentationModel constructor requires a pointer to a NuclearSegmentation class. The SegmenationModel class will convert the object feature information into a QStandardItemModel and construct a QItemSelectionModel.

3. Show Table and Scatter Views

  • These require a pointer to the QItemSelectionModel (which also has a pointer to the QStandardItemModel).

4. Create/Show Segmentation Window (in NucleusEditor)

  • This window is specific to nuclei.
  • Key press events within this view are connected to Triggers in the SegmentationModel using signals/slots.
  • Requires a pointer to SegmentationModel and the data/label images to work correctly.

Adding a New Editor

To add in new editors you have two choices:
1. Make your results look like nuclei and use the current tool, ,
2. Create a new editing GUI that handles the QItemSelectionModel appropriately for proper linking to the scatter plot and table views.
This second method is much more desirable - it will allow for a much better quality editing tool.

Your new editor has very few classes and associated signals and slots to be aware of:

  • QAbstractItemModel - this class contains all of the feature information in a table format.
    • Data can be accessed using the headerData() and data() methods.
    • The dataChanged() signal will be emitted every time the model is modified
  • QItemSelectionModel - this class contains the index information of all of the selected items from the model
    • Use clearSelection() to clear the selection list
    • Use select() to modify the selected items
    • Use selectedRows() to query which objects are selected
    • The selectionChanged() signal will be emitted every time the list of selected items changes.

Adding a New View

Below I describe how to add a new view.

The simplest way to create a new view is to subclass QAbstractItemView. This is an abstract class with certain pure virtual methods that must be implemented.

This is a list of the pure virtual methods and an example implementation:

  • QModelIndex indexAt ( const QPoint &point ) - Returns the model index of the item at the viewport coordinates point, return an empty QModelIndex if no item at these coordinates.
 QModelIndex SegmentationView::indexAt(const QPoint &point) const
 {
   QModelIndex retval = QModelIndex();
   // Transform the view coordinates into contents viewport coordinates.
   double wx = point.x() + horizontalScrollBar()->value();
   double wy = point.y() + verticalScrollBar()->value();
   //Find the extrema of the image area in viewport coordinates
   QRect rect(0,0,totalWidth,totalHeight);
   //First check to be sure clicked location is inside of plot area
   if ( (wx > rect.left()) && (wx < rect.right()) && (wy > rect.top()) && (wy < rect.bottom()) )
   {
      //Now find out what Label ID this point has (by checking on label image)
      int labelval = (int)labelImg->GetPixel(currentT,0,currentZ,int(wy),int(wx));
      //return index of first item in model at this row
      if(labelval > 0)
        retval = model()->index(resultModel->RowForID(labelval),0,rootIndex());
    }
    return retval;
 }
  • void scrollTo ( const QModelIndex & index, ScrollHint hint = EnsureVisible ) - Scrolls the view if necessary to ensure that the item at index is visible. The view will try to position the item according to the given hint.
  void ScatterView::scrollTo(const QModelIndex &index, ScrollHint)
  {
    QRect area = viewport()->rect();
    QRect rect = visualRect(index);
    if (rect.left() < area.left())
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + rect.left() - area.left());
    else if (rect.right() > area.right())
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + qMin(
                rect.right() - area.right(), rect.left() - area.left()));
    if (rect.top() < area.top())
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + rect.top() - area.top());
    else if (rect.bottom() > area.bottom())
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + qMin(
                rect.bottom() - area.bottom(), rect.top() - area.top()));
    update();
  }
  • QRect visualRect ( const QModelIndex & index ) - Returns the rectangle on the viewport occupied by the item at index. If your item is displayed in several areas then visualRect should return the primary area that contains index and not the complete area that index might encompasses, touch or cause drawing.
 QRect SegmentationView::visualRect(const QModelIndex &index) const
 {
   QRect rect = itemRect(index);  //Get the rectangle for the item in viewport coordinates
   if (rect.isValid())
       return QRect(rect.left() - horizontalScrollBar()->value(), rect.top() - verticalScrollBar()->value(), rect.width(), rect.height());
   else
       return rect;
 }
  • int horizontalOffset () - Returns the horizontal offset of the view
 int SegmentationView::horizontalOffset() const
 {
   return horizontalScrollBar()->value();
 }
  • int verticalOffset () - Returns the vertical offset of the view
 int SegmentationView::verticalOffset() const
 {
   return verticalScrollBar()->value();
 }
  • bool isIndexHidden ( const QModelIndex & index ) - Returns true if the item referred to by the given index is hidden in the view, otherwise returns false. Hiding is a view specific feature. For example in TableView a column can be marked as hidden or a row in the TreeView.
 bool ScatterView::isIndexHidden(const QModelIndex & /*index*/) const
 {
   return false;
 }
  • QModelIndex moveCursor ( CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) - Returns a QModelIndex object pointing to the next object in the view, based on the given cursorAction and keyboard modifiers specified by modifiers.
 QModelIndex ScatterView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers /*modifiers*/)
 {
    return currentIndex(); \\We have no implementation
 }
  • void setSelection ( const QRect & rect, QItemSelectionModel::SelectionFlags flags ) - Applies the selection flags to the items in or touched by the rectangle, rect. When implementing your own itemview setSelection should call selectionModel()->select(selection, flags) where selection is either an empty QModelIndex or a QItemSelection that contains all items that are contained in rect.
 void SegmentationView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
 {
   //This is the selection region
   QRect contentsRect = rect.translated( horizontalScrollBar()->value(), verticalScrollBar()->value()).normalized();
   int rows = model()->rowCount(rootIndex());
   QItemSelection selection;
   selection.clear();
   //QModelIndexList indexes;
   for (int row = 0; row < rows; ++row) 
   {
       //get the region of the item
       QModelIndex index1 = model()->index(row, 0, rootIndex());
       QModelIndex index2 = model()->index(row,(model()->columnCount())-1, rootIndex());
       QRegion region = itemRegion(index1);
       //if it intersects with the selection region, save the index
       if (!region.intersect(contentsRect).isEmpty())
       {
         //indexes.append(index);
         //Add each item's region to the selection
         selection.merge(QItemSelection(index1,index2),command);
       }
   }
   selectionModel()->select(selection, command);
   viewport()->update(); 
 }
  • QRegion visualRegionForSelection ( const QItemSelection & selection ) - Returns the region from the viewport of the items in the given selection.
 QRegion SegmentationView::visualRegionForSelection(const QItemSelection &selection) const
 {
   int ranges = selection.count();
   if (ranges == 0)
       return QRect();
   QRegion region;
   for (int i = 0; i < ranges; ++i) 
   {
     QItemSelectionRange range = selection.at(i);
     for (int row = range.top(); row <= range.bottom(); ++row) 
     {
       for (int col = range.left(); col <= range.right(); ++col) 
       {
         QModelIndex index = model()->index(row, col, rootIndex());
         region += visualRect(index);
       }
     }
   }
   return region;
 }

You will probably also need to reimplement these methods to get the desired functionality:

  • void dataChanged ( const QModelIndex &topLeft, const QModelIndex &bottomRight ) - slot that is called when data in the model changes
  • void paintEvent ( QPaintEvent *event ) - paints everything in the viewport
Personal tools