Beispiel #1
0
 def test_team_contributions(self):
     ds = self.n.downsample(20, inplace=False)
     ul = pymaid.get_user_list().set_index('id')
     teams = {'test_team' : [ul.loc[u, 'login'] for u in ds.nodes.creator_id.unique()]}
     self.assertIsInstance(pymaid.get_team_contributions(teams,
                                                         neurons=ds,
                                                         remote_instance=self.rm),
                           pd.DataFrame)
Beispiel #2
0
 def test_get_user_list(self):
     self.assertIsInstance(pymaid.get_user_list(
         remote_instance=self.rm), pd.DataFrame)
Beispiel #3
0
 def test_user_annotations(self):
     ul = pymaid.get_user_list()
     self.assertIsInstance(pymaid.get_user_annotations(ul.sample(1).iloc[0].id),
                           pd.DataFrame)
Beispiel #4
0
def upload_or_update_neurons(neurons,
                             linking_relation='',
                             annotate_source_neuron=False,
                             import_connectors=False,
                             reuse_existing_connectors=True,
                             refuse_to_update=True,
                             verbose=False,
                             fake=True):
    server_responses = []
    start_day = time.strftime('%Y-%m-%d')
    start_time = time.strftime('%Y-%m-%d %I:%M %p')

    if type(neurons) is pymaid.core.CatmaidNeuron:
        neurons = pymaid.core.CatmaidNeuronList(neurons)

    # There are some pesky corner cases where updates will unintentionally create unlinked
    # connectors. When that occurs, the user is warned and asked to investigate manually.
    unlinked_connectors_start = find_unlinked_connectors(
        remote_instance=target_project)

    for source_neuron in neurons:
        source_project.clear_cache()
        target_project.clear_cache()

        # Check if a neuron/skeleton with this neuron's name already exists in the target project
        # If so, replace that neuron/skeleton's data with this neuron's data.
        skid_to_update = None
        nid_to_update = None
        force_id = False

        if linking_relation is '':
            linking_annotation_template = 'LINKED NEURON - skeleton id {skid} in project id {pid} on server {server}'
        else:
            linking_annotation_template = 'LINKED NEURON - {relation} skeleton id {skid} in project id {pid} on server {server}'

        linking_annotation_target = linking_annotation_template.format(
            relation=linking_relation,
            skid=source_neuron.skeleton_id,
            name=source_neuron.neuron_name,  #Not used currently
            pid=source_project.project_id,
            server=source_project.server)
        if verbose:
            print("Linking annotation is: '{linking_annotation_target}'")

        try:
            linked_neuron_skid = pymaid.get_skids_by_annotation(
                add_escapes(linking_annotation_target),
                raise_not_found=False,
                remote_instance=target_project)
        except Exception as e:
            # There appears to be a bug in get_skids_by_annotation where it still
            # raises exceptions sometimes even with raise_not_found=False, so
            # use this block to continue through any of those cases without raising.
            #print(e)
            linked_neuron_skid = []

        source_neuron.annotations = [
            annot for annot in source_neuron.annotations
            if 'LINKED NEURON' not in annot
        ]

        if len(linked_neuron_skid) is 0:  # Prepare to upload neuron as new
            print(f'Uploading "{source_neuron.neuron_name}" to project'
                  f' {target_project.project_id} as a new skeleton.')
            source_neuron.annotations.append(linking_annotation_target)
            source_neuron.annotations.append(
                f'UPDATED FROM LINKED NEURON - {start_time}')
        elif len(linked_neuron_skid) is not 1:
            print('Found multiple neurons annotated with'
                  f' "{linking_annotation_target}" in target project.'
                  ' Go fix that! Skipping upload for this neuron.')
        else:  # Prepare to update the linked neuron
            linked_neuron = pymaid.get_neuron(linked_neuron_skid[0],
                                              remote_instance=target_project)
            m = ', connectors,' if import_connectors else ''
            print(f'{source_neuron.neuron_name}: Found linked neuron with '
                  f'skeleton ID {linked_neuron.skeleton_id} in target project.'
                  f' Updating its treenodes{m} and annotations to match the'
                  ' source neuron.')

            # Check whether names match
            if not source_neuron.neuron_name == linked_neuron.neuron_name:
                user_input = input(
                    'WARNING: The linked neuron\'s name is'
                    f' "{linked_neuron.neuron_name}" but was expected to be'
                    f' "{source_neuron.neuron_name}". Continuing will rename'
                    ' the linked neuron to the expected name. Proceed? [Y/n] ')
                if user_input not in ('y', 'Y'):
                    continue

            # TODO
            # Check whether there are any nodes or connectors in the source
            # neuron with edition dates after the previous upload date. If not,
            # skip the upload and tell the user.

            # Check whether any edited nodes will be overwritten
            linked_node_details = pymaid.get_node_details(
                linked_neuron.nodes.node_id.values,
                remote_instance=target_project)
            is_edited = linked_node_details.edition_time != min(
                linked_node_details.edition_time)
            if is_edited.any():
                edited_nodes = linked_node_details.loc[
                    is_edited, ['node_id', 'edition_time', 'editor']]
                users = pymaid.get_user_list(
                    remote_instance=target_project).set_index('id')
                edited_nodes.loc[:, 'editor'] = [
                    users.loc[user_id, 'login']
                    for user_id in edited_nodes.editor
                ]
                print('WARNING: The linked neuron has been manually edited,'
                      f' with {len(edited_nodes)} nodes modified. Those'
                      ' changes will get thrown away if this update is allowed'
                      ' to continue.')
                print(edited_nodes)
                user_input = input(
                    'OK to proceed and throw away the above changes? [Y/n] ')
                if user_input not in ('y', 'Y'):
                    print(f'Skipping update for "{source_neuron.neuron_name}"')
                    continue

            if refuse_to_update:
                print('refuse_to_update set to true. Skipping.\n')
                continue

            # This does NOT annotate the source neuron on the server,
            # it only appends to the object in memory
            source_neuron.annotations.append(
                f'UPDATED FROM LINKED NEURON - {start_time}')
            # Make sure to preserve all annotations on the target neuron. This will not
            # be necessary once # https://github.com/catmaid/CATMAID/issues/2042 is resolved
            source_neuron.annotations.extend([
                a for a in linked_neuron.annotations
                if a not in source_neuron.annotations
            ])

            skid_to_update = linked_neuron.skeleton_id
            nid_to_update = pymaid.get_neuron_id(
                linked_neuron.skeleton_id,
                remote_instance=target_project)[str(linked_neuron.skeleton_id)]
            force_id = True

        if not fake:
            # Actually do the upload/update:
            server_responses.append(
                pymaid.upload_neuron(
                    source_neuron,
                    skeleton_id=skid_to_update,
                    neuron_id=nid_to_update,
                    force_id=force_id,
                    import_tags=True,
                    import_annotations=True,
                    import_connectors=import_connectors,
                    reuse_existing_connectors=reuse_existing_connectors,
                    remote_instance=target_project))

            if annotate_source_neuron:
                try:
                    upload_skid = server_responses[-1]['skeleton_id']
                    source_annotation = linking_annotation_template.format(
                        relation=linking_relation,
                        skid=server_responses[-1]['skeleton_id'],
                        name=source_neuron.neuron_name,  #Not used currently
                        pid=target_project.project_id,
                        server=target_project.server)
                    try:
                        server_responses[-1][
                            'source_annotation'] = pymaid.add_annotations(
                                source_neuron.skeleton_id,
                                source_annotation,
                                remote_instance=source_project)
                    except:
                        m = ('WARNING: annotate_source_neuron was requested,'
                             ' but failed. You may not have permissions to'
                             ' annotate the source project through the API')
                        print(m)
                        input('(Press enter to acknowledge and continue.)')
                        server_responses[-1]['source_annotation'] = m
                except:
                    print('WARNING: upload was not successful,'
                          ' so could not annotate source neuron.')
                    input('(Press enter to acknowledge and continue.)')

            print(f'{source_neuron.neuron_name}: Done with upload or update.')
        print(' ')
    if fake:
        print('fake was set to True. Set fake=False to actually run'
              ' upload_or_update_neurons with settings:\n'
              f'annotate_source_neuron={annotate_source_neuron}\n'
              f'import_connectors={import_connectors},\n'
              f'reuse_existing_connectors={reuse_existing_connectors},\n'
              f'refuse_to_update={refuse_to_update}')
    else:
        # There are some pesky corner cases where updates will unintentionally
        # create unlinked connectors. When that occurs, the user is warned and
        # asked to investigate manually. Note that if a human tracer is
        # annotating in catmaid and happens to make an unlinked connector that
        # exists when the following lines are run, this will throw an warning
        # despite there being nothing to worry about. Not much I can do there.
        target_project.clear_cache()
        unlinked_connectors_end = find_unlinked_connectors(
            remote_instance=target_project)
        newly_unlinked_connectors = set(unlinked_connectors_end).difference(
            set(unlinked_connectors_start))
        if len(newly_unlinked_connectors) != 0:
            print("WARNING: This upload caused some connectors in the "
                  "target project to become unlinked from any skeleton. "
                  "(This can harmlessly result from deleting connectors from "
                  "the source project, or it may indicate a bug in the code.) "
                  "You may want to go clean up the new unlinked connectors:")
            print(newly_unlinked_connectors)
            input('(Press enter to acknowledge warning and continue.)')

    return server_responses