source: sasview/src/sas/sasgui/perspectives/invariant/invariant_details.py @ b2964ef

Last change on this file since b2964ef was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 21.8 KB
RevLine 
[959eb01]1"""
2    Invariant panel
3"""
4import sys
5
[5251ec6]6import wx
7
[959eb01]8from sas.sasgui.guiframe.utils import format_number
[5251ec6]9from .invariant_widgets import OutputTextCtrl
10
[959eb01]11# Dimensions related to chart
12RECTANGLE_WIDTH = 400.0
13RECTANGLE_HEIGHT = 20
14# Invariant panel size
15_BOX_WIDTH = 76
16
17# scale to use for a bar of value zero
18RECTANGLE_SCALE = 0.0001
19DEFAULT_QSTAR = 1.0
20
21if sys.platform.count("win32") > 0:
22    _STATICBOX_WIDTH = 450
23    PANEL_WIDTH = 500
24    PANEL_HEIGHT = 430
25    FONT_VARIANT = 0
26else:
27    _STATICBOX_WIDTH = 480
28    PANEL_WIDTH = 530
29    PANEL_HEIGHT = 430
30    FONT_VARIANT = 1
31
32ERROR_COLOR = wx.Colour(255, 0, 0, 128)
33EXTRAPOLATION_COLOR = wx.Colour(169, 169, 168, 128)
34INVARIANT_COLOR = wx.Colour(67, 208, 128, 128)
35
36
[5251ec6]37class InvariantContainer:
[959eb01]38    """
39    This class stores some values resulting resulting from invariant
40    calculations. Given the value of total invariant, this class can also
41    determine the percentage of invariants resulting from extrapolation.
42    """
43    def __init__(self):
44        # invariant at low range
45        self.qstar_low = None
46        # invariant at low range error
47        self.qstar_low_err = None
48        # invariant
49        self.qstar = None
50        # invariant error
51        self.qstar_err = None
52        # invariant at high range
53        self.qstar_high = None
54        # invariant at high range error
55        self.qstar_high_err = None
56        # invariant total
57        self.qstar_total = None
58        # invariant error
59        self.qstar_total_err = None
60        # scale
61        self.qstar_low_percent = None
62        self.qstar_high_percent = None
63        self.qstar_percent = None
64        # warning message
65        self.existing_warning = False
66        self.warning_msg = "No Details on calculations available...\n"
67
68    def compute_percentage(self):
69        """
70        Compute percentage of each invariant
71        """
72        if self.qstar_total is None:
73            self.qstar_percent = None
74            self.qstar_low = None
75            self.qstar_high = None
76            self.check_values()
77            return
78
79        # compute invariant percentage
80        if self.qstar is None:
81            self.qstar_percent = None
82        else:
83            try:
84                self.qstar_percent = float(self.qstar) / float(self.qstar_total)
85            except:
86                self.qstar_percent = 'error'
87        # compute low q invariant percentage
88        if self.qstar_low is None:
89            self.qstar_low_percent = None
90        else:
91            try:
92                self.qstar_low_percent = float(self.qstar_low)\
93                                            / float(self.qstar_total)
94            except:
95                self.qstar_low_percent = 'error'
96        # compute high q invariant percentage
97        if self.qstar_high is None:
98            self.qstar_high_percent = None
99        else:
100            try:
101                self.qstar_high_percent = float(self.qstar_high)\
102                                                / float(self.qstar_total)
103            except:
104                self.qstar_high_percent = 'error'
105        wx.CallAfter(self.check_values)
106
107    def check_values(self):
108        """
109        check the validity if invariant
110        """
111        if self.qstar_total is None and self.qstar is None:
112            self.warning_msg = "Invariant not calculated.\n"
113            return
114        if self.qstar_total == 0:
115            self.existing_warning = True
116            self.warning_msg = "Invariant is zero. \n"
117            self.warning_msg += "The calculations are likely "
118            self.warning_msg += "to be unreliable!\n"
119            return
120        # warning to the user when the extrapolated invariant is greater than %5
121        msg = ''
122        if self.qstar_percent == 'error':
123            try:
124                float(self.qstar)
125            except:
126                self.existing_warning = True
127                msg += 'Error occurred when computing invariant from data.\n '
128        if self.qstar_percent > 1:
129            self.existing_warning = True
130            msg += "Invariant Q  contribution is greater "
131            msg += "than 100% .\n"
132        if self.qstar_low_percent == 'error':
133            try:
134                float(self.qstar_low)
135            except:
136                self.existing_warning = True
137                msg += "Error occurred when computing extrapolated invariant"
138                msg += " at low-Q region.\n"
139        elif self.qstar_low_percent is not None:
140            if self.qstar_low_percent >= 0.05:
141                self.existing_warning = True
142                msg += "Extrapolated contribution at Low Q is higher "
143                msg += "than 5% of the invariant.\n"
144            elif self.qstar_low_percent < 0:
145                self.existing_warning = True
146                msg += "Extrapolated contribution at Low Q < 0.\n"
147            elif self.qstar_low_percent > 1:
148                self.existing_warning = True
149                msg += "Extrapolated contribution at Low Q is greater "
150                msg += "than 100% .\n"
151        if self.qstar_high_percent == 'error':
152            try:
153                float(self.qstar_high)
154            except:
155                self.existing_warning = True
156                msg += 'Error occurred when computing extrapolated'
157                msg += ' invariant at high-Q region.\n'
158        elif self.qstar_high_percent is not None:
159            if self.qstar_high_percent >= 0.05:
160                self.existing_warning = True
161                msg += "Extrapolated contribution at High Q is higher "
162                msg += "than 5% of the invariant.\n"
163            elif self.qstar_high_percent < 0:
164                self.existing_warning = True
165                msg += "Extrapolated contribution at High Q < 0.\n"
166            elif self.qstar_high_percent > 1:
167                self.existing_warning = True
168                msg += "Extrapolated contribution at High Q is greater "
169                msg += "than 100% .\n"
170        if (self.qstar_low_percent not in [None, "error"]) and \
171            (self.qstar_high_percent not in [None, "error"])\
172            and self.qstar_low_percent + self.qstar_high_percent >= 0.05:
173            self.existing_warning = True
174            msg += "The sum of all extrapolated contributions is higher "
175            msg += "than 5% of the invariant.\n"
176
177        if self.existing_warning:
178            self.warning_msg = ''
179            self.warning_msg += msg
180            self.warning_msg += "The calculations are likely to be"
181            self.warning_msg += " unreliable!\n"
182        else:
183            self.warning_msg = "No Details on calculations available...\n"
184
185class InvariantDetailsPanel(wx.Dialog):
186    """
187    This panel describes proportion of invariants
188    """
189    def __init__(self, parent=None, id=-1, qstar_container=None,
190                 title="Invariant Details",
191                 size=(PANEL_WIDTH, PANEL_HEIGHT)):
192        wx.Dialog.__init__(self, parent=parent, id=id, title=title, size=size)
193
194        # Font size
195        self.SetWindowVariant(variant=FONT_VARIANT)
196        self.parent = parent
197        # self.qstar_container
198        self.qstar_container = qstar_container
199        # warning message
200        self.warning_msg = self.qstar_container.warning_msg
201
202        # Define scale of each bar
203        self.low_inv_percent = self.qstar_container.qstar_low_percent
204        self.low_scale = self.get_scale(percentage=self.low_inv_percent,
205                                        scale_name="Extrapolated at Low Q")
206        self.inv_percent = self.qstar_container.qstar_percent
207        self.inv_scale = self.get_scale(percentage=self.inv_percent,
208                                        scale_name="Inv in Q range")
209        self.high_inv_percent = self.qstar_container.qstar_high_percent
210        self.high_scale = self.get_scale(percentage=self.high_inv_percent,
211                                         scale_name="Extrapolated at High Q")
212
213        # Default color the extrapolation bar is grey
214        self.extrapolation_color_low = EXTRAPOLATION_COLOR
215        self.extrapolation_color_high = EXTRAPOLATION_COLOR
216        self.invariant_color = INVARIANT_COLOR
217        # change color of high and low bar when necessary
218        self.set_color_bar()
219        # draw the panel itself
220        self._do_layout()
221        self.set_values()
222
223    def _define_structure(self):
224        """
225        Define main sizers needed for this panel
226        """
227        # Box sizers must be defined first before defining buttons/textctrls
228        # (MAC).
229        self.main_sizer = wx.BoxSizer(wx.VERTICAL)
230        # Sizer related to chart
231        chart_box = wx.StaticBox(self, -1, "Invariant Chart")
232        self.chart_sizer = wx.StaticBoxSizer(chart_box, wx.VERTICAL)
233        self.chart_sizer.SetMinSize((PANEL_WIDTH - 50, 110))
234        # Sizer related to invariant values
235        self.invariant_sizer = wx.GridBagSizer(4, 4)
236        invariant_box = wx.StaticBox(self, -1, "Numerical Values")
237        self.invariant_box_sizer = wx.StaticBoxSizer(invariant_box, wx.HORIZONTAL)
238
239        self.invariant_box_sizer.SetMinSize((PANEL_WIDTH - 50, -1))
240        # Sizer related to warning message
241        warning_box = wx.StaticBox(self, -1, "Warning")
242        self.warning_sizer = wx.StaticBoxSizer(warning_box, wx.VERTICAL)
243        self.warning_sizer.SetMinSize((PANEL_WIDTH - 50, -1))
244        # Sizer related to button
245        self.button_sizer = wx.BoxSizer(wx.HORIZONTAL)
246
247    def _layout_shart(self):
248        """
249        Draw widgets related to chart
250        """
251        self.panel_chart = wx.Panel(self)
252        self.panel_chart.Bind(wx.EVT_PAINT, self.on_paint)
253        self.chart_sizer.Add(self.panel_chart, 1, wx.EXPAND | wx.ALL, 0)
254
255    def _layout_invariant(self):
256        """
257        Draw widgets related to invariant
258        """
259        uncertainty = "+/-"
260        unit_invariant = '[1/(cm * A^3)]'
261
262        invariant_txt = wx.StaticText(self, -1, 'Q* from Data ')
263        invariant_txt.SetToolTipString("Invariant in the data set's Q range.")
264        self.invariant_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
265        hint_msg = "Invariant in the data set's Q range."
266        self.invariant_tcl.SetToolTipString(hint_msg)
267        self.invariant_err_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
268        hint_msg = "Uncertainty on the invariant from data's range."
269        self.invariant_err_tcl.SetToolTipString(hint_msg)
270        invariant_units_txt = wx.StaticText(self, -1, unit_invariant)
271        hint_msg = "Unit of the invariant from data's Q range"
272        invariant_units_txt.SetToolTipString(hint_msg)
273
274        invariant_low_txt = wx.StaticText(self, -1, 'Q* from Low-Q')
275        hint_msg = "Extrapolated invariant from low-Q range."
276        invariant_low_txt.SetToolTipString(hint_msg)
277        self.invariant_low_tcl = OutputTextCtrl(self, -1, size=(_BOX_WIDTH, -1))
278        hint_msg = "Extrapolated invariant from low-Q range."
279        self.invariant_low_tcl.SetToolTipString(hint_msg)
280        self.invariant_low_err_tcl = OutputTextCtrl(self, -1,
281                                                    size=(_BOX_WIDTH, -1))
282        hint_msg = "Uncertainty on the invariant from low-Q range."
283        self.invariant_low_err_tcl.SetToolTipString(hint_msg)
284        invariant_low_units_txt = wx.StaticText(self, -1, unit_invariant)
285        hint_msg = "Unit of the extrapolated invariant from  low-Q range"
286        invariant_low_units_txt.SetToolTipString(hint_msg)
287
288        invariant_high_txt = wx.StaticText(self, -1, 'Q* from High-Q')
289        hint_msg = "Extrapolated invariant from  high-Q range"
290        invariant_high_txt.SetToolTipString(hint_msg)
291        self.invariant_high_tcl = OutputTextCtrl(self, -1,
292                                                 size=(_BOX_WIDTH, -1))
293        hint_msg = "Extrapolated invariant from  high-Q range"
294        self.invariant_high_tcl.SetToolTipString(hint_msg)
295        self.invariant_high_err_tcl = OutputTextCtrl(self, -1,
296                                                     size=(_BOX_WIDTH, -1))
297        hint_msg = "Uncertainty on the invariant from high-Q range."
298        self.invariant_high_err_tcl.SetToolTipString(hint_msg)
299        invariant_high_units_txt = wx.StaticText(self, -1, unit_invariant)
300        hint_msg = "Unit of the extrapolated invariant from  high-Q range"
301        invariant_high_units_txt.SetToolTipString(hint_msg)
302
303        # Invariant low
304        iy = 0
305        ix = 0
306        self.invariant_sizer.Add(invariant_low_txt, (iy, ix), (1, 1),
307                                 wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
308        ix += 1
309        self.invariant_sizer.Add(self.invariant_low_tcl, (iy, ix), (1, 1),
310                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
311        ix += 1
312        self.invariant_sizer.Add(wx.StaticText(self, -1, uncertainty),
313                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
314        ix += 1
315        self.invariant_sizer.Add(self.invariant_low_err_tcl, (iy, ix), (1, 1),
316                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
317        ix += 1
318        self.invariant_sizer.Add(invariant_low_units_txt,
319                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
320        # Invariant
321        iy += 1
322        ix = 0
323        self.invariant_sizer.Add(invariant_txt, (iy, ix), (1, 1),
324                                 wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
325        ix += 1
326        self.invariant_sizer.Add(self.invariant_tcl, (iy, ix), (1, 1),
327                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
328        ix += 1
329        self.invariant_sizer.Add(wx.StaticText(self, -1, uncertainty),
330                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
331        ix += 1
332        self.invariant_sizer.Add(self.invariant_err_tcl, (iy, ix), (1, 1),
333                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
334        ix += 1
335        self.invariant_sizer.Add(invariant_units_txt,
336                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
337        # Invariant high
338        iy += 1
339        ix = 0
340        self.invariant_sizer.Add(invariant_high_txt, (iy, ix), (1, 1),
341                                 wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 15)
342        ix += 1
343        self.invariant_sizer.Add(self.invariant_high_tcl, (iy, ix), (1, 1),
344                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
345        ix += 1
346        self.invariant_sizer.Add(wx.StaticText(self, -1, uncertainty),
347                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
348        ix += 1
349        self.invariant_sizer.Add(self.invariant_high_err_tcl, (iy, ix), (1, 1),
350                                 wx.EXPAND | wx.ADJUST_MINSIZE, 0)
351        ix += 1
352        self.invariant_sizer.Add(invariant_high_units_txt,
353                                 (iy, ix), (1, 1), wx.EXPAND | wx.ADJUST_MINSIZE, 0)
354        self.invariant_box_sizer.Add(self.invariant_sizer, 0, wx.TOP | wx.BOTTOM, 10)
355
356    def _layout_warning(self):
357        """
358        Draw widgets related to warning
359        """
360        # Warning [string]
361        self.warning_msg_txt = wx.StaticText(self, -1, self.warning_msg)
362        if self.qstar_container.existing_warning:
363            self.warning_msg_txt.SetForegroundColour('red')
364        self.warning_sizer.AddMany([(self.warning_msg_txt, 0,
365                                     wx.LEFT | wx.EXPAND | wx.ADJUST_MINSIZE, 10)])
366
367    def _layout_button(self):
368        """
369        Draw widgets related to button
370        """
371        # Close button
372        id = wx.NewId()
373        button_ok = wx.Button(self, id, "Ok")
374        button_ok.SetToolTipString("Give Details on Computation")
375        self.Bind(wx.EVT_BUTTON, self.on_close, id=id)
376        self.button_sizer.AddMany([((20, 20), 0, wx.LEFT, 350),
377                                   (button_ok, 0, wx.RIGHT, 10)])
378    def _do_layout(self):
379        """
380        Draw window content
381        """
382        self._define_structure()
383        self._layout_shart()
384        self._layout_invariant()
385        self._layout_warning()
386        self._layout_button()
387        self.main_sizer.AddMany([(self.chart_sizer, 0, wx.ALL, 10),
388                                 (self.invariant_box_sizer, 0, wx.ALL, 10),
389                                 (self.warning_sizer, 0, wx.ALL, 10),
390                                 (self.button_sizer, 0, wx.ALL, 10)])
391        self.SetSizer(self.main_sizer)
392
393
394    def set_values(self):
395        """
396        Set value of txtcrtl
397        """
398        value = format_number(self.qstar_container.qstar)
399        self.invariant_tcl.SetValue(value)
400        value = format_number(self.qstar_container.qstar_err)
401        self.invariant_err_tcl.SetValue(value)
402        value = format_number(self.qstar_container.qstar_low)
403        self.invariant_low_tcl.SetValue(value)
404        value = format_number(self.qstar_container.qstar_low_err)
405        self.invariant_low_err_tcl.SetValue(value)
406        value = format_number(self.qstar_container.qstar_high)
407        self.invariant_high_tcl.SetValue(value)
408        value = format_number(self.qstar_container.qstar_high_err)
409        self.invariant_high_err_tcl.SetValue(value)
410
411    def get_scale(self, percentage, scale_name='scale'):
412        """
413        Check scale receive in this panel.
414        """
415        scale = RECTANGLE_SCALE
416        try:
417            if percentage in [None, 0.0, "error"]:
418                scale = RECTANGLE_SCALE
419                return scale
420            elif percentage < 0:
421                scale = RECTANGLE_SCALE
422                return scale
423            scale = float(percentage)
424        except:
425            scale = RECTANGLE_SCALE
[20fa5fe]426            self.warning_msg += "Receive an invalid scale for %s\n"
[959eb01]427            self.warning_msg += "check this value : %s\n" % str(percentage)
428        return  scale
429
430    def set_color_bar(self):
431        """
432        Change the color for low and high bar when necessary
433        """
434        self.extrapolation_color_low = EXTRAPOLATION_COLOR
435        self.extrapolation_color_high = EXTRAPOLATION_COLOR
436        self.invariant_color = INVARIANT_COLOR
437        # warning to the user when the extrapolated invariant is greater than %5
438        if self.low_scale >= 0.05 or self.low_scale > 1 or self.low_scale < 0:
439            self.extrapolation_color_low = ERROR_COLOR
440        if self.high_scale >= 0.05 or self.high_scale > 1 or self.high_scale < 0:
441            self.extrapolation_color_high = ERROR_COLOR
442        if self.inv_scale > 1 or self.inv_scale < 0:
443            self.invariant_color = ERROR_COLOR
444
445    def on_close(self, event):
446        """
447        Close the current window
448        """
449        self.Close()
450
451    def on_paint(self, event):
452        """
453        Draw the chart
454        """
455        dc = wx.PaintDC(self.panel_chart)
456        try:
457            gc = wx.GraphicsContext.Create(dc)
458        except NotImplementedError:
459            msg = "This build of wxPython does not support "
460            msg += "the wx.GraphicsContext family of classes."
461            dc.DrawText(msg, 25, 25)
462            return
463        # Start the drawing
464        font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
465        font.SetWeight(wx.BOLD)
466        gc.SetFont(font)
467        # Draw a rectangle
468        path = gc.CreatePath()
469        path.AddRectangle(-RECTANGLE_WIDTH / 2, -RECTANGLE_HEIGHT / 2,
470                          RECTANGLE_WIDTH / 2, RECTANGLE_HEIGHT / 2)
471        x_origine = 20
472        y_origine = 15
473        # Draw low rectangle
474        gc.PushState()
475        label = "Q* from Low-Q "
476        PathFunc = gc.DrawPath
477        w, h = gc.GetTextExtent(label)
478        gc.DrawText(label, x_origine, y_origine)
479        # Translate the rectangle
480        x_center = x_origine + RECTANGLE_WIDTH * self.low_scale / 2 + w + 10
481        y_center = y_origine + h
482        gc.Translate(x_center, y_center)
483        gc.SetPen(wx.Pen("black", 1))
484        gc.SetBrush(wx.Brush(self.extrapolation_color_low))
485        if self.low_inv_percent is None:
486            low_percent = 'Not Computed'
487        elif self.low_inv_percent == 'error':
488            low_percent = 'Error'
489        else:
490            low_percent = format_number(self.low_inv_percent * 100) + '%'
491        x_center = 20
492        y_center = -h
493        gc.DrawText(low_percent, x_center, y_center)
494        # Increase width by self.low_scale
495        gc.Scale(self.low_scale, 1.0)
496        PathFunc(path)
497        gc.PopState()
498        # Draw rectangle for invariant
499        gc.PushState()  # save it again
500        y_origine += 20
501        gc.DrawText("Q* from Data ", x_origine, y_origine)
502        # offset to the lower part of the window
503        x_center = x_origine + RECTANGLE_WIDTH * self.inv_scale / 2 + w + 10
504        y_center = y_origine + h
505        gc.Translate(x_center, y_center)
506        # 128 == half transparent
507        gc.SetBrush(wx.Brush(self.invariant_color))
508        # Increase width by self.inv_scale
509        if self.inv_percent is None:
510            inv_percent = 'Not Computed'
511        elif self.inv_percent == 'error':
512            inv_percent = 'Error'
513        else:
514            inv_percent = format_number(self.inv_percent * 100) + '%'
515        x_center = 20
516        y_center = -h
517        gc.DrawText(inv_percent, x_center, y_center)
518        gc.Scale(self.inv_scale, 1.0)
519        gc.DrawPath(path)
520        gc.PopState()
521        # restore saved state
522        # Draw rectangle for high invariant
523        gc.PushState()
524        y_origine += 20
525        gc.DrawText("Q* from High-Q ", x_origine, y_origine)
526        # define the position of the new rectangle
527        x_center = x_origine + RECTANGLE_WIDTH * self.high_scale / 2 + w + 10
528        y_center = y_origine + h
529        gc.Translate(x_center, y_center)
530        gc.SetBrush(wx.Brush(self.extrapolation_color_high))
531        # increase scale by self.high_scale
532        if self.high_inv_percent is None:
533            high_percent = 'Not Computed'
534        elif self.high_inv_percent == 'error':
535            high_percent = 'Error'
536        else:
537            high_percent = format_number(self.high_inv_percent * 100) + '%'
538        x_center = 20
539        y_center = -h
540        gc.DrawText(high_percent, x_center, y_center)
541
542        gc.Scale(self.high_scale, 1.0)
543        gc.DrawPath(path)
544        gc.PopState()
Note: See TracBrowser for help on using the repository browser.