Ejemplo n.º 1
0
def test_mesh():
    resource_folder = 'tests/test_files/'
    pdb_folder = resource_folder + 'pdb/'
    obj_folder = resource_folder + 'obj/'

    bio_explorer = BioExplorer('localhost:5000')
    bio_explorer.reset()
    print('BioExplorer version ' + bio_explorer.version())

    mesh_source = obj_folder + 'suzanne.obj'
    scale = Vector3(5, 5, 5)

    # Membrane
    protein_source = pdb_folder + 'membrane/popc.pdb'
    mesh_based_membrane = MeshBasedMembrane(mesh_source=mesh_source,
                                            protein_source=protein_source,
                                            density=5.0,
                                            surface_variable_offset=2.0)
    bio_explorer.add_mesh_based_membrane('Mesh',
                                         mesh_based_membrane,
                                         scale=scale)

    # Receptors
    protein_source = pdb_folder + '6m1d.pdb'
    mesh_based_membrane = MeshBasedMembrane(mesh_source=mesh_source,
                                            protein_source=protein_source,
                                            density=0.02,
                                            surface_fixed_offset=5.0)
    bio_explorer.add_mesh_based_membrane('Receptors',
                                         mesh_based_membrane,
                                         scale=scale)
class LowGlucoseScenario():

    def __init__(self, hostname, port, projection, output_folder, image_k=4,
                 image_samples_per_pixel=64, log_level=1, shaders=list(['bio_explorer'])):
        self._log_level = log_level
        self._hostname = hostname
        self._url = hostname + ':' + str(port)
        self._be = BioExplorer(self._url)
        self._core = self._be.core_api()
        self._image_size = [1920, 1080]
        self._image_samples_per_pixel = image_samples_per_pixel
        self._image_projection = projection
        self._image_output_folder = output_folder
        self._shaders = shaders
        self._prepare_movie(projection, image_k)
        self._log(1, '================================================================================')
        self._log(1, '- Version          : ' + self._be.version())
        self._log(1, '- URL              : ' + self._url)
        self._log(1, '- Projection       : ' + projection)
        self._log(1, '- Frame size       : ' + str(self._image_size))
        self._log(1, '- Export folder    : ' + self._image_output_folder)
        self._log(1, '- Samples per pixel: ' + str(self._image_samples_per_pixel))
        self._log(1, '================================================================================')

    def _log(self, level, message):
        if level <= self._log_level:
            print('[' + str(datetime.now()) + '] ' + message)

    def _get_transformation(self, start_frame, end_frame, frame, data):
        '''Progress'''
        progress = (frame - start_frame) * 1.0 / (end_frame - start_frame)
        progress = max(0.0, progress)
        progress = min(1.0, progress)

        '''Position'''
        start_pos = data[0].to_list()
        end_pos = data[2].to_list()
        pos = start_pos
        for i in range(3):
            pos[i] += (end_pos[i] - start_pos[i]) * progress

        '''Rotation'''
        start_rot = data[1]
        end_rot = data[3]
        rot = Quaternion.slerp(start_rot, end_rot, progress)
        if data[4] == ROTATION_MODE_SINUSOIDAL:
            rot = Quaternion.slerp(start_rot, end_rot, math.cos((progress - 0.5) * math.pi))

        return [Vector3(pos[0], pos[1], pos[2]), rot, progress * 100.0]

    def _add_viruses(self, frame):
        virus_radii = [45.0, 44.0, 45.0, 43.0, 44.0]
        virus_sequences = [
            [[0, 2599], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6]],
            [[0, 2599], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6]],
            [[0, 2599], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6]],
            [[0, 2999], [3000, 3099], [3100, 3299], [3300, 3750], [1e6, 1e6], [1e6, 1e6]],
            [[0, 599], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1e6, 1e6], [1600, 3750]],
        ]
        virus_flights_in = [
            [Vector3(-35.0, 300.0, -70.0), Quaternion(0.519, 0.671, 0.528, -0.036),
             Vector3(-5.0, 45.0, -33.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            [Vector3(153.0, 300.0, -200.0), Quaternion(0.456, 0.129, -0.185, -0.860),
             Vector3(73.0, 93.0, -130.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Virus used for SP-D zoom
            [Vector3(-100.0, 300.0, 20.0), Quaternion(0.087, 0.971, -0.147, -0.161),
             Vector3(-79.0, 108.0, 80.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Virus getting inside cell
            [Vector3(224.9, 300.0, -220.0), Quaternion(-0.095, 0.652, -0.326, 0.677),
             Vector3(211.5, -104.9, -339.2), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Virus used for detailed view of the Spike
            [Vector3(200.0, 20.0, -150.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(200.0, 20.0, -150.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR]
        ]

        virus_flights_out = [
            # Unused
            [Vector3(-5.0, 45.0, -33.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(-105.0, 45.0, -33.0), Quaternion(0.0, 1.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Unused
            [Vector3(73.0, 93.0, -130.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(-33.0, 93.0, -130.0), Quaternion(0.0, 0.0, 1.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Virus used for SP-D zoom
            [Vector3(-84.0, 110.0, 75.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(-100.0, -100.0, 51.2), Quaternion(0.087, 0.971, -0.147, -0.161),
             ROTATION_MODE_LINEAR],
            # Unused
            [Vector3(0.0, 0.0, 0.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(0.0, 0.0, 0.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # Virus used for detailed view of the Spike
            [Vector3(200.0, 20.0, -150.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             Vector3(300.0, -100.0, -100.0), Quaternion(0.456, 0.129, -0.185, -0.860),
             ROTATION_MODE_LINEAR]
        ]

        for virus_index in range(len(virus_sequences)):
            name = 'Coronavirus ' + str(virus_index)
            current_sequence = 0
            sequences = virus_sequences[virus_index]
            for i in range(len(sequences)):
                if frame >= sequences[i][0]:
                    current_sequence = i

            '''Initialize position and rotation to end-of-flight values'''
            start_frame = sequences[current_sequence][0]
            end_frame = sequences[current_sequence][1]
            progress_in_sequence = (frame - start_frame) / (end_frame - start_frame)
            morphing_step = 0.0

            if current_sequence == 0:
                '''Flying'''
                pos, rot, progress = self._get_transformation(start_frame, end_frame,
                                                              frame, virus_flights_in[virus_index])
                self._log(3, '-   Virus %d is flying in... (%.01f pct)' % (virus_index, progress))
            elif current_sequence == 1:
                '''Landing'''
                pos = virus_flights_in[virus_index][2]
                rot = virus_flights_in[virus_index][3]
                pos.y -= landing_distance * progress_in_sequence
                self._log(3, '-   Virus %d is landing...' % virus_index)
            elif current_sequence == 2:
                '''Merging into cell'''
                pos = virus_flights_in[virus_index][2]
                rot = virus_flights_in[virus_index][3]
                morphing_step = (frame - start_frame) / (end_frame - start_frame)
                pos.y -= landing_distance
                self._log(3, '-   Virus %d is merging in (%.01f pct)' %
                          (virus_index, morphing_step * 100.0))
            elif current_sequence == 3:
                '''Inside cell'''
                self._log(3, '-   Virus %d is inside cell' % virus_index)
                '''Virus is not added to the scene'''
                self._be.remove_assembly(name=name)
                continue
            elif current_sequence == 4:
                '''Merging out of cell'''
                pos = virus_flights_out[virus_index][0]
                rot = virus_flights_out[virus_index][1]
                morphing_step = 1.0 - (frame - start_frame) / (end_frame - start_frame)
                self._log(3, '-   Virus %d is merging out (%.01f pct)' %
                          (virus_index, morphing_step * 100.0))
            else:
                '''Flying out'''
                pos, rot, progress = self._get_transformation(start_frame, end_frame,
                                                              frame, virus_flights_out[virus_index])
                self._log(3, '-   Virus %d is flying out... (%.01f pct)' % (virus_index, progress))

            if False:
                self._be.add_sphere(name=name, position=pos, radius=virus_radii[virus_index])
            else:
                self._be.add_coronavirus(
                    name=name, resource_folder=resource_folder,
                    representation=protein_representation, position=pos, rotation=rot,
                    add_glycans=add_glycans,
                    assembly_params=[virus_radii[virus_index], 5 * frame + 2 * virus_index,
                                     0.25, frame + 2 * virus_index + 1, 0.1, morphing_step]
                )

    def _add_cell(self, frame):

        name = 'Cell'
        nb_receptors = cell_nb_receptors
        size = scene_size * 2.0
        height = scene_size / 10.0
        position = Vector3(4.5, -186.0, 7.0)
        random_seed = 10

        nb_lipids = cell_nb_lipids
        ace2_receptor = Protein(
            sources=[pdb_folder + '6m18.pdb'], occurences=nb_receptors,
            position=Vector3(0.0, 6.0, 0.0))

        membrane = ParametricMembrane(
            sources=[
                membrane_folder + 'segA.pdb',
                membrane_folder + 'segB.pdb',
                membrane_folder + 'segC.pdb',
                membrane_folder + 'segD.pdb'
            ],
            occurences=cell_nb_lipids
        )

        cell = Cell(
            name=name, size=size, extra_parameters=[height],
            shape=BioExplorer.ASSEMBLY_SHAPE_SINUSOIDAL,
            membrane=membrane, receptor=ace2_receptor,
            random_position_seed=frame + 1, random_position_strength=0.025,
            random_rotation_seed=frame + 2, random_rotation_strength=0.2
        )

        self._be.add_cell(
            cell=cell, position=position,
            representation=protein_representation,
            random_seed=random_seed)

        '''Modify receptor position when attached virus enters the cell'''
        receptors_instances = [37]
        receptors_sequences = [[3000, 3099]]

        for i in range(len(receptors_instances)):
            instance_index = receptors_instances[i]
            sequence = receptors_sequences[i]
            start_frame = sequence[0]
            end_frame = sequence[1]
            if frame >= start_frame:
                if frame > end_frame:
                    '''Send receptor to outter space'''
                    status = self._be.set_protein_instance_transformation(
                        assembly_name=name, name=name + '_' + BioExplorer.NAME_RECEPTOR,
                        instance_index=instance_index, position=Vector3(0.0, 1e6, 0.0)
                    )
                else:
                    '''Current receptor transformation'''
                    transformation = self._be.get_protein_instance_transformation(
                        assembly_name=name, name=name + '_' + BioExplorer.NAME_RECEPTOR,
                        instance_index=instance_index
                    )
                    p = transformation['position'].split(',')
                    q = transformation['rotation'].split(',')
                    pos = Vector3(float(p[0]), float(p[1]), float(p[2]))
                    q2 = Quaternion(float(q[0]), float(q[1]), float(q[2]), float(q[3]))

                    '''Bend receptor'''
                    progress = (frame - start_frame) * 1.0 / (end_frame - start_frame)
                    q1 = Quaternion(axis=[0, 1, 0], angle=math.pi * progress)
                    rot = q2 * q1

                    pos.x += landing_distance * progress * 0.3
                    pos.y -= landing_distance * progress * 0.3

                    status = self._be.set_protein_instance_transformation(
                        assembly_name=name, name=name + '_' + BioExplorer.NAME_RECEPTOR,
                        instance_index=instance_index, position=pos, rotation=rot
                    )

        '''Glycans'''
        if nb_receptors != 0 and add_glycans:
            self._be.add_multiple_glycans(
                representation=glycan_representation, assembly_name=name,
                glycan_type=BioExplorer.NAME_GLYCAN_COMPLEX,
                protein_name=BioExplorer.NAME_RECEPTOR, paths=complex_paths,
                indices=[53, 90, 103, 322, 432, 690],
                assembly_params=[0, 0, 0.0, frame + 3, 0.2]
            )
            self._be.add_multiple_glycans(
                representation=glycan_representation, assembly_name=name,
                glycan_type=BioExplorer.NAME_GLYCAN_HYBRID,
                protein_name=BioExplorer.NAME_RECEPTOR, paths=hybrid_paths,
                indices=[546],
                assembly_params=[0, 0, 0.0, frame + 4, 0.2])

            indices = [[155, Quaternion(0.707, 0.0, 0.707, 0.0)],
                       [730, Quaternion(0.707, 0.0, 0.707, 0.0)]]
            count = 0
            for index in indices:
                o_glycan_name = name + '_' + BioExplorer.NAME_GLYCAN_O_GLYCAN + '_' + str(index[0])
                o_glycan = Sugars(
                    assembly_name=name, name=o_glycan_name, source=o_glycan_paths[0],
                    protein_name=name + '_' + BioExplorer.NAME_RECEPTOR, representation=glycan_representation,
                    chain_ids=[2, 4], site_indices=[index[0]], rotation=index[1],
                    assembly_params=[0, 0, 0.0, frame + count + 5, 0.2])
                self._be.add_sugars(o_glycan)
                count += 1

    def _add_surfactant_d(self, name, position, rotation, random_seed):
        surfactant_d = Surfactant(
            name=name, surfactant_protein=BioExplorer.SURFACTANT_PROTEIN_D,
            head_source=surfactant_head_source,
            branch_source=surfactant_branch_source)
        self._be.add_surfactant(
            surfactant=surfactant_d, representation=protein_representation,
            position=position, rotation=rotation, random_seed=random_seed)

    def _add_surfactant_a(self, name, position, rotation, random_seed):
        surfactant_a = Surfactant(
            name=name, surfactant_protein=BioExplorer.SURFACTANT_PROTEIN_A,
            head_source=surfactant_head_source,
            branch_source=surfactant_branch_source)
        self._be.add_surfactant(
            surfactant=surfactant_a, representation=protein_representation,
            position=position, rotation=rotation, random_seed=random_seed)

    def _add_surfactants_d(self, frame):
        spd_sequences = [[0, 3750], [0, 2600], [0, 2600], [0, 3750]]
        spd_random_seeds = [1, 1, 1, 2]

        spd_flights = [
            [Vector3(300,  124.0, 0.0), Quaternion(-0.095, 0.652, -0.326, 0.677),
             Vector3(74.0,  24.0, -45.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_SINUSOIDAL],
            # SP-D is used for the head focus on 3rd virus spike
            [Vector3(-50,  250.0, 20.0), Quaternion(0.087, 0.971, -0.147, -0.161),
             Vector3(-11.0,  108.0, 20.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            # SP-D attaching to lymphocyte
            [Vector3(-200.0, 100.0, 100.0), Quaternion(0.519, 0.671, 0.528, -0.036),
             Vector3(-135.0, 135.0, 140.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_LINEAR],
            [Vector3(100.0,  0.0, -80.0), Quaternion(-0.095, 0.652, -0.326, 0.677),
             Vector3(-260.0,  50.0, 150.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_SINUSOIDAL]
        ]

        for surfactant_index in range(len(spd_sequences)):
            name = 'Surfactant-D ' + str(surfactant_index)
            sequence = spd_sequences[surfactant_index]
            pos, rot, progress = self._get_transformation(
                start_frame=sequence[0], end_frame=sequence[1],
                frame=frame, data=spd_flights[surfactant_index])
            self._log(3, '-   ' + name + ' (%.01f pct)' % progress)
            self._add_surfactant_d(
                name=name, position=pos, rotation=rot,
                random_seed=spd_random_seeds[surfactant_index])

    def _add_surfactants_a(self, frame):
        spa_sequences = [[0, 3750]]
        spa_random_seeds = [2]
        spa_frames = [
            [Vector3(300.0,  -50.0, -50.0), Quaternion(-0.095, 0.652, -0.326, 0.677),
             Vector3(100.0,  50.0, 150.0), Quaternion(1.0, 0.0, 0.0, 0.0),
             ROTATION_MODE_SINUSOIDAL],
        ]

        for surfactant_index in range(len(spa_frames)):
            name = 'Surfactant-A ' + str(surfactant_index)
            sequence = spa_sequences[surfactant_index]
            pos, rot, progress = self._get_transformation(
                start_frame=sequence[0], end_frame=sequence[1],
                frame=frame, data=spa_frames[surfactant_index])
            self._log(3, '-   ' + name + ' (%.01f pct)' % progress)
            self._add_surfactant_a(
                name=name, position=pos, rotation=rot,
                random_seed=spa_random_seeds[surfactant_index])

    def _add_glucose(self, frame):
        glucose = Protein(
            sources=[glucose_path], load_non_polymer_chemicals=True,
            occurences=nb_glucoses)
        volume = Volume(
            name=BioExplorer.NAME_GLUCOSE, size=scene_size, protein=glucose,
            random_position_seed=frame + 20, random_position_stength=scene_size / 600.0,
            random_rotation_seed=frame + 21, random_rotation_stength=0.3
        )
        status = self._be.add_volume(
            volume=volume, representation=protein_representation,
            position=Vector3(0.0, scene_size / 2.0 - 200.0, 0.0),
            random_seed=100)

    def _add_lactoferrins(self, frame):
        lactoferrin = Protein(
            sources=[lactoferrin_path], load_non_polymer_chemicals=True,
            occurences=nb_lactoferrins)
        lactoferrins_volume = Volume(
            name=BioExplorer.NAME_LACTOFERRIN, size=scene_size, protein=lactoferrin,
            random_position_seed=frame + 30, random_position_stength=scene_size / 400.0,
            random_rotation_seed=frame + 31, random_rotation_stength=0.3
        )
        status = self._be.add_volume(
            volume=lactoferrins_volume, representation=protein_representation,
            position=Vector3(0.0, scene_size / 2.0 - 200.0, 0.0),
            random_seed=101)

    def _add_defensins(self, frame):
        defensin = Protein(
            sources=[defensin_path], load_non_polymer_chemicals=True,
            occurences=nb_defensins)
        defensins_volume = Volume(
            name=BioExplorer.NAME_DEFENSIN, size=scene_size, protein=defensin,
            random_position_seed=frame + 40, random_position_stength=scene_size / 400.0,
            random_rotation_seed=frame + 41, random_rotation_stength=0.3
        )
        status = self._be.add_volume(
            volume=defensins_volume, representation=protein_representation,
            position=Vector3(0.0, scene_size / 2.0 - 200.0, 0.0),
            random_seed=102)

    def _add_lymphocyte(self, frame):
        if frame < 1400:
            '''Lymphocyte is not in the field of view'''
            return

        '''Protein animation params'''
        params = [0, 0, 0.0, frame + 2, 0.2]

        clip_planes = [
            [1.0, 0.0, 0.0, scene_size * 1.5 + 5],
            [-1.0, 0.0, 0.0, scene_size * 1.5 + 5],
            [0.0, 0.0, 1.0, scene_size + 5],
            [0.0, 0.0, -1.0, scene_size + 5]
        ]

        name = 'Emile'
        lymphocyte_sequence = [0, 3750]
        lymphocyte_frames = [Vector3(-2500.0, 100.0, 30.0), Quaternion(1.0, 0.0, 0.0, 0.0),
                             Vector3(-830.0, 100.0, 30.0), Quaternion(0.707, 0.707, 0.0, 0.0),
                             ROTATION_MODE_LINEAR]

        protein_sources = [
            membrane_folder + 'segA.pdb',
            membrane_folder + 'segB.pdb',
            membrane_folder + 'segC.pdb',
            membrane_folder + 'segD.pdb'
        ]

        mesh_based_membrane = MeshBasedMembrane(
            mesh_source=lymphocyte_path, protein_sources=protein_sources,
            density=lymphocyte_density, surface_variable_offset=lymphocyte_surface_variable_offset,
            assembly_params=params
        )

        pos, rot, progress = self._get_transformation(
            start_frame=lymphocyte_sequence[0], end_frame=lymphocyte_sequence[1],
            frame=frame, data=lymphocyte_frames)
        self._log(3, '-   ' + name + ' (%.01f pct)' % progress)

        scale = Vector3(1.0, 1.0, 1.0)
        status = self._be.add_mesh_based_membrane(
            name, mesh_based_membrane, position=pos,
            rotation=rot, scale=scale,
            clipping_planes=clip_planes
        )

        for i in range(len(protein_sources)):
            status = self._be.set_protein_color_scheme(
                assembly_name=name, name=BioExplorer.NAME_MEMBRANE + '_' + str(i),
                color_scheme=BioExplorer.COLOR_SCHEME_CHAINS,
                palette_name='OrRd', palette_size=5)

    def _set_materials(self):
        '''Default materials'''
        self._be.apply_default_color_scheme(
            shading_mode=BioExplorer.SHADING_MODE_DIFFUSE, specular_exponent=50.0)

    def _create_snapshot(self, shader, frame, movie_maker):
        samples_per_pixel = self._image_samples_per_pixel

        '''Renderer'''
        if shader == 'albedo':
            self._core.set_renderer(current='albedo')
        elif shader == 'ambient_occlusion':
            self._core.set_renderer(
                current='ambient_occlusion', samples_per_pixel=1, subsampling=1, max_accum_frames=1)
            params = self._core.AmbientOcclusionRendererParams()
            params.samplesPerFrame = 32
            params.rayLength = 5.0
            self._core.set_renderer_params(params)
            samples_per_pixel = 4
        elif shader == 'depth':
            status = self._core.set_renderer(
                current='depth', samples_per_pixel=1, subsampling=1, max_accum_frames=1)
            params = status = self._core.DepthRendererParams()
            params.infinity = 2000.0
            status = self._core.set_renderer_params(params)
            samples_per_pixel = 2
        elif shader == 'raycast_Ns':
            status = self._core.set_renderer(
                current='raycast_Ns', samples_per_pixel=1, subsampling=1, max_accum_frames=1)
            samples_per_pixel = 2
        else:
            status = self._core.set_renderer(
                background_color=[96 / 255, 125 / 255, 139 / 255],
                current='bio_explorer', head_light=False,
                samples_per_pixel=1, subsampling=1, max_accum_frames=1)
            params = self._core.BioExplorerRendererParams()
            params.exposure = 1.0
            params.gi_samples = 1
            params.gi_weight = 0.3
            params.gi_distance = 5000
            params.shadows = 0.8
            params.soft_shadows = 0.05
            params.fog_start = 1000
            params.fog_thickness = 300
            params.max_bounces = 1
            params.use_hardware_randomizer = True
            status = self._core.set_renderer_params(params)
            samples_per_pixel = self._image_samples_per_pixel

            '''Lights'''
            status = self._core.clear_lights()
            status = self._core.add_light_directional(
                angularDiameter=0.5, color=[1, 1, 1], direction=[-0.7, -0.4, -1],
                intensity=1.0, is_visible=False
            )

        movie_maker.create_snapshot(
            size=self._image_size, path=self._image_output_folder + '/' + shader,
            base_name='%05d' % frame, samples_per_pixel=samples_per_pixel)

        '''Camera'''
        status = self._core.set_camera(current='bio_explorer_perspective')

    def _build_frame(self, frame):
        self._log(2, '- Resetting scene...')
        self._be.reset()

        self._log(2, '- Building surfactants...')
        self._add_surfactants_d(frame)
        self._add_surfactants_a(frame)

        self._log(2, '- Building glucose...')
        self._add_glucose(frame)

        self._log(2, '- Building lactoferrins...')
        self._add_lactoferrins(frame)

        self._log(2, '- Building defensins...')
        self._add_defensins(frame)

        self._log(2, '- Building viruses...')
        self._add_viruses(frame)

        self._log(2, '- Building cell...')
        self._add_cell(frame)

        self._log(2, '- Building lymphocyte...')
        self._add_lymphocyte(frame)

        self._log(2, '- Setting materials...')
        self._set_materials()

        self._log(2, '- Showing models...')
        status = self._be.set_models_visibility(True)
        status = self._core.set_renderer()

    def _make_export_folder(self, folder):
        import os
        path = self._image_output_folder + '/' + folder
        command_line = 'mkdir -p ' + path
        os.system(command_line)
        command_line = 'ls ' + path
        if os.system(command_line) != 0:
            self._log(3, 'ERROR: Failed to create folder ' + path)

    def _make_export_folders(self):
        for folder in self._shaders:
            self._make_export_folder(folder)

    def _prepare_movie(self, projection, image_k):
        if projection == 'perspective':
            aperture_ratio = 1.0
            self._image_size = [image_k*960, image_k*540]
            self._core.set_camera(current='bio_explorer_perspective')
        elif projection == 'fisheye':
            self._image_size = [int(image_k*1024), int(image_k*1024)]
            self._core.set_camera(current='fisheye')
        elif projection == 'panoramic':
            self._image_size = [int(image_k*1024), int(image_k*1024)]
            self._core.set_camera(current='panoramic')
        elif projection == 'opendeck':
            self._image_size = [7*2160, 3840]
            self._core.set_camera(current='cylindric')

        self._image_output_folder = self._image_output_folder + '/' + \
            projection + '/' + str(self._image_size[0]) + 'x' + str(self._image_size[1])
        self._make_export_folders()

    def _set_clipping_planes(self):
        '''Clipping planes'''
        clip_planes = [
            [1.0, 0.0, 0.0, scene_size * 1.5 + 5],
            [-1.0, 0.0, 0.0, scene_size * 1.5 + 5],
            [0.0, 0.0, 1.0, scene_size + 5],
            [0.0, 0.0, -1.0, scene_size + 5]
        ]
        cps = self._core.get_clip_planes()
        ids = list()
        if cps:
            for cp in cps:
                ids.append(cp['id'])
        self._core.remove_clip_planes(ids)
        for plane in clip_planes:
            self._core.add_clip_plane(plane)

    def render_movie(self, start_frame=0, end_frame=0, frame_step=1, frame_list=list()):
        '''Accelerate loading by not showing models as they are loaded'''
        status = self._be.set_general_settings(model_visibility_on_creation=False)

        aperture_ratio = 0.0
        cameras_key_frames = [
            {  # Virus overview (on 5th virus)
                'apertureRadius': aperture_ratio * 0.02,
                'direction': [0.0, 0.0, -1.0],
                'focusDistance': 139.56,
                'origin': [199.840, 20.634, 34.664],
                'up': [0.0, 1.0, 0.0]
            },
            {  # Protein S (on 5th virus)
                'apertureRadius': aperture_ratio * 0.02,
                'direction': [0.0, 0.0, -1.0],
                'focusDistance': 23.60,
                'origin': [195.937, 74.319, -111.767],
                'up': [0.0, 1.0, 0.0]
            },
            {  # Protein M and E
                'apertureRadius': aperture_ratio * 0.02,
                'direction': [-0.047, -0.298, -0.953],
                'focusDistance': 54.56,
                'origin': [208.156, 55.792, -59.805],
                'up': [0.003, 0.954, -0.298]
            },
            {  # Overview SPA
                'apertureRadius': aperture_ratio * 0.001,
                'direction': [-0.471, -0.006, -0.882],
                'focusDistance': 444.63,
                'origin': [238.163, 46.437, 372.585],
                'up': [0.0, 1.0, 0.0]
            },
            {  # Overview SPD
                'apertureRadius': aperture_ratio * 0.001,
                'focusDistance': 444.63,
                'direction': [-0.471, -0.005, -0.881],
                'origin': [238.163, 46.436, 372.584],
                'up': [0.0, 1.0, 0.0]
            },
            {  # Zoom SPD (on 3rd virus)
                'apertureRadius': aperture_ratio * 0.02,
                'focusDistance': 31.86,
                'direction': [-0.821, 0.202, -0.533],
                'origin': [-9.827, 110.720, 60.944],
                'up': [0.178, 0.979, 0.098]
            },
            {  # Overview scene
                'apertureRadius': aperture_ratio * 0.0,
                'focusDistance': 1.0,
                'direction': [-1.0, 0.0, 0.0],
                'origin': [1008.957, 29.057, 113.283],
                'up': [0.0, 1.0, 0.0]
            },
            {  # Cell view
                'apertureRadius': aperture_ratio * 0.0,
                'direction': [0.0, 0.0, -1.0],
                'focusDistance': 1.0,
                'origin': [150.0, -170.0, 400.0],
                'up': [0.0, 1.0, 0.0]
            }
        ]

        '''Double the frames to make it smoother'''
        key_frames = list()
        for cameras_key_frame in cameras_key_frames:
            key_frames.append(cameras_key_frame)
            key_frames.append(cameras_key_frame)

        mm = MovieMaker(self._be)
        mm.build_camera_path(key_frames, 250, 150)
        self._log(1, '- Total number of frames: %d' % mm.get_nb_frames())

        self._core.set_application_parameters(viewport=self._image_size)
        self._core.set_application_parameters(image_stream_fps=0)

        frames_to_render = list()
        if len(frame_list) != 0:
            frames_to_render = frame_list
        else:
            if end_frame == 0:
                end_frame = mm.get_nb_frames()
            for i in range(start_frame, end_frame + 1, frame_step):
                frames_to_render.append(i)

        cumulated_rendering_time = 0
        nb_frames = len(frames_to_render)
        frame_count = 1

        '''Clipping planes'''
        self._set_clipping_planes()

        '''Frames'''
        for frame in frames_to_render:
            try:
                start = time.time()
                self._log(1, '- Rendering frame %i (%i/%i)' % (frame, frame_count, nb_frames))
                self._log(1, '------------------------------')

                '''Stop rendering during the loading of the scene'''
                status = self._core.set_renderer(
                    samples_per_pixel=1, subsampling=1, max_accum_frames=1)

                '''Frame setup'''
                self._build_frame(frame)
                mm.set_current_frame(
                    frame=frame, camera_params=self._core.BioExplorerPerspectiveCameraParams())

                self._log(1, '- Frame buffers')
                for shader in self._shaders:
                    '''Rendering settings'''
                    self._log(2, '-   ' + shader)
                    self._create_snapshot(shader, frame, mm)

                end = time.time()

                rendering_time = end - start
                cumulated_rendering_time += rendering_time
                average_rendering_time = cumulated_rendering_time / frame_count
                remaining_rendering_time = (nb_frames - frame_count) * average_rendering_time
                self._log(1, '------------------------------')
                self._log(1, 'Frame %i successfully rendered in %i seconds' %
                          (frame, rendering_time))

                hours = math.floor(remaining_rendering_time / 3600)
                minutes = math.floor((remaining_rendering_time - hours * 3600) / 60)
                seconds = math.floor(remaining_rendering_time - hours * 3600 - minutes * 60)

                expected_end_time = datetime.now() + timedelta(seconds=remaining_rendering_time)
                self._log(1, 'Estimated remaining time: %i hours, %i minutes, %i seconds' %
                          (hours, minutes, seconds))
                self._log(1, 'Expected end time       : %s' % expected_end_time)
                self._log(
                    1, '--------------------------------------------------------------------------------')
                frame_count += 1
            except Exception as e:
                self._log(1, 'ERROR: Failed to render frame %i' % frame)
                self._log(1, str(e))
                time.sleep(10)
                self._be = BioExplorer(self._url)
                self._core = self._be.core_api()
                mm = MovieMaker(self._be)

        self._core.set_application_parameters(image_stream_fps=20)
        self._log(1, 'Movie rendered, live long and prosper \V/')