def calculate_p_nearfar(catalog, return_intermediates=False,
                        A_coefficient=0.5,
                        B_coefficient=0.5,
                        scatter_in_log10_R=0.5,
                        molecular_HWHM_height=60*u.pc
                        ):

    near_catalog = catalog.copy(copy_data=True)
    near_catalog['distance'] = catalog['near_distance']
    far_catalog = catalog.copy(copy_data=True)
    far_catalog['distance'] = catalog['far_distance']

    assign_properties(near_catalog)
    assign_properties(far_catalog)

    p_near_larson = p_from_size_linewidth(near_catalog['size'], near_catalog['v_rms'],
                                          A_coefficient, B_coefficient, scatter_in_log10_R=scatter_in_log10_R)
    p_far_larson = p_from_size_linewidth(far_catalog['size'], far_catalog['v_rms'],
                                         A_coefficient, B_coefficient, scatter_in_log10_R=scatter_in_log10_R)

    p_near_latitude = p_from_latitude((near_catalog['z_gal']).to(u.pc),
                                      molecular_HWHM_height=molecular_HWHM_height)

    p_far_latitude = p_from_latitude((far_catalog['z_gal']).to(u.pc),
                                     molecular_HWHM_height=molecular_HWHM_height)

    p_near = p_near_larson * p_near_latitude
    p_far = p_far_larson * p_far_latitude

    if return_intermediates:
        near_output = [near_catalog, p_near_larson, p_near_latitude, p_near]
        far_output = [far_catalog, p_far_larson, p_far_latitude, p_far]
        return near_output, far_output
    else:
        return p_near, p_far
Exemple #2
0
def second_quad_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the second quadrant survey.

    It identifies edge structures, computes tree statistics,
    determines the kinematic distances, and then assigns physical
    properties to the structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    and `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    # DISTANCE assignment
    distance_table = make_universal_distance_column(catalog, nearfar='near')

    distance_assigner_with_plusminus_errors(catalog_cp,
                                            distance_table,
                                            distance_column_name='distance')
    assign_distance_columns_trivial(catalog_cp)

    # assignment of physical properties to unambigously-distanced structures
    assign_properties(catalog_cp)

    return catalog_cp
def third_quad_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the third quadrant survey.

    It identifies edge structures, computes tree statistics,
    determines the kinematic distances, and then assigns physical
    properties to the structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    and `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    # DISTANCE assignment
    distance_table = make_universal_distance_column(catalog, nearfar='near')

    distance_assigner_with_plusminus_errors(catalog_cp, distance_table, distance_column_name='distance')
    assign_distance_columns_trivial(catalog_cp)

    # assignment of physical properties to unambigously-distanced structures
    assign_properties(catalog_cp)

    return catalog_cp
Exemple #4
0
def get_positive_velocity_clouds(input_catalog,
                                 max_descendants=10,
                                 dendrogram=d):
    """
    Extracts clouds from the positive-velocity region of the first quad.

    This is the 1Q's inner Galaxy.
    Here, the KDA applies to most structures, so we first define
    clouds based on (a) position in velocity space, (b) a cloud is not
    on an edge, (c) line widths between 1-10 km/s, (d) a cloud has less
    than `max_descendants` descendants, (e) the `fractional_gain` is
    below 0.9.

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and their physical properties are 
    computed.

    A final list of clouds is generated by taking structures with a mass
    greater than 3 x 10^4 solar masses; this is what's returned.

    """

    catalog = input_catalog.copy(copy_data=True)

    # one. grab the clouds we think are real
    disqualified = ((catalog['v_cen'] < 20) | (catalog['on_edge'] == 1) |
                    (catalog['v_rms'] <= 1) | (catalog['v_rms'] > 10) |
                    (catalog['max_vsplit'] > 3))

    qualified = ((catalog['n_descendants'] < max_descendants) &
                 (catalog['fractional_gain'] < 0.9))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(dendrogram, pre_output_catalog)

    # disambiguate distances here
    assign_distance_columns(
        almost_output_catalog,
        *distance_disambiguator(almost_output_catalog,
                                ambiguous_threshold=0.05))

    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = ((almost_output_catalog['mass'] > 3e4) |
                       ((almost_output_catalog['KDA_resolution'] == 'A') &
                        (almost_output_catalog['far_mass'] > 3e4)))
    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog
