Changeset b10cf92 in sasview for src/sas/qtgui
- Timestamp:
- May 17, 2018 3:49:28 PM (7 years ago)
- Branches:
- ESS_GUI, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc
- Children:
- 3aa3351, a0ed202
- Parents:
- 6459916 (diff), 57be490 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent. - Location:
- src/sas/qtgui
- Files:
-
- 5 added
- 13 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/qtgui/Calculators/media/gen_gui_help.png
- Property mode changed from 100755 to 100644
-
src/sas/qtgui/Calculators/media/sas_calculator_help.rst
r417c03f r2f539b2 24 24 25 25 Assuming that all the pixel sizes are the same, the elastic scattering 26 intensity from the particle is 26 intensity from the particle is defined as 27 27 28 28 .. image:: gen_i.png … … 33 33 the position of the $j^\text{th}$ pixel respectively. 34 34 35 The total volume $V$ 35 The total volume $V$ is equal to 36 36 37 37 .. math:: … … 43 43 density * Avogadro number) for the atomic structures). 44 44 45 $V$ can be corrected by users. This correction is useful especially for an 46 atomic structure (such as taken from a PDB file) to get the right normalization. 47 48 *NOTE! $\beta_j$ displayed in the GUI may be incorrect but this will not 49 affect the scattering computation if the correction of the total volume V is made.* 45 $V$ can be corrected by users (input parameter `Total volume`). This correction 46 is useful especially for an atomic structure (such as taken from a PDB file) 47 to get the right normalization. 48 49 *NOTE! $\beta_j$ displayed in the GUI may be incorrect (input parameter 50 `solvent_SLD`) but this will not affect the scattering computation if the 51 correction of the total volume V is made.* 50 52 51 53 The scattering length density (SLD) of each pixel, where the SLD is uniform, is … … 99 101 .. image:: mxp.png 100 102 103 104 101 105 .. image:: myp.png 102 106 107 108 103 109 .. image:: mzp.png 104 110 111 112 105 113 .. image:: mqx.png 106 114 115 116 107 117 .. image:: mqy.png 108 118 109 Here the $M 0_x$, $M0_y$ and $M0_z$ are the $x$, $y$ and $z$119 Here the $M_{0x}$, $M_{0y}$ and $M_{0z}$ are the $x$, $y$ and $z$ 110 120 components of the magnetisation vector in the laboratory $xyz$ frame. 121 122 123 .. .. image:: Mxyzp.png 124 111 125 112 126 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ … … 115 129 -------------- 116 130 117 .. image:: gen_gui_help.png 118 119 After computation the result will appear in the *Theory* box in the SasView 131 .. figure:: gen_gui_help.png 132 133 .. 134 135 1) Load .sld, .txt, or .omf datafile 136 2) Select default shape of sample 137 3) Draw magnetization with arrows (not recommended for a large number of 138 pixels). 139 4) Ratio of (+/total) neutrons after analyser 140 5) Ratio of (+/total) neutrons before sample 141 6) Polarization angle in degrees 142 7) Default volume calculated from the pixel info 143 (or natural density of pdf file) 144 8) Compute the scattering pattern 145 9) Reset GUI to initial state 146 10) Display mean values or enter a new value if enabled 147 11) Save the sld data as sld format 148 149 .. After computation the result will appear in the *Theory* box in the SasView 120 150 *Data Explorer* panel. 121 151 … … 126 156 of neutrons before the sample and at the analyzer, respectively. 127 157 128 *NOTE 1. The values of * Up_frac_in *and* Up_frac_out *must be in the range158 *NOTE 1. The values of Up_frac_in and Up_frac_out must be in the range 129 159 0.0 to 1.0. Both values are 0.5 for unpolarized neutrons.* 130 160 … … 133 163 134 164 *NOTE 3. For the nuclear scattering length density, only the real component 135 is taken account.*165 is taken into account.* 136 166 137 167 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ … … 151 181 152 182 where $v_j \beta_j \equiv b_j$ is the scattering 153 length of the $j^\text{th}$ atom. The calculation output is passed to the *Data Explorer* 183 length of the $j^\text{th}$ atom. 184 .. The calculation output is passed to the *Data Explorer* 154 185 for further use. 155 186 156 .. image:: pdb_combo.jpg 187 .. figure:: pdb_combo.png 188 189 .. 190 191 1) PDB file loaded 192 2) disabled input for *Up_frac_in*, *Up_frac_oupt*, *Up_theta* 193 3) option to perform the calculations using "Fixed orientations" (2D output) 194 or "Averaging over all orientations using Debye equation" (1D output). 195 This choice is only available for PDB files. 196 197 157 198 158 199 .. ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ -
src/sas/qtgui/GUITests.py
r01ef3f7 r57be490 49 49 from Utilities.UnitTesting import TabbedModelEditorTest 50 50 from Utilities.UnitTesting import AddMultEditorTest 51 from Utilities.UnitTesting import ReportDialogTest 51 52 52 53 # Unit Testing … … 109 110 unittest.makeSuite(TabbedModelEditorTest.TabbedModelEditorTest,'test'), 110 111 unittest.makeSuite(AddMultEditorTest.AddMultEditorTest, 'test'), 112 unittest.makeSuite(ReportDialogTest.ReportDialogTest, 'test'), 111 113 112 114 # Calculators -
src/sas/qtgui/MainWindow/GuiManager.py
rfa05c6c1 r57be490 23 23 from sas.qtgui.Utilities.GridPanel import BatchOutputPanel 24 24 25 from sas.qtgui.Utilities.ReportDialog import ReportDialog 25 26 from sas.qtgui.MainWindow.UI.AcknowledgementsUI import Ui_Acknowledgements 26 27 from sas.qtgui.MainWindow.AboutBox import AboutBox … … 65 66 # Add signal callbacks 66 67 self.addCallbacks() 68 69 # Assure model categories are available 70 self.addCategories() 67 71 68 72 # Create the data manager … … 142 146 self.ResolutionCalculator = ResolutionCalculatorPanel(self) 143 147 self.DataOperation = DataOperationUtilityPanel(self) 148 149 def addCategories(self): 150 """ 151 Make sure categories.json exists and if not compile it and install in ~/.sasview 152 """ 153 try: 154 from sas.sascalc.fit.models import ModelManager 155 from sas.qtgui.Utilities.CategoryInstaller import CategoryInstaller 156 model_list = ModelManager().cat_model_list() 157 CategoryInstaller.check_install(model_list=model_list) 158 except Exception: 159 logger.error("%s: could not load SasView models") 160 logger.error(traceback.format_exc()) 144 161 145 162 def statusBarSetup(self): … … 472 489 def actionSave_Analysis(self): 473 490 """ 474 """ 475 print("actionSave_Analysis TRIGGERED") 476 477 pass 491 Menu File/Save Analysis 492 """ 493 self.communicate.saveAnalysisSignal.emit() 478 494 479 495 def actionQuit(self): … … 510 526 def actionReport(self): 511 527 """ 512 """ 513 print("actionReport TRIGGERED") 514 pass 528 Show the Fit Report dialog. 529 """ 530 report_list = None 531 if getattr(self._current_perspective, "currentTab"): 532 try: 533 report_list = self._current_perspective.currentTab.getReport() 534 except Exception as ex: 535 logging.error("Report generation failed with: " + str(ex)) 536 537 if report_list is not None: 538 self.report_dialog = ReportDialog(parent=self, report_list=report_list) 539 self.report_dialog.show() 515 540 516 541 def actionReset(self): -
src/sas/qtgui/Perspectives/Fitting/FittingPerspective.py
r3b3b40b r57be490 37 37 self.maxIndex = 0 38 38 39 # Index of the current tab40 self.currentTab = 039 ## Index of the current tab 40 #self.currentTab = 0 41 41 42 42 # The default optimizer … … 318 318 319 319 pass 320 321 @property 322 def currentTab(self): 323 """ 324 Returns the tab widget currently shown 325 """ 326 return self.currentWidget() 327 -
src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py
rb5cc06e r57be490 68 68 multishell_parameters = getIterParams(parameters) 69 69 multishell_param_name, _ = getMultiplicity(parameters) 70 70 71 if is2D: 71 72 params = [p for p in parameters.kernel_parameters if p.type != 'magnetic'] … … 443 444 444 445 446 def getStandardParam(model=None): 447 """ 448 Returns a list with standard parameters for the current model 449 """ 450 param = [] 451 num_rows = model.rowCount() 452 if num_rows < 1: 453 return None 454 455 for row in range(num_rows): 456 param_name = model.item(row, 0).text() 457 checkbox_state = model.item(row,0).checkState() == QtCore.Qt.Checked 458 value= model.item(row, 1).text() 459 column_shift = 0 460 if model.columnCount() == 5: # no error column 461 error_state = False 462 error_value = 0.0 463 else: 464 error_state = True 465 error_value = model.item(row, 2).text() 466 column_shift = 1 467 min_state = True 468 max_state = True 469 min_value = model.item(row, 2+column_shift).text() 470 max_value = model.item(row, 3+column_shift).text() 471 unit = "" 472 if model.item(row, 4+column_shift) is not None: 473 unit = model.item(row, 4+column_shift).text() 474 475 param.append([checkbox_state, param_name, value, "", 476 [error_state, error_value], 477 [min_state, min_value], 478 [max_state, max_value], unit]) 479 480 return param 481 482 def getOrientationParam(kernel_module=None): 483 """ 484 Get the dictionary with orientation parameters 485 """ 486 param = [] 487 if kernel_module is None: 488 return None 489 for param_name in list(kernel_module.params.keys()): 490 name = param_name 491 value = kernel_module.params[param_name] 492 min_state = True 493 max_state = True 494 error_state = False 495 error_value = 0.0 496 checkbox_state = True #?? 497 details = kernel_module.details[param_name] #[unit, mix, max] 498 param.append([checkbox_state, name, value, "", 499 [error_state, error_value], 500 [min_state, details[1]], 501 [max_state, details[2]], details[0]]) 502 503 return param -
src/sas/qtgui/Perspectives/Fitting/FittingWidget.py
raed0532 r57be490 1 1 import json 2 2 import os 3 import copy 3 4 from collections import defaultdict 4 5 … … 21 22 22 23 from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit 24 from sas.sascalc.fit.pagestate import PageState 23 25 24 26 import sas.qtgui.Utilities.GuiUtils as GuiUtils … … 45 47 from sas.qtgui.Perspectives.Fitting.Constraint import Constraint 46 48 from sas.qtgui.Perspectives.Fitting.MultiConstraint import MultiConstraint 49 from sas.qtgui.Perspectives.Fitting.ReportPageLogic import ReportPageLogic 50 47 51 48 52 … … 506 510 # Signals from other widgets 507 511 self.communicate.customModelDirectoryChanged.connect(self.onCustomModelChange) 512 self.communicate.saveAnalysisSignal.connect(self.savePageState) 513 #self.communicate.saveReportSignal.connect(self.saveReport) 508 514 509 515 def modelName(self): … … 2633 2639 self.page_stack.pop() 2634 2640 2641 def getReport(self): 2642 """ 2643 Create and return HTML report with parameters and charts 2644 """ 2645 index = None 2646 if self.all_data: 2647 index = self.all_data[self.data_index] 2648 report_logic = ReportPageLogic(self, 2649 kernel_module=self.kernel_module, 2650 data=self.data, 2651 index=index, 2652 model=self._model_model) 2653 2654 return report_logic.reportList() 2655 2656 def savePageState(self): 2657 """ 2658 Create and serialize local PageState 2659 """ 2660 from sas.sascalc.fit.pagestate import Reader 2661 model = self.kernel_module 2662 2663 # Old style PageState object 2664 state = PageState(model=model, data=self.data) 2665 2666 # Add parameter data to the state 2667 self.getCurrentFitState(state) 2668 2669 # Create the filewriter, aptly named 'Reader' 2670 state_reader = Reader(self.loadPageStateCallback) 2671 filepath = self.saveAsAnalysisFile() 2672 if filepath is None: 2673 return 2674 state_reader.write(filename=filepath, fitstate=state) 2675 pass 2676 2677 def saveAsAnalysisFile(self): 2678 """ 2679 Show the save as... dialog and return the chosen filepath 2680 """ 2681 default_name = "FitPage"+str(self.tab_id)+".fitv" 2682 2683 wildcard = "fitv files (*.fitv)" 2684 kwargs = { 2685 'caption' : 'Save As', 2686 'directory' : default_name, 2687 'filter' : wildcard, 2688 'parent' : None, 2689 } 2690 # Query user for filename. 2691 filename_tuple = QtWidgets.QFileDialog.getSaveFileName(**kwargs) 2692 filename = filename_tuple[0] 2693 return filename 2694 2695 def loadPageStateCallback(self,state=None, datainfo=None, format=None): 2696 """ 2697 This is a callback method called from the CANSAS reader. 2698 We need the instance of this reader only for writing out a file, 2699 so there's nothing here. 2700 Until Load Analysis is implemented, that is. 2701 """ 2702 pass 2703 2704 def loadPageState(self, pagestate=None): 2705 """ 2706 Load the PageState object and update the current widget 2707 """ 2708 pass 2709 2710 def getCurrentFitState(self, state=None): 2711 """ 2712 Store current state for fit_page 2713 """ 2714 # save model option 2715 #if self.model is not None: 2716 # self.disp_list = self.getDispParamList() 2717 # state.disp_list = copy.deepcopy(self.disp_list) 2718 # #state.model = self.model.clone() 2719 2720 # Comboboxes 2721 state.categorycombobox = self.cbCategory.currentText() 2722 state.formfactorcombobox = self.cbModel.currentText() 2723 if self.cbStructureFactor.isEnabled(): 2724 state.structureCombobox = self.cbStructureFactor.currentText() 2725 state.tcChi = self.chi2 2726 2727 state.enable2D = self.is2D 2728 2729 #state.weights = copy.deepcopy(self.weights) 2730 # save data 2731 state.data = copy.deepcopy(self.data) 2732 2733 # save plotting range 2734 state.qmin = self.q_range_min 2735 state.qmax = self.q_range_max 2736 state.npts = self.npts 2737 2738 # self.state.enable_disp = self.enable_disp.GetValue() 2739 # self.state.disable_disp = self.disable_disp.GetValue() 2740 2741 # self.state.enable_smearer = \ 2742 # copy.deepcopy(self.enable_smearer.GetValue()) 2743 # self.state.disable_smearer = \ 2744 # copy.deepcopy(self.disable_smearer.GetValue()) 2745 2746 #self.state.pinhole_smearer = \ 2747 # copy.deepcopy(self.pinhole_smearer.GetValue()) 2748 #self.state.slit_smearer = copy.deepcopy(self.slit_smearer.GetValue()) 2749 #self.state.dI_noweight = copy.deepcopy(self.dI_noweight.GetValue()) 2750 #self.state.dI_didata = copy.deepcopy(self.dI_didata.GetValue()) 2751 #self.state.dI_sqrdata = copy.deepcopy(self.dI_sqrdata.GetValue()) 2752 #self.state.dI_idata = copy.deepcopy(self.dI_idata.GetValue()) 2753 2754 p = self.model_parameters 2755 # save checkbutton state and txtcrtl values 2756 state.parameters = FittingUtilities.getStandardParam() 2757 state.orientation_params_disp = FittingUtilities.getOrientationParam() 2758 2759 #self._copy_parameters_state(self.orientation_params_disp, self.state.orientation_params_disp) 2760 #self._copy_parameters_state(self.parameters, self.state.parameters) 2761 #self._copy_parameters_state(self.fittable_param, self.state.fittable_param) 2762 #self._copy_parameters_state(self.fixed_param, self.state.fixed_param) 2763 2764 -
src/sas/qtgui/Perspectives/Fitting/OptionsWidget.py
rd6b8a1d r976978b 116 116 self.mapper.addMapping(self.txtNpts, MODEL.index('NPTS')) 117 117 self.mapper.addMapping(self.chkLogData, MODEL.index('LOG_SPACED')) 118 # FIXME DOESNT WORK WITH QT5 119 #self.mapper.toFirst()118 119 self.mapper.toFirst() 120 120 121 121 def toggleLogData(self, isChecked): -
src/sas/qtgui/Utilities/CategoryInstaller.py
rcee5c78 rc889a3e 174 174 model_enabled_dict) 175 175 176 json.dump(master_category_dict, open(serialized_file, 'wb'))176 json.dump(master_category_dict, open(serialized_file, "w", encoding="utf8")) 177 177 -
src/sas/qtgui/Utilities/GuiUtils.py
rd4dac80 r57be490 1 # -*- coding: utf-8 -*- 1 2 """ 2 3 Global defaults and various utility functions usable by the general GUI … … 248 249 sendDataToGridSignal = QtCore.pyqtSignal(list) 249 250 251 # Action Save Analysis triggered 252 saveAnalysisSignal = QtCore.pyqtSignal() 250 253 251 254 def updateModelItemWithPlot(item, update_data, name=""): … … 378 381 if str(i.text()) == filename]) 379 382 return item[0] if len(item)>0 else None 383 384 def plotsFromModel(model_name, model_item): 385 """ 386 Returns the list of plots for the item with model name in the model 387 """ 388 assert isinstance(model_item, QtGui.QStandardItem) 389 assert isinstance(model_name, str) 390 391 plot_data = [] 392 # Iterate over model looking for named items 393 for index in range(model_item.rowCount()): 394 item = model_item.child(index) 395 if isinstance(item.data(), (Data1D, Data2D)): 396 plot_data.append(item.data()) 397 if model_name in str(item.text()): 398 #plot_data.append(item.child(0).data()) 399 # Going 1 level deeper only 400 for index_2 in range(item.rowCount()): 401 item_2 = item.child(index_2) 402 if item_2 and isinstance(item_2.data(), (Data1D, Data2D)): 403 plot_data.append(item_2.data()) 404 405 return plot_data 380 406 381 407 def plotsFromFilename(filename, model_item): … … 841 867 return output.lstrip().rstrip() 842 868 869 def replaceHTMLwithUTF8(html): 870 """ 871 Replace some important HTML-encoded characters 872 with their UTF-8 equivalents 873 """ 874 # Angstrom 875 html_out = html.replace("Å", "à 876 ") 877 # infinity 878 html_out = html_out.replace("∞", "â") 879 # +/- 880 html_out = html_out.replace("±", "±") 881 882 return html_out 883 884 def replaceHTMLwithASCII(html): 885 """ 886 Replace some important HTML-encoded characters 887 with their ASCII equivalents 888 """ 889 # Angstrom 890 html_out = html.replace("Å", "Ang") 891 # infinity 892 html_out = html_out.replace("∞", "inf") 893 # +/- 894 html_out = html_out.replace("±", "+/-") 895 896 return html_out 897 898 def convertUnitToUTF8(unit): 899 """ 900 Convert ASCII unit display into UTF-8 symbol 901 """ 902 if unit == "1/A": 903 return "à 904 <sup>-1</sup>" 905 elif unit == "1/cm": 906 return "cm<sup>-1</sup>" 907 elif unit == "Ang": 908 return "à 909 " 910 elif unit == "1e-6/Ang^2": 911 return "10<sup>-6</sup>/à 912 <sup>2</sup>" 913 elif unit == "inf": 914 return "â" 915 elif unit == "-inf": 916 return "-â" 917 else: 918 return unit 919 843 920 def convertUnitToHTML(unit): 844 921 """ -
src/sas/qtgui/Utilities/UnitTesting/GuiUtilsTest.py
r6cb305a r57be490 1 # -*- coding: utf-8 -*- 1 2 import sys 2 3 import unittest … … 446 447 self.assertEqual(yscale, "log") 447 448 449 def testReplaceHTMLwithUTF8(self): 450 ''' test single character replacement ''' 451 s = None 452 with self.assertRaises(AttributeError): 453 result = replaceHTMLwithUTF8(s) 454 455 s = "" 456 self.assertEqual(replaceHTMLwithUTF8(s), s) 457 458 s = "aaaa" 459 self.assertEqual(replaceHTMLwithUTF8(s), s) 460 461 s = "Å ∞ ±" 462 self.assertEqual(replaceHTMLwithUTF8(s), "à 463 â ±") 464 465 def testReplaceHTMLwithASCII(self): 466 ''' test single character replacement''' 467 s = None 468 with self.assertRaises(AttributeError): 469 result = replaceHTMLwithASCII(s) 470 471 s = "" 472 self.assertEqual(replaceHTMLwithASCII(s), s) 473 474 s = "aaaa" 475 self.assertEqual(replaceHTMLwithASCII(s), s) 476 477 s = "Å ∞ ±" 478 self.assertEqual(replaceHTMLwithASCII(s), "Ang inf +/-") 479 480 def testConvertUnitToUTF8(self): 481 ''' test unit string replacement''' 482 s = None 483 self.assertIsNone(convertUnitToUTF8(s)) 484 485 s = "" 486 self.assertEqual(convertUnitToUTF8(s), s) 487 488 s = "aaaa" 489 self.assertEqual(convertUnitToUTF8(s), s) 490 491 s = "1/A" 492 self.assertEqual(convertUnitToUTF8(s), "à 493 <sup>-1</sup>") 494 495 s = "Ang" 496 self.assertEqual(convertUnitToUTF8(s), "à 497 ") 498 499 s = "1e-6/Ang^2" 500 self.assertEqual(convertUnitToUTF8(s), "10<sup>-6</sup>/à 501 <sup>2</sup>") 502 503 s = "inf" 504 self.assertEqual(convertUnitToUTF8(s), "â") 505 506 s = "1/cm" 507 self.assertEqual(convertUnitToUTF8(s), "cm<sup>-1</sup>") 508 509 def testConvertUnitToHTML(self): 510 ''' test unit string replacement''' 511 s = None 512 self.assertIsNone(convertUnitToHTML(s)) 513 514 s = "" 515 self.assertEqual(convertUnitToHTML(s), s) 516 517 s = "aaaa" 518 self.assertEqual(convertUnitToHTML(s), s) 519 520 s = "1/A" 521 self.assertEqual(convertUnitToHTML(s), "Å<sup>-1</sup>") 522 523 s = "Ang" 524 self.assertEqual(convertUnitToHTML(s), "Å") 525 526 s = "1e-6/Ang^2" 527 self.assertEqual(convertUnitToHTML(s), "10<sup>-6</sup>/Å<sup>2</sup>") 528 529 s = "inf" 530 self.assertEqual(convertUnitToHTML(s), "∞") 531 s = "-inf" 532 533 self.assertEqual(convertUnitToHTML(s), "-∞") 534 535 s = "1/cm" 536 self.assertEqual(convertUnitToHTML(s), "cm<sup>-1</sup>") 537 448 538 def testParseName(self): 449 539 '''test parse out a string from the beinning of a string''' -
src/sas/qtgui/convertUI.py
- Property mode changed from 100755 to 100644
r4992ff2 r2e27cdb6 1 1 # Convert all .ui files in all subdirectories of the current script 2 2 import os 3 import sys 3 4 4 5 def pyrrc(in_file, out_file): … … 21 22 22 23 # RC file in UI directory 23 ui_root = 'UI' 24 execute_root = os.path.split(sys.modules[__name__].__file__)[0] 25 ui_root = os.path.join(execute_root, 'UI') 24 26 rc_file = 'main_resources.qrc' 25 27 out_file = 'main_resources_rc.py' 28 26 29 pyrrc(os.path.join(ui_root, rc_file), os.path.join(ui_root, out_file)) 27 30 28 31 # Images 29 images_root = 'images'32 images_root = os.path.join(execute_root, 'images') 30 33 rc_file = 'images.qrc' 31 34 out_file = 'images_rc.py' 32 pyrrc(os.path.join(images_root, rc_file), os.path.join( ui_root, out_file))35 pyrrc(os.path.join(images_root, rc_file), os.path.join(images_root, out_file))
Note: See TracChangeset
for help on using the changeset viewer.