source: sasview/src/sas/sasgui/guiframe/local_perspectives/plotting/boxSum.py @ f5f8553

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

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 26.8 KB
Line 
1"""
2    Boxsum Class: determine 2 rectangular area to compute
3    the sum of pixel of a Data.
4"""
5import math
6
7import wx
8
9from sas.sasgui.guiframe.events import SlicerParamUpdateEvent
10from sas.sasgui.guiframe.events import EVT_SLICER_PARS
11from sas.sasgui.guiframe.events import StatusEvent
12
13from .BaseInteractor import _BaseInteractor
14
15
16class BoxSum(_BaseInteractor):
17    """
18        Boxsum Class: determine 2 rectangular area to compute
19        the sum of pixel of a Data.
20        Uses PointerInteractor , VerticalDoubleLine,HorizontalDoubleLine.
21        @param zorder:  Artists with lower zorder values are drawn first.
22        @param x_min: the minimum value of the x coordinate
23        @param x_max: the maximum value of the x coordinate
24        @param y_min: the minimum value of the y coordinate
25        @param y_max: the maximum value of the y coordinate
26
27    """
28    def __init__(self, base, axes, color='black', zorder=3, x_min=0.008,
29                 x_max=0.008, y_min=0.0025, y_max=0.0025):
30        """
31        """
32        _BaseInteractor.__init__(self, base, axes, color=color)
33        # # class initialization
34        # # list of Boxsmun markers
35        self.markers = []
36        self.axes = axes
37        # # connect the artist for the motion
38        self.connect = self.base.connect
39        # # when qmax is reached the selected line is reset the its previous value
40        self.qmax = min(self.base.data2D.xmax, self.base.data2D.xmin)
41        # # Define the boxsum limits
42        self.xmin = -1 * 0.5 * min(math.fabs(self.base.data2D.xmax),
43                                   math.fabs(self.base.data2D.xmin))
44        self.ymin = -1 * 0.5 * min(math.fabs(self.base.data2D.xmax),
45                                   math.fabs(self.base.data2D.xmin))
46        self.xmax = 0.5 * min(math.fabs(self.base.data2D.xmax),
47                              math.fabs(self.base.data2D.xmin))
48        self.ymax = 0.5 * min(math.fabs(self.base.data2D.xmax),
49                              math.fabs(self.base.data2D.xmin))
50        # # center of the boxSum
51        self.center_x = 0.0002
52        self.center_y = 0.0003
53        # # Number of points on the plot
54        self.nbins = 20
55        # # Define initial result the summation
56        self.count = 0
57        self.error = 0
58        self.total = 0
59        self.totalerror = 0
60        self.points = 0
61        # # Flag to determine if the current figure has moved
62        # # set to False == no motion , set to True== motion
63        self.has_move = False
64        # # Create Boxsum edges
65        self.horizontal_lines = HorizontalDoubleLine(self,
66                                                     self.base.subplot,
67                                                     color='blue',
68                                                     zorder=zorder,
69                                                     y=self.ymax,
70                                                     x=self.xmax,
71                                                     center_x=self.center_x,
72                                                     center_y=self.center_y)
73        self.horizontal_lines.qmax = self.qmax
74
75        self.vertical_lines = VerticalDoubleLine(self,
76                                                 self.base.subplot,
77                                                 color='black',
78                                                 zorder=zorder,
79                                                 y=self.ymax,
80                                                 x=self.xmax,
81                                                 center_x=self.center_x,
82                                                 center_y=self.center_y)
83        self.vertical_lines.qmax = self.qmax
84
85        self.center = PointInteractor(self,
86                                      self.base.subplot, color='grey',
87                                      zorder=zorder,
88                                      center_x=self.center_x,
89                                      center_y=self.center_y)
90        # # Save the name of the slicer panel associate with this slicer
91        self.panel_name = ""
92        # # Update and post slicer parameters
93        self.update()
94        self._post_data()
95        # # Bind to slice parameter events
96        self.base.Bind(EVT_SLICER_PARS, self._onEVT_SLICER_PARS)
97
98    def set_panel_name(self, name):
99        """
100            Store the name of the panel associated to this slicer
101            @param name: the name of this panel
102        """
103        self.panel_name = name
104
105    def _onEVT_SLICER_PARS(self, event):
106        """
107            receive an event containing parameters values to reset the slicer
108            @param event: event of type SlicerParameterEvent with params as
109            attribute
110        """
111        # # Post e message to declare what kind of event has being received
112        wx.PostEvent(self.base.parent,
113                     StatusEvent(status="Boxsum._onEVT_SLICER_PARS"))
114        event.Skip()
115        # # reset the slicer with the values contains the event.params dictionary
116        if event.type == self.__class__.__name__:
117            self.set_params(event.params)
118            self.base.update()
119
120    def set_layer(self, n):
121        """
122        Allow adding plot to the same panel
123        :param n: the number of layer
124        """
125        self.layernum = n
126        self.update()
127
128    def clear(self):
129        """
130        Clear the slicer and all connected events related to this slicer
131        """
132        self.clear_markers()
133        self.horizontal_lines.clear()
134        self.vertical_lines.clear()
135        self.center.clear()
136        self.base.connect.clearall()
137        self.base.Unbind(EVT_SLICER_PARS)
138
139    def update(self):
140        """
141        Respond to changes in the model by recalculating the profiles and
142        resetting the widgets.
143        """
144        # # check if the center point has moved and update the figure accordingly
145        if self.center.has_move:
146            self.center.update()
147            self.horizontal_lines.update(center=self.center)
148            self.vertical_lines.update(center=self.center)
149        # # check if the horizontal lines have moved and
150        # update the figure accordingly
151        if self.horizontal_lines.has_move:
152            self.horizontal_lines.update()
153            self.vertical_lines.update(y1=self.horizontal_lines.y1,
154                                       y2=self.horizontal_lines.y2,
155                                       height=self.horizontal_lines.half_height)
156        # # check if the vertical lines have moved and
157        # update the figure accordingly
158        if self.vertical_lines.has_move:
159            self.vertical_lines.update()
160            self.horizontal_lines.update(x1=self.vertical_lines.x1,
161                                         x2=self.vertical_lines.x2,
162                                         width=self.vertical_lines.half_width)
163
164    def save(self, ev):
165        """
166        Remember the roughness for this layer and the next so that we
167        can restore on Esc.
168        """
169        self.base.freeze_axes()
170        self.horizontal_lines.save(ev)
171        self.vertical_lines.save(ev)
172        self.center.save(ev)
173
174    def _post_data(self):
175        """
176        Get the limits of the boxsum and compute the sum of the pixel
177        contained in that region and the error on that sum
178        """
179        # # the region of the summation
180        x_min = self.horizontal_lines.x2
181        x_max = self.horizontal_lines.x1
182        y_min = self.vertical_lines.y2
183        y_max = self.vertical_lines.y1
184        # #computation of the sum and its error
185        from sas.sascalc.dataloader.manipulations import Boxavg
186        box = Boxavg(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
187        self.count, self.error = box(self.base.data2D)
188        # Dig out number of points summed, SMK & PDB, 04/03/2013
189        from sas.sascalc.dataloader.manipulations import Boxsum
190        boxtotal = Boxsum(x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max)
191        self.total, self.totalerror, self.points = boxtotal(self.base.data2D)
192
193    def moveend(self, ev):
194        """
195            After a dragging motion this function is called to compute
196            the error and the sum of pixel of a given data 2D
197        """
198        self.base.thaw_axes()
199        # # compute error an d sum of data's pixel
200        self._post_data()
201        # # Create and event ( posted to guiframe)that  set the
202        # #current slicer parameter to a panel of name self.panel_name
203        self.type = self.__class__.__name__
204        params = self.get_params()
205        event = SlicerParamUpdateEvent(type=self.type,
206                                       params=params,
207                                       panel_name=self.panel_name)
208        wx.PostEvent(self.base.parent, event)
209
210    def restore(self):
211        """
212        Restore the roughness for this layer.
213        """
214        self.horizontal_lines.restore()
215        self.vertical_lines.restore()
216        self.center.restore()
217
218    def move(self, x, y, ev):
219        """
220        Process move to a new position, making sure that the move is allowed.
221        """
222        pass
223
224    def set_cursor(self, x, y):
225        """
226        """
227        pass
228
229    def get_params(self):
230        """
231        Store a copy of values of parameters of the slicer into a dictionary.
232        :return params: the dictionary created
233        """
234        params = {}
235        params["Width"] = math.fabs(self.vertical_lines.half_width) * 2
236        params["Height"] = math.fabs(self.horizontal_lines.half_height) * 2
237        params["center_x"] = self.center.x
238        params["center_y"] = self.center.y
239        params["num_points"] = self.points
240        params["avg"] = self.count
241        params["avg_error"] = self.error
242        params["sum"] = self.total
243        params["sum_error"] = self.totalerror
244        return params
245
246    def get_result(self):
247        """
248            return the result of box summation
249        """
250        result = {}
251        result["num_points"] = self.points
252        result["avg"] = self.count
253        result["avg_error"] = self.error
254        result["sum"] = self.total
255        result["sum_error"] = self.totalerror
256        return result
257
258    def set_params(self, params):
259        """
260        Receive a dictionary and reset the slicer with values contained
261        in the values of the dictionary.
262        :param params: a dictionary containing name of slicer parameters and values the user assigned to the slicer.
263        """
264        x_max = math.fabs(params["Width"]) / 2
265        y_max = math.fabs(params["Height"]) / 2
266
267        self.center_x = params["center_x"]
268        self.center_y = params["center_y"]
269        # update the slicer given values of params
270        self.center.update(center_x=self.center_x, center_y=self.center_y)
271        self.horizontal_lines.update(center=self.center,
272                                     width=x_max, height=y_max)
273        self.vertical_lines.update(center=self.center,
274                                   width=x_max, height=y_max)
275        # compute the new error and sum given values of params
276        self._post_data()
277
278    def freeze_axes(self):
279        """
280        """
281        self.base.freeze_axes()
282
283    def thaw_axes(self):
284        """
285        """
286        self.base.thaw_axes()
287
288    def draw(self):
289        """
290        """
291        self.base.draw()
292
293
294
295class PointInteractor(_BaseInteractor):
296    """
297    Draw a point that can be dragged with the marker.
298    this class controls the motion the center of the BoxSum
299    """
300    def __init__(self, base, axes, color='black', zorder=5, center_x=0.0,
301                 center_y=0.0):
302        """
303        """
304        _BaseInteractor.__init__(self, base, axes, color=color)
305        # # Initialization the class
306        self.markers = []
307        self.axes = axes
308        # center coordinates
309        self.x = center_x
310        self.y = center_y
311        # # saved value of the center coordinates
312        self.save_x = center_x
313        self.save_y = center_y
314        # # Create a marker
315        self.center_marker = self.axes.plot([self.x], [self.y], linestyle='',
316                                            marker='s', markersize=10,
317                                            color=self.color, alpha=0.6,
318                                            pickradius=5, label="pick",
319                                            zorder=zorder,
320                                            visible=True)[0]
321        # # Draw a point
322        self.center = self.axes.plot([self.x], [self.y],
323                                     linestyle='-', marker='',
324                                     color=self.color,
325                                     visible=True)[0]
326        # # Flag to determine the motion this point
327        self.has_move = False
328        # # connecting the marker to allow them to move
329        self.connect_markers([self.center_marker])
330        # # Update the figure
331        self.update()
332
333    def set_layer(self, n):
334        """
335            Allow adding plot to the same panel
336            @param n: the number of layer
337        """
338        self.layernum = n
339        self.update()
340
341    def clear(self):
342        """
343            Clear this figure and its markers
344        """
345        self.clear_markers()
346        try:
347            self.center.remove()
348            self.center_marker.remove()
349        except:
350            # Old version of matplotlib
351            for item in range(len(self.axes.lines)):
352                del self.axes.lines[0]
353
354    def update(self, center_x=None, center_y=None):
355        """
356            Draw the new roughness on the graph.
357        """
358        if center_x is not None:
359            self.x = center_x
360        if center_y is not None:
361            self.y = center_y
362        self.center_marker.set(xdata=[self.x], ydata=[self.y])
363        self.center.set(xdata=[self.x], ydata=[self.y])
364
365    def save(self, ev):
366        """
367        Remember the roughness for this layer and the next so that we
368        can restore on Esc.
369        """
370        self.save_x = self.x
371        self.save_y = self.y
372        self.base.freeze_axes()
373
374    def moveend(self, ev):
375        """
376        """
377        self.has_move = False
378        self.base.moveend(ev)
379
380    def restore(self):
381        """
382        Restore the roughness for this layer.
383        """
384        self.y = self.save_y
385        self.x = self.save_x
386
387    def move(self, x, y, ev):
388        """
389        Process move to a new position, making sure that the move is allowed.
390        """
391        self.x = x
392        self.y = y
393        self.has_move = True
394        self.base.base.update()
395
396    def set_cursor(self, x, y):
397        """
398        """
399        self.move(x, y, None)
400        self.update()
401
402class VerticalDoubleLine(_BaseInteractor):
403    """
404         Draw 2 vertical lines moving in opposite direction and centered on
405         a point (PointInteractor)
406    """
407    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5,
408                 center_x=0.0, center_y=0.0):
409        """
410        """
411        _BaseInteractor.__init__(self, base, axes, color=color)
412        # # Initialization the class
413        self.markers = []
414        self.axes = axes
415        # # Center coordinates
416        self.center_x = center_x
417        self.center_y = center_y
418        # # defined end points vertical lignes and their saved values
419        self.y1 = y + self.center_y
420        self.save_y1 = self.y1
421
422        delta = self.y1 - self.center_y
423        self.y2 = self.center_y - delta
424        self.save_y2 = self.y2
425
426        self.x1 = x + self.center_x
427        self.save_x1 = self.x1
428
429        delta = self.x1 - self.center_x
430        self.x2 = self.center_x - delta
431        self.save_x2 = self.x2
432        # # save the color of the line
433        self.color = color
434        # # the height of the rectangle
435        self.half_height = math.fabs(y)
436        self.save_half_height = math.fabs(y)
437        # # the with of the rectangle
438        self.half_width = math.fabs(self.x1 - self.x2) / 2
439        self.save_half_width = math.fabs(self.x1 - self.x2) / 2
440        # # Create marker
441        self.right_marker = self.axes.plot([self.x1], [0], linestyle='',
442                                           marker='s', markersize=10,
443                                           color=self.color, alpha=0.6,
444                                           pickradius=5, label="pick",
445                                           zorder=zorder, visible=True)[0]
446
447        # # define the left and right lines of the rectangle
448        self.right_line = self.axes.plot([self.x1, self.x1], [self.y1, self.y2],
449                                         linestyle='-', marker='',
450                                         color=self.color, visible=True)[0]
451        self.left_line = self.axes.plot([self.x2, self.x2], [self.y1, self.y2],
452                                        linestyle='-', marker='',
453                                        color=self.color, visible=True)[0]
454        # # Flag to determine if the lines have moved
455        self.has_move = False
456        # # connection the marker and draw the pictures
457        self.connect_markers([self.right_marker])
458        self.update()
459
460    def set_layer(self, n):
461        """
462        Allow adding plot to the same panel
463        :param n: the number of layer
464        """
465        self.layernum = n
466        self.update()
467
468    def clear(self):
469        """
470        Clear this slicer  and its markers
471        """
472        self.clear_markers()
473        try:
474            self.right_marker.remove()
475            self.right_line.remove()
476            self.left_line.remove()
477        except:
478            # Old version of matplotlib
479            for item in range(len(self.axes.lines)):
480                del self.axes.lines[0]
481
482    def update(self, x1=None, x2=None, y1=None, y2=None, width=None,
483               height=None, center=None):
484        """
485        Draw the new roughness on the graph.
486        :param x1: new maximum value of x coordinates
487        :param x2: new minimum value of x coordinates
488        :param y1: new maximum value of y coordinates
489        :param y2: new minimum value of y coordinates
490        :param width: is the width of the new rectangle
491        :param height: is the height of the new rectangle
492        :param center: provided x, y  coordinates of the center point
493        """
494        # # save the new height, witdh of the rectangle if given as a param
495        if width is not None:
496            self.half_width = width
497        if height is not None:
498            self.half_height = height
499        # # If new  center coordinates are given draw the rectangle
500        # #given these value
501        if center is not None:
502            self.center_x = center.x
503            self.center_y = center.y
504            self.x1 = self.half_width + self.center_x
505            self.x2 = -self.half_width + self.center_x
506            self.y1 = self.half_height + self.center_y
507            self.y2 = -self.half_height + self.center_y
508
509            self.right_marker.set(xdata=[self.x1], ydata=[self.center_y])
510            self.right_line.set(xdata=[self.x1, self.x1],
511                                ydata=[self.y1, self.y2])
512            self.left_line.set(xdata=[self.x2, self.x2],
513                               ydata=[self.y1, self.y2])
514            return
515        # # if x1, y1, y2, y3 are given draw the rectangle with this value
516        if x1 is not None:
517            self.x1 = x1
518        if x2 is not None:
519            self.x2 = x2
520        if y1 is not None:
521            self.y1 = y1
522        if y2 is not None:
523            self.y2 = y2
524        # # Draw 2 vertical lines and a marker
525        self.right_marker.set(xdata=[self.x1], ydata=[self.center_y])
526        self.right_line.set(xdata=[self.x1, self.x1], ydata=[self.y1, self.y2])
527        self.left_line.set(xdata=[self.x2, self.x2], ydata=[self.y1, self.y2])
528
529    def save(self, ev):
530        """
531        Remember the roughness for this layer and the next so that we
532        can restore on Esc.
533        """
534        self.save_x2 = self.x2
535        self.save_y2 = self.y2
536        self.save_x1 = self.x1
537        self.save_y1 = self.y1
538        self.save_half_height = self.half_height
539        self.save_half_width = self.half_width
540        self.base.freeze_axes()
541
542    def moveend(self, ev):
543        """
544            After a dragging motion reset the flag self.has_move to False
545        """
546        self.has_move = False
547        self.base.moveend(ev)
548
549    def restore(self):
550        """
551        Restore the roughness for this layer.
552        """
553        self.y2 = self.save_y2
554        self.x2 = self.save_x2
555        self.y1 = self.save_y1
556        self.x1 = self.save_x1
557        self.half_height = self.save_half_height
558        self.half_width = self.save_half_width
559
560    def move(self, x, y, ev):
561        """
562        Process move to a new position, making sure that the move is allowed.
563        """
564        self.x1 = x
565        delta = self.x1 - self.center_x
566        self.x2 = self.center_x - delta
567        self.half_width = math.fabs(self.x1 - self.x2) / 2
568        self.has_move = True
569        self.base.base.update()
570
571    def set_cursor(self, x, y):
572        """
573            Update the figure given x and y
574        """
575        self.move(x, y, None)
576        self.update()
577
578class HorizontalDoubleLine(_BaseInteractor):
579    """
580         Select an annulus through a 2D plot
581    """
582    def __init__(self, base, axes, color='black', zorder=5, x=0.5, y=0.5,
583                 center_x=0.0, center_y=0.0):
584
585        _BaseInteractor.__init__(self, base, axes, color=color)
586        # # Initialization the class
587        self.markers = []
588        self.axes = axes
589        # # Center coordinates
590        self.center_x = center_x
591        self.center_y = center_y
592        self.y1 = y + self.center_y
593        self.save_y1 = self.y1
594        delta = self.y1 - self.center_y
595        self.y2 = self.center_y - delta
596        self.save_y2 = self.y2
597        self.x1 = x + self.center_x
598        self.save_x1 = self.x1
599        delta = self.x1 - self.center_x
600        self.x2 = self.center_x - delta
601        self.save_x2 = self.x2
602        self.color = color
603        self.half_height = math.fabs(y)
604        self.save_half_height = math.fabs(y)
605        self.half_width = math.fabs(x)
606        self.save_half_width = math.fabs(x)
607        self.top_marker = self.axes.plot([0], [self.y1], linestyle='',
608                                         marker='s', markersize=10,
609                                         color=self.color, alpha=0.6,
610                                         pickradius=5, label="pick",
611                                         zorder=zorder, visible=True)[0]
612
613        # Define 2 horizotnal lines
614        self.top_line = self.axes.plot([self.x1, -self.x1], [self.y1, self.y1],
615                                       linestyle='-', marker='',
616                                       color=self.color, visible=True)[0]
617        self.bottom_line = self.axes.plot([self.x1, -self.x1],
618                                          [self.y2, self.y2],
619                                          linestyle='-', marker='',
620                                          color=self.color, visible=True)[0]
621        # # Flag to determine if the lines have moved
622        self.has_move = False
623        # # connection the marker and draw the pictures
624        self.connect_markers([self.top_marker])
625        self.update()
626
627    def set_layer(self, n):
628        """
629            Allow adding plot to the same panel
630            @param n: the number of layer
631        """
632        self.layernum = n
633        self.update()
634
635    def clear(self):
636        """
637            Clear this figure and its markers
638        """
639        self.clear_markers()
640        try:
641            self.top_marker.remove()
642            self.bottom_line.remove()
643            self.top_line.remove()
644        except:
645            # Old version of matplotlib
646            for item in range(len(self.axes.lines)):
647                del self.axes.lines[0]
648
649    def update(self, x1=None, x2=None, y1=None, y2=None,
650               width=None, height=None, center=None):
651        """
652        Draw the new roughness on the graph.
653        :param x1: new maximum value of x coordinates
654        :param x2: new minimum value of x coordinates
655        :param y1: new maximum value of y coordinates
656        :param y2: new minimum value of y coordinates
657        :param width: is the width of the new rectangle
658        :param height: is the height of the new rectangle
659        :param center: provided x, y  coordinates of the center point
660        """
661        # # save the new height, witdh of the rectangle if given as a param
662        if width is not None:
663            self.half_width = width
664        if height is not None:
665            self.half_height = height
666        # # If new  center coordinates are given draw the rectangle
667        # #given these value
668        if center is not None:
669            self.center_x = center.x
670            self.center_y = center.y
671            self.x1 = self.half_width + self.center_x
672            self.x2 = -self.half_width + self.center_x
673
674            self.y1 = self.half_height + self.center_y
675            self.y2 = -self.half_height + self.center_y
676
677            self.top_marker.set(xdata=[self.center_x], ydata=[self.y1])
678            self.top_line.set(xdata=[self.x1, self.x2],
679                              ydata=[self.y1, self.y1])
680            self.bottom_line.set(xdata=[self.x1, self.x2],
681                                 ydata=[self.y2, self.y2])
682            return
683        # # if x1, y1, y2, y3 are given draw the rectangle with this value
684        if x1 is not None:
685            self.x1 = x1
686        if x2 is not None:
687            self.x2 = x2
688        if y1 is not None:
689            self.y1 = y1
690        if y2 is not None:
691            self.y2 = y2
692        # # Draw 2 vertical lines and a marker
693        self.top_marker.set(xdata=[self.center_x], ydata=[self.y1])
694        self.top_line.set(xdata=[self.x1, self.x2], ydata=[self.y1, self.y1])
695        self.bottom_line.set(xdata=[self.x1, self.x2], ydata=[self.y2, self.y2])
696
697    def save(self, ev):
698        """
699        Remember the roughness for this layer and the next so that we
700        can restore on Esc.
701        """
702        self.save_x2 = self.x2
703        self.save_y2 = self.y2
704        self.save_x1 = self.x1
705        self.save_y1 = self.y1
706        self.save_half_height = self.half_height
707        self.save_half_width = self.half_width
708        self.base.freeze_axes()
709
710    def moveend(self, ev):
711        """
712        After a dragging motion reset the flag self.has_move to False
713        """
714        self.has_move = False
715        self.base.moveend(ev)
716
717    def restore(self):
718        """
719        Restore the roughness for this layer.
720        """
721        self.y2 = self.save_y2
722        self.x2 = self.save_x2
723        self.y1 = self.save_y1
724        self.x1 = self.save_x1
725        self.half_height = self.save_half_height
726        self.half_width = self.save_half_width
727
728    def move(self, x, y, ev):
729        """
730        Process move to a new position, making sure that the move is allowed.
731        """
732        self.y1 = y
733        delta = self.y1 - self.center_y
734        self.y2 = self.center_y - delta
735        self.half_height = math.fabs(self.y1) - self.center_y
736        self.has_move = True
737        self.base.base.update()
738
739    def set_cursor(self, x, y):
740        """
741            Update the figure given x and y
742        """
743        self.move(x, y, None)
744        self.update()
Note: See TracBrowser for help on using the repository browser.