source: inundation/ga/storm_surge/pyvolution/data_manager.py @ 480

Last change on this file since 480 was 480, checked in by ole, 20 years ago
File size: 23.5 KB
RevLine 
[281]1"""Functions to store and retrieve data for the Shallow Water Wave equation.
2There are two kinds of data
3
[341]4  1: Constant data: Vertex coordinates and field values. Stored once
5  2: Variable data: Conserved quantities. Stored once per timestep.
[281]6
7All data is assumed to reside at vertex locations. 
8
9"""
10
11from Numeric import concatenate       
12
13
14def make_filename(s):
15    """Transform argument string into a suitable filename
16    """
17
18    s = s.strip()
19    s = s.replace(' ', '_')
20    s = s.replace('(', '')
21    s = s.replace(')', '')
22    s = s.replace('__', '_')               
23       
24    return s
25
26
27def check_dir(path, verbose=None):
28    """Check that specified path exists.
29    If path does not exist it will be created if possible   
30
31    USAGE:
32       checkdir(path, verbose):
33
34    ARGUMENTS:
35        path -- Directory
36        verbose -- Flag verbose output (default: None)
37
38    RETURN VALUE:
39        Verified path including trailing separator
40       
41    """
42
43    import os, sys
44    import os.path
45
46    if sys.platform in ['nt', 'dos', 'win32', 'what else?']:
47        unix = 0
48    else:
49        unix = 1
50
51
52    if path[-1] != os.sep: 
53        path = path + os.sep  # Add separator for directories
54
55    path = os.path.expanduser(path) # Expand ~ or ~user in pathname
56    if not (os.access(path,os.R_OK and os.W_OK) or path == ''):
57        try:
58            exitcode=os.mkdir(path)
59
60            # Change access rights if possible
61            #
62            if unix:
63                exitcode=os.system('chmod 775 '+path)
64            else:
65                pass  # FIXME: What about acces rights under Windows?
66           
67            if verbose: print 'MESSAGE: Directory', path, 'created.'
68         
69        except:
70            print 'WARNING: Directory', path, 'could not be created.'
71            if unix:
72                path = '/tmp/'
73            else:
74                path = 'C:'
75               
76            print 'Using directory %s instead' %path
77
78    return(path)
79
80   
81
82def del_dir(path):
83    """Recursively delete directory path and all its contents
84    """
85
86    import os
87
88    if os.path.isdir(path):
89        for file in os.listdir(path):
90            X = os.path.join(path, file)
91
92
93            if os.path.isdir(X) and not os.path.islink(X):
94                del_dir(X) 
95            else:
96                try:
97                    os.remove(X)
98                except:
99                    print "Could not remove file %s" %X
100
101        os.rmdir(path)
102
103
104       
105def create_filename(filename, format, size, time=None):
106
107    import os
108    from config import data_dir
109
110           
111    FN = check_dir(data_dir) + filename + '_size%d' %size
[479]112
[281]113    if time is not None:
114        FN += '_time%.2f' %time
115       
116    FN += '.' + format
117    return FN
118   
119
120def get_files(filename, format, size):
121    """Get all file (names) with gven name, size and format
122    """
123
124    import glob
125
126    import os
127    from config import data_dir
128
129           
130    dir = check_dir(data_dir)
131
132    pattern = dir + os.sep + filename + '_size=%d*.%s' %(size, format)
133    return glob.glob(pattern)
134
135
136
[287]137#Generic class for storing output to e.g. visualisation or checkpointing
[281]138class Data_format:
139    """Generic interface to data formats
140    """
141
142       
143    def __init__(self, domain, extension, mode = 'w'):
144        assert mode in ['r', 'w', 'a'], '''Mode %s must be either:''' %mode +\
145                                        '''   'w' (write)'''+\
146                                        '''   'r' (read)''' +\
147                                        '''   'a' (append)'''   
148
149        #Create filename
150        self.filename = create_filename(domain.get_name(), extension, len(domain))
151        self.timestep = 0
152        self.number_of_volumes = len(domain)
153        self.domain = domain
154   
155   
156    #FIXME: Should we have a general set_precision function?
157   
158
[288]159
160#Class for storing output to e.g. visualisation
[281]161class Data_format_sww(Data_format):
162    """Interface to native NetCDF format (.sww)
163    """
164
165       
166    def __init__(self, domain, mode = 'w'):
167        from Scientific.IO.NetCDF import NetCDFFile
168        from Numeric import Int, Float, Float32
169
170        self.precision = Float32 #Use single precision
171       
172        Data_format.__init__(self, domain, 'sww', mode)
173
174
175        # NetCDF file definition
176        fid = NetCDFFile(self.filename, mode)
177       
178        if mode == 'w':
179           
180            #Create new file
181            fid.institution = 'Geoscience Australia'
[288]182            fid.description = 'Output from pyvolution suitable for plotting'
183           
[281]184            if domain.smooth:
185                fid.smoothing = 'Yes'
186            else:   
187                fid.smoothing = 'No'
188
189            fid.order = domain.default_order   
190
191            # dimension definitions
192            fid.createDimension('number_of_volumes', self.number_of_volumes) 
193            fid.createDimension('number_of_vertices', 3)
194
195            if domain.smooth is True:
196                fid.createDimension('number_of_points', len(domain.vertexlist))
197            else:   
198                fid.createDimension('number_of_points', 3*self.number_of_volumes)
199               
200            fid.createDimension('number_of_timesteps', None) #extensible
201
202            # variable definitions
203            fid.createVariable('x', self.precision, ('number_of_points',))
204            fid.createVariable('y', self.precision, ('number_of_points',))
205            fid.createVariable('z', self.precision, ('number_of_points',))
206       
207            fid.createVariable('volumes', Int, ('number_of_volumes',
208                                                'number_of_vertices'))
209
210            fid.createVariable('time', self.precision,
211                               ('number_of_timesteps',))
212       
213            fid.createVariable('stage', self.precision,
214                               ('number_of_timesteps',
215                                'number_of_points'))
216
217
218        #Close
219        fid.close()               
220
221
222    def store_connectivity(self):
223        """Specialisation of store_connectivity for net CDF format
224
225        Writes x,y,z coordinates of triangles constituting
226        the bed elevation.
227        """
228
229        from Scientific.IO.NetCDF import NetCDFFile
230
231        from Numeric import concatenate
232
233        domain = self.domain
234
235        #Get NetCDF
236        fid = NetCDFFile(self.filename, 'a')  #Open existing file for append
237       
238        # Get the variables
239        x = fid.variables['x']
240        y = fid.variables['y']
241        z = fid.variables['z']
242       
243        volumes = fid.variables['volumes']
244
245        # Get X, Y and bed elevation Z
246        Q = domain.quantities['elevation']
247        X,Y,Z,V = Q.get_vertex_values(xy=True,
248                                      precision = self.precision)
249                                     
250
251
252        x[:] = X.astype(self.precision)
253        y[:] = Y.astype(self.precision)
254        z[:] = Z.astype(self.precision)
255
256        volumes[:] = V
257       
258        #Close
259        fid.close()
260
261
262    def store_timestep(self, name):
263        """Store time and named quantity to file
264        """
265        from Scientific.IO.NetCDF import NetCDFFile
266        from time import sleep
267               
268        #Get NetCDF
269        retries = 0
270        file_open = False
271        while not file_open and retries < 10:
272            try:       
273                fid = NetCDFFile(self.filename, 'a') #Open existing file
274            except IOError:
275                #This could happen if someone was reading the file.
276                #In that case, wait a while and try again
277                msg = 'Warning (store_timestep): File %s could not be opened'\
278                %self.filename
279                msg += ' - trying again'
280                print msg
281                retries += 1
282                sleep(1)
283            else:
284               file_open = True
285               
286        if not file_open:
287            msg = 'File %s could not be opened for append' %self.filename
288            raise msg
289               
290           
291        domain = self.domain
292       
293        # Get the variables
294        time = fid.variables['time']
295        stage = fid.variables['stage']       
296        i = len(time)
297
298        #Store stage
299        time[i] = self.domain.time
300
301        # Get quantity
302        Q = domain.quantities[name]
303        A,V = Q.get_vertex_values(xy=False,
304                                  precision = self.precision)
305       
306        stage[i,:] = A.astype(self.precision)
307
308        #Flush and close
309        fid.sync()
310        fid.close()
311
312
[288]313#Class for handling checkpoints data
314class Data_format_cpt(Data_format):
315    """Interface to native NetCDF format (.cpt)
316    """
[287]317
[288]318       
319    def __init__(self, domain, mode = 'w'):
320        from Scientific.IO.NetCDF import NetCDFFile
321        from Numeric import Int, Float, Float
[287]322
[288]323        self.precision = Float #Use full precision
324       
325        Data_format.__init__(self, domain, 'sww', mode)
326
327
328        # NetCDF file definition
329        fid = NetCDFFile(self.filename, mode)
330       
331        if mode == 'w':
332            #Create new file
333            fid.institution = 'Geoscience Australia'
334            fid.description = 'Checkpoint data'
335            #fid.smooth = domain.smooth               
336            fid.order = domain.default_order   
337
338            # dimension definitions
339            fid.createDimension('number_of_volumes', self.number_of_volumes) 
340            fid.createDimension('number_of_vertices', 3)
341
342            #Store info at all vertices (no smoothing)
343            fid.createDimension('number_of_points', 3*self.number_of_volumes)
344            fid.createDimension('number_of_timesteps', None) #extensible
345
346            # variable definitions
347
348            #Mesh
349            fid.createVariable('x', self.precision, ('number_of_points',))
350            fid.createVariable('y', self.precision, ('number_of_points',))
351            #fid.createVariable('z', self.precision, ('number_of_points',))
352       
353            fid.createVariable('volumes', Int, ('number_of_volumes',
354                                                'number_of_vertices'))
355
356            fid.createVariable('time', self.precision,
357                               ('number_of_timesteps',))
358
359            #Allocate space for all quantities
360            for name in domain.quantities.keys():
361                fid.createVariable(name, self.precision,
362                                   ('number_of_timesteps',
363                                    'number_of_points'))
364
365        #Close
366        fid.close()               
367
368
369    def store_checkpoint(self):
370        """
371        Write x,y coordinates of triangles.
372        Write connectivity (
373        constituting
374        the bed elevation.
375        """
376
377        from Scientific.IO.NetCDF import NetCDFFile
378
379        from Numeric import concatenate
380
381        domain = self.domain
382
383        #Get NetCDF
384        fid = NetCDFFile(self.filename, 'a')  #Open existing file for append
385       
386        # Get the variables
387        x = fid.variables['x']
388        y = fid.variables['y']
389       
390        volumes = fid.variables['volumes']
391
392        # Get X, Y and bed elevation Z
393        Q = domain.quantities['elevation']
394        X,Y,Z,V = Q.get_vertex_values(xy=True,
395                                      precision = self.precision)
396                                     
397
398
399        x[:] = X.astype(self.precision)
400        y[:] = Y.astype(self.precision)
401        z[:] = Z.astype(self.precision)
402
403        volumes[:] = V
404       
405        #Close
406        fid.close()
407
408
409    def store_timestep(self, name):
410        """Store time and named quantity to file
411        """
412        from Scientific.IO.NetCDF import NetCDFFile
413        from time import sleep
414               
415        #Get NetCDF
416        retries = 0
417        file_open = False
418        while not file_open and retries < 10:
419            try:       
420                fid = NetCDFFile(self.filename, 'a') #Open existing file
421            except IOError:
422                #This could happen if someone was reading the file.
423                #In that case, wait a while and try again
424                msg = 'Warning (store_timestep): File %s could not be opened'\
425                %self.filename
426                msg += ' - trying again'
427                print msg
428                retries += 1
429                sleep(1)
430            else:
431               file_open = True
432               
433        if not file_open:
434            msg = 'File %s could not be opened for append' %self.filename
435            raise msg
436               
437           
438        domain = self.domain
439       
440        # Get the variables
441        time = fid.variables['time']
442        stage = fid.variables['stage']       
443        i = len(time)
444
445        #Store stage
446        time[i] = self.domain.time
447
448        # Get quantity
449        Q = domain.quantities[name]
450        A,V = Q.get_vertex_values(xy=False,
451                                  precision = self.precision)
452       
453        stage[i,:] = A.astype(self.precision)
454
455        #Flush and close
456        fid.sync()
457        fid.close()
458
459
460
461
462
463
464
[287]465#Function for storing xya output
[283]466#FIXME Not done yet for this version
[281]467class Data_format_xya(Data_format):
468    """Generic interface to data formats
469    """
470
471
472    def __init__(self, domain, mode = 'w'):
473        from Scientific.IO.NetCDF import NetCDFFile
474        from Numeric import Int, Float, Float32
475
476        self.precision = Float32 #Use single precision
477       
478        Data_format.__init__(self, domain, 'xya', mode)
479       
480   
481   
482
483    def store_all(self):
484        """Specialisation of store all for xya format
485
486        Writes x,y,z coordinates of triangles constituting
487        the bed elevation.
488        """
489
490        from Numeric import concatenate
491
492        domain = self.domain
493       
494        fd = open(self.filename, 'w')
495
496
497        if domain.smooth is True:
498            number_of_points =  len(domain.vertexlist)
499        else:   
500            number_of_points = 3*self.number_of_volumes
501
502        numVertAttrib = 3 #Three attributes is what is assumed by the xya format
503
504        fd.write(str(number_of_points) + " " + str(numVertAttrib) +\
505                 " # <vertex #> <x> <y> [attributes]" + "\n")
506
507
508        # Get X, Y, bed elevation and friction (index=0,1)
509        X,Y,A,V = domain.get_vertex_values(xy=True, value_array='field_values',
510                                           indices = (0,1), precision = self.precision)
511
512        bed_eles = A[:,0]
513        fricts = A[:,1]
514
515        # Get stage (index=0)
516        B,V = domain.get_vertex_values(xy=False, value_array='conserved_quantities',
517                                       indices = (0,), precision = self.precision)
518
519        stages = B[:,0]
520
521        #<vertex #> <x> <y> [attributes]
522        for x, y, bed_ele, stage, frict in map(None, X, Y, bed_eles,
523                                               stages, fricts):
524
525            s = '%.6f %.6f %.6f %.6f %.6f\n' %(x, y, bed_ele, stage, frict)
526            fd.write(s)
527           
528        #close
529        fd.close()
530
531
532    def store_timestep(self, t, V0, V1, V2):
533        """Store time, water heights (and momentums) to file
534        """
535        pass
536   
537
538#Auxiliary
539def write_obj(filename,x,y,z):
540    """Store x,y,z vectors into filename (obj format)
541       Vectors are assumed to have dimension (M,3) where
542       M corresponds to the number elements.
543       triangles are assumed to be disconnected
544
545       The three numbers in each vector correspond to three vertices,
546
547       e.g. the x coordinate of vertex 1 of element i is in x[i,1]
548       
549    """
550    #print 'Writing obj to %s' % filename
551
552    import os.path
553
554    root, ext = os.path.splitext(filename)
555    if ext == '.obj':
556        FN = filename
557    else:
558        FN = filename + '.obj'
559       
560
561    outfile = open(FN, 'wb')
562    outfile.write("# Triangulation as an obj file\n")
563
564    M, N = x.shape
565    assert N==3  #Assuming three vertices per element
566
567    for i in range(M):
568        for j in range(N):
569            outfile.write("v %f %f %f\n" % (x[i,j],y[i,j],z[i,j]))
570
571    for i in range(M):
572        base = i*N
573        outfile.write("f %d %d %d\n" % (base+1,base+2,base+3))
574
575    outfile.close()
576
577
578
579#Conversion routines
580def sww2obj(basefilename, size):
581    """Convert netcdf based data output to obj
582    """
583    from Scientific.IO.NetCDF import NetCDFFile       
584
585    from Numeric import Float, zeros
586
587    #Get NetCDF
588    FN = create_filename(basefilename, 'sww', size)
589    print 'Reading from ', FN
590    fid = NetCDFFile(FN, 'r')  #Open existing file for read
591
592       
593    # Get the variables
594    x = fid.variables['x']
595    y = fid.variables['y']
596    z = fid.variables['z']       
597    time = fid.variables['time']
598    stage = fid.variables['stage']
599       
600    M = size  #Number of lines
601    xx = zeros((M,3), Float)
602    yy = zeros((M,3), Float)
603    zz = zeros((M,3), Float)
604
605    for i in range(M):
606        for j in range(3):
607            xx[i,j] = x[i+j*M]
608            yy[i,j] = y[i+j*M]
609            zz[i,j] = z[i+j*M]
610       
611    #Write obj for bathymetry
612    FN = create_filename(basefilename, 'obj', size)       
613    write_obj(FN,xx,yy,zz)
614
615
616    #Now read all the data with variable information, combine with
617    #x,y info and store as obj
618
619    for k in range(len(time)):
620        t = time[k]
621        print 'Processing timestep %f' %t
622
623        for i in range(M):
624            for j in range(3):
625                zz[i,j] = stage[k,i+j*M]
626
627
628        #Write obj for variable data
629        #FN = create_filename(basefilename, 'obj', size, time=t)
630        FN = create_filename(basefilename[:5], 'obj', size, time=t)   
631        write_obj(FN,xx,yy,zz)
632
633
634def dat2obj(basefilename):
635    """Convert line based data output to obj
636    """
637
638    import glob, os
639    from config import data_dir
640   
641   
642    #Get bathymetry and x,y's
643    lines = open(data_dir+os.sep+basefilename+'_geometry.dat', 'r').readlines()
644   
645    from Numeric import zeros, Float
646
647    M = len(lines)  #Number of lines
648    x = zeros((M,3), Float)
649    y = zeros((M,3), Float)
650    z = zeros((M,3), Float)
651
652    ##i = 0
653    for i, line in enumerate(lines):
654        tokens = line.split()
655        values = map(float,tokens)       
656
657        for j in range(3):
658            x[i,j] = values[j*3]
659            y[i,j] = values[j*3+1]
660            z[i,j] = values[j*3+2]       
661       
662        ##i += 1
663
664
665    #Write obj for bathymetry
666    write_obj(data_dir+os.sep+basefilename+'_geometry',x,y,z)
667
668
669    #Now read all the data files with variable information, combine with
670    #x,y info
671    #and store as obj
672
673    files = glob.glob(data_dir+os.sep+basefilename+'*.dat')
674
675    for filename in files:
676        print 'Processing %s' % filename
677
678        lines = open(data_dir+os.sep+filename,'r').readlines()
679        assert len(lines) == M
680        root, ext = os.path.splitext(filename)
681
682        #Get time from filename
683        i0 = filename.find('_time=')
684        if i0 == -1:
685            #Skip bathymetry file
686            continue
687       
688        i0 += 6  #Position where time starts           
689        i1 = filename.find('.dat')         
690
691        if i1 > i0:
692            t = float(filename[i0:i1])
693        else:
694            raise 'Hmmmm'
695               
696       
697       
698        ##i = 0
699        for i, line in enumerate(lines):
700            tokens = line.split()
701            values = map(float,tokens)       
702
703            for j in range(3):
704                z[i,j] = values[j]       
705       
706            ##i += 1
707
708        #Write obj for variable data
709        write_obj(data_dir+os.sep+basefilename+'_time=%.4f' %t,x,y,z)
710
711
712def filter_netcdf(filename1, filename2, first=0, last=None, step = 1):
713    """Read netcdf filename1, pick timesteps first:step:last and save to
714    nettcdf file filename2
715    """
716    from Scientific.IO.NetCDF import NetCDFFile       
717   
718    #Get NetCDF
719    infile = NetCDFFile(filename1, 'r')  #Open existing file for read
720    outfile = NetCDFFile(filename2, 'w')  #Open new file
721   
722
723    #Copy dimensions
724    for d in infile.dimensions:
725        outfile.createDimension(d, infile.dimensions[d])
726
727    for name in infile.variables:
728        var = infile.variables[name]
729        outfile.createVariable(name, var.typecode(), var.dimensions)
730       
731   
732    #Copy the static variables
733    for name in infile.variables:
734        if name == 'time' or name == 'stage':
735            pass
736        else:
737            #Copy
738            outfile.variables[name][:] = infile.variables[name][:]           
739
740    #Copy selected timesteps
741    time = infile.variables['time']
742    stage = infile.variables['stage']
743
744    newtime = outfile.variables['time']
745    newstage = outfile.variables['stage']   
746
747    if last is None:
748        last = len(time)
749
750    selection = range(first, last, step)
751    for i, j in enumerate(selection):
752        print 'Copying timestep %d of %d (%f)' %(j, last-first, time[j])
753        newtime[i] = time[j]
754        newstage[i,:] = stage[j,:]       
755       
756    #Close
757    infile.close()
758    outfile.close()
759
760
761#Get data objects
762def get_dataobject(domain, mode='w'):
763    """Return instance of class of given format using filename
764    """
765
766    cls = eval('Data_format_%s' %domain.format)
767    return cls(domain, mode)
768
769
770
771
[341]772#OBSOLETE STUFF
773#Native checkpoint format.
774#Information needed to recreate a state is preserved
775#FIXME: Rethink and maybe use netcdf format
776def cpt_variable_writer(filename, t, v0, v1, v2):
777    """Store all conserved quantities to file
778    """
779
780    M, N = v0.shape
781       
782    FN = create_filename(filename, 'cpt', M, t)
783    #print 'Writing to %s' %FN
784   
785    fid = open(FN, 'w')
786    for i in range(M):
787        for j in range(N):       
788            fid.write('%.16e ' %v0[i,j])           
789        for j in range(N):
790            fid.write('%.16e ' %v1[i,j])                       
791        for j in range(N):
792            fid.write('%.16e ' %v2[i,j])                       
793           
794        fid.write('\n')
795    fid.close()
796
797
798def cpt_variable_reader(filename, t, v0, v1, v2):
799    """Store all conserved quantities to file
800    """
801
802    M, N = v0.shape
803       
804    FN = create_filename(filename, 'cpt', M, t)
805    #print 'Reading from %s' %FN
806   
807    fid = open(FN)
808
809
810    for i in range(M):
811        values = fid.readline().split() #Get one line
812
813        for j in range(N):
814            v0[i,j] = float(values[j])
815            v1[i,j] = float(values[3+j])
816            v2[i,j] = float(values[6+j])
817   
818    fid.close()
819
820def cpt_constant_writer(filename, X0, X1, X2, v0, v1, v2):
821    """Writes x,y,z,z,z coordinates of triangles constituting the bed
822    elevation.
823    Not in use pt
824    """
825
826    M, N = v0.shape
827
828    print X0
829    import sys; sys.exit() 
830    FN = create_filename(filename, 'cpt', M)
831    print 'Writing to %s' %FN
832   
833    fid = open(FN, 'w')
834    for i in range(M):
835        for j in range(2):       
836            fid.write('%.16e ' %X0[i,j])   #x, y
837        for j in range(N):                   
838            fid.write('%.16e ' %v0[i,j])       #z,z,z,
839           
840        for j in range(2):                   
841            fid.write('%.16e ' %X1[i,j])   #x, y
842        for j in range(N):                               
843            fid.write('%.16e ' %v1[i,j]) 
844       
845        for j in range(2):                               
846            fid.write('%.16e ' %X2[i,j])   #x, y
847        for j in range(N):                                           
848            fid.write('%.16e ' %v2[i,j]) 
849           
850        fid.write('\n')
851    fid.close()
852
853
854
855#Function for storing out to e.g. visualisation
856#FIXME: Do we want this?
857#FIXME: Not done yet for this version
858def dat_constant_writer(filename, X0, X1, X2, v0, v1, v2):
859    """Writes x,y,z coordinates of triangles constituting the bed elevation.
860    """
861
862    M, N = v0.shape
863       
864    FN = create_filename(filename, 'dat', M)
865    #print 'Writing to %s' %FN
866   
867    fid = open(FN, 'w')
868    for i in range(M):
869        for j in range(2):       
870            fid.write('%f ' %X0[i,j])   #x, y
871        fid.write('%f ' %v0[i,0])       #z
872           
873        for j in range(2):                   
874            fid.write('%f ' %X1[i,j])   #x, y
875        fid.write('%f ' %v1[i,0])       #z
876       
877        for j in range(2):                               
878            fid.write('%f ' %X2[i,j])   #x, y
879        fid.write('%f ' %v2[i,0])       #z
880           
881        fid.write('\n')
882    fid.close()
883
884
885
886def dat_variable_writer(filename, t, v0, v1, v2):
887    """Store water height to file
888    """
889
890    M, N = v0.shape
891       
892    FN = create_filename(filename, 'dat', M, t)
893    #print 'Writing to %s' %FN
894   
895    fid = open(FN, 'w')
896    for i in range(M):
897        fid.write('%.4f ' %v0[i,0])
898        fid.write('%.4f ' %v1[i,0])
899        fid.write('%.4f ' %v2[i,0])       
900
901        fid.write('\n')
902    fid.close()
903
Note: See TracBrowser for help on using the repository browser.