def get_negative_velocity_clouds(input_catalog, max_descendants=10):
    """
    Extracts clouds from the negative-velocity region of the Carina survey.

    This is the 4Q's inner Galaxy.
    Here, the KDA might apply to some structures, so we first define
    clouds based on (a) position in velocity space, (b) a cloud is not
    on an edge, (c) line widths between 1-10 km/s, (d) a cloud has less
    than `max_descendants` descendants, (e) the `fractional_gain` is
    below 0.81.

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and their physical properties are 
    computed.

    A final list of clouds is generated by taking structures with a mass
    greater than 3 x 10^4 solar masses; this is what's returned.

    """

    catalog = input_catalog.copy(copy_data=True)

    # one. grab the clouds we think are real
    disqualified = (
        (catalog['v_cen'] > -15) |
        (catalog['on_edge'] == 1) |
        (catalog['v_rms'] <= 1) |
        (catalog['v_rms'] > 10) |
        (catalog['x_cen'] < 280)
    )

    qualified = (
        (catalog['n_descendants'] < max_descendants) &
        (catalog['fractional_gain'] < 0.9))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(d, pre_output_catalog)

    # disambiguate distances here
    assign_distance_columns(almost_output_catalog, *distance_disambiguator(almost_output_catalog, ambiguous_threshold=0.05))

    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = ((almost_output_catalog['mass'] > 3e4) | 
                       ((almost_output_catalog['KDA_resolution']=='A') & (almost_output_catalog['far_mass'] > 3e4) ) )
    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog
def get_positive_velocity_clouds(input_catalog, max_descendants=30):
    """
    This extracts clouds from the positive-velocity region of the first quad.

    Here, the KDA applies to most structures, so we first define
    clouds based on (a) position in velocity space, (b) a cloud is not
    on an edge, (c) line widths between 1-10 km/s, (d) a cloud has less
    than `max_descendants` descendants, (e) the `fractional_gain` is 
    below 0.81.

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and 

    """


    catalog = input_catalog.copy(copy_data=True)

    # one. grab the clouds we think are real
    disqualified = (
        (catalog['v_cen'] < 20) |
        #        (catalog['mass'] < 10**3.5 * u.solMass) |
        # (catalog['disparate'] == 0) |
        (catalog['on_edge'] == 1) |
        (catalog['v_rms'] <= 1) |
        (catalog['v_rms'] > 10)
        # (np.abs(catalog['fractional_gain'] - 0.5) > 0.05)
    )

    qualified = (
        (catalog['n_descendants'] < max_descendants) &
        (catalog['fractional_gain'] < 0.81))  # &
    # (catalog['mass'] > 10**5 * u.solMass))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(d, pre_output_catalog)

    # disambiguate distances here
    best_distance = distance_disambiguator(almost_output_catalog)
    almost_output_catalog['distance'] = best_distance
    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = (almost_output_catalog['mass'] > 3e4)
    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog
Exemple #7
0
def get_positive_velocity_clouds(input_catalog, max_descendants=30):
    """
    This extracts clouds from the positive-velocity region of the first quad.

    Here, the KDA applies to most structures, so we first define
    clouds based on (a) position in velocity space, (b) a cloud is not
    on an edge, (c) line widths between 1-10 km/s, (d) a cloud has less
    than `max_descendants` descendants, (e) the `fractional_gain` is 
    below 0.81.

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and 

    """

    catalog = input_catalog.copy(copy_data=True)

    # one. grab the clouds we think are real
    disqualified = (
        (catalog['v_cen'] < 20) |
        #        (catalog['mass'] < 10**3.5 * u.solMass) |
        # (catalog['disparate'] == 0) |
        (catalog['on_edge'] == 1) | (catalog['v_rms'] <= 1) |
        (catalog['v_rms'] > 10)
        # (np.abs(catalog['fractional_gain'] - 0.5) > 0.05)
    )

    qualified = ((catalog['n_descendants'] < max_descendants) &
                 (catalog['fractional_gain'] < 0.81))  # &
    # (catalog['mass'] > 10**5 * u.solMass))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(d, pre_output_catalog)

    # disambiguate distances here
    best_distance = distance_disambiguator(almost_output_catalog)
    almost_output_catalog['distance'] = best_distance
    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = (almost_output_catalog['mass'] > 3e4)
    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog
