def test_track_finding(self):
     # Test 1:
     track_analysis.find_tracks(input_tracklets_file=analysis_utils.get_data('fixtures/track_analysis/Tracklets_small.h5',
                                                                             output=os.path.join(testing_path,
                                                                                                 'fixtures/track_analysis/Tracklets_small.h5')),
                                input_alignment_file=analysis_utils.get_data('fixtures/track_analysis/Alignment_result.h5',
                                                                             output=os.path.join(testing_path,
                                                                                                 'fixtures/track_analysis/Alignment_result.h5')),
                                output_track_candidates_file=os.path.join(self.output_folder, 'TrackCandidates.h5'))
     data_equal, error_msg = test_tools.compare_h5_files(analysis_utils.get_data('fixtures/track_analysis/TrackCandidates_result.h5',
                                                                                 output=os.path.join(testing_path,
                                                                                                     'fixtures/track_analysis/TrackCandidates_result.h5')), os.path.join(self.output_folder, 'TrackCandidates.h5'))
     self.assertTrue(data_equal, msg=error_msg)
     # Test 2: chunked
     track_analysis.find_tracks(input_tracklets_file=analysis_utils.get_data('fixtures/track_analysis/Tracklets_small.h5',
                                                                             output=os.path.join(testing_path,
                                                                                                 'fixtures/track_analysis/Tracklets_small.h5')),
                                input_alignment_file=analysis_utils.get_data('fixtures/track_analysis/Alignment_result.h5',
                                                                             output=os.path.join(testing_path,
                                                                                                 'fixtures/track_analysis/Alignment_result.h5')),
                                output_track_candidates_file=os.path.join(self.output_folder, 'TrackCandidates_2.h5'),
                                chunk_size=293)
     data_equal, error_msg = test_tools.compare_h5_files(analysis_utils.get_data('fixtures/track_analysis/TrackCandidates_result.h5',
                                                                                 output=os.path.join(testing_path,
                                                                                                     'fixtures/track_analysis/TrackCandidates_result.h5')),
                                                         os.path.join(self.output_folder, 'TrackCandidates_2.h5'))
     self.assertTrue(data_equal, msg=error_msg)
