001 /*
002 * $Id: HydraCombo.java,v 1.25 2012/02/19 17:35:38 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 *
012 * All Rights Reserved
013 *
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.
016 *
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 *
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
025 * GNU Lesser Public License for more details.
026 *
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program. If not, see http://www.gnu.org/licenses.
029 */
030
031 package edu.wisc.ssec.mcidasv.control;
032
033 import java.awt.Color;
034 import java.awt.Container;
035 import java.awt.FlowLayout;
036 import java.awt.event.ActionEvent;
037 import java.awt.event.ActionListener;
038 import java.rmi.RemoteException;
039 import java.util.ArrayList;
040 import java.util.HashMap;
041 import java.util.Hashtable;
042 import java.util.List;
043 import java.util.Map;
044
045 import javax.swing.JButton;
046 import javax.swing.JComboBox;
047 import javax.swing.JComponent;
048 import javax.swing.JLabel;
049 import javax.swing.JPanel;
050 import javax.swing.JTabbedPane;
051 import javax.swing.JTextField;
052 import javax.swing.border.LineBorder;
053
054 import org.python.core.PyObject;
055
056 import visad.ConstantMap;
057 import visad.Data;
058 import visad.VisADException;
059 import visad.georef.MapProjection;
060
061 import ucar.unidata.data.DataChoice;
062 import ucar.unidata.data.DataSource;
063 import ucar.unidata.data.DirectDataChoice;
064 import ucar.unidata.idv.MapViewManager;
065 import ucar.unidata.util.GuiUtils;
066 import ucar.unidata.util.LogUtil;
067 import ucar.unidata.view.geoloc.MapProjectionDisplay;
068
069 import edu.wisc.ssec.mcidasv.Constants;
070 import edu.wisc.ssec.mcidasv.McIDASV;
071 import edu.wisc.ssec.mcidasv.control.LinearCombo.Combination;
072 import edu.wisc.ssec.mcidasv.control.LinearCombo.Selector;
073 import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
074 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
075 import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource;
076 import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
077 import edu.wisc.ssec.mcidasv.jython.Console;
078 import edu.wisc.ssec.mcidasv.jython.ConsoleCallback;
079
080 public class HydraCombo extends HydraControl {
081
082
083 private MultiSpectralDisplay display;
084
085 private DataChoice dataChoice = null;
086
087 private CombinationPanel comboPanel;
088
089 private final Hashtable<String, Object> persistable = new Hashtable<String, Object>();
090
091 private MultiSpectralDataSource source;
092
093 float init_wavenumber;
094
095 private Map<String, Selector> selectorMap = new HashMap<String, Selector>();
096
097 private static final String defaultButtonLabel = "Compute New Field";
098 private JButton computeButton = new JButton(defaultButtonLabel);
099
100 public HydraCombo() {
101 super();
102 setHelpUrl("idv.controls.hydra.channelcombinationcontrol");
103 }
104
105 @Override public boolean init(final DataChoice choice)
106 throws VisADException, RemoteException {
107 ((McIDASV)getIdv()).getMcvDataManager().setHydraControl(choice, this);
108 dataChoice = choice;
109 List<DataSource> sources = new ArrayList<DataSource>();
110 choice.getDataSources(sources);
111 source = ((MultiSpectralDataSource)sources.get(0));
112
113 Float fieldSelectorChannel = (Float)getDataSelection().getProperty(Constants.PROP_CHAN);
114 if (fieldSelectorChannel == null)
115 fieldSelectorChannel = 0f;
116 init_wavenumber = fieldSelectorChannel;
117
118 display = new MultiSpectralDisplay((DirectDataChoice)choice);
119 display.setWaveNumber(fieldSelectorChannel);
120 display.setDisplayControl(this);
121
122 ((McIDASV)getIdv()).getMcvDataManager().setHydraDisplay(choice, display);
123
124 comboPanel = new CombinationPanel(this);
125 if (!persistable.isEmpty()) {
126 comboPanel.unpersistData(persistable);
127 persistable.clear();
128 }
129 return true;
130 }
131
132 @Override public void initDone() {
133 MapViewManager viewManager = (MapViewManager) getViewManager();
134 MapProjectionDisplay dispMaster =
135 (MapProjectionDisplay) viewManager.getMaster();
136 try {
137 dispMaster.setMapProjection(getDataProjection());
138 } catch (Exception e) {
139 logException("problem setting MapProjection", e);
140 }
141
142 getIdv().getIdvUIManager().showDashboard();
143 }
144
145 public Hashtable<String, Object> getPersistable() {
146 return comboPanel.persistData();
147 }
148
149 public void setPersistable(final Hashtable<String, Object> table) {
150 persistable.clear();
151 persistable.putAll(table);
152 }
153
154 @Override public MapProjection getDataProjection() {
155 MapProjection mp = null;
156 HashMap subset = null;
157 Hashtable table = dataChoice.getProperties();
158 MultiDimensionSubset dataSel =
159 (MultiDimensionSubset) table.get(MultiDimensionSubset.key);
160 if (dataSel != null) {
161 subset = dataSel.getSubset();
162 }
163 mp = source.getDataProjection(subset);
164 return mp;
165 }
166
167 @Override public Container doMakeContents() {
168 JTabbedPane pane = new JTabbedPane();
169 pane.add("Channel Combination Tool", GuiUtils.inset(getComboTab(), 5));
170 GuiUtils.handleHeavyWeightComponentsInTabs(pane);
171 return pane;
172 }
173
174 public void updateComboPanel(final String id, final float value) {
175 if (comboPanel == null)
176 return;
177 comboPanel.updateSelector(id, value);
178 }
179
180 protected void enableSelectorForWrapper(final SelectorWrapper wrapper) {
181 if (comboPanel == null)
182 return;
183 comboPanel.enableSelector(wrapper, false);
184 }
185
186 protected void disableSelectorForWrapper(final SelectorWrapper wrapper) {
187 if (comboPanel == null)
188 return;
189 comboPanel.disableSelector(wrapper, false);
190 }
191
192 protected MultiSpectralDisplay getMultiSpectralDisplay() {
193 return display;
194 }
195
196 protected JButton getComputeButton() {
197 return computeButton;
198 }
199
200 private JComponent getComboTab() {
201 computeButton.addActionListener(new ActionListener() {
202 public void actionPerformed(final ActionEvent e) {
203 comboPanel.queueCombination();
204 computeButton.setEnabled(false);
205 showWaitCursor();
206 }
207 });
208 // wrap compute button in JPanel to retain preferred size
209 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
210 buttonPanel.add(computeButton);
211 JPanel tmp = GuiUtils.topCenterBottom(display.getDisplayComponent(), comboPanel.getPanel(), buttonPanel);
212 return tmp;
213 }
214
215 public void addCombination(final String name, final Data combination) {
216 if (combination != null)
217 source.addChoice(name, combination);
218 }
219
220 public void addCombination(final Combination combination) {
221 if (combination != null)
222 source.addChoice(combination.getName(), combination.getData());
223 }
224
225 protected void addSelector(final Selector selector) throws Exception {
226 ConstantMap[] mapping = selector.getColor();
227 float r = new Double(mapping[0].getConstant()).floatValue();
228 float g = new Double(mapping[1].getConstant()).floatValue();
229 float b = new Double(mapping[2].getConstant()).floatValue();
230 Color javaColor = new Color(r, g, b);
231 display.createSelector(selector.getId(), javaColor);
232 display.setSelectorValue(selector.getId(), selector.getWaveNumber());
233 selectorMap.put(selector.getId(), selector);
234 }
235
236 public void moveSelector(final String id, final float wavenum) {
237 if (!selectorMap.containsKey(id))
238 return;
239 display.updateControlSelector(id, wavenum);
240 }
241
242 public void updateSelector(final String id, final float wavenum) {
243 if (!selectorMap.containsKey(id))
244 return;
245
246 selectorMap.get(id).setWaveNumber(wavenum);
247 }
248
249
250
251 public enum DataType { HYPERSPECTRAL, MULTISPECTRAL };
252
253 public enum WrapperState { ENABLED, DISABLED };
254
255 public static class CombinationPanel implements ConsoleCallback {
256 private final SelectorWrapper a;
257 private final SelectorWrapper b;
258 private final SelectorWrapper c;
259 private final SelectorWrapper d;
260
261 private final OperationXY ab;
262 private final OperationXY cd;
263
264 private final CombineOperations abcd;
265
266 private final MultiSpectralDisplay display;
267
268 private final HydraCombo control;
269
270 private final Console console;
271
272 private final Map<String, Selector> selectorMap = new HashMap<String, Selector>();
273 private final Map<String, SelectorWrapper> wrapperMap = new HashMap<String, SelectorWrapper>();
274
275 private final DataType dataType;
276
277 public CombinationPanel(final HydraCombo control) {
278 this.control = control;
279 display = control.getMultiSpectralDisplay();
280 if (display == null)
281 throw new NullPointerException("Display hasn't been initialized");
282
283 MultiSpectralData data = display.getMultiSpectralData();
284 if (data.hasBandNames())
285 dataType = DataType.MULTISPECTRAL;
286 else
287 dataType = DataType.HYPERSPECTRAL;
288
289 this.console = new Console();
290 console.setCallbackHandler(this);
291
292 a = makeWrapper("a", Color.RED);
293 b = makeWrapper("b", Color.GREEN);
294 c = makeWrapper("c", Color.BLUE);
295 d = makeWrapper("d", Color.MAGENTA);
296
297 enableSelector(a, true);
298
299 ab = new OperationXY(this, a, b);
300 cd = new OperationXY(this, c, d);
301
302 abcd = new CombineOperations(this, ab, cd);
303 }
304
305 // public Console getConsole() {
306 // return console;
307 // }
308
309 public void ranBlock(final String line) {
310 PyObject jythonObj = console.getJythonObject("combo");
311 // if (jythonObj instanceof PyJavaInstance) {
312 Object combination = jythonObj.__tojava__(Object.class);
313 if (combination instanceof Combination) {
314 control.addCombination((Combination)combination);
315 control.getComputeButton().setEnabled(true);
316 control.showNormalCursor();
317 }
318 // }
319 }
320
321 public void updateSelector(final String id, final float channel) {
322 if (!selectorMap.containsKey(id))
323 return;
324
325 Selector selector = selectorMap.get(id);
326 selector.setWaveNumber(channel);
327 wrapperMap.get(id).update();
328 }
329
330 protected void addSelector(final Selector selector, final boolean enabled) throws Exception {
331 String id = selector.getId();
332 if (enabled) {
333 display.createSelector(id, selector.getColor());
334 display.setSelectorValue(id, selector.getWaveNumber());
335 }
336 selectorMap.put(id, selector);
337 }
338
339 protected void disableSelector(final SelectorWrapper wrapper, final boolean disableWrapper) {
340 if (disableWrapper)
341 wrapper.disable();
342
343 try {
344 display.removeSelector(wrapper.getSelector().getId());
345 } catch (Exception e) {
346 LogUtil.logException("HydraCombo.disableSelector", e);
347 }
348 }
349
350 protected void enableSelector(final SelectorWrapper wrapper, final boolean enableWrapper) {
351 if (enableWrapper)
352 wrapper.enable();
353
354 try {
355 Selector selector = wrapper.getSelector();
356 String id = selector.getId();
357 display.createSelector(id, selector.getColor());
358 display.setSelectorValue(id, selector.getWaveNumber());
359 } catch (Exception e) {
360 LogUtil.logException("HydraCombo.disableSelector", e);
361 }
362 }
363
364 private SelectorWrapper makeWrapper(final String var, final Color color) {
365 try {
366 ConstantMap[] mappedColor = MultiSpectralDisplay.makeColorMap(color);
367
368 SelectorWrapper tmp;
369 if (dataType == DataType.HYPERSPECTRAL)
370 tmp = new HyperspectralSelectorWrapper(var, mappedColor, control, console);
371 else
372 tmp = new MultispectralSelectorWrapper(var, mappedColor, control, console);
373 addSelector(tmp.getSelector(), false);
374 wrapperMap.put(tmp.getSelector().getId(), tmp);
375 // console.injectObject(var, new PyJavaInstance(tmp.getSelector()));
376 console.injectObject(var, tmp.getSelector());
377 return tmp;
378 } catch (Exception e) {
379 LogUtil.logException("HydraCombo.makeWrapper", e);
380 }
381 return null;
382 }
383
384 public JPanel getPanel() {
385 JPanel panel = new JPanel(new FlowLayout());
386 panel.add(new JLabel("("));
387 panel.add(a.getPanel());
388 panel.add(ab.getBox());
389 panel.add(b.getPanel());
390 panel.add(new JLabel(")"));
391 panel.add(abcd.getBox());
392 panel.add(new JLabel("("));
393 panel.add(c.getPanel());
394 panel.add(cd.getBox());
395 panel.add(d.getPanel());
396 panel.add(new JLabel(")"));
397 return panel;
398 }
399
400 public void queueCombination() {
401 String jy = "combo="+abcd.getJython();
402 System.err.println("jython=" + jy);
403 console.queueLine(jy);
404 }
405
406 public Hashtable<String, Object> persistData() {
407 Hashtable<String, Object> table = new Hashtable<String, Object>();
408
409 table.put("a", a.persistSelectorWrapper());
410 table.put("b", b.persistSelectorWrapper());
411 table.put("c", c.persistSelectorWrapper());
412 table.put("d", d.persistSelectorWrapper());
413 table.put("ab", ab.getOperation());
414 table.put("cd", cd.getOperation());
415 table.put("abcd", abcd.getOperation());
416 return table;
417 }
418
419 public void unpersistData(final Hashtable<String, Object> table) {
420 Hashtable<String, String> tableA = (Hashtable<String, String>)table.get("a");
421 Hashtable<String, String> tableB = (Hashtable<String, String>)table.get("b");
422 Hashtable<String, String> tableC = (Hashtable<String, String>)table.get("c");
423 Hashtable<String, String> tableD = (Hashtable<String, String>)table.get("d");
424
425 a.unpersistSelectorWrapper(tableA);
426 b.unpersistSelectorWrapper(tableB);
427 c.unpersistSelectorWrapper(tableC);
428 d.unpersistSelectorWrapper(tableD);
429
430 String opAb = (String)table.get("ab");
431 String opCd = (String)table.get("cd");
432 String opAbcd = (String)table.get("abcd");
433
434 ab.setOperation(opAb);
435 cd.setOperation(opCd);
436 abcd.setOperation(opAbcd);
437 }
438 }
439
440 private static class OperationXY {
441 private static final String[] OPERATIONS = { "+", "-", "/", "*", " " };
442 private static final String INVALID_OP = " ";
443 private final JComboBox combo = new JComboBox(OPERATIONS);
444
445 private CombinationPanel comboPanel;
446 private SelectorWrapper x;
447 private SelectorWrapper y;
448
449 public OperationXY(final CombinationPanel comboPanel, final SelectorWrapper x, final SelectorWrapper y) {
450 this.x = x;
451 this.y = y;
452 this.comboPanel = comboPanel;
453 combo.setSelectedItem(" ");
454 combo.addActionListener(new ActionListener() {
455 public void actionPerformed(final ActionEvent e) {
456 if (getOperation().equals(" ")) {
457 comboPanel.disableSelector(y, true);
458 } else {
459 comboPanel.enableSelector(y, true);
460 }
461 }
462 });
463 }
464
465 public void disable() {
466 comboPanel.disableSelector(x, true);
467 combo.setSelectedItem(INVALID_OP);
468 comboPanel.disableSelector(y, true);
469 }
470
471 public void enable() {
472 comboPanel.enableSelector(x, true);
473 }
474
475 public String getOperation() {
476 return (String)combo.getSelectedItem();
477 }
478
479 public void setOperation(final String operation) {
480 combo.setSelectedItem(operation);
481 }
482
483 public JComboBox getBox() {
484 return combo;
485 }
486
487 public String getJython() {
488 String operation = getOperation();
489 if (operation.equals(INVALID_OP))
490 operation = "";
491
492 String jython = x.getJython() + operation + y.getJython();
493 if (x.isValid() && y.isValid())
494 return "(" + jython + ")";
495 return jython;
496 }
497 }
498
499 private static class CombineOperations {
500 private static final String[] OPERATIONS = { "+", "-", "/", "*", " "};
501 private static final String INVALID_OP = " ";
502 private final JComboBox combo = new JComboBox(OPERATIONS);
503
504 private OperationXY x;
505 private OperationXY y;
506
507 public CombineOperations(final CombinationPanel comboPanel, final OperationXY x, final OperationXY y) {
508 this.x = x;
509 this.y = y;
510 combo.setSelectedItem(INVALID_OP);
511 combo.addActionListener(new ActionListener() {
512 public void actionPerformed(final ActionEvent e) {
513 if (getOperation().equals(" ")) {
514 y.disable();
515 } else {
516 y.enable();
517 }
518 }
519 });
520 }
521
522 public String getOperation() {
523 return (String)combo.getSelectedItem();
524 }
525
526 public void setOperation(final String operation) {
527 combo.setSelectedItem(operation);
528 }
529
530 public JComboBox getBox() {
531 return combo;
532 }
533
534 public String getJython() {
535 String operation = getOperation();
536 if (operation.equals(INVALID_OP))
537 operation = "";
538
539 String jython = x.getJython() + operation + y.getJython();
540 return jython;
541 }
542 }
543
544 private static abstract class SelectorWrapper {
545 protected static final String BLANK = "-----";
546 private String variable;
547 private final ConstantMap[] color;
548 protected final Selector selector;
549 protected final MultiSpectralDisplay display;
550 protected final MultiSpectralData data;
551 private final JTextField scale = new JTextField(String.format("%3.1f", 1.0));
552 protected WrapperState currentState = WrapperState.DISABLED;
553
554 public SelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
555 this.display = control.getMultiSpectralDisplay();
556 this.data = control.getMultiSpectralDisplay().getMultiSpectralData();
557 this.variable = variable;
558 this.color = color;
559 this.selector = new Selector(control.init_wavenumber, color, control, console);
560 this.selector.addName(variable);
561 }
562
563 public Selector getSelector() {
564 return selector;
565 }
566
567 public JPanel getPanel() {
568 JPanel panel = new JPanel(new FlowLayout());
569 JComponent comp = getWavenumberComponent();
570 float r = new Double(color[0].getConstant()).floatValue();
571 float g = new Double(color[1].getConstant()).floatValue();
572 float b = new Double(color[2].getConstant()).floatValue();
573 comp.setBorder(new LineBorder(new Color(r, g, b), 2));
574 panel.add(scale);
575 panel.add(comp);
576 return panel;
577 }
578
579 public String getJython() {
580 if (!isValid())
581 return "";
582 return "(" + scale.getText() + "*" + variable + ")";
583 }
584
585 public boolean isValid() {
586 return !getValue().equals(BLANK);
587 }
588
589 public void enable() {
590 setValue(Float.toString(selector.getWaveNumber()));
591 currentState = WrapperState.ENABLED;
592 }
593
594 public void disable() {
595 setValue(BLANK);
596 currentState = WrapperState.DISABLED;
597 }
598
599 public void update() {
600 setValue(Float.toString(selector.getWaveNumber()));
601 }
602
603 public Hashtable<String, String> persistSelectorWrapper() {
604 Hashtable<String, String> table = new Hashtable<String, String>(3);
605 String scaleText = scale.getText();
606 String waveText = getValue();
607
608 if (scaleText == null || scaleText.length() == 0)
609 scaleText = "1.0";
610 if (waveText == null || waveText.length() == 0 || !isValid())
611 waveText = BLANK;
612
613 table.put("variable", variable);
614 table.put("scale", scale.getText());
615 table.put("wave", getValue());
616 return table;
617 }
618
619 public void unpersistSelectorWrapper(final Hashtable<String, String> table) {
620 variable = table.get("variable");
621 selector.addName(variable);
622 scale.setText(String.format("%3.1f", new Float(table.get("scale"))));
623
624 String waveText = table.get("wave");
625 if (waveText.equals(BLANK)) {
626 disable();
627 } else {
628 float wave = new Float(table.get("wave"));
629 selector.setWaveNumber(wave);
630 if (isValid())
631 update();
632 }
633 }
634
635 public abstract JComponent getWavenumberComponent();
636
637 public abstract void setValue(final String value);
638
639 public abstract String getValue();
640 }
641
642 private static class HyperspectralSelectorWrapper extends SelectorWrapper {
643 private final JTextField wavenumber;
644 public HyperspectralSelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
645 super(variable, color, control, console);
646 wavenumber = new JTextField(BLANK, 7);
647
648 wavenumber.addActionListener(new ActionListener() {
649 public void actionPerformed(final ActionEvent e) {
650 String textVal = wavenumber.getText();
651 if (textVal.equals(BLANK))
652 return;
653
654 float wave = Float.parseFloat(textVal.trim());
655 control.updateComboPanel(getSelector().getId(), wave);
656 }
657 });
658 }
659
660 @Override public JComponent getWavenumberComponent() {
661 return wavenumber;
662 }
663
664 @Override public void setValue(final String value) {
665 if (value == null)
666 throw new NullPointerException("");
667
668 if (!value.equals(BLANK)) {
669 float wave = Float.parseFloat(value);
670 String fmt = String.format("%7.2f", wave);
671 wavenumber.setText(fmt);
672 }
673 }
674
675 public String getValue() {
676 return wavenumber.getText();
677 }
678 }
679
680 private static class MultispectralSelectorWrapper extends SelectorWrapper {
681
682 private final JComboBox bands;
683
684 public MultispectralSelectorWrapper(final String variable, final ConstantMap[] color, final HydraCombo control, final Console console) {
685 super(variable, color, control, console);
686 removeMSDListeners();
687 bands = bigBadBox(control);
688 }
689
690 private void removeMSDListeners() {
691 JComboBox box = display.getBandSelectComboBox();
692 for (ActionListener l : box.getActionListeners())
693 box.removeActionListener(l);
694 }
695
696 private JComboBox bigBadBox(final HydraCombo control) {
697 final JComboBox box = new JComboBox();
698 box.addItem(BLANK);
699
700 for (String name : data.getBandNames())
701 box.addItem(name);
702
703 final SelectorWrapper wrapper = this;
704 box.addActionListener(new ActionListener() {
705 public void actionPerformed(final ActionEvent e) {
706 String selected = (String)box.getSelectedItem();
707 float wave = Float.NaN;
708
709 if (!selected.equals(BLANK)) {
710 Map<String, Float> map = data.getBandNameMap();
711 if (map.containsKey(selected))
712 wave = map.get(selected);
713
714 if (currentState == WrapperState.DISABLED)
715 control.enableSelectorForWrapper(wrapper);
716 control.updateComboPanel(getSelector().getId(), wave);
717 } else {
718 control.disableSelectorForWrapper(wrapper);
719 }
720 }
721 });
722
723 return box;
724 }
725
726 @Override public JComponent getWavenumberComponent() {
727 return bands;
728 }
729
730 @Override public void setValue(final String value) {
731 if (value == null)
732 throw new NullPointerException();
733
734 if (!value.equals(BLANK)) {
735 String name = data.getBandNameFromWaveNumber(Float.parseFloat(value));
736 bands.setSelectedItem(name);
737 } else {
738 bands.setSelectedItem(BLANK);
739 }
740 }
741
742 @Override public String getValue() {
743 return (String)bands.getSelectedItem();
744 }
745 }
746 }