Exemple #8
0
def first_quad_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the first quadrant.

    It identifies edge structures, computes tree statistics,
    determines the near & far distances (but does not resolve them),
    assigns a distance when there is no KDA, and then assigns physical
    properties to the unambiguously-distanced structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    `get_perseus_arm_low_velocity_clouds()`, and
    `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    near_distance_table = make_reid_distance_column(catalog, nearfar='near')
    far_distance_table = make_reid_distance_column(catalog, nearfar='far')

    near_distance_column = near_distance_table['D_k']
    far_distance_column = far_distance_table['D_k']

    # DISTANCE assignment
    catalog_cp['near_distance'] = near_distance_column
    catalog_cp['far_distance'] = far_distance_column

    # where there's no degeneracy, go ahead and apply the thing
    no_degeneracy = catalog_cp['near_distance'] == catalog_cp['far_distance']
    distance_column = np.zeros_like(near_distance_column) * np.nan
    distance_column[no_degeneracy] = catalog_cp['near_distance'][no_degeneracy]

    catalog_cp['distance'] = distance_column

    # assignment of physical properties to unambigously-distanced structures
    assign_properties(catalog_cp)

    # note disparate distances
    # catalog_cp['disparate'] = detect_disparate_distances(d, catalog_cp)

    return catalog_cp
def first_quad_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the first quadrant.

    It identifies edge structures, computes tree statistics,
    determines the near & far distances (but does not resolve them),
    assigns a distance when there is no KDA, and then assigns physical
    properties to the unambiguously-distanced structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    `get_perseus_arm_low_velocity_clouds()`, and
    `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    near_distance_table = make_reid_distance_column(catalog, nearfar='near')
    far_distance_table = make_reid_distance_column(catalog, nearfar='far')

    near_distance_column = near_distance_table['D_k']
    far_distance_column = far_distance_table['D_k']

    # DISTANCE assignment
    catalog_cp['near_distance'] = near_distance_column
    catalog_cp['far_distance'] = far_distance_column

    # where there's no degeneracy, go ahead and apply the thing
    no_degeneracy = catalog_cp['near_distance'] == catalog_cp['far_distance']
    distance_column = np.zeros_like(near_distance_column) * np.nan
    distance_column[no_degeneracy] = catalog_cp['near_distance'][no_degeneracy]

    catalog_cp['distance'] = distance_column

    # assignment of physical properties to unambigously-distanced structures
    assign_properties(catalog_cp)

    # note disparate distances
    # catalog_cp['disparate'] = detect_disparate_distances(d, catalog_cp)

    return catalog_cp
def calculate_p_nearfar(catalog,
                        return_intermediates=False,
                        A_coefficient=0.5,
                        B_coefficient=0.5,
                        scatter_in_log10_R=0.5,
                        molecular_HWHM_height=60 * u.pc):

    near_catalog = catalog.copy(copy_data=True)
    near_catalog['distance'] = catalog['near_distance']
    far_catalog = catalog.copy(copy_data=True)
    far_catalog['distance'] = catalog['far_distance']

    assign_properties(near_catalog)
    assign_properties(far_catalog)

    p_near_larson = p_from_size_linewidth(
        near_catalog['size'],
        near_catalog['v_rms'],
        A_coefficient,
        B_coefficient,
        scatter_in_log10_R=scatter_in_log10_R)
    p_far_larson = p_from_size_linewidth(far_catalog['size'],
                                         far_catalog['v_rms'],
                                         A_coefficient,
                                         B_coefficient,
                                         scatter_in_log10_R=scatter_in_log10_R)

    p_near_latitude = p_from_latitude(
        (near_catalog['z_gal']).to(u.pc),
        molecular_HWHM_height=molecular_HWHM_height)

    p_far_latitude = p_from_latitude(
        (far_catalog['z_gal']).to(u.pc),
        molecular_HWHM_height=molecular_HWHM_height)

    p_near = p_near_larson * p_near_latitude
    p_far = p_far_larson * p_far_latitude

    if return_intermediates:
        near_output = [near_catalog, p_near_larson, p_near_latitude, p_near]
        far_output = [far_catalog, p_far_larson, p_far_latitude, p_far]
        return near_output, far_output
    else:
        return p_near, p_far
