001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.component; 020 021import java.awt.BorderLayout; 022import java.awt.Color; 023import java.awt.Dimension; 024import java.awt.Image; 025import java.awt.Point; 026import java.awt.event.KeyEvent; 027import java.awt.event.MouseAdapter; 028import java.awt.event.MouseEvent; 029import java.lang.ref.WeakReference; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.Comparator; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.Iterator; 036import java.util.LinkedHashSet; 037import java.util.List; 038import java.util.Map; 039import java.util.Map.Entry; 040import java.util.Set; 041import java.util.concurrent.Semaphore; 042import java.util.concurrent.TimeUnit; 043 044import javax.swing.ActionMap; 045import javax.swing.Box; 046import javax.swing.DefaultListSelectionModel; 047import javax.swing.InputMap; 048import javax.swing.JComponent; 049import javax.swing.JLabel; 050import javax.swing.JPanel; 051import javax.swing.JScrollPane; 052import javax.swing.KeyStroke; 053import javax.swing.ListSelectionModel; 054import javax.swing.ScrollPaneConstants; 055import javax.swing.event.ListSelectionEvent; 056import javax.swing.event.ListSelectionListener; 057import javax.swing.table.AbstractTableModel; 058import javax.swing.table.JTableHeader; 059import javax.swing.table.TableColumn; 060import javax.swing.table.TableColumnModel; 061import javax.swing.table.TableModel; 062 063import org.jdesktop.swingx.JXTable; 064import org.jdesktop.swingx.decorator.HighlighterFactory; 065import org.jdesktop.swingx.sort.DefaultSortController; 066import org.jdesktop.swingx.table.DefaultTableColumnModelExt; 067import org.jdesktop.swingx.table.TableColumnExt; 068import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer; 069import org.pushingpixels.substance.api.skin.SkinChangeListener; 070 071import icy.action.RoiActions; 072import icy.canvas.IcyCanvas; 073import icy.canvas.IcyCanvas2D; 074import icy.canvas.IcyCanvas3D; 075import icy.gui.component.IcyTextField.TextChangeListener; 076import icy.gui.component.button.IcyButton; 077import icy.gui.component.renderer.ImageTableCellRenderer; 078import icy.gui.inspector.RoiSettingFrame; 079import icy.gui.main.ActiveSequenceListener; 080import icy.gui.util.GuiUtil; 081import icy.gui.util.LookAndFeelUtil; 082import icy.gui.viewer.Viewer; 083import icy.main.Icy; 084import icy.math.MathUtil; 085import icy.plugin.PluginLoader; 086import icy.plugin.PluginLoader.PluginLoaderEvent; 087import icy.plugin.PluginLoader.PluginLoaderListener; 088import icy.plugin.interface_.PluginROIDescriptor; 089import icy.preferences.XMLPreferences; 090import icy.roi.ROI; 091import icy.roi.ROIDescriptor; 092import icy.roi.ROIEvent; 093import icy.roi.ROIEvent.ROIEventType; 094import icy.roi.ROIListener; 095import icy.roi.ROIUtil; 096import icy.sequence.Sequence; 097import icy.sequence.SequenceEvent; 098import icy.sequence.SequenceEvent.SequenceEventSourceType; 099import icy.system.IcyExceptionHandler; 100import icy.system.thread.InstanceProcessor; 101import icy.system.thread.ThreadUtil; 102import icy.type.rectangle.Rectangle5D; 103import icy.util.ClassUtil; 104import icy.util.StringUtil; 105import plugins.kernel.roi.descriptor.intensity.ROIMaxIntensityDescriptor; 106import plugins.kernel.roi.descriptor.intensity.ROIMeanIntensityDescriptor; 107import plugins.kernel.roi.descriptor.intensity.ROIMinIntensityDescriptor; 108import plugins.kernel.roi.descriptor.intensity.ROISumIntensityDescriptor; 109import plugins.kernel.roi.descriptor.measure.ROIAreaDescriptor; 110import plugins.kernel.roi.descriptor.measure.ROIContourDescriptor; 111import plugins.kernel.roi.descriptor.measure.ROIInteriorDescriptor; 112import plugins.kernel.roi.descriptor.measure.ROIMassCenterCDescriptor; 113import plugins.kernel.roi.descriptor.measure.ROIMassCenterTDescriptor; 114import plugins.kernel.roi.descriptor.measure.ROIMassCenterXDescriptor; 115import plugins.kernel.roi.descriptor.measure.ROIMassCenterYDescriptor; 116import plugins.kernel.roi.descriptor.measure.ROIMassCenterZDescriptor; 117import plugins.kernel.roi.descriptor.measure.ROIPerimeterDescriptor; 118import plugins.kernel.roi.descriptor.measure.ROISurfaceAreaDescriptor; 119import plugins.kernel.roi.descriptor.measure.ROIVolumeDescriptor; 120import plugins.kernel.roi.descriptor.property.ROIColorDescriptor; 121import plugins.kernel.roi.descriptor.property.ROIGroupIdDescriptor; 122import plugins.kernel.roi.descriptor.property.ROIIconDescriptor; 123import plugins.kernel.roi.descriptor.property.ROINameDescriptor; 124import plugins.kernel.roi.descriptor.property.ROIOpacityDescriptor; 125import plugins.kernel.roi.descriptor.property.ROIPositionCDescriptor; 126import plugins.kernel.roi.descriptor.property.ROIPositionTDescriptor; 127import plugins.kernel.roi.descriptor.property.ROIPositionXDescriptor; 128import plugins.kernel.roi.descriptor.property.ROIPositionYDescriptor; 129import plugins.kernel.roi.descriptor.property.ROIPositionZDescriptor; 130import plugins.kernel.roi.descriptor.property.ROISizeCDescriptor; 131import plugins.kernel.roi.descriptor.property.ROISizeTDescriptor; 132import plugins.kernel.roi.descriptor.property.ROISizeXDescriptor; 133import plugins.kernel.roi.descriptor.property.ROISizeYDescriptor; 134import plugins.kernel.roi.descriptor.property.ROISizeZDescriptor; 135 136/** 137 * Abstract ROI panel component 138 */ 139public abstract class AbstractRoisPanel extends ExternalizablePanel 140 implements ActiveSequenceListener, TextChangeListener, ListSelectionListener, PluginLoaderListener 141{ 142 /** 143 * 144 */ 145 protected static final long serialVersionUID = -2870878233087117178L; 146 147 protected static final String ID_VIEW = "view"; 148 protected static final String ID_EXPORT = "export"; 149 150 protected static final String ID_PROPERTY_MINSIZE = "minSize"; 151 protected static final String ID_PROPERTY_MAXSIZE = "maxSize"; 152 protected static final String ID_PROPERTY_DEFAULTSIZE = "defaultSize"; 153 protected static final String ID_PROPERTY_ORDER = "order"; 154 protected static final String ID_PROPERTY_VISIBLE = "visible"; 155 156 // default row comparator 157 protected static Comparator<Object> comparator = new Comparator<Object>() 158 { 159 @SuppressWarnings({"unchecked", "rawtypes"}) 160 @Override 161 public int compare(Object o1, Object o2) 162 { 163 if (o1 == null) 164 { 165 if (o2 == null) 166 return 0; 167 return -1; 168 } 169 if (o2 == null) 170 return 1; 171 172 Object obj1 = o1; 173 Object obj2 = o2; 174 175 if (o1 instanceof String) 176 { 177 if (o1.equals("-" + MathUtil.INFINITE_STRING)) 178 obj1 = Double.valueOf(Double.NEGATIVE_INFINITY); 179 else if (o1.equals(MathUtil.INFINITE_STRING)) 180 obj1 = Double.valueOf(Double.POSITIVE_INFINITY); 181 } 182 183 if (o2 instanceof String) 184 { 185 if (o2.equals("-" + MathUtil.INFINITE_STRING)) 186 obj2 = Double.valueOf(Double.NEGATIVE_INFINITY); 187 else if (o2.equals(MathUtil.INFINITE_STRING)) 188 obj2 = Double.valueOf(Double.POSITIVE_INFINITY); 189 } 190 191 if ((obj1 instanceof Number) && (obj2 instanceof Number)) 192 { 193 final double d1 = ((Number) obj1).doubleValue(); 194 final double d2 = ((Number) obj2).doubleValue(); 195 196 if (Double.isNaN(d1)) 197 { 198 if (Double.isNaN(d2)) 199 return 0; 200 return -1; 201 } 202 if (Double.isNaN(d2)) 203 return 1; 204 205 if (d1 < d2) 206 return -1; 207 if (d1 > d2) 208 return 1; 209 210 return 0; 211 } 212 else if ((obj1 instanceof Comparable) && (obj1.getClass() == obj2.getClass())) 213 return ((Comparable) obj1).compareTo(obj2); 214 215 return o1.toString().compareTo(o2.toString()); 216 } 217 }; 218 219 // GUI 220 protected ROITableModel roiTableModel; 221 protected ListSelectionModel roiSelectionModel; 222 protected JXTable roiTable; 223 protected IcyTextField nameFilter; 224 protected JLabel roiNumberLabel; 225 protected JLabel selectedRoiNumberLabel; 226 227 // PluginDescriptors / ROIDescriptor map 228 protected Map<ROIDescriptor, PluginROIDescriptor> descriptorMap; 229 // DescriptorComputer / ROIDescriptor map 230 protected Map<ROIDescriptor, DescriptorComputer> descriptorComputerMap; 231 232 // Descriptor / column info (static to the class) 233 protected List<ColumnInfo> columnInfoList; 234 // // last visible columns (used to detect change in column configuration) 235 // List<String> lastVisibleColumnIds; 236 237 // ROI info list cache 238 protected Set<ROI> roiSet; 239 protected Map<ROI, ROIResults> roiResultsMap; 240 protected List<ROI> filteredRoiList; 241 protected List<ROIResults> filteredRoiResultsList; 242 243 // internals 244 protected final XMLPreferences basePreferences; 245 protected final XMLPreferences viewPreferences; 246 protected final XMLPreferences exportPreferences; 247 protected final Semaphore modifySelection; 248 249 // complete refresh of the roiTable 250 protected final Runnable roiListRefresher; 251 protected final Runnable filteredRoiListRefresher; 252 protected final Runnable tableDataStructureRefresher; 253 protected final Runnable tableDataRefresher; 254 protected final Runnable tableSelectionRefresher; 255 protected final Runnable columnInfoListRefresher; 256 protected final InstanceProcessor processor; 257 258 protected final DescriptorComputer primaryDescriptorComputer; 259 protected final DescriptorComputer basicDescriptorComputer; 260 protected final DescriptorComputer advancedDescriptorComputer; 261 262 protected long lastTableDataRefresh; 263 264 /** 265 * Create a new ROI table panel.<br> 266 * 267 * @param preferences 268 * XML preferences node which will contains the ROI table settings 269 */ 270 public AbstractRoisPanel(XMLPreferences preferences) 271 { 272 super("ROI", "roiPanel", new Point(100, 100), new Dimension(400, 600)); 273 274 basePreferences = preferences; 275 viewPreferences = basePreferences.node(ID_VIEW); 276 exportPreferences = basePreferences.node(ID_EXPORT); 277 278 roiSet = new HashSet<ROI>(); 279 roiResultsMap = new HashMap<ROI, ROIResults>(); 280 filteredRoiList = new ArrayList<ROI>(); 281 filteredRoiResultsList = new ArrayList<ROIResults>(); 282 modifySelection = new Semaphore(1); 283 columnInfoList = new ArrayList<ColumnInfo>(); 284 285 lastTableDataRefresh = 0L; 286 287 initialize(); 288 289 roiListRefresher = new Runnable() 290 { 291 @Override 292 public void run() 293 { 294 refreshRoisInternal(); 295 } 296 }; 297 filteredRoiListRefresher = new Runnable() 298 { 299 @Override 300 public void run() 301 { 302 refreshFilteredRoisInternal(); 303 } 304 }; 305 tableDataStructureRefresher = new Runnable() 306 { 307 @Override 308 public void run() 309 { 310 refreshTableDataStructureInternal(); 311 } 312 }; 313 tableDataRefresher = new Runnable() 314 { 315 @Override 316 public void run() 317 { 318 refreshTableDataInternal(); 319 } 320 }; 321 tableSelectionRefresher = new Runnable() 322 { 323 @Override 324 public void run() 325 { 326 refreshTableSelectionInternal(); 327 } 328 }; 329 columnInfoListRefresher = new Runnable() 330 { 331 @Override 332 public void run() 333 { 334 refreshColumnInfoListInternal(); 335 } 336 }; 337 338 LookAndFeelUtil.addSkinChangeListener(new SkinChangeListener() 339 { 340 @Override 341 public void skinChanged() 342 { 343 // regenerate column model to redefine the Cell Renderer (else colors are wrong) 344 roiTable.setColumnModel(new ROITableColumnModel()); 345 346 // final TableColumnModel columnModel = roiTable.getColumnModel(); 347 // 348 // for (int i = 0; i < columnModel.getColumnCount(); i++) 349 // { 350 // final TableColumn column = columnModel.getColumn(i); 351 // 352 // // need to reset specific renderer as background color can be wrong 353 // if (column.getCellRenderer() instanceof ImageTableCellRenderer) 354 // column.setCellRenderer(new ImageTableCellRenderer(18)); 355 // } 356 357 // modify highlighter 358 roiTable.setHighlighters(HighlighterFactory.createSimpleStriping()); 359 } 360 }); 361 362 processor = new InstanceProcessor(); 363 processor.setThreadName("ROI panel GUI refresher"); 364 processor.setKeepAliveTime(30, TimeUnit.SECONDS); 365 366 primaryDescriptorComputer = new DescriptorComputer(DescriptorType.PRIMARY); 367 basicDescriptorComputer = new DescriptorComputer(DescriptorType.BASIC); 368 advancedDescriptorComputer = new DescriptorComputer(DescriptorType.EXTERNAL); 369 primaryDescriptorComputer.start(); 370 basicDescriptorComputer.start(); 371 advancedDescriptorComputer.start(); 372 373 // update descriptors list (this rebuild the column model of the tree table) 374 refreshDescriptorList(); 375 // set shortcuts 376 buildActionMap(); 377 378 refreshRois(); 379 380 // listen plugin loader changes 381 PluginLoader.addListener(this); 382 } 383 384 protected void initialize() 385 { 386 // need filter before load 387 nameFilter = new IcyTextField(); 388 nameFilter.setToolTipText("Filter ROI by name"); 389 nameFilter.addTextChangeListener(this); 390 391 selectedRoiNumberLabel = new JLabel("0"); 392 roiNumberLabel = new JLabel("0"); 393 394 // build roiTable model 395 roiTableModel = new ROITableModel(); 396 397 // build roiTable 398 roiTable = new JXTable(roiTableModel); 399 roiTable.setAutoStartEditOnKeyStroke(false); 400 roiTable.setAutoCreateRowSorter(false); 401 roiTable.setAutoCreateColumnsFromModel(false); 402 roiTable.setShowVerticalLines(false); 403 roiTable.setColumnControlVisible(false); 404 roiTable.setColumnSelectionAllowed(false); 405 roiTable.setRowSelectionAllowed(true); 406 roiTable.setSortable(true); 407 // set highlight 408 roiTable.setHighlighters(HighlighterFactory.createSimpleStriping()); 409 roiTable.addMouseListener(new MouseAdapter() 410 { 411 @Override 412 public void mousePressed(MouseEvent event) 413 { 414 if (event.getClickCount() == 2) 415 { 416 final int c = roiTable.columnAtPoint(event.getPoint()); 417 TableColumn col = null; 418 419 if (c != -1) 420 col = roiTable.getColumn(c); 421 422 if ((col == null) || !col.getHeaderValue().equals(new ROINameDescriptor().getName())) 423 roiTableDoubleClicked(); 424 } 425 } 426 }); 427 428 // set header settings 429 final JTableHeader tableHeader = roiTable.getTableHeader(); 430 tableHeader.setReorderingAllowed(false); 431 tableHeader.setResizingAllowed(true); 432 433 // set selection model 434 roiSelectionModel = roiTable.getSelectionModel(); 435 roiSelectionModel.addListSelectionListener(this); 436 roiSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 437 438 roiTable.setRowSorter(new ROITableSortController<ROITableModel>()); 439 440 final JPanel middlePanel = new JPanel(new BorderLayout(0, 0)); 441 442 middlePanel.add(roiTable.getTableHeader(), BorderLayout.NORTH); 443 middlePanel.add(new JScrollPane(roiTable, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, 444 ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER); 445 446 final IcyButton settingButton = new IcyButton(RoiActions.settingAction); 447 settingButton.setHideActionText(true); 448 settingButton.setFlat(true); 449 450 final IcyButton xlsExportButton = new IcyButton(RoiActions.xlsExportAction); 451 xlsExportButton.setHideActionText(true); 452 xlsExportButton.setFlat(true); 453 454 setLayout(new BorderLayout()); 455 add(GuiUtil.createLineBoxPanel(nameFilter, Box.createHorizontalStrut(8), selectedRoiNumberLabel, 456 new JLabel(" / "), roiNumberLabel, Box.createHorizontalStrut(4), settingButton, xlsExportButton), 457 BorderLayout.NORTH); 458 add(middlePanel, BorderLayout.CENTER); 459 460 validate(); 461 } 462 463 protected void buildActionMap() 464 { 465 final InputMap imap = roiTable.getInputMap(JComponent.WHEN_FOCUSED); 466 final ActionMap amap = roiTable.getActionMap(); 467 468 imap.put(RoiActions.unselectAction.getKeyStroke(), RoiActions.unselectAction.getName()); 469 imap.put(RoiActions.deleteAction.getKeyStroke(), RoiActions.deleteAction.getName()); 470 // also allow backspace key for delete operation here 471 imap.put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), RoiActions.deleteAction.getName()); 472 imap.put(RoiActions.copyAction.getKeyStroke(), RoiActions.copyAction.getName()); 473 imap.put(RoiActions.pasteAction.getKeyStroke(), RoiActions.pasteAction.getName()); 474 imap.put(RoiActions.copyLinkAction.getKeyStroke(), RoiActions.copyLinkAction.getName()); 475 imap.put(RoiActions.pasteLinkAction.getKeyStroke(), RoiActions.pasteLinkAction.getName()); 476 477 // disable search feature (we have our own filter) 478 amap.remove("find"); 479 amap.put(RoiActions.unselectAction.getName(), RoiActions.unselectAction); 480 amap.put(RoiActions.deleteAction.getName(), RoiActions.deleteAction); 481 amap.put(RoiActions.copyAction.getName(), RoiActions.copyAction); 482 amap.put(RoiActions.pasteAction.getName(), RoiActions.pasteAction); 483 amap.put(RoiActions.copyLinkAction.getName(), RoiActions.copyLinkAction); 484 amap.put(RoiActions.pasteLinkAction.getName(), RoiActions.pasteLinkAction); 485 } 486 487 protected ROIResults createNewROIResults(ROI roi) 488 { 489 return new ROIResults(roi); 490 } 491 492 /** 493 * Returns number of channel of current sequence 494 */ 495 protected int getChannelCount() 496 { 497 final Sequence sequence = getSequence(); 498 499 if (sequence != null) 500 return sequence.getSizeC(); 501 502 return 1; 503 } 504 505 /** 506 * Returns roiTable column suffix for the specified channel 507 */ 508 protected String getChannelNameSuffix(int ch) 509 { 510 final Sequence sequence = getSequence(); 511 512 if ((sequence != null) && (ch < getChannelCount())) 513 return " (" + sequence.getChannelName(ch) + ")"; 514 515 return ""; 516 } 517 518 /** 519 * Returns ROI descriptor given its id. 520 */ 521 protected ROIDescriptor getROIDescriptor(String descriptorId) 522 { 523 final ROIDescriptor[] descriptors; 524 525 synchronized (descriptorMap) 526 { 527 descriptors = descriptorMap.keySet().toArray(new ROIDescriptor[descriptorMap.size()]); 528 } 529 530 for (ROIDescriptor descriptor : descriptors) 531 if (descriptor.getId().equals(descriptorId)) 532 return descriptor; 533 534 return null; 535 } 536 537 // /** 538 // * Get column info for specified visible column index. 539 // */ 540 // protected ColumnInfo getVisibleColumnInfo(List<ColumnInfo> columns, int column) 541 // { 542 // int ind = 0; 543 // for (int c = 0; c < columns.size(); c++) 544 // { 545 // final ColumnInfo col = columns.get(c); 546 // 547 // if (col.visible) 548 // { 549 // if (ind == column) 550 // return col; 551 // ind++; 552 // } 553 // } 554 // 555 // return null; 556 // } 557 // 558 // /** 559 // * Get column info for specified visible column index. 560 // */ 561 // protected ColumnInfo getVisibleColumnInfo(int column) 562 // { 563 // return getVisibleColumnInfo(columnInfoList, column); 564 // } 565 566 /** 567 * Get column info for specified column index. 568 */ 569 protected ColumnInfo getColumnInfo(List<ColumnInfo> columns, int column) 570 { 571 if (column < columns.size()) 572 return columns.get(column); 573 574 return null; 575 } 576 577 /** 578 * Get column info for specified column index. 579 */ 580 protected ColumnInfo getColumnInfo(int column) 581 { 582 return getColumnInfo(columnInfoList, column); 583 } 584 585 protected ColumnInfo getColumnInfo(List<ColumnInfo> columns, ROIDescriptor descriptor, int channel) 586 { 587 for (ColumnInfo ci : columns) 588 if (ci.descriptor.equals(descriptor) && (ci.channel == channel)) 589 return ci; 590 591 return null; 592 } 593 594 protected ColumnInfo getColumnInfo(ROIDescriptor descriptor, int channel) 595 { 596 return getColumnInfo(columnInfoList, descriptor, channel); 597 } 598 599 protected abstract Sequence getSequence(); 600 601 public void setNameFilter(String name) 602 { 603 nameFilter.setText(name); 604 } 605 606 protected boolean computeROIResults(ROIResults roiResults, Sequence seq, ColumnInfo columnInfo) 607 { 608 final Map<ColumnInfo, DescriptorResult> results = roiResults.descriptorResults; 609 final ROIDescriptor descriptor = columnInfo.descriptor; 610 final DescriptorResult result; 611 612 synchronized (results) 613 { 614 // get result 615 result = results.get(columnInfo); 616 } 617 618 // need to refresh this column result 619 if ((result != null) && result.isOutdated()) 620 { 621 // get the corresponding plugin 622 final PluginROIDescriptor plugin; 623 624 synchronized (descriptorMap) 625 { 626 plugin = descriptorMap.get(descriptor); 627 } 628 629 if (plugin != null) 630 { 631 final Map<ROIDescriptor, Object> newResults; 632 633 try 634 { 635 // need computation per channel ? 636 if (descriptor.separateChannel()) 637 { 638 // retrieve the ROI for this channel 639 final ROI roi = roiResults.getRoiForChannel(columnInfo.channel); 640 641 if (roi == null) 642 throw new UnsupportedOperationException( 643 "Can't retrieve sub ROI for channel " + columnInfo.channel); 644 645 newResults = plugin.compute(roi, seq); 646 } 647 else 648 newResults = plugin.compute(roiResults.roi, seq); 649 650 for (Entry<ROIDescriptor, Object> entryNewResult : newResults.entrySet()) 651 { 652 // get the column for this result 653 final ColumnInfo resultColumnInfo = getColumnInfo(entryNewResult.getKey(), columnInfo.channel); 654 final DescriptorResult oResult; 655 656 synchronized (results) 657 { 658 // get corresponding result 659 oResult = results.get(resultColumnInfo); 660 } 661 662 if (oResult != null) 663 { 664 // set the result value 665 oResult.setValue(entryNewResult.getValue()); 666 // result is up to date 667 oResult.setOutdated(false); 668 } 669 } 670 } 671 catch (Throwable t) 672 { 673 // not an UnsupportedOperationException --> show the error 674 if (!(t instanceof UnsupportedOperationException)) 675 IcyExceptionHandler.handleException(t, true); 676 677 final List<ROIDescriptor> descriptors = plugin.getDescriptors(); 678 679 if (descriptors != null) 680 { 681 // not supported --> clear associated results and set them as computed 682 for (ROIDescriptor desc : descriptors) 683 { 684 // get the column for this result 685 final ColumnInfo resultColumnInfo = getColumnInfo(desc, columnInfo.channel); 686 final DescriptorResult oResult; 687 688 synchronized (results) 689 { 690 // get corresponding result 691 oResult = results.get(resultColumnInfo); 692 } 693 694 if (oResult != null) 695 { 696 oResult.setValue(null); 697 oResult.setOutdated(false); 698 } 699 } 700 } 701 } 702 703 // we updated result 704 return true; 705 } 706 } 707 708 return false; 709 } 710 711 /** 712 * Return index of specified ROI in the filtered ROI list 713 */ 714 protected int getRoiIndex(ROI roi) 715 { 716 final int result = Collections.binarySearch(filteredRoiList, roi, ROI.idComparator); 717 718 if (result >= 0) 719 return result; 720 721 return -1; 722 } 723 724 /** 725 * Return index of specified ROI in the model 726 */ 727 protected int getRoiModelIndex(ROI roi) 728 { 729 return getRoiIndex(roi); 730 } 731 732 /** 733 * Return index of specified ROI in the table (view) 734 */ 735 protected int getRoiViewIndex(ROI roi) 736 { 737 final int ind = getRoiModelIndex(roi); 738 739 if (ind == -1) 740 return ind; 741 742 try 743 { 744 return roiTable.convertRowIndexToView(ind); 745 } 746 catch (IndexOutOfBoundsException e) 747 { 748 return -1; 749 } 750 } 751 752 protected ROIResults getRoiResults(int rowModelIndex) 753 { 754 final List<ROIResults> entries = filteredRoiResultsList; 755 756 if ((rowModelIndex >= 0) && (rowModelIndex < entries.size())) 757 return entries.get(rowModelIndex); 758 759 return null; 760 } 761 762 /** 763 * Returns the visible ROI in the ROI control panel. 764 */ 765 public List<ROI> getVisibleRois() 766 { 767 return new ArrayList<ROI>(filteredRoiList); 768 } 769 770 // /** 771 // * Returns the ROI informations for the specified ROI. 772 // */ 773 // public ROIInfo getROIInfo(ROI roi) 774 // { 775 // final int index = getRoiIndex(roi); 776 // 777 // if (index != -1) 778 // return filteredRois.get(index); 779 // 780 // return null; 781 // } 782 783 /** 784 * Returns the number of selected ROI from the table. 785 */ 786 public int getSelectedRoisCount() 787 { 788 int result = 0; 789 790 synchronized (roiSelectionModel) 791 { 792 if (!roiSelectionModel.isSelectionEmpty()) 793 { 794 for (int i = roiSelectionModel.getMinSelectionIndex(); i <= roiSelectionModel 795 .getMaxSelectionIndex(); i++) 796 if (roiSelectionModel.isSelectedIndex(i)) 797 result++; 798 } 799 } 800 801 return result; 802 } 803 804 /** 805 * Returns the selected ROI from the table. 806 */ 807 public List<ROI> getSelectedRois() 808 { 809 final List<ROIResults> roiResults = filteredRoiResultsList; 810 final List<ROI> result = new ArrayList<ROI>(roiResults.size()); 811 812 synchronized (roiSelectionModel) 813 { 814 if (!roiSelectionModel.isSelectionEmpty()) 815 { 816 for (int i = roiSelectionModel.getMinSelectionIndex(); i <= roiSelectionModel 817 .getMaxSelectionIndex(); i++) 818 { 819 if (roiSelectionModel.isSelectedIndex(i)) 820 { 821 try 822 { 823 final int index = roiTable.convertRowIndexToModel(i); 824 825 if ((index >= 0) && (index < roiResults.size())) 826 result.add(roiResults.get(index).roi); 827 } 828 catch (IndexOutOfBoundsException e) 829 { 830 // ignore 831 } 832 } 833 } 834 } 835 } 836 837 return result; 838 } 839 840 /** 841 * Select the specified list of ROI in the ROI Table 842 */ 843 protected void setSelectedRoisInternal(Set<ROI> newSelected) 844 { 845 final List<Integer> selectedIndexes = new ArrayList<Integer>(); 846 final List<ROI> roiList = filteredRoiList; 847 848 for (int i = 0; i < roiList.size(); i++) 849 { 850 final ROI roi = roiList.get(i); 851 852 // HashSet provides fast "contains" 853 if (newSelected.contains(roi)) 854 { 855 int ind; 856 857 try 858 { 859 // convert model index to view index 860 ind = roiTable.convertRowIndexToView(i); 861 } 862 catch (IndexOutOfBoundsException e) 863 { 864 ind = -1; 865 } 866 867 if (ind > -1) 868 selectedIndexes.add(Integer.valueOf(ind)); 869 } 870 } 871 872 synchronized (roiSelectionModel) 873 { 874 // start selection change 875 roiSelectionModel.setValueIsAdjusting(true); 876 try 877 { 878 // start by clearing selection 879 roiSelectionModel.clearSelection(); 880 881 for (Integer index : selectedIndexes) 882 roiSelectionModel.addSelectionInterval(index.intValue(), index.intValue()); 883 } 884 finally 885 { 886 // end selection change 887 roiSelectionModel.setValueIsAdjusting(false); 888 } 889 } 890 } 891 892 protected Set<ROI> getFilteredSet(String filter) 893 { 894 final Set<ROI> rois = roiSet; 895 final Set<ROI> result = new HashSet<ROI>(); 896 897 if (StringUtil.isEmpty(filter, true)) 898 result.addAll(rois); 899 else 900 { 901 final String text = filter.trim().toLowerCase(); 902 903 // filter on name 904 for (ROI roi : rois) 905 if (roi.getName().toLowerCase().indexOf(text) != -1) 906 result.add(roi); 907 } 908 909 return result; 910 } 911 912 /** 913 * Display the roi in the table (scroll if needed) 914 */ 915 public void scrollTo(ROI roi) 916 { 917 final int index = getRoiIndex(roi); 918 919 if (index != -1) 920 roiTable.scrollRowToVisible(index); 921 } 922 923 protected void refreshRoiNumbers() 924 { 925 final int selectedCount = getSelectedRoisCount(); 926 final int roisCount = roiTable.getRowCount(); 927 928 selectedRoiNumberLabel.setText(Integer.toString(selectedCount)); 929 roiNumberLabel.setText(Integer.toString(roisCount)); 930 931 if (selectedCount == 0) 932 selectedRoiNumberLabel.setToolTipText("No selected ROI"); 933 else if (selectedCount == 1) 934 selectedRoiNumberLabel.setToolTipText("1 selected ROI"); 935 else 936 selectedRoiNumberLabel.setToolTipText(selectedCount + " selected ROIs"); 937 938 if (roisCount == 0) 939 roiNumberLabel.setToolTipText("No ROI"); 940 else if (roisCount == 1) 941 roiNumberLabel.setToolTipText("1 ROI"); 942 else 943 roiNumberLabel.setToolTipText(roisCount + " ROIs"); 944 } 945 946 /** 947 * refresh whole ROI list 948 */ 949 protected void refreshRois() 950 { 951 processor.submit(true, roiListRefresher); 952 } 953 954 /** 955 * refresh whole ROI list (internal) 956 */ 957 protected void refreshRoisInternal() 958 { 959 final Set<ROI> currentRoiSet = roiSet; 960 final Set<ROI> newRoiSet; 961 final Sequence sequence = getSequence(); 962 963 if (sequence != null) 964 newRoiSet = sequence.getROISet(); 965 else 966 newRoiSet = new HashSet<ROI>(); 967 968 // no change --> exit 969 if (newRoiSet.equals(currentRoiSet)) 970 return; 971 972 final Set<ROI> removedSet = new HashSet<ROI>(); 973 974 // build removed set 975 for (ROI roi : currentRoiSet) 976 if (!newRoiSet.contains(roi)) 977 removedSet.add(roi); 978 979 // remove from ROI entry map 980 for (ROI roi : removedSet) 981 { 982 final ROIResults roiResults; 983 984 // must be synchronized 985 synchronized (roiResultsMap) 986 { 987 roiResults = roiResultsMap.remove(roi); 988 } 989 990 // cancel results computation 991 if (roiResults != null) 992 cancelDescriptorComputation(roiResults); 993 } 994 995 // set new ROI set 996 roiSet = newRoiSet; 997 998 // refresh filtered list now 999 refreshFilteredRoisInternal(); 1000 } 1001 1002 /** 1003 * refresh filtered ROI list 1004 */ 1005 protected void refreshFilteredRois() 1006 { 1007 processor.submit(true, filteredRoiListRefresher); 1008 } 1009 1010 /** 1011 * refresh filtered ROI list (internal) 1012 */ 1013 protected void refreshFilteredRoisInternal() 1014 { 1015 // get new filtered list 1016 final List<ROI> currentFilteredRoiList = filteredRoiList; 1017 final Set<ROI> newFilteredRoiSet = getFilteredSet(nameFilter.getText()); 1018 1019 // no change --> exit 1020 if (newFilteredRoiSet.equals(currentFilteredRoiList)) 1021 return; 1022 1023 // update filtered lists 1024 final List<ROI> newFilteredRoiList = new ArrayList<ROI>(newFilteredRoiSet); 1025 final List<ROIResults> newFilteredResultsList = new ArrayList<ROIResults>(newFilteredRoiList.size()); 1026 1027 // sort on id 1028 Collections.sort(newFilteredRoiList, ROI.idComparator); 1029 // then build filtered results list 1030 for (ROI roi : newFilteredRoiList) 1031 { 1032 ROIResults roiResults; 1033 1034 synchronized (roiResultsMap) 1035 { 1036 // try to get the ROI results from the map first 1037 roiResults = roiResultsMap.get(roi); 1038 // and create it if needed 1039 if (roiResults == null) 1040 { 1041 roiResults = createNewROIResults(roi); 1042 roiResultsMap.put(roi, roiResults); 1043 } 1044 } 1045 1046 newFilteredResultsList.add(roiResults); 1047 } 1048 1049 filteredRoiList = newFilteredRoiList; 1050 filteredRoiResultsList = newFilteredResultsList; 1051 1052 // update the table model (should always correspond to the filtered roi results list) 1053 refreshTableDataStructureInternal(); 1054 } 1055 1056 public void refreshTableDataStructure() 1057 { 1058 processor.submit(true, tableDataStructureRefresher); 1059 } 1060 1061 protected void refreshTableDataStructureInternal() 1062 { 1063 // don't eat too much time on data structure refresh 1064 ThreadUtil.sleep(1); 1065 1066 final Set<ROI> newSelectedRois; 1067 final Sequence sequence = getSequence(); 1068 1069 if (sequence != null) 1070 newSelectedRois = sequence.getSelectedROISet(); 1071 else 1072 newSelectedRois = new HashSet<ROI>(); 1073 1074 ThreadUtil.invokeNow(new Runnable() 1075 { 1076 @Override 1077 public void run() 1078 { 1079 modifySelection.acquireUninterruptibly(); 1080 try 1081 { 1082 synchronized (roiTableModel) 1083 { 1084 try 1085 { 1086 // notify table data changed 1087 roiTableModel.fireTableDataChanged(); 1088 } 1089 catch (Exception e) 1090 { 1091 // Sorter don't like when we change data while it's sorting... 1092 } 1093 } 1094 1095 // selection to restore ? 1096 if (!newSelectedRois.isEmpty()) 1097 setSelectedRoisInternal(newSelectedRois); 1098 1099 // // force loading values on sorted column 1100 // final List<? extends SortKey> keys = roiTable.getRowSorter().getSortKeys(); 1101 // if (!keys.isEmpty()) 1102 // forceComputationForColumn(keys.get(0).getColumn()); 1103 } 1104 finally 1105 { 1106 modifySelection.release(); 1107 } 1108 } 1109 }); 1110 1111 refreshRoiNumbers(); 1112 } 1113 1114 public void refreshTableData() 1115 { 1116 processor.submit(true, tableDataRefresher); 1117 } 1118 1119 protected void refreshTableDataInternal() 1120 { 1121 final long time = System.currentTimeMillis(); 1122 final boolean hasPendingTask = primaryDescriptorComputer.hasPendingComputation() 1123 && basicDescriptorComputer.hasPendingComputation() 1124 && advancedDescriptorComputer.hasPendingComputation(); 1125 1126 // still pending descriptor task ? 1127 if (hasPendingTask) 1128 { 1129 // avoid too much table data update 1130 if ((time - lastTableDataRefresh) < 200) 1131 return; 1132 } 1133 1134 lastTableDataRefresh = time; 1135 1136 // don't eat too much time on data structure refresh 1137 ThreadUtil.sleep(1); 1138 1139 ThreadUtil.invokeNow(new Runnable() 1140 { 1141 @Override 1142 public void run() 1143 { 1144 final int rowCount = roiTable.getRowCount(); 1145 1146 // we use 'RowsUpdated' event to keep selection (DataChanged remove selection) 1147 if (rowCount > 0) 1148 { 1149 // save anchor index which is lost with 'RowsUpdated' event 1150 final int anchorInd = ((DefaultListSelectionModel) roiSelectionModel).getAnchorSelectionIndex(); 1151 1152 synchronized (roiTableModel) 1153 { 1154 try 1155 { 1156 roiTableModel.fireTableRowsUpdated(0, rowCount - 1); 1157 } 1158 catch (Exception e) 1159 { 1160 // Sorter don't like when we change data while it's sorting... 1161 } 1162 } 1163 1164 // restore anchor index 1165 if (anchorInd != -1) 1166 ((DefaultListSelectionModel) roiSelectionModel).setAnchorSelectionIndex(anchorInd); 1167 } 1168 } 1169 }); 1170 1171 refreshRoiNumbers(); 1172 } 1173 1174 public void refreshTableSelection() 1175 { 1176 processor.submit(true, tableSelectionRefresher); 1177 } 1178 1179 protected void refreshTableSelectionInternal() 1180 { 1181 // don't eat too much time on selection refresh 1182 ThreadUtil.sleep(1); 1183 1184 final Set<ROI> newSelectedRois; 1185 final Sequence sequence = getSequence(); 1186 1187 if (sequence != null) 1188 newSelectedRois = sequence.getSelectedROISet(); 1189 else 1190 newSelectedRois = new HashSet<ROI>(); 1191 1192 ThreadUtil.invokeNow(new Runnable() 1193 { 1194 @Override 1195 public void run() 1196 { 1197 modifySelection.acquireUninterruptibly(); 1198 try 1199 { 1200 // set selection 1201 setSelectedRoisInternal(newSelectedRois); 1202 } 1203 finally 1204 { 1205 modifySelection.release(); 1206 } 1207 } 1208 }); 1209 1210 refreshRoiNumbers(); 1211 } 1212 1213 protected void refreshDescriptorList() 1214 { 1215 descriptorMap = ROIUtil.getROIDescriptors(); 1216 refreshColumnInfoList(); 1217 } 1218 1219 public void refreshColumnInfoList() 1220 { 1221 processor.submit(true, columnInfoListRefresher); 1222 } 1223 1224 protected void refreshColumnInfoListInternal() 1225 { 1226 // rebuild the column property list 1227 final List<ColumnInfo> newColumnInfos = new ArrayList<ColumnInfo>(); 1228 final int numChannel = getChannelCount(); 1229 1230 for (ROIDescriptor descriptor : descriptorMap.keySet()) 1231 { 1232 for (int ch = 0; ch < (descriptor.separateChannel() ? numChannel : 1); ch++) 1233 newColumnInfos.add(new ColumnInfo(descriptor, ch, viewPreferences, false)); 1234 } 1235 1236 // sort the list on order 1237 Collections.sort(newColumnInfos); 1238 // set new column info 1239 columnInfoList = newColumnInfos; 1240 // rebuild table columns 1241 ThreadUtil.invokeNow(new Runnable() 1242 { 1243 @Override 1244 public void run() 1245 { 1246 // regenerate column model 1247 roiTable.setColumnModel(new ROITableColumnModel()); 1248 } 1249 }); 1250 } 1251 1252 // protected void forceComputationForColumn(int column) 1253 // { 1254 // final int numRow = roiTableModel.getRowCount(); 1255 // 1256 // try 1257 // { 1258 // // force computation of the column values if needed 1259 // for (int i = 0; i < numRow; i++) 1260 // roiTableModel.getValueAt(i, column); 1261 // } 1262 // catch (Exception e) 1263 // { 1264 // // ignore 1265 // System.out.println(e); 1266 // } 1267 // } 1268 1269 // protected boolean hasPendingComputation(ROIResults results) 1270 // { 1271 // return primaryDescriptorComputer.hasPendingComputation(results) 1272 // || basicDescriptorComputer.hasPendingComputation(results) 1273 // || advancedDescriptorComputer.hasPendingComputation(results); 1274 // } 1275 1276 protected void requestDescriptorComputation(ROIResults results) 1277 { 1278 primaryDescriptorComputer.requestDescriptorComputation(results); 1279 basicDescriptorComputer.requestDescriptorComputation(results); 1280 advancedDescriptorComputer.requestDescriptorComputation(results); 1281 } 1282 1283 protected void cancelDescriptorComputation(ROIResults results) 1284 { 1285 primaryDescriptorComputer.cancelDescriptorComputation(results); 1286 basicDescriptorComputer.cancelDescriptorComputation(results); 1287 advancedDescriptorComputer.cancelDescriptorComputation(results); 1288 } 1289 1290 protected void cancelDescriptorComputation(ROI roi) 1291 { 1292 primaryDescriptorComputer.cancelDescriptorComputation(roi); 1293 basicDescriptorComputer.cancelDescriptorComputation(roi); 1294 advancedDescriptorComputer.cancelDescriptorComputation(roi); 1295 } 1296 1297 protected void cancelAllDescriptorComputation() 1298 { 1299 primaryDescriptorComputer.cancelAllDescriptorComputation(); 1300 basicDescriptorComputer.cancelAllDescriptorComputation(); 1301 advancedDescriptorComputer.cancelAllDescriptorComputation(); 1302 } 1303 1304 /** 1305 * @deprecated Use {@link #getCSVFormattedInfos()} instead. 1306 */ 1307 @Deprecated 1308 public String getCSVFormattedInfosOfSelectedRois() 1309 { 1310 // Check to ensure we have selected only a contiguous block of cells 1311 final int numcols = roiTable.getColumnCount(); 1312 final int numrows = roiTable.getSelectedRowCount(); 1313 1314 // roiTable is empty --> returns empty string 1315 if (numrows == 0) 1316 return ""; 1317 1318 final StringBuffer sbf = new StringBuffer(); 1319 final int[] rowsselected = roiTable.getSelectedRows(); 1320 1321 // column name 1322 for (int j = 1; j < numcols; j++) 1323 { 1324 sbf.append(roiTable.getModel().getColumnName(j)); 1325 if (j < numcols - 1) 1326 sbf.append("\t"); 1327 } 1328 sbf.append("\r\n"); 1329 1330 // then content 1331 for (int i = 0; i < numrows; i++) 1332 { 1333 for (int j = 1; j < numcols; j++) 1334 { 1335 final Object value = roiTable.getModel().getValueAt(roiTable.convertRowIndexToModel(rowsselected[i]), 1336 j); 1337 1338 // special case of double array 1339 if (value instanceof double[]) 1340 { 1341 final double[] darray = (double[]) value; 1342 1343 for (int l = 0; l < darray.length; l++) 1344 { 1345 sbf.append(darray[l]); 1346 if (l < darray.length - 1) 1347 sbf.append(" "); 1348 } 1349 } 1350 else 1351 sbf.append(value); 1352 1353 if (j < numcols - 1) 1354 sbf.append("\t"); 1355 } 1356 sbf.append("\r\n"); 1357 } 1358 1359 return sbf.toString(); 1360 } 1361 1362 /** 1363 * Returns all ROI informations in CSV format (tab separated) immediately. 1364 */ 1365 public String getCSVFormattedInfos() 1366 { 1367 final List<ColumnInfo> exportColumnInfos = new ArrayList<ColumnInfo>(); 1368 final Sequence seq = getSequence(); 1369 final int numChannel = getChannelCount(); 1370 1371 // get export column informations 1372 for (ROIDescriptor descriptor : descriptorMap.keySet()) 1373 { 1374 for (int ch = 0; ch < (descriptor.separateChannel() ? numChannel : 1); ch++) 1375 exportColumnInfos.add(new ColumnInfo(descriptor, ch, exportPreferences, true)); 1376 } 1377 1378 // sort the list on order 1379 Collections.sort(exportColumnInfos); 1380 1381 final StringBuffer sbf = new StringBuffer(); 1382 1383 // column title 1384 for (ColumnInfo columnInfo : exportColumnInfos) 1385 { 1386 if (columnInfo.visible) 1387 { 1388 sbf.append(columnInfo.name); 1389 sbf.append("\t"); 1390 } 1391 } 1392 sbf.append("\r\n"); 1393 1394 final List<ROI> rois = new ArrayList<ROI>(filteredRoiList); 1395 1396 // content 1397 for (ROI roi : rois) 1398 { 1399 final ROIResults results = createNewROIResults(roi); 1400 final Map<ColumnInfo, DescriptorResult> descriptorResults = results.descriptorResults; 1401 1402 // compute results 1403 for (ColumnInfo columnInfo : exportColumnInfos) 1404 { 1405 if (columnInfo.visible) 1406 { 1407 // try to retrieve result for this column 1408 final DescriptorResult result = descriptorResults.get(columnInfo); 1409 1410 // not yet created/computed --> create it and compute it now 1411 if (result == null) 1412 { 1413 descriptorResults.put(columnInfo, new DescriptorResult(columnInfo)); 1414 computeROIResults(results, seq, columnInfo); 1415 } 1416 } 1417 } 1418 1419 // display results 1420 for (ColumnInfo columnInfo : exportColumnInfos) 1421 { 1422 if (columnInfo.visible) 1423 { 1424 final DescriptorResult result = descriptorResults.get(columnInfo); 1425 final String id = columnInfo.descriptor.getId(); 1426 final Object value; 1427 1428 if (result != null) 1429 value = results.formatValue(result.getValue(), id); 1430 else 1431 value = null; 1432 1433 if (value != null) 1434 { 1435 // special case of icon --> use the ROI class name 1436 if (StringUtil.equals(id, ROIIconDescriptor.ID)) 1437 sbf.append(roi.getSimpleClassName()); 1438 // special case of color --> use the color code 1439 else if (StringUtil.equals(id, ROIColorDescriptor.ID)) 1440 sbf.append(String.format("%06X", Integer.valueOf(roi.getColor().getRGB() & 0xFFFFFF))); 1441 else 1442 sbf.append(value); 1443 } 1444 1445 sbf.append("\t"); 1446 } 1447 } 1448 sbf.append("\r\n"); 1449 } 1450 1451 return sbf.toString(); 1452 } 1453 1454 public void showSettingPanel() 1455 { 1456 // create and display the setting frame 1457 new RoiSettingFrame(viewPreferences, exportPreferences, new Runnable() 1458 { 1459 @Override 1460 public void run() 1461 { 1462 // refresh table columns 1463 refreshColumnInfoListInternal(); 1464 } 1465 }); 1466 } 1467 1468 @Override 1469 public void textChanged(IcyTextField source, boolean validate) 1470 { 1471 if (source == nameFilter) 1472 refreshFilteredRois(); 1473 } 1474 1475 // called when selection changed in the ROI table 1476 @Override 1477 public void valueChanged(ListSelectionEvent e) 1478 { 1479 // currently changing the selection ? --> exit 1480 if (e.getValueIsAdjusting()) 1481 return; 1482 // currently changing the selection ? --> exit 1483 if (roiSelectionModel.getValueIsAdjusting()) 1484 return; 1485 1486 if (modifySelection.tryAcquire()) 1487 { 1488 // semaphore acquired here 1489 try 1490 { 1491 final List<ROI> selectedRois = getSelectedRois(); 1492 final Sequence sequence = getSequence(); 1493 1494 // update selected ROI in sequence 1495 if (sequence != null) 1496 sequence.setSelectedROIs(selectedRois); 1497 } 1498 finally 1499 { 1500 modifySelection.release(); 1501 } 1502 } 1503 1504 refreshRoiNumbers(); 1505 } 1506 1507 // called when a ROI has been double clicked in the ROI table 1508 protected void roiTableDoubleClicked() 1509 { 1510 final List<ROI> selectedRois = getSelectedRois(); 1511 1512 if (selectedRois.size() > 0) 1513 { 1514 final ROI selected = selectedRois.get(0); 1515 // get active viewer 1516 final Viewer v = Icy.getMainInterface().getActiveViewer(); 1517 1518 if ((v != null) && (selected != null)) 1519 { 1520 // get canvas 1521 final IcyCanvas canvas = v.getCanvas(); 1522 1523 if (canvas instanceof IcyCanvas2D) 1524 { 1525 // center view on selected ROI 1526 ((IcyCanvas2D) canvas).centerOn(selected.getBounds5D().toRectangle2D().getBounds()); 1527 } 1528 else if (canvas instanceof IcyCanvas3D) 1529 { 1530 // center view on selected ROI 1531 ((IcyCanvas3D) canvas).centerOn(selected.getBounds5D().toRectangle3D().toInteger()); 1532 } 1533 1534 final Rectangle5D bnd = selected.getBounds5D(); 1535 final int t = (int) (bnd.isInfiniteT() ? -1 : bnd.getCenterT()); 1536 final int z = (int) (bnd.isInfiniteZ() ? -1 : bnd.getCenterZ()); 1537 1538 // change position if needed 1539 if (t != -1) 1540 v.setPositionT(t); 1541 if (z != -1) 1542 v.setPositionZ(z); 1543 } 1544 } 1545 } 1546 1547 @Override 1548 public void sequenceActivated(Sequence value) 1549 { 1550 // refresh table columns 1551 refreshColumnInfoList(); 1552 // refresh ROI list 1553 refreshRois(); 1554 } 1555 1556 @Override 1557 public void sequenceDeactivated(Sequence sequence) 1558 { 1559 // nothing here 1560 } 1561 1562 @Override 1563 public void activeSequenceChanged(SequenceEvent event) 1564 { 1565 // we are modifying externally 1566 // if (modifySelection.availablePermits() == 0) 1567 // return; 1568 1569 final SequenceEventSourceType sourceType = event.getSourceType(); 1570 1571 switch (sourceType) 1572 { 1573 case SEQUENCE_ROI: 1574 switch (event.getType()) 1575 { 1576 case ADDED: 1577 case REMOVED: 1578 refreshRois(); 1579 break; 1580 1581 case CHANGED: 1582 // already handled by ROIResults directly 1583 break; 1584 } 1585 break; 1586 1587 case SEQUENCE_META: 1588 // refresh column name (unit can change when pixel size changed) 1589 for (ColumnInfo col : columnInfoList) 1590 col.refreshName(); 1591 1592 // refresh column model 1593 final TableColumnModel model = roiTable.getColumnModel(); 1594 if (model instanceof ROITableColumnModel) 1595 ((ROITableColumnModel) model).updateHeaders(); 1596 1597 // don't use break, we also need to send the event to descriptors 1598 1599 case SEQUENCE_DATA: 1600 final ROIResults[] allRoiResults; 1601 1602 // get all ROI results 1603 synchronized (roiResultsMap) 1604 { 1605 allRoiResults = roiResultsMap.values().toArray(new ROIResults[roiResultsMap.size()]); 1606 } 1607 1608 // notify ROI results that sequence has changed 1609 for (ROIResults roiResults : allRoiResults) 1610 roiResults.sequenceChanged(event); 1611 1612 // refresh table data 1613 refreshTableData(); 1614 break; 1615 1616 case SEQUENCE_TYPE: 1617 // number of channel can have changed 1618 refreshColumnInfoList(); 1619 break; 1620 } 1621 } 1622 1623 @Override 1624 public void pluginLoaderChanged(PluginLoaderEvent e) 1625 { 1626 refreshDescriptorList(); 1627 } 1628 1629 protected class ROITableModel extends AbstractTableModel 1630 { 1631 /** 1632 * 1633 */ 1634 private static final long serialVersionUID = -6537163170625368503L; 1635 1636 public ROITableModel() 1637 { 1638 super(); 1639 } 1640 1641 @Override 1642 public int getColumnCount() 1643 { 1644 return columnInfoList.size(); 1645 } 1646 1647 @Override 1648 public String getColumnName(int column) 1649 { 1650 final ColumnInfo ci = getColumnInfo(column); 1651 1652 if ((ci != null) && (ci.showName)) 1653 return ci.name; 1654 1655 return ""; 1656 } 1657 1658 @Override 1659 public Class<?> getColumnClass(int column) 1660 { 1661 final ColumnInfo ci = getColumnInfo(column); 1662 1663 if (ci != null) 1664 return ci.descriptor.getType(); 1665 1666 return String.class; 1667 } 1668 1669 @Override 1670 public int getRowCount() 1671 { 1672 return filteredRoiResultsList.size(); 1673 } 1674 1675 @Override 1676 public Object getValueAt(int row, int column) 1677 { 1678 final ROIResults roiResults = getRoiResults(row); 1679 1680 if (roiResults != null) 1681 return roiResults.getValueAt(column); 1682 1683 return null; 1684 } 1685 1686 @Override 1687 public void setValueAt(Object value, int row, int column) 1688 { 1689 final ROIResults roiResults = getRoiResults(row); 1690 1691 if (roiResults != null) 1692 roiResults.setValueAt(value, column); 1693 } 1694 1695 @Override 1696 public boolean isCellEditable(int row, int column) 1697 { 1698 final ROIResults roiResults = getRoiResults(row); 1699 1700 if (roiResults != null) 1701 return roiResults.isEditable(column); 1702 1703 return false; 1704 } 1705 } 1706 1707 protected class ROIResults implements ROIListener 1708 { 1709 public final Map<ColumnInfo, DescriptorResult> descriptorResults; 1710 public final ROI roi; 1711 private final Map<Integer, WeakReference<ROI>> channelRois; 1712 1713 protected ROIResults(ROI roi) 1714 { 1715 super(); 1716 1717 this.roi = roi; 1718 descriptorResults = new HashMap<ColumnInfo, DescriptorResult>(); 1719 channelRois = new HashMap<Integer, WeakReference<ROI>>(); 1720 1721 // listen for ROI change event 1722 roi.addListener(this); 1723 } 1724 1725 // boolean areResultsUpToDate() 1726 // { 1727 // for (DescriptorResult result : descriptorResults.values()) 1728 // if (result.isOutdated()) 1729 // return false; 1730 // 1731 // return true; 1732 // } 1733 1734 private void clearChannelRois() 1735 { 1736 synchronized (channelRois) 1737 { 1738 channelRois.clear(); 1739 } 1740 } 1741 1742 public ROI getRoiForChannel(int channel) 1743 { 1744 final Integer key = Integer.valueOf(channel); 1745 WeakReference<ROI> reference; 1746 ROI result; 1747 1748 synchronized (channelRois) 1749 { 1750 reference = channelRois.get(key); 1751 } 1752 1753 if (reference != null) 1754 result = reference.get(); 1755 else 1756 result = null; 1757 1758 // channel ROI does not exist ? 1759 if (result == null) 1760 { 1761 // create it 1762 result = roi.getSubROI(-1, -1, channel); 1763 1764 // failed ? try again 1765 if (result == null) 1766 result = roi.getSubROI(-1, -1, channel); 1767 1768 if (result != null) 1769 { 1770 // and put it in map 1771 synchronized (channelRois) 1772 { 1773 // we use WeakReference to not waste memory 1774 channelRois.put(key, new WeakReference<ROI>(result)); 1775 } 1776 } 1777 } 1778 1779 return result; 1780 } 1781 1782 public boolean isEditable(int column) 1783 { 1784 final ColumnInfo ci = getColumnInfo(column); 1785 1786 if (ci != null) 1787 { 1788 final ROIDescriptor descriptor = ci.descriptor; 1789 final String id = descriptor.getId(); 1790 1791 // only name and color descriptor are editable (a bit hacky) 1792 return id.equals(ROINameDescriptor.ID) || id.equals(ROIColorDescriptor.ID); 1793 } 1794 1795 return false; 1796 } 1797 1798 public Object formatValue(Object value, String id) 1799 { 1800 Object result = value; 1801 1802 // format result if needed 1803 if (result instanceof Number) 1804 { 1805 final double doubleValue = ((Number) result).doubleValue(); 1806 1807 // replace 'infinity' by infinite symbol 1808 if (doubleValue == Double.POSITIVE_INFINITY) 1809 result = MathUtil.INFINITE_STRING; 1810 else if (doubleValue == Double.NEGATIVE_INFINITY) 1811 { 1812 // position descriptor ? negative infinite means 'ALL' here 1813 if (id.equals(ROIPositionXDescriptor.ID) || id.equals(ROIPositionYDescriptor.ID) 1814 || id.equals(ROIPositionZDescriptor.ID) || id.equals(ROIPositionTDescriptor.ID) 1815 || id.equals(ROIPositionCDescriptor.ID) || id.equals(ROIMassCenterXDescriptor.ID) 1816 || id.equals(ROIMassCenterYDescriptor.ID) || id.equals(ROIMassCenterZDescriptor.ID) 1817 || id.equals(ROIMassCenterTDescriptor.ID) || id.equals(ROIMassCenterCDescriptor.ID)) 1818 result = "ALL"; 1819 else 1820 result = "-" + MathUtil.INFINITE_STRING; 1821 } 1822 else if (doubleValue == -1d) 1823 { 1824 // position descriptor ? -1 means 'ALL' here 1825 if (id.equals(ROIPositionXDescriptor.ID) || id.equals(ROIPositionYDescriptor.ID) 1826 || id.equals(ROIPositionZDescriptor.ID) || id.equals(ROIPositionTDescriptor.ID) 1827 || id.equals(ROIPositionCDescriptor.ID) || id.equals(ROIMassCenterXDescriptor.ID) 1828 || id.equals(ROIMassCenterYDescriptor.ID) || id.equals(ROIMassCenterZDescriptor.ID) 1829 || id.equals(ROIMassCenterTDescriptor.ID) || id.equals(ROIMassCenterCDescriptor.ID)) 1830 result = "ALL"; 1831 } 1832 else 1833 { 1834 // value not too large ? 1835 if (Math.abs(doubleValue) < 10000000) 1836 { 1837 // simple integer ? -> show it as integer 1838 if (doubleValue == (int) doubleValue) 1839 result = Integer.valueOf((int) doubleValue); 1840 // small integer value ? 1841 else if (Math.abs(doubleValue) < 100) 1842 result = Double.valueOf(MathUtil.roundSignificant(doubleValue, 5)); 1843 // medium integer value ? 1844 else if (Math.abs(doubleValue) < 10000) 1845 result = Double.valueOf(MathUtil.round(doubleValue, 2)); 1846 // medium large integer value ? 1847 else if (Math.abs(doubleValue) < 1000000) 1848 result = Double.valueOf(MathUtil.round(doubleValue, 1)); 1849 else 1850 // large integer value ? 1851 result = Integer.valueOf((int) Math.round(doubleValue)); 1852 } 1853 else 1854 // format double value 1855 result = Double.valueOf(MathUtil.roundSignificant(doubleValue, 5)); 1856 } 1857 } 1858 1859 return result; 1860 } 1861 1862 /** 1863 * Retrieve the DescriptorResult for the specified column 1864 */ 1865 public DescriptorResult getDescriptorResult(ColumnInfo column) 1866 { 1867 // get result for this descriptor 1868 DescriptorResult result; 1869 1870 synchronized (descriptorResults) 1871 { 1872 result = descriptorResults.get(column); 1873 1874 // no result --> create it and request computation 1875 if (result == null) 1876 { 1877 // create descriptor result 1878 result = new DescriptorResult(column); 1879 // and put it in results map 1880 descriptorResults.put(column, result); 1881 } 1882 } 1883 1884 return result; 1885 } 1886 1887 /** 1888 * Retrieve the value for the specified descriptor 1889 */ 1890 public Object getValue(ColumnInfo column) 1891 { 1892 // get result for this descriptor 1893 final DescriptorResult result = getDescriptorResult(column); 1894 1895 // out dated result ? --> request for descriptor computation 1896 if (result.isOutdated()) 1897 requestDescriptorComputation(this); 1898 1899 return formatValue(result.getValue(), column.descriptor.getId()); 1900 } 1901 1902 public Object getValueAt(int column) 1903 { 1904 final ColumnInfo ci = getColumnInfo(column); 1905 1906 if (ci != null) 1907 return getValue(ci); 1908 1909 return null; 1910 } 1911 1912 public void setValueAt(Object aValue, int column) 1913 { 1914 final ColumnInfo ci = getColumnInfo(column); 1915 1916 if (ci != null) 1917 { 1918 final ROIDescriptor descriptor = ci.descriptor; 1919 final String id = descriptor.getId(); 1920 1921 // only name descriptor is editable (a bit hacky) 1922 if (id.equals(ROINameDescriptor.ID)) 1923 roi.setName((String) aValue); 1924 else if (id.equals(ROIColorDescriptor.ID)) 1925 roi.setColor((Color) aValue); 1926 } 1927 } 1928 1929 @Override 1930 public void roiChanged(ROIEvent event) 1931 { 1932 switch (event.getType()) 1933 { 1934 case ROI_CHANGED: 1935 case PROPERTY_CHANGED: 1936 final Object[] entries; 1937 1938 synchronized (descriptorResults) 1939 { 1940 entries = descriptorResults.entrySet().toArray(); 1941 } 1942 1943 for (Object entryObj : entries) 1944 { 1945 final Entry<ColumnInfo, DescriptorResult> entry = (Entry<ColumnInfo, DescriptorResult>) entryObj; 1946 final ColumnInfo key = entry.getKey(); 1947 final ROIDescriptor descriptor = key.descriptor; 1948 1949 // need to recompute this descriptor ? 1950 if (descriptor.needRecompute(event)) 1951 { 1952 final DescriptorResult result = entry.getValue(); 1953 1954 // mark as outdated 1955 if (result != null) 1956 result.setOutdated(true); 1957 } 1958 } 1959 1960 // need to recompute channel rois 1961 if (event.getType() == ROIEventType.ROI_CHANGED) 1962 clearChannelRois(); 1963 1964 // and refresh table data 1965 refreshTableData(); 1966 break; 1967 1968 case SELECTION_CHANGED: 1969 // not modifying selection from panel ? 1970 if (modifySelection.availablePermits() > 0) 1971 // update ROI selection 1972 refreshTableSelection(); 1973 break; 1974 } 1975 } 1976 1977 /** 1978 * Called when the sequence changed, in which case we need to invalidate results. 1979 * 1980 * @param event 1981 * Sequence change event 1982 */ 1983 public void sequenceChanged(SequenceEvent event) 1984 { 1985 final Object[] entries; 1986 1987 synchronized (descriptorResults) 1988 { 1989 entries = descriptorResults.entrySet().toArray(); 1990 } 1991 1992 for (Object entryObj : entries) 1993 { 1994 final Entry<ColumnInfo, DescriptorResult> entry = (Entry<ColumnInfo, DescriptorResult>) entryObj; 1995 final ColumnInfo key = entry.getKey(); 1996 final ROIDescriptor descriptor = key.descriptor; 1997 1998 // need to recompute this descriptor ? 1999 if (descriptor.needRecompute(event)) 2000 { 2001 final DescriptorResult result = entry.getValue(); 2002 2003 // mark as outdated 2004 if (result != null) 2005 result.setOutdated(true); 2006 } 2007 } 2008 } 2009 } 2010 2011 protected class DescriptorComputer extends Thread 2012 { 2013 protected final LinkedHashSet<ROIResults> resultsToCompute; 2014 protected final DescriptorType type; 2015 2016 public DescriptorComputer(DescriptorType type) 2017 { 2018 super("ROI " + type.toString() + " descriptor calculator"); 2019 2020 resultsToCompute = new LinkedHashSet<AbstractRoisPanel.ROIResults>(256); 2021 this.type = type; 2022 2023 setPriority(Thread.MIN_PRIORITY); 2024 } 2025 2026 public boolean hasPendingComputation() 2027 { 2028 return resultsToCompute.size() > 0; 2029 } 2030 2031 public boolean hasPendingComputation(ROIResults results) 2032 { 2033 synchronized (resultsToCompute) 2034 { 2035 return resultsToCompute.contains(results); 2036 } 2037 } 2038 2039 public void requestDescriptorComputation(ROIResults results) 2040 { 2041 synchronized (resultsToCompute) 2042 { 2043 resultsToCompute.add(results); 2044 resultsToCompute.notifyAll(); 2045 } 2046 } 2047 2048 public void cancelDescriptorComputation(ROIResults roiResults) 2049 { 2050 synchronized (resultsToCompute) 2051 { 2052 resultsToCompute.remove(roiResults); 2053 resultsToCompute.notifyAll(); 2054 } 2055 } 2056 2057 public void cancelDescriptorComputation(ROI roi) 2058 { 2059 synchronized (resultsToCompute) 2060 { 2061 final Iterator<ROIResults> it = resultsToCompute.iterator(); 2062 2063 while (it.hasNext()) 2064 { 2065 final ROIResults roiResults = it.next(); 2066 2067 // remove all results for this ROI 2068 if (roiResults.roi == roi) 2069 it.remove(); 2070 } 2071 2072 resultsToCompute.notifyAll(); 2073 } 2074 } 2075 2076 public void cancelAllDescriptorComputation() 2077 { 2078 synchronized (resultsToCompute) 2079 { 2080 resultsToCompute.clear(); 2081 resultsToCompute.notifyAll(); 2082 } 2083 } 2084 2085 @Override 2086 public void run() 2087 { 2088 while (!Thread.interrupted()) 2089 { 2090 final ROIResults[] roiResultsList; 2091 2092 synchronized (resultsToCompute) 2093 { 2094 try 2095 { 2096 while (resultsToCompute.isEmpty()) 2097 resultsToCompute.wait(); 2098 } 2099 catch (InterruptedException e) 2100 { 2101 // ignore and just interrupt now 2102 Thread.currentThread().interrupt(); 2103 } 2104 2105 // get results to compute 2106 roiResultsList = resultsToCompute.toArray(new ROIResults[resultsToCompute.size()]); 2107 // and remove them 2108 resultsToCompute.clear(); 2109 } 2110 2111 final Sequence seq = getSequence(); 2112 2113 if (seq != null) 2114 { 2115 // start with primaries descriptors 2116 for (ROIResults roiResults : roiResultsList) 2117 { 2118 // active sequence changed ? --> quickly discard other calculations 2119 if (seq != getSequence()) 2120 break; 2121 2122 computeROIResults(roiResults, seq); 2123 } 2124 } 2125 } 2126 } 2127 2128 protected void computeROIResults(ROIResults roiResults, Sequence seq) 2129 { 2130 final Map<ColumnInfo, DescriptorResult> results = roiResults.descriptorResults; 2131 final ColumnInfo[] columnInfos; 2132 2133 synchronized (results) 2134 { 2135 columnInfos = results.keySet().toArray(new ColumnInfo[results.size()]); 2136 } 2137 2138 boolean needUpdate = false; 2139 for (ColumnInfo columnInfo : columnInfos) 2140 { 2141 // only compute a specific kind of descriptor 2142 if (columnInfo.getDescriptorType() == type) 2143 needUpdate |= AbstractRoisPanel.this.computeROIResults(roiResults, seq, columnInfo); 2144 } 2145 2146 // need to refresh data 2147 if (needUpdate) 2148 refreshTableData(); 2149 } 2150 } 2151 2152 protected class DescriptorResult 2153 { 2154 private Object value; 2155 private boolean outdated; 2156 2157 public DescriptorResult(ColumnInfo column) 2158 { 2159 super(); 2160 2161 value = null; 2162 2163 // by default we consider it as out dated 2164 outdated = true; 2165 } 2166 2167 public Object getValue() 2168 { 2169 return value; 2170 } 2171 2172 public void setValue(Object value) 2173 { 2174 this.value = value; 2175 } 2176 2177 public boolean isOutdated() 2178 { 2179 return outdated; 2180 } 2181 2182 public void setOutdated(boolean value) 2183 { 2184 outdated = value; 2185 } 2186 } 2187 2188 public static enum DescriptorType 2189 { 2190 PRIMARY, BASIC, EXTERNAL 2191 }; 2192 2193 public static class BaseColumnInfo implements Comparable<BaseColumnInfo> 2194 { 2195 public final ROIDescriptor descriptor; 2196 public int minSize; 2197 public int maxSize; 2198 public int defaultSize; 2199 public int order; 2200 public boolean visible; 2201 2202 public BaseColumnInfo(ROIDescriptor descriptor, XMLPreferences preferences, boolean export) 2203 { 2204 super(); 2205 2206 this.descriptor = descriptor; 2207 2208 load(preferences, export); 2209 } 2210 2211 public boolean load(XMLPreferences preferences, boolean export) 2212 { 2213 final XMLPreferences p = preferences.node(descriptor.getId()); 2214 2215 if (p != null) 2216 { 2217 minSize = p.getInt(ID_PROPERTY_MINSIZE, getDefaultMinSize()); 2218 maxSize = p.getInt(ID_PROPERTY_MAXSIZE, getDefaultMaxSize()); 2219 defaultSize = p.getInt(ID_PROPERTY_DEFAULTSIZE, getDefaultDefaultSize()); 2220 order = p.getInt(ID_PROPERTY_ORDER, getDefaultOrder()); 2221 visible = p.getBoolean(ID_PROPERTY_VISIBLE, getDefaultVisible(export)); 2222 2223 return true; 2224 } 2225 2226 return false; 2227 } 2228 2229 public boolean save(XMLPreferences preferences) 2230 { 2231 final XMLPreferences p = preferences.node(descriptor.getId()); 2232 2233 if (p != null) 2234 { 2235 // p.putInt(ID_PROPERTY_MINSIZE, minSize); 2236 // p.putInt(ID_PROPERTY_MAXSIZE, maxSize); 2237 // p.putInt(ID_PROPERTY_DEFAULTSIZE, defaultSize); 2238 p.putInt(ID_PROPERTY_ORDER, order); 2239 p.putBoolean(ID_PROPERTY_VISIBLE, visible); 2240 2241 return true; 2242 } 2243 2244 return false; 2245 } 2246 2247 protected boolean getDefaultVisible(boolean export) 2248 { 2249 if (descriptor == null) 2250 return false; 2251 2252 final String id = descriptor.getId(); 2253 2254 if (export) 2255 { 2256 if (StringUtil.equals(id, ROIOpacityDescriptor.ID)) 2257 return false; 2258 2259 final Class<?> type = descriptor.getType(); 2260 return ClassUtil.isSubClass(type, String.class) || ClassUtil.isSubClass(type, Number.class); 2261 } 2262 2263 if (StringUtil.equals(id, ROIIconDescriptor.ID)) 2264 return true; 2265 if (StringUtil.equals(id, ROINameDescriptor.ID)) 2266 return true; 2267 2268 if (StringUtil.equals(id, ROIContourDescriptor.ID)) 2269 return true; 2270 if (StringUtil.equals(id, ROIInteriorDescriptor.ID)) 2271 return true; 2272 2273 return false; 2274 } 2275 2276 protected int getDefaultOrder() 2277 { 2278 if (descriptor == null) 2279 return Integer.MAX_VALUE; 2280 2281 final String id = descriptor.getId(); 2282 int order = -1; 2283 2284 order++; 2285 if (StringUtil.equals(id, ROIIconDescriptor.ID)) 2286 return order; 2287 order++; 2288 if (StringUtil.equals(id, ROIColorDescriptor.ID)) 2289 return order; 2290 order++; 2291 if (StringUtil.equals(id, ROIGroupIdDescriptor.ID)) 2292 return order; 2293 order++; 2294 if (StringUtil.equals(id, ROINameDescriptor.ID)) 2295 return order; 2296 2297 order++; 2298 if (StringUtil.equals(id, ROIPositionXDescriptor.ID)) 2299 return order; 2300 order++; 2301 if (StringUtil.equals(id, ROIPositionYDescriptor.ID)) 2302 return order; 2303 order++; 2304 if (StringUtil.equals(id, ROIPositionZDescriptor.ID)) 2305 return order; 2306 order++; 2307 if (StringUtil.equals(id, ROIPositionTDescriptor.ID)) 2308 return order; 2309 order++; 2310 if (StringUtil.equals(id, ROIPositionCDescriptor.ID)) 2311 return order; 2312 2313 order++; 2314 if (StringUtil.equals(id, ROISizeXDescriptor.ID)) 2315 return order; 2316 order++; 2317 if (StringUtil.equals(id, ROISizeYDescriptor.ID)) 2318 return order; 2319 order++; 2320 if (StringUtil.equals(id, ROISizeZDescriptor.ID)) 2321 return order; 2322 order++; 2323 if (StringUtil.equals(id, ROISizeTDescriptor.ID)) 2324 return order; 2325 order++; 2326 if (StringUtil.equals(id, ROISizeCDescriptor.ID)) 2327 return order; 2328 2329 order++; 2330 if (StringUtil.equals(id, ROIMassCenterXDescriptor.ID)) 2331 return order; 2332 order++; 2333 if (StringUtil.equals(id, ROIMassCenterYDescriptor.ID)) 2334 return order; 2335 order++; 2336 if (StringUtil.equals(id, ROIMassCenterZDescriptor.ID)) 2337 return order; 2338 order++; 2339 if (StringUtil.equals(id, ROIMassCenterTDescriptor.ID)) 2340 return order; 2341 order++; 2342 if (StringUtil.equals(id, ROIMassCenterCDescriptor.ID)) 2343 return order; 2344 2345 order++; 2346 if (StringUtil.equals(id, ROIContourDescriptor.ID)) 2347 return order; 2348 order++; 2349 if (StringUtil.equals(id, ROIInteriorDescriptor.ID)) 2350 return order; 2351 2352 order++; 2353 if (StringUtil.equals(id, ROIPerimeterDescriptor.ID)) 2354 return order; 2355 order++; 2356 if (StringUtil.equals(id, ROIAreaDescriptor.ID)) 2357 return order; 2358 order++; 2359 if (StringUtil.equals(id, ROISurfaceAreaDescriptor.ID)) 2360 return order; 2361 order++; 2362 if (StringUtil.equals(id, ROIVolumeDescriptor.ID)) 2363 return order; 2364 2365 order++; 2366 if (StringUtil.equals(id, ROIMinIntensityDescriptor.ID)) 2367 return order; 2368 order++; 2369 if (StringUtil.equals(id, ROIMeanIntensityDescriptor.ID)) 2370 return order; 2371 order++; 2372 if (StringUtil.equals(id, ROIMaxIntensityDescriptor.ID)) 2373 return order; 2374 order++; 2375 if (StringUtil.equals(id, ROISumIntensityDescriptor.ID)) 2376 return order; 2377 2378 return Integer.MAX_VALUE; 2379 } 2380 2381 protected int getDefaultMinSize() 2382 { 2383 if (descriptor == null) 2384 return Integer.MAX_VALUE; 2385 2386 final String id = descriptor.getId(); 2387 2388 if (StringUtil.equals(id, ROIIconDescriptor.ID)) 2389 return 22; 2390 if (StringUtil.equals(id, ROIColorDescriptor.ID)) 2391 return 18; 2392 if (StringUtil.equals(id, ROIGroupIdDescriptor.ID)) 2393 return 18; 2394 if (StringUtil.equals(id, ROINameDescriptor.ID)) 2395 return 60; 2396 2397 final Class<?> type = descriptor.getType(); 2398 2399 if (type == Integer.class) 2400 return 30; 2401 if (type == Float.class) 2402 return 40; 2403 if (type == Double.class) 2404 return 40; 2405 if (type == String.class) 2406 return 50; 2407 2408 return 40; 2409 } 2410 2411 protected int getDefaultMaxSize() 2412 { 2413 if (descriptor == null) 2414 return Integer.MAX_VALUE; 2415 2416 final String id = descriptor.getId(); 2417 2418 if (StringUtil.equals(id, ROIIconDescriptor.ID)) 2419 return 22; 2420 if (StringUtil.equals(id, ROIColorDescriptor.ID)) 2421 return 18; 2422 if (StringUtil.equals(id, ROIGroupIdDescriptor.ID)) 2423 return 18; 2424 2425 return Integer.MAX_VALUE; 2426 } 2427 2428 protected int getDefaultDefaultSize() 2429 { 2430 final int maxSize = getDefaultMaxSize(); 2431 final int minSize = getDefaultMinSize(); 2432 2433 if (maxSize == Integer.MAX_VALUE) 2434 return minSize * 2; 2435 2436 return (minSize + maxSize) / 2; 2437 } 2438 2439 /** 2440 * Used to know if this is a primary (name, color...) ROI descriptor. 2441 * 2442 * @see #isBasicDescriptor() 2443 * @see #isExtendedDescriptor() 2444 * @see #getDescriptorType() 2445 */ 2446 protected boolean isPrimaryDescriptor() 2447 { 2448 if (descriptor == null) 2449 return false; 2450 2451 final String id = descriptor.getId(); 2452 2453 return (StringUtil.equals(id, ROIIconDescriptor.ID)) || (StringUtil.equals(id, ROIColorDescriptor.ID)) 2454 || (StringUtil.equals(id, ROINameDescriptor.ID)) || (StringUtil.equals(id, ROIGroupIdDescriptor.ID)) 2455 || (StringUtil.equals(id, ROIPositionXDescriptor.ID)) 2456 || (StringUtil.equals(id, ROIPositionYDescriptor.ID)) 2457 || (StringUtil.equals(id, ROIPositionZDescriptor.ID)) 2458 || (StringUtil.equals(id, ROIPositionTDescriptor.ID)) 2459 || (StringUtil.equals(id, ROIPositionCDescriptor.ID)) 2460 || (StringUtil.equals(id, ROISizeXDescriptor.ID)) || (StringUtil.equals(id, ROISizeYDescriptor.ID)) 2461 || (StringUtil.equals(id, ROISizeZDescriptor.ID)) || (StringUtil.equals(id, ROISizeTDescriptor.ID)) 2462 || (StringUtil.equals(id, ROISizeCDescriptor.ID)); 2463 } 2464 2465 /** 2466 * Used to know if this is a primary or basic (interior, contour, intensities..) ROI descriptor. 2467 * 2468 * @see #isPrimaryDescriptor() 2469 * @see #isExtendedDescriptor() 2470 * @see #getDescriptorType() 2471 */ 2472 protected boolean isBasicDescriptor() 2473 { 2474 if (descriptor == null) 2475 return false; 2476 2477 final String id = descriptor.getId(); 2478 2479 return isPrimaryDescriptor() || (StringUtil.equals(id, ROIMassCenterXDescriptor.ID)) 2480 || (StringUtil.equals(id, ROIMassCenterYDescriptor.ID)) 2481 || (StringUtil.equals(id, ROIMassCenterZDescriptor.ID)) 2482 || (StringUtil.equals(id, ROIMassCenterTDescriptor.ID)) 2483 || (StringUtil.equals(id, ROIMassCenterCDescriptor.ID)) 2484 || (StringUtil.equals(id, ROIContourDescriptor.ID)) 2485 || (StringUtil.equals(id, ROIInteriorDescriptor.ID)) 2486 || (StringUtil.equals(id, ROIPerimeterDescriptor.ID)) 2487 || (StringUtil.equals(id, ROIAreaDescriptor.ID)) 2488 || (StringUtil.equals(id, ROISurfaceAreaDescriptor.ID)) 2489 || (StringUtil.equals(id, ROIVolumeDescriptor.ID)) 2490 || (StringUtil.equals(id, ROIMinIntensityDescriptor.ID)) 2491 || (StringUtil.equals(id, ROIMeanIntensityDescriptor.ID)) 2492 || (StringUtil.equals(id, ROIMaxIntensityDescriptor.ID)); 2493 } 2494 2495 /** 2496 * Used to know if this is a extended (added by an external plugin) ROI descriptor 2497 * 2498 * @see #isPrimaryDescriptor() 2499 * @see #isBasicDescriptor() 2500 * @see #getDescriptorType() 2501 */ 2502 protected boolean isExtendedDescriptor() 2503 { 2504 if (descriptor == null) 2505 return false; 2506 2507 return !isBasicDescriptor(); 2508 } 2509 2510 /** 2511 * Returns the kind of ROI descriptor 2512 */ 2513 public DescriptorType getDescriptorType() 2514 { 2515 if (descriptor == null) 2516 return null; 2517 2518 if (isPrimaryDescriptor()) 2519 return DescriptorType.PRIMARY; 2520 if (isBasicDescriptor()) 2521 return DescriptorType.BASIC; 2522 2523 return DescriptorType.EXTERNAL; 2524 } 2525 2526 @Override 2527 public int compareTo(BaseColumnInfo obj) 2528 { 2529 return Integer.valueOf(order).compareTo(Integer.valueOf(obj.order)); 2530 } 2531 2532 @Override 2533 public int hashCode() 2534 { 2535 return descriptor.hashCode(); 2536 } 2537 2538 @Override 2539 public boolean equals(Object obj) 2540 { 2541 if (obj instanceof BaseColumnInfo) 2542 // equality on descriptor 2543 return ((BaseColumnInfo) obj).descriptor.equals(descriptor); 2544 2545 return super.equals(obj); 2546 } 2547 } 2548 2549 protected class ColumnInfo extends BaseColumnInfo 2550 { 2551 boolean showName; 2552 String name; 2553 final int channel; 2554 2555 public ColumnInfo(ROIDescriptor descriptor, int channel, XMLPreferences prefs, boolean export) 2556 { 2557 super(descriptor, prefs, export); 2558 2559 this.channel = channel; 2560 refreshName(); 2561 } 2562 2563 protected String getSuffix() 2564 { 2565 String result = ""; 2566 2567 final String unit = descriptor.getUnit(getSequence()); 2568 2569 if (!StringUtil.isEmpty(unit)) 2570 result += " (" + unit + ")"; 2571 2572 // separate channel 2573 if (descriptor.separateChannel()) 2574 result += getChannelNameSuffix(channel); 2575 2576 return result; 2577 } 2578 2579 protected void refreshName() 2580 { 2581 name = descriptor.getName() + getSuffix(); 2582 2583 final String id = descriptor.getId(); 2584 2585 // we don't want to display name for these descriptors 2586 if (StringUtil.equals(id, ROIIconDescriptor.ID) || StringUtil.equals(id, ROIColorDescriptor.ID) 2587 || StringUtil.equals(id, ROIGroupIdDescriptor.ID)) 2588 showName = false; 2589 else 2590 showName = true; 2591 } 2592 2593 @Override 2594 public int hashCode() 2595 { 2596 return descriptor.hashCode() ^ channel; 2597 } 2598 2599 @Override 2600 public boolean equals(Object obj) 2601 { 2602 if (obj instanceof ColumnInfo) 2603 { 2604 final ColumnInfo ci = (ColumnInfo) obj; 2605 2606 // equality on descriptor and channel number 2607 return (ci.descriptor.equals(descriptor) && (ci.channel == ci.channel)); 2608 } 2609 2610 return super.equals(obj); 2611 } 2612 } 2613 2614 protected class ROITableColumnModel extends DefaultTableColumnModelExt 2615 { 2616 /** 2617 * 2618 */ 2619 private static final long serialVersionUID = -8024047283485991234L; 2620 2621 public ROITableColumnModel() 2622 { 2623 super(); 2624 2625 final List<ColumnInfo> columnInfos = columnInfoList; 2626 2627 // column info are sorted on their order 2628 int index = 0; 2629 for (ColumnInfo ci : columnInfos) 2630 { 2631 final ROIDescriptor descriptor = ci.descriptor; 2632 final TableColumnExt column = new TableColumnExt(index++); 2633 2634 column.setIdentifier(descriptor.getId()); 2635 column.setMinWidth(ci.minSize); 2636 column.setPreferredWidth(ci.defaultSize); 2637 if (ci.maxSize != Integer.MAX_VALUE) 2638 column.setMaxWidth(ci.maxSize); 2639 if (ci.minSize == ci.maxSize) 2640 column.setResizable(false); 2641 column.setHeaderValue(ci.showName ? ci.name : ""); 2642 column.setToolTipText(descriptor.getDescription() + ci.getSuffix()); 2643 column.setVisible(ci.visible); 2644 column.setSortable(true); 2645 2646 final Class<?> type = descriptor.getType(); 2647 2648 // image class type column --> use a special renderer 2649 if (type == Image.class) 2650 column.setCellRenderer(new ImageTableCellRenderer(18)); 2651 else if (type == Color.class) 2652 column.setCellRenderer(new ImageTableCellRenderer(16)); 2653 // use the number cell renderer 2654 else if (ClassUtil.isSubClass(type, Number.class)) 2655 column.setCellRenderer(new SubstanceDefaultTableCellRenderer.NumberRenderer()); 2656 // column.setCellRenderer(new NumberTableCellRenderer()); 2657 2658 // and finally add to the model 2659 addColumn(column); 2660 } 2661 2662 setColumnSelectionAllowed(false); 2663 } 2664 2665 public void updateHeaders() 2666 { 2667 final List<ColumnInfo> columnInfos = columnInfoList; 2668 final List<TableColumn> columns = getColumns(true); 2669 for (TableColumn column : columns) 2670 { 2671 final ColumnInfo ci = getColumnInfo(columnInfos, column.getModelIndex()); 2672 2673 if (ci != null) 2674 { 2675 final ROIDescriptor descriptor = ci.descriptor; 2676 2677 // that should be always the case 2678 if (StringUtil.equals((String) column.getIdentifier(), descriptor.getId())) 2679 { 2680 column.setHeaderValue(ci.showName ? ci.name : ""); 2681 if (column instanceof TableColumnExt) 2682 ((TableColumnExt) column).setToolTipText(descriptor.getDescription() + ci.getSuffix()); 2683 } 2684 } 2685 } 2686 } 2687 } 2688 2689 // class CustomTableCellRenderer extends DefaultTableCellRenderer 2690 // { 2691 // @Override 2692 // public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, 2693 // boolean hasFocus, int row, int column) 2694 // { 2695 // super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); 2696 // 2697 // ROIResults roiResults; 2698 // try 2699 // { 2700 // roiResults = getROIResults(roiTable.convertRowIndexToModel(row)); 2701 // 2702 // } 2703 // catch (IndexOutOfBoundsException e) 2704 // { 2705 // roiResults = null; 2706 // } 2707 // 2708 // final Color defaultColor; 2709 // final Color computeColor; 2710 // 2711 // if (isSelected) 2712 // defaultColor = UIManager.getColor("Table.selectionBackground"); 2713 // else 2714 // defaultColor = UIManager.getColor("Table.background"); 2715 // if (roiResults == null) 2716 // computeColor = Color.green; 2717 // else if (roiResults.areResultsUpToDate()) 2718 // computeColor = Color.green; 2719 // else if (hasPendingComputation(roiResults)) 2720 // computeColor = Color.orange; 2721 // else 2722 // computeColor = Color.red; 2723 // 2724 // // define background color 2725 // setBackground(ColorUtil.mix(defaultColor, computeColor, 0.15f)); 2726 // 2727 // return this; 2728 // } 2729 // } 2730 2731 protected class ROITableSortController<M extends TableModel> extends DefaultSortController<M> 2732 { 2733 public ROITableSortController() 2734 { 2735 super(); 2736 2737 cachedModelRowCount = roiTableModel.getRowCount(); 2738 setModelWrapper(new TableRowSorterModelWrapper()); 2739 } 2740 2741 @Override 2742 public void sort() 2743 { 2744 try 2745 { 2746 super.sort(); 2747 } 2748 catch (Exception e) 2749 { 2750 // ignore this... 2751 // System.err.println("ROI table column sort failed:"); 2752 // System.err.println(e.getMessage()); 2753 } 2754 } 2755 2756 // @Override 2757 // protected void fireSortOrderChanged() 2758 // { 2759 // super.fireSortOrderChanged(); 2760 // 2761 // final List<? extends SortKey> keys = getSortKeys(); 2762 // 2763 // if (!keys.isEmpty()) 2764 // forceComputationForColumn(keys.get(0).getColumn()); 2765 // } 2766 2767 /** 2768 * Returns the <code>Comparator</code> for the specified 2769 * column. If a <code>Comparator</code> has not been specified using 2770 * the <code>setComparator</code> method a <code>Comparator</code> will be returned based on the column class 2771 * (<code>TableModel.getColumnClass</code>) of the specified column. 2772 * 2773 * @throws IndexOutOfBoundsException 2774 * {@inheritDoc} 2775 */ 2776 @Override 2777 public Comparator<?> getComparator(int column) 2778 { 2779 return comparator; 2780 } 2781 2782 /** 2783 * {@inheritDoc} 2784 * <p> 2785 * Note: must implement same logic as the overridden comparator lookup, otherwise will throw ClassCastException 2786 * because here the comparator is never null. 2787 * <p> 2788 * PENDING JW: think about implications to string value lookup! 2789 * 2790 * @throws IndexOutOfBoundsException 2791 * {@inheritDoc} 2792 */ 2793 @Override 2794 protected boolean useToString(int column) 2795 { 2796 return false; 2797 } 2798 2799 /** 2800 * Implementation of DefaultRowSorter.ModelWrapper that delegates to a 2801 * TableModel. 2802 */ 2803 private class TableRowSorterModelWrapper extends ModelWrapper<M, Integer> 2804 { 2805 public TableRowSorterModelWrapper() 2806 { 2807 super(); 2808 } 2809 2810 @Override 2811 public M getModel() 2812 { 2813 return (M) roiTableModel; 2814 } 2815 2816 @Override 2817 public int getColumnCount() 2818 { 2819 return roiTableModel.getColumnCount(); 2820 } 2821 2822 @Override 2823 public int getRowCount() 2824 { 2825 return roiTableModel.getRowCount(); 2826 } 2827 2828 @Override 2829 public Object getValueAt(int row, int column) 2830 { 2831 return roiTableModel.getValueAt(row, column); 2832 } 2833 2834 @Override 2835 public String getStringValueAt(int row, int column) 2836 { 2837 return getStringValueProvider().getStringValue(row, column).getString(getValueAt(row, column)); 2838 } 2839 2840 @Override 2841 public Integer getIdentifier(int index) 2842 { 2843 return Integer.valueOf(index); 2844 } 2845 } 2846 } 2847 2848 // protected class NumberTableCellRenderer extends SubstanceDefaultTableCellRenderer.NumberRenderer 2849 // { 2850 // 2851 // /** 2852 // * 2853 // */ 2854 // private static final long serialVersionUID = 5033090596184731420L; 2855 // 2856 // @Override 2857 // public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, 2858 // boolean hasFocus, int row, int column) 2859 // { 2860 // final Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, 2861 // column); 2862 // 2863 // final ROIResults roiResults = getRoiResults(row); 2864 // 2865 // if (roiResults != null) 2866 // { 2867 // final ColumnInfo ci = getVisibleColumnInfo(column); 2868 // 2869 // if (ci != null) 2870 // { 2871 // final DescriptorResult descResult = roiResults.getDescriptorResult(ci); 2872 // 2873 // if (descResult != null) 2874 // { 2875 // if (descResult.isOutdated()) 2876 // result.setBackground(ColorUtil.mix(Color.red, result.getBackground())); 2877 // } 2878 // } 2879 // } 2880 // 2881 // return result; 2882 // } 2883 // } 2884}