Ejemplo n.º 1
0
 def get(cls, cachedir):
     if os.path.isdir(cachedir):
         log.info("load chroma_geometry from %s " % cachedir)
         from chroma.loader import load_bvh
         instance = cls.load_instance(cachedir)
         instance.bvh = load_bvh(instance)
     else:
         instance = None
     pass
     return instance
Ejemplo n.º 2
0
 def add_bvh(self,
             bvh_name="default",
             auto_build_bvh=True,
             read_bvh_cache=True,
             update_bvh_cache=True,
             cache_dir=None,
             cuda_device=None):
     """
     As done by chroma.loader
     """
     log.debug("ColladaToChroma adding BVH")
     self.chroma_geometry.bvh = load_bvh(self.chroma_geometry,
                                         bvh_name=bvh_name,
                                         auto_build_bvh=auto_build_bvh,
                                         read_bvh_cache=read_bvh_cache,
                                         update_bvh_cache=update_bvh_cache,
                                         cache_dir=cache_dir,
                                         cuda_device=cuda_device)
     log.debug("completed adding BVH")
Ejemplo n.º 3
0
def driver_funct(configname):
    from chroma.loader import load_bvh  # Requires CUDA so only import it when necessary
    kabamland = Detector(lm.create_scintillation_material())
    config = detectorconfig.configdict(configname)
    #get_lens_triangle_centers(vtx, rad_assembly, config.diameter_ratio, config.thickness_ratio, config.half_EPD, config.blockers, blocker_thickness_ratio=config.blocker_thickness_ratio, light_confinement=config.light_confinement, focal_length=config.focal_length, lens_system_name=config.lens_system_name)
    #print get_curved_surf_triangle_centers(config.vtx, config.half_EPD/config.EPD_ratio, config.detector_r, config.focal_length, config.nsteps, config.b_pixel)[0]
    build_lens_icosahedron(
        kabamland,
        config.vtx,
        config.half_EPD / config.EPD_ratio,
        config.diameter_ratio,
        config.thickness_ratio,
        config.half_EPD,
        config.blockers,
        blocker_thickness_ratio=config.blocker_thickness_ratio,
        light_confinement=config.light_confinement,
        focal_length=config.focal_length,
        lens_system_name=config.lens_system_name)
    #build_curvedsurface_icosahedron(kabamland, config.vtx, config.half_EPD/config.EPD_ratio, config.diameter_ratio, focal_length=config.focal_length, detector_r=config.detector_r, nsteps=config.nsteps, b_pxl=config.b_pixel)
    #build_pmt_icosahedron(kabamland, np.linalg.norm(config.vtx[0]), focal_length=config.focal_length)
    kabamland.flatten()
    kabamland.bvh = load_bvh(kabamland)
Ejemplo n.º 4
0
from chroma import make, view
from chroma.sim import Simulation
from chroma.sample import uniform_sphere
from chroma.event import Photons
from chroma.loader import load_bvh
from chroma.generator import vertex
from chroma.camera import Camera
import matplotlib.pyplot as plt
import sys

from geometry_cylinder import build_detector

g = build_detector()
g.flatten()
g.bvh = load_bvh(g)
#view(g)


ys.exit()

sim = Simulation(g)

n=1
gun = vertex.particle_gun(['mu-']*n,
                          vertex.constant((0,10000,10000)),
                          vertex.constant([1,0,-1]),
                          vertex.constant(1000),
                          vertex.constant(0))