def carina_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the Carina survey.

    It identifies edge structures, computes tree statistics,
    determines the near & far distances (but does not resolve them),
    assigns a distance when there is no KDA, and then assigns physical
    properties to the unambiguously-distanced structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    and `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    near_distance_table = make_universal_distance_column(catalog,
                                                         nearfar='near')
    far_distance_table = make_universal_distance_column(catalog, nearfar='far')

    # DISTANCE assignment
    distance_assigner_with_plusminus_errors(
        catalog_cp, near_distance_table, distance_column_name='near_distance')
    distance_assigner_with_plusminus_errors(
        catalog_cp, far_distance_table, distance_column_name='far_distance')

    # where there's no degeneracy, go ahead and apply the thing
    no_degeneracy = catalog_cp['near_distance'] == catalog_cp['far_distance']
    distance_column = np.zeros_like(near_distance_table['D_k']) * np.nan
    error_distance_column_plus = np.zeros_like(
        near_distance_table['D_k']) * np.nan
    error_distance_column_minus = np.zeros_like(
        near_distance_table['D_k']) * np.nan
    KDA_resolution_column = np.array(['-'] * len(distance_column))

    distance_column[no_degeneracy] = catalog_cp['near_distance'][no_degeneracy]
    error_distance_column_plus[no_degeneracy] = catalog_cp[
        'error_near_distance_plus'][no_degeneracy]
    error_distance_column_minus[no_degeneracy] = catalog_cp[
        'error_near_distance_minus'][no_degeneracy]
    KDA_resolution_column[no_degeneracy] = 'U'

    assign_distance_columns(catalog_cp, distance_column, KDA_resolution_column,
                            np.zeros_like(distance_column),
                            np.zeros_like(distance_column),
                            error_distance_column_plus,
                            error_distance_column_minus)

    # assignment of physical properties to unambigously-distanced structures
    # let's think critically about whether this step is needed.
    assign_properties(catalog_cp)

    near_catalog = catalog_cp.copy(copy_data=True)
    far_catalog = catalog_cp.copy(copy_data=True)

    near_catalog['distance'] = catalog_cp['near_distance']
    far_catalog['distance'] = catalog_cp['far_distance']
    assign_properties(near_catalog)
    assign_properties(far_catalog)

    catalog_cp['near_size'] = near_catalog['size']
    catalog_cp['far_size'] = far_catalog['size']

    catalog_cp['near_z_gal'] = near_catalog['z_gal']
    catalog_cp['far_z_gal'] = far_catalog['z_gal']

    catalog_cp['near_mass'] = near_catalog['mass']
    catalog_cp['far_mass'] = far_catalog['mass']

    return catalog_cp
def carina_cloud_catalog():
    """
    This generates a 'processed' dendrogram catalog, made
    from the global, pre-loaded `catalog` from the Carina survey.

    It identifies edge structures, computes tree statistics,
    determines the near & far distances (but does not resolve them),
    assigns a distance when there is no KDA, and then assigns physical
    properties to the unambiguously-distanced structures.

    Its output is meant to be the input to `get_positive_velocity_clouds()`,
    and `get_negative_velocity_clouds()`.

    """

    catalog_cp = catalog.copy(copy_data=True)

    # assignment of tree statistic properties
    compute_tree_stats(catalog_cp, d)

    # note edge structures
    catalog_cp['on_edge'] = identify_edge_structures(d)

    near_distance_table = make_universal_distance_column(catalog, nearfar='near')
    far_distance_table = make_universal_distance_column(catalog, nearfar='far')

    # DISTANCE assignment
    distance_assigner_with_plusminus_errors(catalog_cp, near_distance_table, distance_column_name='near_distance')
    distance_assigner_with_plusminus_errors(catalog_cp, far_distance_table, distance_column_name='far_distance')

    # where there's no degeneracy, go ahead and apply the thing
    no_degeneracy = catalog_cp['near_distance'] == catalog_cp['far_distance']
    distance_column = np.zeros_like(near_distance_table['D_k']) * np.nan
    error_distance_column_plus = np.zeros_like(near_distance_table['D_k']) * np.nan
    error_distance_column_minus = np.zeros_like(near_distance_table['D_k']) * np.nan
    KDA_resolution_column = np.array(['-']*len(distance_column))

    distance_column[no_degeneracy] = catalog_cp['near_distance'][no_degeneracy]
    error_distance_column_plus[no_degeneracy] = catalog_cp['error_near_distance_plus'][no_degeneracy]
    error_distance_column_minus[no_degeneracy] = catalog_cp['error_near_distance_minus'][no_degeneracy]
    KDA_resolution_column[no_degeneracy] = 'U'

    assign_distance_columns(catalog_cp, distance_column, KDA_resolution_column, 
                            np.zeros_like(distance_column), np.zeros_like(distance_column),
                            error_distance_column_plus, error_distance_column_minus)

    # assignment of physical properties to unambigously-distanced structures
    # let's think critically about whether this step is needed.
    assign_properties(catalog_cp)

    near_catalog = catalog_cp.copy(copy_data=True)
    far_catalog = catalog_cp.copy(copy_data=True)

    near_catalog['distance'] = catalog_cp['near_distance']
    far_catalog['distance'] = catalog_cp['far_distance']
    assign_properties(near_catalog)
    assign_properties(far_catalog)

    catalog_cp['near_size'] = near_catalog['size']
    catalog_cp['far_size'] = far_catalog['size']

    catalog_cp['near_z_gal'] = near_catalog['z_gal']
    catalog_cp['far_z_gal'] = far_catalog['z_gal']

    catalog_cp['near_mass'] = near_catalog['mass']
    catalog_cp['far_mass'] = far_catalog['mass']

    return catalog_cp
Exemple #13
0
def get_low_velocity_perseus_clouds(input_catalog,
                                    max_descendants=10,
                                    dendrogram=d):
    """
    Extracts clouds from the low-velocity Perseus region of Q1.

    This is the 1Q's Perseus Arm at low velocities.
    Here, the KDA applies to some structures, and we are targeting clouds 
    that might overlap with local emission...
    so we first define clouds based on 
    (a) position in velocity space AND LATITUDE,
    (b) a cloud is not on an edge,
    (c) line widths between 1-10 km/s, 
    (d) a cloud has less than `max_descendants` descendants, 
    (e) the `fractional_gain` is below 0.9,

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and their physical properties are 
    computed.

    A final list of clouds is generated by taking structures with a mass
    greater than 3 x 10^4 solar masses AND a distance consistent with 
    the Perseus Arm; this is what's returned.

    """

    catalog = input_catalog.copy(copy_data=True)

    disqualified = ((catalog['v_cen'] < -5) | (catalog['v_cen'] > 20) |
                    (np.abs(catalog['y_cen'] > 1)) | (catalog['x_cen'] < 35) |
                    (catalog['on_edge'] == 1) | (catalog['v_rms'] <= 1) |
                    (catalog['v_rms'] > 10))

    qualified = ((catalog['n_descendants'] < max_descendants) &
                 (catalog['fractional_gain'] < 0.9))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(dendrogram, pre_output_catalog)

    # disambiguate distances here
    assign_distance_columns(
        almost_output_catalog,
        *distance_disambiguator(almost_output_catalog,
                                ambiguous_threshold=0.05))

    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = ((almost_output_catalog['mass'] > 3e4) &
                       (almost_output_catalog['distance'] > 5) &
                       (almost_output_catalog['distance'] < 14))

    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog
def get_low_velocity_perseus_clouds(input_catalog, max_descendants=10, dendrogram=d):
    """
    Extracts clouds from the low-velocity Perseus region of Q1.

    This is the 1Q's Perseus Arm at low velocities.
    Here, the KDA applies to some structures, and we are targeting clouds 
    that might overlap with local emission...
    so we first define clouds based on 
    (a) position in velocity space AND LATITUDE,
    (b) a cloud is not on an edge,
    (c) line widths between 1-10 km/s, 
    (d) a cloud has less than `max_descendants` descendants, 
    (e) the `fractional_gain` is below 0.9,

    Structures that meet the above criteria go into a pre-candidate-cloud
    list, and that list is "flattened" or "reduced" to remove degenerate
    substructure.

    Then the KDA is disambiguated for the relevant structures using the
    function `distance_disambiguator` and their physical properties are 
    computed.

    A final list of clouds is generated by taking structures with a mass
    greater than 3 x 10^4 solar masses AND a distance consistent with 
    the Perseus Arm; this is what's returned.

    """

    catalog = input_catalog.copy(copy_data=True)

    disqualified = (
        (catalog['v_cen'] < -5) |
        (catalog['v_cen'] > 20) |
        (np.abs(catalog['y_cen'] > 1)) |
        (catalog['x_cen'] < 35) |
        (catalog['on_edge'] == 1) |
        (catalog['v_rms'] <= 1) |
        (catalog['v_rms'] > 10)
    )

    qualified = (
        (catalog['n_descendants'] < max_descendants) &
        (catalog['fractional_gain'] < 0.9))

    pre_output_catalog = catalog[~disqualified & qualified]

    # now it's just got clouds that COULD be real
    almost_output_catalog = reduce_catalog(dendrogram, pre_output_catalog)

    # disambiguate distances here
    assign_distance_columns(almost_output_catalog, *distance_disambiguator(almost_output_catalog, ambiguous_threshold=0.05))

    assign_properties(almost_output_catalog)

    # now let's do a thing
    final_qualified = (
        (almost_output_catalog['mass'] > 3e4) &
        (almost_output_catalog['distance'] > 5) &
        (almost_output_catalog['distance'] < 14)
        )

    output_catalog = almost_output_catalog[final_qualified]

    return output_catalog