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 it under the terms of the GNU General 007 * Public License as 008 * published by the Free Software Foundation, either version 3 of the License, or (at your option) 009 * any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the 012 * implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 014 * details. 015 * 016 * You should have received a copy of the GNU General Public License along with Icy. If not, see 017 * <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.lut; 020 021import icy.gui.component.CheckTabbedPane; 022import icy.gui.component.button.IcyButton; 023import icy.gui.dialog.MessageDialog; 024import icy.gui.lut.abstract_.IcyLutViewer; 025import icy.gui.util.GuiUtil; 026import icy.gui.viewer.Viewer; 027import icy.image.colormap.IcyColorMap; 028import icy.image.colormap.IcyColorMap.IcyColorMapType; 029import icy.image.colormap.IcyColorMapEvent; 030import icy.image.colormap.IcyColorMapListener; 031import icy.image.colormap.LinearColorMap; 032import icy.image.lut.LUT; 033import icy.image.lut.LUT.LUTChannel; 034import icy.math.Scaler; 035import icy.preferences.ApplicationPreferences; 036import icy.preferences.XMLPreferences; 037import icy.resource.ResourceUtil; 038import icy.resource.icon.IcyIcon; 039import icy.sequence.Sequence; 040import icy.sequence.SequenceEvent; 041import icy.sequence.SequenceListener; 042import icy.system.thread.ThreadUtil; 043import icy.type.DataType; 044import icy.util.StringUtil; 045 046import java.awt.BorderLayout; 047import java.awt.event.ActionEvent; 048import java.awt.event.ActionListener; 049import java.util.ArrayList; 050import java.util.List; 051 052import javax.swing.Box; 053import javax.swing.ButtonGroup; 054import javax.swing.JCheckBox; 055import javax.swing.JRadioButton; 056import javax.swing.JTabbedPane; 057import javax.swing.SwingConstants; 058import javax.swing.event.ChangeEvent; 059import javax.swing.event.ChangeListener; 060 061public class LUTViewer extends IcyLutViewer implements IcyColorMapListener, SequenceListener 062{ 063 private static final long serialVersionUID = 8385018166371243663L; 064 065 /** 066 * pref id 067 */ 068 private static final String PREF_ID_HISTO = "gui.histo"; 069 070 private static final String ID_AUTO_REFRESH = "autoRefresh"; 071 private static final String ID_AUTO_BOUNDS = "autoBounds"; 072 private static final String ID_LOG_VIEW = "logView"; 073 074 /** 075 * gui 076 */ 077 final CheckTabbedPane bottomPane; 078 079 final JCheckBox autoRefreshHistoCheckBox; 080 final JCheckBox autoBoundsCheckBox; 081 final ButtonGroup scaleGroup; 082 final JRadioButton logButton; 083 final JRadioButton linearButton; 084 final IcyButton exportXLSButton; 085 086 /** 087 * data 088 */ 089 final List<LUTChannelViewer> lutChannelViewers; 090 091 /** 092 * preferences 093 */ 094 final XMLPreferences pref; 095 096 final Runnable boundsUpdater; 097 final Runnable channelNameUpdater; 098 final Runnable channelEnableUpdater; 099 final Runnable channelTabColorUpdater; 100 101 public LUTViewer(Viewer viewer, LUT lut) 102 { 103 super(viewer, lut); 104 105 pref = ApplicationPreferences.getPreferences().node(PREF_ID_HISTO); 106 107 boundsUpdater = new Runnable() 108 { 109 @Override 110 public void run() 111 { 112 final Sequence sequence = getSequence(); 113 114 if (sequence != null) 115 { 116 double[][] typeBounds = sequence.getChannelsTypeBounds(); 117 double[][] bounds = sequence.getChannelsBounds(); 118 119 for (int i = 0; i < Math.min(getLut().getNumChannel(), typeBounds.length); i++) 120 { 121 double[] tb = typeBounds[i]; 122 double[] b = bounds[i]; 123 124 final Scaler scaler = getLut().getLutChannel(i).getScaler(); 125 126 scaler.setAbsLeftRightIn(tb[0], tb[1]); 127 scaler.setLeftRightIn(b[0], b[1]); 128 } 129 } 130 } 131 }; 132 channelEnableUpdater = new Runnable() 133 { 134 @Override 135 public void run() 136 { 137 for (int c = 0; c < Math.min(getLut().getNumChannel(), bottomPane.getTabCount()); c++) 138 bottomPane.setTabChecked(c, getLut().getLutChannel(c).isEnabled()); 139 } 140 }; 141 channelTabColorUpdater = new Runnable() 142 { 143 @Override 144 public void run() 145 { 146 for (int c = 0; c < Math.min(getLut().getNumChannel(), bottomPane.getTabCount()); c++) 147 { 148 final IcyColorMap colormap = getLut().getLutChannel(c).getColorMap(); 149 bottomPane.setBackgroundAt(c, colormap.getDominantColor()); 150 } 151 } 152 }; 153 channelNameUpdater = new Runnable() 154 { 155 @Override 156 public void run() 157 { 158 final Sequence sequence = getSequence(); 159 160 if (sequence != null) 161 { 162 // need to be done on EDT 163 ThreadUtil.invokeNow(new Runnable() 164 { 165 @Override 166 public void run() 167 { 168 for (int c = 0; c < Math.min(sequence.getSizeC(), bottomPane.getTabCount()); c++) 169 { 170 final String channelName = sequence.getChannelName(c); 171 172 bottomPane.setTitleAt(c, StringUtil.limit(channelName, 10)); 173 if (sequence.getDefaultChannelName(c).equals(channelName)) 174 bottomPane.setToolTipTextAt(c, "Channel " + c); 175 else 176 bottomPane.setToolTipTextAt(c, channelName + " (channel " + c + ")"); 177 } 178 } 179 }); 180 } 181 } 182 }; 183 184 lutChannelViewers = new ArrayList<LUTChannelViewer>(); 185 186 // build GUI 187 bottomPane = new CheckTabbedPane(SwingConstants.BOTTOM, true); 188 bottomPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); 189 190 // add tab for each channel 191 for (int c = 0; c < lut.getNumChannel(); c++) 192 { 193 final LUTChannel lutChannel = lut.getLutChannel(c); 194 final LUTChannelViewer lbv = new LUTChannelViewer(viewer, lutChannel); 195 196 lutChannel.getColorMap().addListener(this); 197 198 lutChannelViewers.add(lbv); 199 bottomPane.addTab("ch " + c, lbv); 200 } 201 202 bottomPane.addChangeListener(new ChangeListener() 203 { 204 @Override 205 public void stateChanged(ChangeEvent e) 206 { 207 final int size = lutChannelViewers.size(); 208 boolean changedState[] = new boolean[size]; 209 boolean enabledState[] = new boolean[size]; 210 211 for (int i = 0; i < size; i++) 212 { 213 try 214 { 215 // null pointer exception can sometime happen here, normal 216 enabledState[i] = bottomPane.isTabChecked(i); 217 changedState[i] = lutChannelViewers.get(i).getLutChannel().isEnabled() != enabledState[i]; 218 } 219 catch (Exception exc) 220 { 221 enabledState[i] = true; 222 changedState[i] = false; 223 } 224 } 225 226 // we really want to only set state which changed here and not the one which has 227 // been set from a "setEnabled" event 228 for (int i = 0; i < size; i++) 229 { 230 if (changedState[i]) 231 { 232 lutChannelViewers.get(i).getLutChannel().setEnabled(enabledState[i]); 233 } 234 } 235 } 236 }); 237 238 autoRefreshHistoCheckBox = new JCheckBox("Refresh", pref.getBoolean(ID_AUTO_REFRESH, true)); 239 autoRefreshHistoCheckBox.setToolTipText("Automatically refresh histogram when data is modified"); 240 autoRefreshHistoCheckBox.addActionListener(new ActionListener() 241 { 242 @Override 243 public void actionPerformed(ActionEvent e) 244 { 245 final boolean value = autoRefreshHistoCheckBox.isSelected(); 246 if (value) 247 refreshAllHistogram(); 248 pref.putBoolean(ID_AUTO_REFRESH, value); 249 } 250 }); 251 if (autoRefreshHistoCheckBox.isSelected()) 252 refreshAllHistogram(); 253 254 autoBoundsCheckBox = new JCheckBox("Auto bounds", getPreferredAutoBounds()); 255 autoBoundsCheckBox.setToolTipText("Automatically ajdust bounds when data is modified"); 256 autoBoundsCheckBox.addActionListener(new ActionListener() 257 { 258 @Override 259 public void actionPerformed(ActionEvent e) 260 { 261 final boolean value = autoBoundsCheckBox.isSelected(); 262 263 if (value) 264 { 265 ThreadUtil.runSingle(boundsUpdater); 266 refreshAllHistogram(); 267 autoRefreshHistoCheckBox.setSelected(true); 268 autoRefreshHistoCheckBox.setEnabled(false); 269 } 270 else 271 { 272 final boolean refreshValue = pref.getBoolean(ID_AUTO_REFRESH, true); 273 if (refreshValue) 274 refreshAllHistogram(); 275 autoRefreshHistoCheckBox.setSelected(refreshValue); 276 autoRefreshHistoCheckBox.setEnabled(true); 277 } 278 279 pref.putBoolean(ID_AUTO_BOUNDS, value); 280 } 281 }); 282 283 final Sequence seq = getSequence(); 284 285 scaleGroup = new ButtonGroup(); 286 logButton = new JRadioButton("log"); 287 logButton.setToolTipText("Display histogram in a logarithm form"); 288 logButton.addActionListener(new ActionListener() 289 { 290 @Override 291 public void actionPerformed(ActionEvent e) 292 { 293 scaleTypeChanged(true); 294 } 295 }); 296 linearButton = new JRadioButton("linear"); 297 linearButton.setToolTipText("Display histogram in a linear form"); 298 linearButton.addActionListener(new ActionListener() 299 { 300 @Override 301 public void actionPerformed(ActionEvent e) 302 { 303 scaleTypeChanged(false); 304 } 305 }); 306 307 scaleGroup.add(logButton); 308 scaleGroup.add(linearButton); 309 310 // default 311 if (pref.getBoolean(ID_LOG_VIEW, true)) 312 logButton.setSelected(true); 313 else 314 linearButton.setSelected(true); 315 316 exportXLSButton = new IcyButton(new IcyIcon(ResourceUtil.ICON_XLS_EXPORT, 18)); 317 exportXLSButton.setFlat(true); 318 exportXLSButton.setToolTipText("Export histogram data into an excel file"); 319 exportXLSButton.addActionListener(new ActionListener() 320 { 321 @Override 322 public void actionPerformed(ActionEvent e) 323 { 324 try 325 { 326 // export current visible histogram 327 lutChannelViewers.get(bottomPane.getSelectedIndex()).getScalerPanel().getScalerViewer() 328 .getHistogram().getHistogram().doXLSExport(); 329 } 330 catch (Exception e1) 331 { 332 MessageDialog.showDialog("Error", e1.getMessage(), MessageDialog.ERROR_MESSAGE); 333 } 334 } 335 }); 336 337 setLayout(new BorderLayout()); 338 339 add(GuiUtil.createLineBoxPanel(autoRefreshHistoCheckBox, autoBoundsCheckBox, Box.createHorizontalGlue(), 340 Box.createHorizontalStrut(4), logButton, linearButton, Box.createHorizontalStrut(4), exportXLSButton), 341 BorderLayout.NORTH); 342 add(bottomPane, BorderLayout.CENTER); 343 344 validate(); 345 346 // update channel name and color 347 channelTabColorUpdater.run(); 348 channelNameUpdater.run(); 349 350 if (seq != null) 351 { 352 if (!seq.hasUserLUT() && autoBoundsCheckBox.isSelected()) 353 { 354 ThreadUtil.runSingle(boundsUpdater); 355 refreshAllHistogram(); 356 autoRefreshHistoCheckBox.setSelected(true); 357 autoRefreshHistoCheckBox.setEnabled(false); 358 } 359 360 seq.addListener(this); 361 } 362 } 363 364 private boolean getPreferredAutoBounds() 365 { 366 boolean result = pref.getBoolean(ID_AUTO_BOUNDS, true); 367 368 if (!result) 369 return false; 370 371 final Sequence sequence = getSequence(); 372 373 if (sequence != null) 374 { 375 // byte data type ? 376 if (sequence.getDataType_() == DataType.UBYTE) 377 { 378 final int numChannel = getLut().getNumChannel(); 379 380 // custom colormaps --> cannot use auto bounds 381 for (int c = 0; c < numChannel; c++) 382 if (!getLut().getLutChannel(c).getColorMap().isLinear()) 383 return false; 384 385 if ((numChannel == 3) || (numChannel == 4)) 386 { 387 boolean rgb; 388 389 // check if we have classic RGB 390 rgb = getLut().getLutChannel(0).getColorMap().equals(LinearColorMap.red_) 391 && getLut().getLutChannel(1).getColorMap().equals(LinearColorMap.green_) 392 && getLut().getLutChannel(2).getColorMap().equals(LinearColorMap.blue_); 393 394 // ARGB 395 if (numChannel == 4) 396 rgb &= (getLut().getLutChannel(3).getColorMap().getType() == IcyColorMapType.ALPHA); 397 398 // do not use auto bounds for classic (A)RGB images 399 if (rgb) 400 return false; 401 } 402 } 403 } 404 405 return true; 406 } 407 408 @Override 409 public Sequence getSequence() 410 { 411 return super.getSequence(); 412 } 413 414 @Override 415 public LUT getLut() 416 { 417 return super.getLut(); 418 } 419 420 public boolean getAutoBounds() 421 { 422 return autoBoundsCheckBox.isSelected(); 423 } 424 425 public void setAutoBound(boolean value) 426 { 427 autoBoundsCheckBox.setSelected(value); 428 } 429 430 public boolean getAutoRefreshHistogram() 431 { 432 return autoRefreshHistoCheckBox.isSelected(); 433 } 434 435 public void setAutoRefreshHistogram(boolean value) 436 { 437 autoRefreshHistoCheckBox.setSelected(value); 438 } 439 440 public boolean getLogScale() 441 { 442 return logButton.isSelected(); 443 } 444 445 public void setLogScale(boolean value) 446 { 447 if (value) 448 logButton.setSelected(true); 449 else 450 linearButton.setSelected(true); 451 } 452 453 void refreshAllHistogram() 454 { 455 for (int i = 0; i < lutChannelViewers.size(); i++) 456 lutChannelViewers.get(i).getScalerPanel().refreshHistogram(); 457 } 458 459 void scaleTypeChanged(boolean log) 460 { 461 pref.putBoolean(ID_LOG_VIEW, log); 462 // change histogram scale type 463 for (int i = 0; i < lutChannelViewers.size(); i++) 464 lutChannelViewers.get(i).getScalerPanel().getScalerViewer().scaleTypeChanged(log); 465 } 466 467 @Override 468 public void colorMapChanged(IcyColorMapEvent e) 469 { 470 switch (e.getType()) 471 { 472 case ENABLED_CHANGED: 473 ThreadUtil.runSingle(channelEnableUpdater); 474 break; 475 476 case MAP_CHANGED: 477 ThreadUtil.runSingle(channelTabColorUpdater); 478 break; 479 480 case TYPE_CHANGED: 481 break; 482 } 483 } 484 485 public void dispose() 486 { 487 removeAll(); 488 489 Sequence seq = getSequence(); 490 if (seq != null) 491 seq.removeListener(this); 492 } 493 494 @Override 495 public void sequenceChanged(SequenceEvent sequenceEvent) 496 { 497 final SequenceEvent e = sequenceEvent; 498 499 switch (e.getSourceType()) 500 { 501 case SEQUENCE_META: 502 ThreadUtil.runSingle(channelNameUpdater); 503 break; 504 505 case SEQUENCE_COMPONENTBOUNDS: 506 if (autoBoundsCheckBox.isSelected()) 507 ThreadUtil.runSingle(boundsUpdater); 508 break; 509 } 510 } 511 512 @Override 513 public void sequenceClosed(Sequence sequence) 514 { 515 516 } 517}