i = 0
for ev in sim.simulate(gun,keep_detected_photons=True,
Ejemplo n.º 5
0
def load_or_build_detector(config,
                           detector_material,
                           g4_detector_parameters,
                           force_build=False):
    configname = config.config_name
    filename_base = paths.detector_config_path + configname
    if not os.path.exists(paths.detector_config_path):
        os.makedirs(paths.detector_config_path)

    kabamland = None
    # How to ensure the material and detector parameters are correct??
    if not force_build:
        try:
            detector_config = dd.io.load(filename_base + '.h5')
            kabamland = detector_config['detector']
            logger.info("** Loaded HDF5 (deepdish) detector configuration: " +
                        configname)
        except IOError as error:  # Will dd throw an exception?
            try:
                with open(filename_base + '.pickle', 'rb') as pickle_file:
                    kabamland = pickle.load(pickle_file)
                    logger.info("** Loaded pickle detector configuration: " +
                                configname)
            except IOError as error:
                pass
    if kabamland is not None:
        config_has_g4_dp = hasattr(
            kabamland, 'g4_detector_parameters'
        ) and kabamland.g4_detector_parameters is not None
        config_has_g4_dm = hasattr(
            kabamland,
            'detector_material') and kabamland.detector_material is not None
        if g4_detector_parameters is not None:
            logger.info('*** Using Geant4 detector parameters specified' +
                        (' - replacement' if config_has_g4_dp else '') +
                        ' ***')
            kabamland.g4_detector_parameters = g4_detector_parameters
        elif config_has_g4_dp:
            logger.info(
                '*** Using Geant4 detector parameters found in loaded file ***'
            )
        else:
            logger.info('*** No Geant4 detector parameters found at all ***')

            if detector_material is not None:
                logger.info('*** Using Geant4 detector material specified' +
                            (' - replacement' if config_has_g4_dm else '') +
                            ' ***')
                kabamland.detector_material = detector_material
            elif config_has_g4_dm:
                logger.info(
                    '*** Using Geant4 detector material found in loaded file ***'
                )
            else:
                logger.info('*** No Geant4 detector material found at all ***')
    else:
        from chroma.loader import load_bvh  # Requires CUDA so only import it when necessary

        logger.info("** Building detector configuration: " + configname)
        kabamland = Detector(lm.create_scintillation_material(),
                             g4_detector_parameters=g4_detector_parameters)
        kbl2.build_kabamland(kabamland, config)
        # view(kabamland)
        kabamland.flatten()
        kabamland.bvh = load_bvh(kabamland,
                                 bvh_name=config.config_name,
                                 read_bvh_cache=(not force_build))
        '''
        try:
            with open(filename_base+'.pickle','wb') as pickle_file:
                pickle.dump(kabamland, pickle_file)
        except IOError as error:
            logger.info("Error writing pickle file: " + filename_base+'.pickle')
        '''

        # Write h5 file with configuration data structure
        logger.info('Saving h5 detector configuration.  UUID: %s' %
                    config.uuid)
        '''   # This was created to minimize what is saved from the Detector object.  But for simplicity, we are currently pickling the whole object.
        detector_dict = {
            'detector_material' : kabamland.detector_material,
            'solids' : kabamland.solids,
            'solid_rotations' : kabamland.solid_rotations,
            'solid_displacements' : kabamland.solid_displacements,
            'bvh' : kabamland.bvh,
            'g4_detector_parameters' : kabamland.g4_detector_parameters,
            'solid_id_to_channel_index' : kabamland.solid_id_to_channel_index,
            'channel_index_to_solid_id' : kabamland.channel_index_to_solid_id,
            'channel_index_to_channel_id' : kabamland.channel_index_to_channel_id,
            'channel_id_to_channel_index' : kabamland.channel_id_to_channel_index,
            'time_cdf' : kabamland.time_cdf,
            'charge_cdf' : kabamland.charge_cdf
        }
        '''
        # TODO: Saving the whole dict and the object is redundant
        # TODO: Also, saving all of kabamland vs. just the parameters above adds about 1 Meg to the file size (I think)
        import lenssystem

        ld_name = configname.split('_')[0][2:]
        lens_design = lenssystem.get_lens_sys(ld_name)
        config_data = {
            'detector_config': config,
            'detector_config_dict': vars(config),
            'lens_config_dict': vars(lens_design)
        }
        detector_data = {'config': config_data, 'detector': kabamland}
        dd.io.save(filename_base + '.h5', detector_data)

    return kabamland
Ejemplo n.º 6
0
def main(n, wavelength, width, beamsize, regen=False):

    config_path = open('config/vars.txt', 'rb')
    test = config_path.readline()
    config_path.close()
    if not (abs(float(test) - width) < 0.1 / 25400):
        regen = True
    if os.path.exists('config/geometry.pickle') and regen == False:
        geometry_path = open('config/geometry.pickle', 'rb')
        world = cPickle.load(geometry_path)
        geometry_path.close()
    else:
        world = build(width)

    world.flatten()
    world.bvh = load_bvh(world)  #Create bounding volume hierarchy
    sim = Simulation(world)

    def init_source(n, wavelength, width, beamsize):
        """Generates laser profile of n photons, with wavelength, emanating from position pos_offset with direction 'direction'."""

        pos, direction = mfunctions.get_source(n, width, beamsize,
                                               wavelength / 25400000.0)
        #Note: Chroma only natively handles integer wavelengths, so we convert to inches here instead of earlier in the code.
        source_center = mfunctions.get_center('161') + np.array(
            (0, -0.1, 0))  #Position source just in front of slits
        pos = pos + np.tile(source_center, (n, 1))
        pol = np.cross(direction, (0, 0, 1))  #Polarisation
        wavelengths = np.repeat(wavelength, n)
        return Photons(pos, direction, pol, wavelengths)

    start = []
    end = []

    print 'Simulating photons...'

    for ev in sim.simulate([init_source(n, wavelength, width, beamsize)],
                           keep_photons_beg=True,
                           keep_photons_end=True,
                           run_daq=False,
                           max_steps=100):
        #print ev.photons_end.flags

        start.append(ev.photons_beg.pos)
        end.append(ev.photons_end.pos)

    print 'Saving data to file...'

    photon_id = list(range(n))
    flags = ev.photons_end.flags
    wavs = ev.photons_end.wavelengths

    ##### Saving data to file #####

    current = dt.datetime.now()
    filename = 'results/' + current.strftime(
        '%Y-%m-%d_%H:%M') + '_%d:%d:%.2f:%.2f.dat' % (n, wavelength, width *
                                                      25400, beamsize * 25400)
    out_file = open(filename, 'w')
    out_file.write('\t'.join([
        'ID', 'xi', 'yi', 'zi', 'xf', 'yf', 'zf', 'wavelength (nm)', 'flag\n'
    ]))

    for i in range(n):
        output = [photon_id[i]] + [item for item in start[0][i]] + [
            item for item in end[0][i]
        ] + [int(wavs[i]), flags[i]]
        out_file.write('\t'.join([str(item) for item in output]) + '\n')

    out_file.close()

    print 'Generating seed file...'
    seed = np.random.uniform(
        size=n)  #This is the seed file for the analysis script
    np.savetxt('config/seed.dat', seed, delimiter=',')
    print 'Done.'
Ejemplo n.º 7
0
        photon_track[i, :, 0] = photons.pos[:, 0]
        photon_track[i, :, 1] = photons.pos[:, 1]
        photon_track[i, :, 2] = photons.pos[:, 2]
    return photons, photon_track


if __name__ == '__main__':

    config = "cfJiani3_2"
    #config = "cfSam1_1"

    kabamland = Detector(lm.ls)
    lens, surf = build_kabamland(kabamland, config)
    kabamland.flatten()
    kabamland.bvh = load_bvh(kabamland)
    #view(kabamland)
    #quit()
    sim = Simulation(kabamland, geant4_processes=0)
    runs = 1
    numPhotons = 6
    events, pos = photon_angle(rep=numPhotons)
    nr_hits = np.zeros((runs))
    doRandom = getRandom()
    for ii in range(runs):
        photons, photon_track = propagate_photon(events, numPhotons, 20,
                                                 kabamland, doRandom[0],
                                                 doRandom[1], doRandom[2])
        detected = (photons.flags & (0x1 << 2)).astype(bool)
        nr_hits[ii] = len(photons.pos[detected])
        print "  Number of photons detected			", nr_hits[ii]
Ejemplo n.º 8
0
    def __init__(self,
                 daefile,
                 acrylic_detect=True,
                 acrylic_wls=False,
                 bvh_name="uboone_bvh_default",
                 detector_volumes=[],
                 wireplane_volumes=[],
                 auto_build_bvh=True,
                 read_bvh_cache=True,
                 calculate_ndsar_tree=False,
                 update_bvh_cache=True,
                 cache_dir=None,
                 bvh_method='grid',
                 bvh_target_degree='3',
                 cuda_device=None,
                 cl_device=None,
                 dump_node_info=False):

        if acrylic_detect and acrylic_wls:
            raise ValueError(
                'cannot have acrylic act as both detector and wavelength shifter'
            )
        # run constructor of base class
        if acrylic_detect:
            super(ubooneDet, self).__init__("Acrylic")
        elif acrylic_wls:
            super(ubooneDet, self).__init__("bialkali")
        else:
            print "Warning: no detector material specified"
        self.acrylic_detect = acrylic_detect
        self.acrylic_wls = acrylic_wls

        if len(detector_volumes) == 0:
            raise ValueError("No detector volumes specified!")

        # We use g4dae tools to create geometry with mesh whose triangles have materials assigned to them
        DAENode.parse(daefile, sens_mats=[])
        self.collada2chroma = ColladaToChroma(DAENode, dump_node_info=False)
        geom = self.collada2chroma.convert_geometry_partial()

        # Look for wireplane alogorithms
        if len(wireplane_volumes) > 0:
            wireplaneset = map(lambda x: x[0], wireplane_volumes)
            for n, solid in enumerate(geom.solids):
                node = solid.node
                if node.pv.id in wireplaneset:
                    for wireplane in wireplane_volumes:
                        if wireplane[0] == node.pv.id:
                            wireplane[1](solid)
            print "Found Solids to add wireplanes: ", len(wireplaneset)

        geom = self.collada2chroma.finish_converting_geometry()

        if dump_node_info:
            for n, solid in enumerate(geom.solids):
                node = solid.node
                mesh = solid.mesh
                material2names = map(lambda x: x.name,
                                     np.unique(solid.material2))
                material1names = map(lambda x: x.name,
                                     np.unique(solid.material1))
                surfaces = map(
                    lambda x: x.name,
                    filter(lambda x: x != None, solid.unique_surfaces))
                print "[SOLID %d, NODE %05d:%s,%s]" % (
                    n, node.index, node.pv.id, node.lv.id
                ), " NTriangles=%d OuterMat=%s InnerMat=%s Surface=%s" % (len(
                    mesh.triangles), material2names, material1names, surfaces)

        # copy member objects (maybe use copy module instead?)
        self.mesh = geom.mesh
        self.colors = geom.colors
        self.solids = geom.solids
        self.solid_id = geom.solid_id
        self.unique_materials = geom.unique_materials
        self.material1_index = geom.material1_index
        self.material2_index = geom.material2_index
        material_lookup = dict(
            zip(self.unique_materials, range(len(self.unique_materials))))

        # Next we need to go through all the triangles and make sure they have the right surfaces attached to them
        # We use the material lists to loop over all triangles
        self.unique_surfaces = []
        surface_index_dict = {}
        surface_indices = []
        for id, mats in enumerate(
                zip(self.material1_index, self.material2_index)):
            existing_surface = geom.surface_index[id]
            if existing_surface != -1:
                print "Triangle %d already has specified surface: " % (
                    id), geom.unique_surfaces[existing_surface]
                surface = geom.unique_surfaces[existing_surface]
            else:
                surface = uboonesurfaces.get_boundary_surface(
                    self.unique_materials[mats[0]].name,
                    self.unique_materials[mats[1]].name)
            if surface == None:
                surface_indices.append(-1)
            else:
                if surface not in self.unique_surfaces:
                    self.unique_surfaces.append(surface)
                    surface_index_dict[surface] = self.unique_surfaces.index(
                        surface)
                surface_indices.append(surface_index_dict[surface])
        self.surface_index = np.array(surface_indices, dtype=np.int)
        print "number of surface indicies: ", len(self.surface_index)

        # Finally, setup channels
        self._setup_channels(acrylic_detect, detector_volumes)
        self._setup_photodetectors()

        if self.bvh is None:
            self.bvh = load_bvh(self,
                                auto_build_bvh=auto_build_bvh,
                                read_bvh_cache=read_bvh_cache,
                                update_bvh_cache=update_bvh_cache,
                                cache_dir=cache_dir,
                                bvh_method=bvh_method,
                                target_degree=bvh_target_degree,
                                cuda_device=cuda_device,
                                cl_device=cl_device)

        if calculate_ndsar_tree:
            # This tree helps with the navigation of the node tree (deprecated)
            print "Calculate node DSAR tree ...",
            sndsar = time.time()
            self.node_dsar_tree = NodeDSARtree(self.bvh)
            endsar = time.time()
            print " done ", endsar - sndsar, " secs."
Ejemplo n.º 9
0
    g.add_solid(world)

    return g


if __name__ == '__main__':
    from chroma.sim import Simulation
    from chroma.sample import uniform_sphere
    from chroma.event import Photons
    from chroma.loader import load_bvh
    from chroma.generator import vertex
    import matplotlib.pyplot as plt

    g = build_detector()
    g.flatten()
    g.bvh = load_bvh(g)

    sim = Simulation(g)

    # photon bomb from center
    def photon_bomb(n, wavelength, pos):
        pos = np.tile(pos, (n, 1))
        dir = uniform_sphere(n)
        pol = np.cross(dir, uniform_sphere(n))
        wavelengths = np.repeat(wavelength, n)
        return Photons(pos, dir, pol, wavelengths)

    # write it to a root file
    from chroma.io.root import RootWriter
    f = RootWriter('test.root')
Ejemplo n.º 10
0
    def __init__(self,
                 name,
                 daefile,
                 bvh_name="bvh_default",
                 auto_build_bvh=True,
                 read_bvh_cache=True,
                 update_bvh_cache=True,
                 cache_dir=None,
                 bvh_method='grid',
                 bvh_target_degree='3',
                 cuda_device=None,
                 cl_device=None,
                 dump_node_info=False):
        super(UserVG4DEAGeo, self).__init__()
        # save data members
        self.Name = name
        self.daefile = os.path.basename(daefile)
        # setup geometry cache

        # We use g4dae tools to create geometry with mesh whose triangles have materials assigned to them
        DAENode.parse(daefile, sens_mats=[])
        self.collada2chroma = ColladaToChroma(DAENode, dump_node_info=False)
        geom = self.collada2chroma.convert_geometry_partial()

        # Apply wireplane alogorith
        if len(self.wireplanevolumes()) > 0:
            nwireplanes = 0
            for n, solid in enumerate(geom.solids):
                node = solid.node
                name = node.pv.id.split("0x")[0]
                if name in self.wireplanevolumes():
                    ok = self.setaswireplane(name, solid)
                    if ok:
                        nwireplanes += 1
            print "Number of solids with wireplane surface: ", nwireplanes

        # Finish Geometry
        geom = self.collada2chroma.finish_converting_geometry()
        self.mesh = geom.mesh
        self.colors = geom.colors
        self.solids = geom.solids
        self.solid_id = geom.solid_id
        self.unique_materials = geom.unique_materials
        self.material1_index = geom.material1_index
        self.material2_index = geom.material2_index
        print "Materials: ", len(self.material1_index), len(
            self.material2_index)

        # Next we need to go through all the triangles and make sure they have the right surfaces attached to them
        # We use the material lists to generate pairs of materials to that require a surface
        # Note material index list is over all triangles already
        # Then we make a list of surfaces
        self.user_surfaces_dict = self.surfacesdict()
        self.unique_surfaces = []
        surface_index_dict = {None: -1}
        surface_index_list = []
        for id, mats in enumerate(
                zip(self.material1_index, self.material2_index)):
            surface = None
            if self.unique_materials[mats[0]].name != self.unique_materials[
                    mats[1]].name:
                existing_surface = geom.surface_index[id]
                if existing_surface != -1:
                    print "Triangle %d already has specified surface: " % (
                        id), geom.unique_surfaces[existing_surface]
                    surface = geom.unique_surfaces[existing_surface]
                else:
                    mat1 = self.unique_materials[mats[0]].name.split("0x")[0]
                    mat2 = self.unique_materials[mats[1]].name.split("0x")[0]
                    if (mat1, mat2) in self.user_surfaces_dict:
                        surface = self.user_surfaces_dict[(mat1, mat2)]
                    elif (mat2, mat1) in self.user_surfaces_dict:
                        surface = self.user_surfaces_dict[(mat2, mat1)]
                    else:
                        raise RuntimeError(
                            "Could not assign a surface between materials %s and %s. Must provide through sufacesdict(self) method."
                            % (mat1, mat2))
            else:
                if geom.surface_index[id] != -1:
                    print "Triangle %d already has specified surface: " % (
                        id), geom.unique_surfaces[existing_surface]
                    surface = geom.unique_surfaces[existing_surface]
                else:
                    surface = None
            if surface is not None and surface not in self.unique_surfaces:
                self.unique_surfaces.append(surface)
                surface_index_dict[surface] = self.unique_surfaces.index(
                    surface)
                print "Registering new surface: [%d] %s" % (
                    surface_index_dict[surface], surface.name)
            surface_index_list.append(surface_index_dict[surface])

        print "number of unique surfaces: ", len(self.unique_surfaces)
        print self.unique_surfaces
        self.surface_index = np.array(surface_index_list, dtype=np.int32)

        # Setup the channels
        self._setup_channels()

        # Setup the BVH
        if self.bvh is None:
            self.bvh = load_bvh(self,
                                auto_build_bvh=auto_build_bvh,
                                read_bvh_cache=read_bvh_cache,
                                update_bvh_cache=update_bvh_cache,
                                cache_dir=cache_dir,
                                bvh_method=bvh_method,
                                target_degree=bvh_target_degree,
                                cuda_device=cuda_device,
                                cl_device=cl_device)
Ejemplo n.º 11
0
    def __init__(self, daefile, acrylic_detect=True, acrylic_wls=False,
                 bvh_name="bvh_default", detector_volumes=[],
                 wireplane_volumes=[],
                 auto_build_bvh=True, read_bvh_cache=True, calculate_ndsar_tree=False,
                 update_bvh_cache=True, cache_dir=None, bvh_method='grid', bvh_target_degree='3',
                 cuda_device=None, cl_device=None, dump_node_info=False ):
        """
        Loads a geometry for Chroma built using the COLLADA file exported by G4DAE.
        
        Parameters
        ----------
        daefile : str
          string with path to COLLADA file
        acrylic_detect : bool
           hacky way to specify detector behavior. needs to be removed
        acrylic_wls : bool
           hacky way to specify that acrylic material should wavelength shift. needs to be removed
        bvh_name : string
           specifies name of BVH cache written to working directory
        detector_volumes : list of str
           logical volume names that will be marked as a sensitive detectors
        wireplane_volumes : list of (str,function) tuples
           If a Solid with an associated physical volume whose name that matches 'str', 
           we give the function the Solid instance.  The intention is for the user to provide 
           one or more of the triangles in the Solid with a wireplane surface.
           For an example see: XXX
        auto_build_bvh : bool
        read_bvh_cache : bool
        update_bvh_cache : bool
        cache_dir : str
        bvh_method : str
        bvh_target_degree : str of number
        cuda_device : pycuda device instance
           build and store geometry on specified CUDA device
        cl_device : pyopencl device instance
           build and store geometry on specified OpenCL device
        calculate_ndsar_tree : bool
           after geometry specified, traverse BVH and build ropes.  deprecated and will likely be removed
        dump_node_info : bool
           if true, will list all of the nodes in the geometry

        Returns
        -------
        instance of object

        Raises
        ------
        ValueError
           if both acrylic_detect and acrylic_wls are True. Need to remove.
        ValueError
           when no detector volumes are specified. likely will be removed.
        """

        if acrylic_detect and acrylic_wls:
            raise ValueError('cannot have acrylic act as both detector and wavelength shifter')
        # run constructor of base class
        if acrylic_detect:
            super( ubooneDet, self ).__init__( "Acrylic" )
        elif acrylic_wls:
            super( ubooneDet, self ).__init__( "bialkali" )
        else:
            print "Warning: no detector material specified"
        self.acrylic_detect = acrylic_detect
        self.acrylic_wls    = acrylic_wls
        
        if len( detector_volumes )==0:
            raise ValueError( "No detector volumes specified!" )

        # We use g4dae tools to create geometry with mesh whose triangles have materials assigned to them
        DAENode.parse( daefile, sens_mats=[] )
        self.collada2chroma = ColladaToChroma( DAENode, dump_node_info=False )
        geom = self.collada2chroma.convert_geometry_partial()

        # Look for wireplane alogorithms
        if len(wireplane_volumes)>0:
            wireplaneset = map( lambda x: x[0], wireplane_volumes )
            for n,solid in enumerate(geom.solids):
                node = solid.node
                if node.pv.id in wireplaneset:
                    for wireplane in wireplane_volumes:
                        if wireplane[0]==node.pv.id:
                            wireplane[1]( solid )
            print "Found Solids to add wireplanes: ",len(wireplaneset)
                    
        geom = self.collada2chroma.finish_converting_geometry()

        if dump_node_info:
            for n,solid in enumerate(geom.solids):
                node = solid.node
                mesh = solid.mesh
                material2names = map( lambda x: x.name, np.unique(solid.material2) )
                material1names = map( lambda x: x.name, np.unique(solid.material1) )
                surfaces = map( lambda x: x.name, filter( lambda x: x!=None, solid.unique_surfaces ) )
                print "[SOLID %d, NODE %05d:%s,%s]"%(n, node.index,node.pv.id,node.lv.id)," NTriangles=%d OuterMat=%s InnerMat=%s Surface=%s"%(len(mesh.triangles), material2names, material1names,surfaces)
        
        # copy member objects (maybe use copy module instead?)
        self.mesh = geom.mesh
        self.colors = geom.colors
        self.solids = geom.solids
        self.solid_id = geom.solid_id
        self.unique_materials = geom.unique_materials
        self.material1_index = geom.material1_index
        self.material2_index = geom.material2_index
        material_lookup = dict(zip(self.unique_materials, range(len(self.unique_materials))))

        # Next we need to go through all the triangles and make sure they have the right surfaces attached to them
        # We use the material lists to loop over all triangles
        self.unique_surfaces = []
        surface_index_dict = {}
        surface_indices = []
        for id, mats in enumerate( zip(self.material1_index, self.material2_index) ):
            existing_surface = geom.surface_index[ id ]
            if existing_surface!=-1:
                print "Triangle %d already has specified surface: "%(id),geom.unique_surfaces[ existing_surface ]
                surface = geom.unique_surfaces[ existing_surface ]
            else:
                surface = uboonesurfaces.get_boundary_surface( self.unique_materials[mats[0]].name, self.unique_materials[mats[1]].name )
            if surface==None:
                surface_indices.append( -1 )
            else:
                if surface not in self.unique_surfaces:
                    self.unique_surfaces.append( surface )
                    surface_index_dict[ surface ] = self.unique_surfaces.index( surface )
                surface_indices.append( surface_index_dict[ surface ] )
        self.surface_index = np.array( surface_indices, dtype=np.int )
        print "number of surface indicies: ",len(self.surface_index)


        # Finally, setup channels
        print "SETUP UBOONE CHANNELS from solids list (",len(self.solids)," solids)"
        self.solid_id_to_channel_index.resize( len(self.solids) )
        self.solid_id_to_channel_index.fill(-1) # default no channels
        self.solid_id_to_channel_id.resize( len(self.solids) )
        self.solid_id_to_channel_id.fill(-1)

        print len( self.solid_id_to_channel_index ), len(  self.solid_id_to_channel_id )
        # prevous calls to add_solid by collada_to_chroma sized this array
        for n,solid in enumerate(self.solids):
            if acrylic_detect and  any( volnames in solid.node.lv.id for volnames in detector_volumes ):
                solid_id = n
                channel_index = len(self.channel_index_to_solid_id)
                channel_id = channel_index # later can do more fancy channel indexing/labeling
                self.solid_id_to_channel_index[solid_id] = channel_index
                self.solid_id_to_channel_id[solid_id] = channel_id

                # resize channel_index arrays before filling
                self.channel_index_to_solid_id.resize(channel_index+1)
                self.channel_index_to_solid_id[channel_index] = solid_id
                self.channel_index_to_channel_id.resize(channel_index+1)
                self.channel_index_to_channel_id[channel_index] = channel_id
                
                # dictionary does not need resizing
                self.channel_id_to_channel_index[channel_id] = channel_index
        print "Number of Channels Added: ",len(self.channel_index_to_solid_id)

        if self.bvh is None:
            self.bvh = load_bvh(self, auto_build_bvh=auto_build_bvh,
                                read_bvh_cache=read_bvh_cache,
                                update_bvh_cache=update_bvh_cache,
                                cache_dir=cache_dir, bvh_method=bvh_method, target_degree=bvh_target_degree,
                                cuda_device=cuda_device, cl_device=cl_device)

        self._setup_photodetectors()

        if calculate_ndsar_tree:
            # This tree helps with the navigation of the node tree (deprecated)
            print "Calculate node DSAR tree ...",
            sndsar = time.time()
            self.node_dsar_tree = NodeDSARtree( self.bvh )
            endsar = time.time()
            print " done ",endsar-sndsar," secs."