EVS/ALISA
Audreys247 (Talk | contribs) (→Adding a New Editor) |
|||
(11 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | The current linkage between views in Farsight (ALISA) that enables the PACE technology is implemented using [http://doc.qtsoftware.com/4.4/model-view-programming.html QT's Model/View Architecture]. The model classes: [http://doc.qtsoftware.com/4.4/qstandarditemmodel.html QStandardItemModel] and [http://doc.qtsoftware.com/4.4/qsitemselectionmodel.html QItemSelectionModel] are | + | === Overview === |
+ | The current linkage between views in Farsight (ALISA) that enables the PACE technology is implemented using [http://doc.qtsoftware.com/4.4/model-view-programming.html QT's Model/View Architecture]. The two model classes: [http://doc.qtsoftware.com/4.4/qstandarditemmodel.html QStandardItemModel] and [http://doc.qtsoftware.com/4.4/qsitemselectionmodel.html QItemSelectionModel] are all that is required for the views to work. | ||
− | The Segmentation Model can be created from Segmentation Result. The | + | 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. |
− | [[Image: | + | [[Image:ALISA2.PNG|thumb|1000px|center]] |
− | The | + | === Location of Files === |
− | * | + | The relevant classes/files can be found in these locations: |
+ | * ftkObject - farsight/trunk/SegmentationCommon | ||
* ftkNuclearSegmentation - farsight/trunk/NuclearSegmentation | * ftkNuclearSegmentation - farsight/trunk/NuclearSegmentation | ||
− | * SegmentationModel, SegmentationView - farsight/trunk/ | + | * 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:<br> | ||
+ | |||
+ | 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 [http://doc.qtsoftware.com/4.4/signalsandslots.html 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: <br>1. Make your results look like nuclei and use the current tool, ,<br>2. Create a new editing GUI that handles the QItemSelectionModel appropriately for proper linking to the scatter plot and table views. <br>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 [http://doc.qtsoftware.com/4.4/qabstractitemview.html 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 |
Latest revision as of 16:12, 30 March 2011
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.
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