def run_analysis():
    # The location of the example data files, one file per DUT
    data_files = [
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane0.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane1.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane2.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\fe_dut-converted-synchronized_plane0.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\fe_dut-converted-synchronized_plane1.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane3.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane4.h5',
        r'H:\Testbeam_05052016_LFCMOS\Telescope_data\kartel-converted-synchronized_plane5.h5'
    ]  # The first device is the reference for the coordinate system

    # Pixel dimesions and matrix size of the DUTs
    pixel_size = [(18.4, 18.4), (18.4, 18.4), (18.4, 18.4), (250, 50),
                  (250, 50), (18.4, 18.4), (18.4, 18.4),
                  (18.4, 18.4)]  # (Column, row) pixel pitch in um
    n_pixels = [(1152, 576), (1152, 576), (1152, 576), (80, 336), (80, 336),
                (1152, 576), (1152, 576),
                (1152, 576)]  # (Column, row) dimensions of the pixel matrix
    z_positions = [
        0., 20000, 40000, 40000 + 101000, 40000 + 101000 + 23000, 247000,
        267000, 287000
    ]  # in um
    dut_names = ("Tel 0", "Tel 1", "Tel 2", "LFCMOS3", "FEI4 Reference",
                 "Tel 3", "Tel 4", "Tel 5")  # Friendly names for plotting

    # Folder where all output data and plots are stored
    output_folder = r'H:\Testbeam_05052016_LFCMOS\output'

    # The following shows a complete test beam analysis by calling the seperate function in correct order

    # Generate noisy pixel mask for all DUTs
    threshold = [2, 2, 2, 10, 10, 2, 2, 2]
    for i, data_file in enumerate(data_files):
        hit_analysis.generate_pixel_mask(input_hits_file=data_file,
                                         n_pixel=n_pixels[i],
                                         pixel_mask_name='NoisyPixelMask',
                                         pixel_size=pixel_size[i],
                                         threshold=threshold[i],
                                         dut_name=dut_names[i])

    # Cluster hits from all DUTs
    column_cluster_distance = [3, 3, 3, 2, 2, 3, 3, 3]
    row_cluster_distance = [3, 3, 3, 3, 3, 3, 3, 3]
    frame_cluster_distance = [0, 0, 0, 0, 0, 0, 0, 0]
    for i, data_file in enumerate(data_files):
        hit_analysis.cluster_hits(
            input_hits_file=data_file,
            input_noisy_pixel_mask_file=os.path.splitext(data_files[i])[0] +
            '_noisy_pixel_mask.h5',
            min_hit_charge=0,
            max_hit_charge=13,
            column_cluster_distance=column_cluster_distance[i],
            row_cluster_distance=row_cluster_distance[i],
            frame_cluster_distance=frame_cluster_distance[i],
            dut_name=dut_names[i])

    # Generate filenames for cluster data
    input_cluster_files = [
        os.path.splitext(data_file)[0] + '_clustered.h5'
        for data_file in data_files
    ]

    # Correlate the row / column of each DUT
    dut_alignment.correlate_cluster(input_cluster_files=input_cluster_files,
                                    output_correlation_file=os.path.join(
                                        output_folder, 'Correlation.h5'),
                                    n_pixels=n_pixels,
                                    pixel_size=pixel_size,
                                    dut_names=dut_names)

    # Create prealignment relative to the first DUT from the correlation data
    dut_alignment.prealignment(
        input_correlation_file=os.path.join(output_folder, 'Correlation.h5'),
        output_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        z_positions=z_positions,
        pixel_size=pixel_size,
        dut_names=dut_names,
        fit_background=True,
        non_interactive=False
    )  # Tries to find cuts automatically; deactivate to do this manualy

    # Merge the cluster tables to one merged table aligned at the event number
    dut_alignment.merge_cluster_data(input_cluster_files=input_cluster_files,
                                     output_merged_file=os.path.join(
                                         output_folder, 'Merged.h5'),
                                     n_pixels=n_pixels,
                                     pixel_size=pixel_size)

    # Apply the prealignment to the merged cluster table to create tracklets
    dut_alignment.apply_alignment(
        input_hit_file=os.path.join(output_folder, 'Merged.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_hit_file=os.path.join(output_folder, 'Tracklets_prealigned.h5'),
        force_prealignment=True)

    # Find tracks from the prealigned tracklets and stores the with quality indicator into track candidates table
    track_analysis.find_tracks(
        input_tracklets_file=os.path.join(output_folder,
                                          'Tracklets_prealigned.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealignment.h5'))

    # Select tracks with a hit in the time reference (DUT 4) and all position devices to increase analysis speed due to data reduction
    data_selection.select_hits(hit_file=os.path.join(
        output_folder, 'TrackCandidates_prealignment.h5'),
                               track_quality=0b11110111,
                               track_quality_mask=0b11110111)

    # Do an alignment step with the track candidates, corrects rotations and is therefore much more precise than simple prealignment
    dut_alignment.alignment(
        input_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealignment_reduced.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        # Order of combinaions of planes to align, one should start with high resoultion planes (here: telescope planes)
        align_duts=[
            [0, 1, 2, 5, 6, 7],  # align the telescope planes first
            [4],  # align the time reference after the telescope alignment
            [3]
        ],  # align the DUT last and not with the reference since it is rather small and would make the time reference alinmnt worse
        # The DUTs to be used in the fit, always just the high resolution Mimosa26 planes used
        select_fit_duts=[0, 1, 2, 5, 6, 7],
        # The DUTs to be required to have a hit for the alignment
        select_hit_duts=[
            [0, 1, 2, 4, 5, 6, 7],  # Take tracks with time reference hit
            [0, 1, 2, 4, 5, 6, 7],  # Take tracks with time reference hit
            [0, 1, 2, 3, 4, 5, 6, 7]
        ],  # Also require hit in the small DUT
        # The required track quality per alignment step and DUT
        selection_track_quality=[
            [1, 1, 1, 0, 1, 1,
             1],  # Do not require a good hit in the time refernce
            [1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 1, 0, 1, 1, 1]
        ],  # Do not require a good hit in the small DUT
        initial_rotation=[
            [0., 0., 0.],
            [0., 0., 0.],
            [0., 0., 0.],
            # Devices 3, 4 are heavily rotated (inverted), this is not implemented now
            # Thus one has to set the correct rotation angles here manually
            [np.pi - 0.05, -0.05, -0.005],
            [np.pi - 0.01, -0.02, -0.0005],
            [0., 0, 0.],
            [0., 0, 0.],
            [0., 0, 0.]
        ],
        initial_translation=[
            [0., 0, 0.],
            [0., 0, 0.],
            [0., 0, 0.],
            # Devices 3, 4 are heavily rotated (inverted), this is not implemented now
            # Thus one has to set the correct positions here manually
            [11540, 18791, 0.],
            [710., 9851., 0.],
            [0., 0, 0.],
            [0., 0, 0.],
            [0., 0, 0.]
        ],
        n_pixels=n_pixels,
        use_n_tracks=
        200000,  # Do the alignment only on a subset of data, needed for reasonable run time
        pixel_size=pixel_size)

    # Apply new alignment to data
    # Revert alignment from track candidates. Usually one would just apply the alignment to the merged data.
    # Due to the large beam angle track finding fails on aligned data. Thus rely on the found tracks from prealignment.
    dut_alignment.apply_alignment(
        input_hit_file=os.path.join(output_folder,
                                    'TrackCandidates_prealignment_reduced.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_hit_file=os.path.join(
            output_folder, 'Merged_small.h5'
        ),  # This is the new not aligned but preselected merged data file to apply (pre-) alignment on
        inverse=True,
        force_prealignment=True)

    # Apply the alignment to the merged cluster table to create tracklets
    dut_alignment.apply_alignment(
        input_hit_file=os.path.join(output_folder, 'Merged_small.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_hit_file=os.path.join(output_folder, 'TrackCandidates.h5'))

    # Fit track using alignment
    track_analysis.fit_tracks(
        input_track_candidates_file=os.path.join(output_folder,
                                                 'TrackCandidates.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks.h5'),
        select_hit_duts=[0, 1, 2, 4, 5, 6, 7],
        select_fit_duts=[0, 1, 2, 5, 6, 7],
        selection_track_quality=1
    )  # Take all tracks with good hits, do not care about time reference hit quality

    # Create unconstrained residuals with aligned data
    result_analysis.calculate_residuals(
        input_tracks_file=os.path.join(output_folder, 'Tracks.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_residuals_file=os.path.join(output_folder, 'Residuals.h5'),
        n_pixels=n_pixels,
        pixel_size=pixel_size)

    # Calculate efficiency with aligned data
    result_analysis.calculate_efficiency(
        input_tracks_file=os.path.join(output_folder, 'Tracks.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_efficiency_file=os.path.join(output_folder, 'Efficiency.h5'),
        bin_size=(10, 10),
        use_duts=[3],
        sensor_size=[(20000, 10000), (20000, 10000), (20000, 10000),
                     (20000, 20000), (20000, 10000), (20000, 10000),
                     (20000, 10000)])

    # Fit tracks using prealignmend
    track_analysis.fit_tracks(
        input_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealignment_reduced.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_tracks_file=os.path.join(output_folder,
                                        'Tracks_prealignment.h5'),
        force_prealignment=True,
        select_hit_duts=[0, 1, 2, 4, 5, 6, 7],
        select_fit_duts=[0, 1, 2, 5, 6, 7],
        selection_track_quality=1
    )  # Take all tracks with good hits, do not care about time reference hit quality

    # Create unconstrained residuals with prealigned data
    result_analysis.calculate_residuals(
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_prealignment.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_prealignment.h5'),
        force_prealignment=True,
        n_pixels=n_pixels,
        pixel_size=pixel_size)

    # Create efficiency plot with prealigned data
    result_analysis.calculate_efficiency(
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_prealignment.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_efficiency_file=os.path.join(output_folder,
                                            'Efficiency_prealignment.h5'),
        force_prealignment=True,
        bin_size=(10, 10),
        use_duts=[3],
        sensor_size=[(20000, 10000), (20000, 10000), (20000, 10000),
                     (20000, 20000), (20000, 10000), (20000, 10000),
                     (20000, 10000)])
def run_analysis(hit_files):
    # Create output subfolder where all output data and plots are stored
    output_folder = os.path.join(
        os.path.split(hit_files[0])[0], 'output_eutelescope')
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    mask_files = [(os.path.splitext(hit_file)[0] + '_mask.h5')
                  for hit_file in hit_files]
    cluster_files = [(os.path.splitext(hit_file)[0] + '_clustered.h5')
                     for hit_file in hit_files]

    z_positions = [0.0, 150000.0, 300000.0, 450000.0, 600000.0,
                   750000.0]  # in um
    material_budget = [
        100.0 / 125390.0, 100.0 / 125390.0, 100.0 / 125390.0, 100.0 / 125390.0,
        100.0 / 125390.0, 100.0 / 125390.0
    ]
    initial_configuration = os.path.join(output_folder, 'telescope.yaml')
    telescope = Telescope()
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=0,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[0],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[0],
                      name="Telescope 1")
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=1,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[1],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[1],
                      name="Telescope 2")
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=2,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[2],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[2],
                      name="Telescope 3")
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=3,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[3],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[3],
                      name="Telescope 4")
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=4,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[4],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[4],
                      name="Telescope 5")
    telescope.add_dut(dut_type="Mimosa26",
                      dut_id=5,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[5],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      material_budget=material_budget[5],
                      name="Telescope 6")
    telescope.save_configuration(initial_configuration)
    prealigned_configuration = os.path.join(output_folder,
                                            'telescope_prealigned.yaml')
    aligned_configuration = os.path.join(output_folder,
                                         'telescope_aligned.yaml')

    check_files = hit_analysis.check(
        telescope_configuration=initial_configuration,
        input_hit_files=hit_files)

    # Generate noisy pixel mask for all DUTs
    thresholds = [2, 2, 2, 2, 2, 2]
    # last plane has noisy cluster, use larger median filter to mask cluster
    pixel_mask_names = [
        "NoisyPixelMask", "NoisyPixelMask", "NoisyPixelMask", "NoisyPixelMask",
        "NoisyPixelMask", "DisabledPixelMask"
    ]
    mask_files = hit_analysis.mask(
        telescope_configuration=initial_configuration,
        input_hit_files=hit_files,
        pixel_mask_names=pixel_mask_names,
        thresholds=thresholds)

    # Cluster hits from all DUTs
    use_positions = [False, False, False, False, False, False]
    min_hit_charges = [0, 0, 0, 0, 0, 0]
    max_hit_charges = [1, 1, 1, 1, 1, 1]
    column_cluster_distances = [3, 3, 3, 3, 3, 3]
    row_cluster_distances = [3, 3, 3, 3, 3, 3]
    frame_cluster_distances = [0, 0, 0, 0, 0, 0]
    cluster_files = hit_analysis.cluster(
        telescope_configuration=initial_configuration,
        select_duts=None,
        input_hit_files=hit_files,
        input_mask_files=[
            None if val else mask_files[i]
            for i, val in enumerate(use_positions)
        ],
        use_positions=use_positions,
        min_hit_charges=min_hit_charges,
        max_hit_charges=max_hit_charges,
        column_cluster_distances=column_cluster_distances,
        row_cluster_distances=row_cluster_distances,
        frame_cluster_distances=frame_cluster_distances)

    # Correlate each DUT with the first DUT
    hit_analysis.correlate(telescope_configuration=initial_configuration,
                           input_files=cluster_files,
                           output_correlation_file=os.path.join(
                               output_folder, 'Correlation.h5'),
                           resolution=(50.0, 50.0),
                           select_reference_duts=0)

    # Create pre-alignment, take first DUT as reference
    prealigned_configuration = dut_alignment.prealign(
        telescope_configuration=initial_configuration,
        input_correlation_file=os.path.join(output_folder, 'Correlation.h5'),
        reduce_background=True,
        select_reference_dut=0)

    # Merge all cluster tables into a single table
    hit_analysis.merge_cluster_data(
        telescope_configuration=initial_configuration,
        input_cluster_files=cluster_files,
        output_merged_file=os.path.join(output_folder, 'Merged.h5'))

    # Example 1:
    # The following 4 steps are for demonstration purpose only.
    # They show track finding, fitting and selection, and residual calculation on pre-aligned hits.
    # Usually you would not do this and you would use fully aligned hits instead.

    # Step 1:
    # Find tracks from the tracklets and create a track candidates table
    track_analysis.find_tracks(
        telescope_configuration=prealigned_configuration,
        input_merged_file=os.path.join(output_folder, 'Merged.h5'),
        output_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealigned.h5'),
        align_to_beam=True)

    # Step 2:
    # Fit the track candidates, assign quality flags, and create a track table
    track_analysis.fit_tracks(telescope_configuration=prealigned_configuration,
                              input_track_candidates_file=os.path.join(
                                  output_folder,
                                  'TrackCandidates_prealigned.h5'),
                              output_tracks_file=os.path.join(
                                  output_folder, 'Tracks_prealigned.h5'),
                              select_duts=[0, 1, 2, 3, 4, 5],
                              select_fit_duts=[0, 1, 2, 3, 4, 5],
                              select_hit_duts=[0, 1, 2, 3, 4, 5],
                              exclude_dut_hit=True,
                              isolation_distances=(1000.0, 1000.0),
                              use_limits=False,
                              plot=True)

    # Step 3:
    # Do additional track selection cuts on the tracks table
    data_selection.select_tracks(
        telescope_configuration=prealigned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_prealigned.h5'),
        output_tracks_file=os.path.join(output_folder,
                                        'Tracks_prealigned_selected.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_hit_duts=[0, 1, 2, 3, 4, 5],
        select_no_hit_duts=None,
        select_quality_duts=[[1, 2, 3, 4, 5], [0, 2, 3, 4, 5], [0, 1, 3, 4, 5],
                             [0, 1, 2, 4, 5], [0, 1, 2, 3, 5], [0, 1, 2, 3,
                                                                4]],
        query='(track_chi2 < 500)')

    # Step 4:
    # Calculate the unconstrained residuals from pre-aligned tracks to check the pre-alignment
    result_analysis.calculate_residuals(
        telescope_configuration=prealigned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_prealigned_selected.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_prealigned.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        nbins_per_pixel=20,
        use_limits=True)

    # Create alignment, take first and last DUT as reference ("select_telescope_duts" parameter)
    # The position (translation and rotation) of telescope DUTs are not changed
    aligned_configuration = dut_alignment.align(
        telescope_configuration=prealigned_configuration,
        input_merged_file=os.path.join(output_folder, 'Merged.h5'),
        select_duts=[[0, 1, 2, 3, 4, 5]],  # align the telescope planes first
        select_telescope_duts=[0, 1, 2, 3, 4, 5],  # telescope planes
        select_fit_duts=[[0, 1, 2, 3, 4, 5]],
        select_hit_duts=[[0, 1, 2, 3, 4, 5]],
        max_iterations=[5],
        max_events=(100000),
        track_chi2=15.0,
        quality_distances=[(18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=True,
        plot=True)

    # Find tracks from the tracklets and create a track candidates table
    track_analysis.find_tracks(telescope_configuration=aligned_configuration,
                               input_merged_file=os.path.join(
                                   output_folder, 'Merged.h5'),
                               output_track_candidates_file=os.path.join(
                                   output_folder,
                                   'TrackCandidates_aligned.h5'),
                               align_to_beam=True)

    # Fit the track candidates, assign quality flags, and create a track table
    track_analysis.fit_tracks(
        telescope_configuration=aligned_configuration,
        input_track_candidates_file=os.path.join(output_folder,
                                                 'TrackCandidates_aligned.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_fit_duts=[0, 1, 2, 3, 4, 5],
        select_hit_duts=[0, 1, 2, 3, 4, 5],
        exclude_dut_hit=True,
        quality_distances=[(18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=False,
        plot=True)

    # Calculate the unconstrained residuals from all tracks
    result_analysis.calculate_residuals(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_aligned.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        nbins_per_pixel=20,
        use_limits=True)

    # Do additional track selection cuts on the tracks table
    data_selection.select_tracks(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        output_tracks_file=os.path.join(output_folder,
                                        'Tracks_aligned_selected.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_hit_duts=[0, 1, 2, 3, 4, 5],
        select_no_hit_duts=None,
        select_quality_duts=[[1, 2, 3, 4, 5], [0, 2, 3, 4, 5], [0, 1, 3, 4, 5],
                             [0, 1, 2, 4, 5], [0, 1, 2, 3, 5], [0, 1, 2, 3,
                                                                4]],
        query='(track_chi2 < 15.0)')

    # Calculate the unconstrained residuals from final tracks (with chi^2 cut and quality selection)
    result_analysis.calculate_residuals(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_aligned_selected.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        nbins_per_pixel=20,
        use_limits=True)

    # Example 2:
    # Use only 2 DUTs next to the fit DUT and cut on track quality.
    # Thus the track fit is just a track interpolation with chi2 = 0.
    # This is better here due to heavily scatterd tracks, where a straight line
    # assumption for all DUTs is wrong.
    # This leads to symmetric residuals in x and y for all DUTs between 2 DUTs
    # (= DUTs: 1, 2, 3, 4)
    track_analysis.fit_tracks(
        telescope_configuration=aligned_configuration,
        input_track_candidates_file=os.path.join(output_folder,
                                                 'TrackCandidates_aligned.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks_pair.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_fit_duts=[
            [1, 2],  # Only select DUTs next to the DUT to fit
            [0, 2],
            [1, 3],
            [2, 4],
            [3, 5],
            [3, 4]
        ],
        exclude_dut_hit=True,
        quality_distances=[(18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2),
                           (18.4 * 2, 18.4 * 2), (18.4 * 2, 18.4 * 2)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=False,
        plot=True)

    # Do additional track selection cuts on the tracks table
    data_selection.select_tracks(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_pair.h5'),
        output_tracks_file=os.path.join(output_folder,
                                        'Tracks_pair_selected.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_hit_duts=[0, 1, 2, 3, 4, 5],
        select_no_hit_duts=None,
        select_quality_duts=[[1, 2, 3, 4, 5], [0, 2, 3, 4, 5], [0, 1, 3, 4, 5],
                             [0, 1, 2, 4, 5], [0, 1, 2, 3, 5], [0, 1, 2, 3,
                                                                4]],
        query='(track_chi2 < 5.0)')

    # Calculate the unconstrained residuals from final from final tracks (with chi^2 cut and quality selection)
    result_analysis.calculate_residuals(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_pair_selected.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_pair_selected.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        nbins_per_pixel=20,
        use_limits=True)
def run_analysis(hit_files):
    # Create output subfolder where all output data and plots are stored
    output_folder = os.path.join(
        os.path.split(hit_files[0])[0], 'output_fei4_telescope')
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    mask_files = [(os.path.splitext(hit_file)[0] + '_mask.h5')
                  for hit_file in hit_files]
    cluster_files = [(os.path.splitext(hit_file)[0] + '_clustered.h5')
                     for hit_file in hit_files]

    z_positions = [0.0, 19500.0, 108800.0, 128300.0]  # in um
    initial_configuration = os.path.join(output_folder, 'telescope.yaml')
    telescope = Telescope()
    telescope.add_dut(dut_type="FEI4",
                      dut_id=0,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[0],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 1")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=1,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[1],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 2")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=2,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[2],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 3")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=3,
                      translation_x=0,
                      translation_y=0,
                      translation_z=z_positions[3],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 4")
    telescope.save_configuration(initial_configuration)
    prealigned_configuration = os.path.join(output_folder,
                                            'telescope_prealigned.yaml')
    aligned_configuration = os.path.join(output_folder,
                                         'telescope_aligned.yaml')

    check_files = hit_analysis.check(
        telescope_configuration=initial_configuration,
        input_hit_files=hit_files)

    # Generate noisy pixel mask for all DUTs
    thresholds = [100, 100, 100, 100]
    pixel_mask_names = ["NoisyPixelMask"] * len(thresholds)
    mask_files = hit_analysis.mask(
        telescope_configuration=initial_configuration,
        input_hit_files=hit_files,
        pixel_mask_names=pixel_mask_names,
        thresholds=thresholds)

    # Cluster hits from all DUTs
    use_positions = [False, False, False, False]
    min_hit_charges = [0, 0, 0, 0]
    max_hit_charges = [13, 13, 13, 13]
    column_cluster_distances = [1, 1, 1, 1]
    row_cluster_distances = [3, 3, 3, 3]
    frame_cluster_distances = [4, 4, 4, 4]
    cluster_files = hit_analysis.cluster(
        telescope_configuration=initial_configuration,
        select_duts=None,
        input_hit_files=hit_files,
        input_mask_files=[
            None if val else mask_files[i]
            for i, val in enumerate(use_positions)
        ],
        use_positions=use_positions,
        min_hit_charges=min_hit_charges,
        max_hit_charges=max_hit_charges,
        column_cluster_distances=column_cluster_distances,
        row_cluster_distances=row_cluster_distances,
        frame_cluster_distances=frame_cluster_distances)

    # Correlate each DUT with the first DUT
    hit_analysis.correlate(telescope_configuration=initial_configuration,
                           input_files=cluster_files,
                           output_correlation_file=os.path.join(
                               output_folder, 'Correlation.h5'),
                           resolution=(250.0, 50.0),
                           select_reference_duts=0)

    # Create pre-alignment, take first DUT as reference
    prealigned_configuration = dut_alignment.prealign(
        telescope_configuration=initial_configuration,
        input_correlation_file=os.path.join(output_folder, 'Correlation.h5'),
        reduce_background=True,
        select_reference_dut=0)

    # Merge all cluster tables into a single table
    hit_analysis.merge_cluster_data(
        telescope_configuration=initial_configuration,
        input_cluster_files=cluster_files,
        output_merged_file=os.path.join(output_folder, 'Merged.h5'))

    # Create alignment, take first and last DUT as reference (telescope DUTs)
    aligned_configuration = dut_alignment.align(
        telescope_configuration=prealigned_configuration,
        input_merged_file=os.path.join(output_folder, 'Merged.h5'),
        select_duts=[[0, 1, 2, 3]],  # align all planes at once
        select_telescope_duts=[
            0, 3
        ],  # add outermost planes, z-axis positions are fixed for telescope DUTs, if not stated otherwise (see select_alignment_parameters)
        select_fit_duts=[0, 1, 2, 3],  # use all DUTs for track fit
        select_hit_duts=[[0, 1, 2, 3]],  # require hits in all DUTs
        max_iterations=[
            7
        ],  # number of alignment iterations, the higher the number the more precise
        max_events=(100000),  # limit number of events to speed up alignment
        quality_distances=[(250.0, 50.0), (250.0, 50.0), (250.0, 50.0),
                           (250.0, 50.0)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=True,
        plot=True)

    # Find tracks from the tracklets and create a track candidates table
    track_analysis.find_tracks(telescope_configuration=aligned_configuration,
                               input_merged_file=os.path.join(
                                   output_folder, 'Merged.h5'),
                               output_track_candidates_file=os.path.join(
                                   output_folder,
                                   'TrackCandidates_aligned.h5'),
                               align_to_beam=True)

    # Fit the track candidates, assign quality flags, and create a track table
    track_analysis.fit_tracks(
        telescope_configuration=aligned_configuration,
        input_track_candidates_file=os.path.join(output_folder,
                                                 'TrackCandidates_aligned.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        select_duts=[0, 1, 2, 3],
        select_fit_duts=(0, 1, 2, 3),
        select_hit_duts=(0, 1, 2, 3),
        exclude_dut_hit=True,
        quality_distances=[(250.0, 50.0), (250.0, 50.0), (250.0, 50.0),
                           (250.0, 50.0)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=False,
        plot=True)

    # Do additional track selection cuts on the tracks table
    data_selection.select_tracks(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        output_tracks_file=os.path.join(output_folder,
                                        'Tracks_aligned_selected.h5'),
        select_duts=[0, 1, 2, 3],
        select_hit_duts=[[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]],
        select_no_hit_duts=None,
        select_quality_duts=[[1, 2, 3], [0, 2, 3], [0, 1, 3], [0, 1, 2]],
        query='(track_chi2 < 10)')

    # Calculate the unconstrained residuals from final tracks to check the alignment
    result_analysis.calculate_residuals(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_aligned.h5'),
        select_duts=[0, 1, 2, 3],
        nbins_per_pixel=20,
        use_limits=True)

    # Plotting of the tracks angles of the final tracks
    result_analysis.histogram_track_angle(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        output_track_angle_file=None,
        n_bins=200,
        select_duts=[0, 1, 2, 3],
        plot=True)

    # Plotting of the 2D tracks density of the final tracks
    plot_utils.plot_track_density(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        select_duts=[0, 1, 2, 3])

    # Plotting of the 2D charge distribution of the final tracks
    plot_utils.plot_charge_distribution(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        select_duts=[0, 1, 2, 3])

    # Plotting of some final tracks (or track candidates) from selected event range
    plot_utils.plot_events(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        output_pdf_file=os.path.join(output_folder, 'Events.pdf'),
        select_duts=[1],
        event_range=(0, 40))

    # Create final efficiency plots from final tracks
    result_analysis.calculate_efficiency(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder,
                                       'Tracks_aligned_selected.h5'),
        output_efficiency_file=os.path.join(output_folder, 'Efficiency.h5'),
        select_duts=[0, 1, 2, 3],
        resolutions=(250, 50),
        extend_areas=(2000, 2000),
        plot_ranges=None,
        efficiency_regions=None,
        minimum_track_density=1,
        cut_distances=(1000.0, 1000.0))
Пример #5
0
def run_analysis(n_events):
    # Start simulator with random seed 0
    sim = SimulateData(random_seed=0)

    # All simulator std. settings are listed here and can be changed
    # Dimensions are in um, angles in mRad, temperatures in Kelvin
    # voltages in Volt

    # General setup
    sim.n_duts = 6  # Number of DUTs in the simulation
    sim.z_positions = [i * 10000 for i in range(sim.n_duts)]
    sim.offsets = [(-10000 + 111 * 0., -10000 + 111 * 0.)
                   for i in range(sim.n_duts)]
    sim.rotations = [(0, 0, 0)] * sim.n_duts  # in rotation around x, y, z axis
    sim.temperature = 300  # needed for charge sharing calculation

    # Beam related settings
    sim.beam_position = (0, 0)  # Average beam position in x, y at z = 0
    sim.beam_position_sigma = (2000, 2000)  # in x, y at z = 0
    sim.beam_momentum = 3200  # MeV
    sim.beam_angle = 0  # Average beam angle in theta at z = 0
    sim.beam_angle_sigma = 2  # Deviation of average beam angle in theta
    sim.tracks_per_event = 3  # Average number of tracks per event
    # Deviation from the average number of tracks
    # Allows for no track per event possible!
    sim.tracks_per_event_sigma = 1

    # Device specific settings
    sim.dut_bias = [50] * sim.n_duts  # Sensor bias voltage
    sim.dut_thickness = [200] * sim.n_duts  # Sensor thickness
    # Detection threshold for each device in electrons, influences efficiency!
    sim.dut_threshold = [0.] * sim.n_duts
    sim.dut_noise = [0.] * sim.n_duts  # Noise for each device in electrons
    sim.dut_pixel_size = [(50, 18.4)] * sim.n_duts  # Pixel size in x / y
    sim.dut_n_pixel = [(400, 1100)] * sim.n_duts  # Number of pixel in x / y
    # Efficiency for each device from 0. to 1. for hits above threshold
    sim.dut_efficiencies = [1.] * sim.n_duts
    # The effective material budget (sensor + passive compoonents) given in
    # total material distance / total radiation length
    # (https://cdsweb.cern.ch/record/1279627/files/PH-EP-Tech-Note-2010-013.pdf)
    # 0 means no multiple scattering; std. setting is the sensor thickness made
    # of silicon as material budget
    sim.dut_material_budget = [
        sim.dut_thickness[i] * 1e-4 / 9.370 for i in range(sim.n_duts)
    ]
    # Digitization settings
    sim.digitization_charge_sharing = True
    # Shuffle hits per event to challenge track finding
    sim.digitization_shuffle_hits = True
    # Translate hit position on DUT plane to channel indices (column / row)
    sim.digitization_pixel_discretization = True

    # Create the data
    output_folder = 'simulation'  # Define a folder for output data
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    sim.create_data_and_store(os.path.join(output_folder, 'simulated_data'),
                              n_events=n_events)

    # The simulated data files, one file per DUT
    data_files = [
        os.path.join(output_folder, r'simulated_data_DUT%d.h5' % i)
        for i in range(sim.n_duts)
    ]

    # The following shows a complete test beam analysis by calling the separate
    # function in correct order

    # Cluster hits from all DUTs
    for i, data_file in enumerate(data_files):
        hit_analysis.cluster_hits(input_hits_file=data_file,
                                  min_hit_charge=1,
                                  max_hit_charge=2**16,
                                  column_cluster_distance=1,
                                  row_cluster_distance=1,
                                  frame_cluster_distance=2,
                                  dut_name=data_files[i])

    # Generate filenames for cluster data
    input_cluster_files = [
        os.path.splitext(data_file)[0] + '_clustered.h5'
        for data_file in data_files
    ]

    # Correlate the row / column of each DUT
    dut_alignment.correlate_cluster(input_cluster_files=input_cluster_files,
                                    output_correlation_file=os.path.join(
                                        output_folder, 'Correlation.h5'),
                                    n_pixels=sim.dut_n_pixel,
                                    pixel_size=sim.dut_pixel_size)

    # Create alignment data for the DUT positions to the first DUT from the
    # correlation data. When needed, set offset and error cut for each DUT
    # as list of tuples
    dut_alignment.prealignment(
        input_correlation_file=os.path.join(output_folder, 'Correlation.h5'),
        output_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        z_positions=sim.z_positions,
        pixel_size=sim.dut_pixel_size,
        # Deactivate if you have a large dataset, enhances alignment slightly
        no_fit=True,
        fit_background=False
        if not (sim.tracks_per_event or sim.tracks_per_event_sigma) else True,
        # Tries to find cuts automatically; deactivate to do this manualy
        non_interactive=True)

    # Correct all DUT hits via alignment information and merge the cluster
    # tables to one tracklets table aligned at the event number
    dut_alignment.merge_cluster_data(input_cluster_files=input_cluster_files,
                                     output_merged_file=os.path.join(
                                         output_folder, 'Merged.h5'),
                                     n_pixels=sim.dut_n_pixel,
                                     pixel_size=sim.dut_pixel_size)

    dut_alignment.apply_alignment(
        input_hit_file=os.path.join(output_folder, 'Merged.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_hit_file=os.path.join(output_folder, 'Tracklets_prealigned.h5'),
        # If there is already an alignment info in the alignment file this has
        # to be set
        force_prealignment=True)

    # Find tracks from the tracklets and stores the with quality indicator
    # into track candidates table
    track_analysis.find_tracks(
        input_tracklets_file=os.path.join(output_folder,
                                          'Tracklets_prealigned.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealigned.h5'))

    # Fit the track candidates and create new track table
    track_analysis.fit_tracks(
        input_track_candidates_file=os.path.join(
            output_folder, 'TrackCandidates_prealigned.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks_prealigned.h5'),
        # To get unconstrained residuals do not use DUT hit for track fit
        exclude_dut_hit=True,
        selection_track_quality=0,
        # To get close to excact efficiency heavily avoid merged tracks
        min_track_distance=1000,
        force_prealignment=True)

    result_analysis.calculate_efficiency(
        input_tracks_file=os.path.join(output_folder, 'Tracks_prealigned.h5'),
        input_alignment_file=os.path.join(output_folder, 'Alignment.h5'),
        output_efficiency_file=os.path.join(output_folder, 'Efficiency.h5'),
        bin_size=[(250, 50)],
        sensor_size=[(250. * 80, 50. * 336)],
        minimum_track_density=2,
        use_duts=None,
        cut_distance=500,
        max_distance=500,
        col_range=None,
        row_range=None,
        pixel_size=sim.dut_pixel_size,
        n_pixels=sim.dut_n_pixel,
        force_prealignment=True,
        show_inefficient_events=True)
Пример #6
0
def run_analysis(n_events):
    # Start simulator with random seed 0
    sim = SimulateData(random_seed=0)

    # All simulator std. settings are listed here and can be changed
    # Dimensions are in um, angles in mRad, temperatures in Kelvin
    # voltages in Volt

    # General setup
    sim.n_duts = 6  # Number of DUTs in the simulation
    sim.z_positions = [i * 10000 for i in range(sim.n_duts)]
    sim.offsets = [(-10000 + 111 * 0., -10000 + 111 * 0.)
                   for i in range(sim.n_duts)]
    sim.rotations = [(0, 0, 0)] * sim.n_duts  # in rotation around x, y, z axis
    sim.temperature = 300  # needed for charge sharing calculation

    # Beam related settings
    sim.beam_position = (0, 0)  # Average beam position in x, y at z = 0
    sim.beam_position_sigma = (2000, 2000)  # in x, y at z = 0
    sim.beam_momentum = 3200  # MeV
    sim.beam_angle = 0  # Average beam angle in theta at z = 0
    sim.beam_angle_sigma = 2  # Deviation of average beam angle in theta
    sim.tracks_per_event = 3  # Average number of tracks per event
    # Deviation from the average number of tracks
    # Allows for no track per event possible!
    sim.tracks_per_event_sigma = 1

    # Device specific settings
    sim.dut_bias = [80] * sim.n_duts  # Sensor bias voltage
    sim.dut_thickness = [200] * sim.n_duts  # Sensor thickness
    # Detection threshold for each device in electrons, influences efficiency!
    sim.dut_threshold = [0.] * sim.n_duts
    sim.dut_noise = [0.] * sim.n_duts  # Noise for each device in electrons
    sim.dut_pixel_size = [(250.0, 50.0)] * sim.n_duts  # Pixel size in x / y
    sim.dut_n_pixel = [(80, 336)] * sim.n_duts  # Number of pixel in x / y
    # Efficiency for each device from 0. to 1. for hits above threshold
    sim.dut_efficiencies = [1.] * sim.n_duts
    # The effective material budget (sensor + passive compoonents) given in
    # total material distance / total radiation length
    # (https://cdsweb.cern.ch/record/1279627/files/PH-EP-Tech-Note-2010-013.pdf)
    # 0 means no multiple scattering; std. setting is the sensor thickness made
    # of silicon as material budget
    sim.dut_material_budget = [
        sim.dut_thickness[i] * 1e-4 / 9.370 for i in range(sim.n_duts)
    ]
    # Digitization settings
    sim.digitization_charge_sharing = True
    # Shuffle hits per event to challenge track finding
    sim.digitization_shuffle_hits = True
    # Translate hit position on DUT plane to channel indices (column / row)
    sim.digitization_pixel_discretization = True

    # Create the data
    output_folder = 'simulation'  # Define a folder for output data
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)
    sim.create_data_and_store(os.path.join(output_folder, 'simulated_data'),
                              n_events=n_events)

    # The simulated data files, one file per DUT
    data_files = [
        os.path.join(output_folder, r'simulated_data_DUT%d.h5' % i)
        for i in range(sim.n_duts)
    ]

    initial_configuration = os.path.join(output_folder, 'telescope.yaml')
    telescope = Telescope()
    telescope.add_dut(dut_type="FEI4",
                      dut_id=0,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[0],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 1")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=1,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[1],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 2")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=2,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[2],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 3")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=3,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[3],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 4")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=4,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[4],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 5")
    telescope.add_dut(dut_type="FEI4",
                      dut_id=5,
                      translation_x=0,
                      translation_y=0,
                      translation_z=sim.z_positions[5],
                      rotation_alpha=0,
                      rotation_beta=0,
                      rotation_gamma=0,
                      name="Telescope 6")
    telescope.save_configuration(initial_configuration)
    prealigned_configuration = os.path.join(output_folder,
                                            'telescope_prealigned.yaml')
    aligned_configuration = os.path.join(output_folder,
                                         'telescope_aligned.yaml')

    # The following shows a complete test beam analysis by calling the separate
    # function in correct order

    # Cluster hits from all DUTs
    cluster_files = hit_analysis.cluster(
        telescope_configuration=initial_configuration,
        input_hit_files=data_files,
        select_duts=None,
        input_mask_files=[None] * sim.n_duts,
        use_positions=[False] * sim.n_duts,
        min_hit_charges=[1] * sim.n_duts,
        max_hit_charges=[2**16] * sim.n_duts,
        column_cluster_distances=[1] * sim.n_duts,
        row_cluster_distances=[1] * sim.n_duts,
        frame_cluster_distances=[2] * sim.n_duts,
    )

    # Generate filenames for cluster data
    # cluster_files = [os.path.splitext(data_file)[0] + '_clustered.h5'
    #                        for data_file in data_files]

    # Correlate the row / column of each DUT
    hit_analysis.correlate(telescope_configuration=initial_configuration,
                           input_files=cluster_files,
                           output_correlation_file=os.path.join(
                               output_folder, 'Correlation.h5'),
                           resolution=(250.0, 50.0),
                           select_reference_duts=0)

    # Create alignment data for the DUT positions to the first DUT from the
    # correlation data. When needed, set offset and error cut for each DUT
    # as list of tuples
    prealigned_configuration = dut_alignment.prealign(
        telescope_configuration=initial_configuration,
        input_correlation_file=os.path.join(output_folder, 'Correlation.h5'),
        reduce_background=True,
        select_reference_dut=0)

    # Merge all cluster tables into a single table
    hit_analysis.merge_cluster_data(
        telescope_configuration=initial_configuration,
        input_cluster_files=cluster_files,
        output_merged_file=os.path.join(output_folder, 'Merged.h5'))

    # Create alignment, take first and last DUT as reference (telescope DUTs)
    aligned_configuration = dut_alignment.align(
        telescope_configuration=prealigned_configuration,
        input_merged_file=os.path.join(output_folder, 'Merged.h5'),
        select_duts=[[0, 1, 2, 3, 4, 5]],  # align all planes at once
        # add outermost planes, z-axis positions are fixed for telescope DUTs, if not stated otherwise (see select_alignment_parameters)
        select_telescope_duts=[0, 5],
        select_fit_duts=[0, 1, 2, 3, 4, 5],  # use all DUTs for track fit
        select_hit_duts=[[0, 1, 2, 3, 4, 5]],  # require hits in all DUTs
        # number of alignment iterations, the higher the number the more precise
        max_iterations=[3],
        max_events=(100000),  # limit number of events to speed up alignment
        quality_distances=[(250.0, 50.0), (250.0, 50.0), (250.0, 50.0),
                           (250.0, 50.0), (250.0, 50.0), (250.0, 50.0)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=True,
        plot=True)

    # Find tracks from the tracklets and stores the with quality indicator
    # into track candidates table
    track_analysis.find_tracks(telescope_configuration=aligned_configuration,
                               input_merged_file=os.path.join(
                                   output_folder, 'Merged.h5'),
                               output_track_candidates_file=os.path.join(
                                   output_folder,
                                   'TrackCandidates_aligned.h5'),
                               align_to_beam=True)

    # Fit the track candidates and create new track table
    track_analysis.fit_tracks(
        telescope_configuration=aligned_configuration,
        input_track_candidates_file=os.path.join(output_folder,
                                                 'TrackCandidates_aligned.h5'),
        output_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        select_fit_duts=(0, 1, 2, 3, 4, 5),
        select_hit_duts=(0, 1, 2, 3, 4, 5),
        exclude_dut_hit=True,
        quality_distances=[(250.0, 50.0), (250.0, 50.0), (250.0, 50.0),
                           (250.0, 50.0), (250.0, 50.0), (250.0, 50.0)],
        isolation_distances=(1000.0, 1000.0),
        use_limits=False,
        plot=True)

    result_analysis.calculate_residuals(
        telescope_configuration=aligned_configuration,
        input_tracks_file=os.path.join(output_folder, 'Tracks_aligned.h5'),
        output_residuals_file=os.path.join(output_folder,
                                           'Residuals_aligned.h5'),
        select_duts=[0, 1, 2, 3, 4, 5],
        nbins_per_pixel=20,
        use_limits=True)