code: def convert_dataset_section(pipe, section):
      processed_section = []
      section = list(x for x in read_paragraphs(section))
  
      for idx, (doc_id, part_id, paragraph) in enumerate(tqdm(section)):
          sentences = [x["words"] for x in paragraph]
          sentence_lens = [len(x) for x in sentences]
          cased_words = [y for x in sentences for y in x]
          sent_id = [
              y
              for idx, sent_len in enumerate(sentence_lens)
              for y in [idx] * sent_len
          ]
          speaker = [
              y
              for x, sent_len in zip(paragraph, sentence_lens)
              for y in [x["speaker"]] * sent_len
          ]
  
          # use the trees to get the xpos tags
          # alternatively, could translate the pos_tags field,
          # but those have numbers, which is annoying
          # tree_text = "\n".join(x['parse_tree'] for x in paragraph)
          # trees = tree_reader.read_trees(tree_text)
          # pos = [x.label for tree in trees for x in tree.yield_preterminals()]
          # actually, the downstream code doesn't use pos at all.  maybe we can skip?
  
          doc = pipe(sentences)
          word_total = 0
          heads = []
          # TODO: does SD vs UD matter?
          deprel = []
          for sentence in doc.sentences:
              for word in sentence.words:
                  deprel.append(word.deprel)
                  if word.head == 0:
                      heads.append("null")
                  else:
                      heads.append(word.head - 1 + word_total)
              word_total += len(sentence.words)
  
          span_clusters = defaultdict(list)
          word_clusters = defaultdict(list)
          head2span = []
          word_total = 0
          for parsed_sentence, ontonotes_sentence in zip(
              doc.sentences, paragraph
          ):
              coref_spans = ontonotes_sentence["coref_spans"]
              sentence_upos = [x.upos for x in parsed_sentence.words]
              sentence_heads = [
                  x.head - 1 if x.head > 0 else None
                  for x in parsed_sentence.words
              ]
              for span in coref_spans:
                  # input is expected to be start word, end word + 1
                  # counting from 0
                  # whereas the OntoNotes coref_span is [start_word, end_word] inclusive
                  span_start = span[1] + word_total
                  span_end = span[2] + word_total + 1
                  candidate_head = find_cconj_head(
                      sentence_heads,
                      sentence_upos,
                      span[1],
                      span[2] + 1,
                  )
                  if candidate_head is None:
                      for candidate_head in range(span[1], span[2] + 1):
                          # stanza uses 0 to mark the head, whereas OntoNotes is counting
                          # words from 0, so we have to subtract 1 from the stanza heads
                          # print(span, candidate_head, parsed_sentence.words[candidate_head].head - 1)
                          # treat the head of the phrase as the first word that has a head outside the phrase
                          if (
                              parsed_sentence.words[candidate_head].head
                              - 1
                              < span[1]
                              or parsed_sentence.words[
                                  candidate_head
                              ].head
                              - 1
                              > span[2]
                          ):
                              break
                      else:
                          # if none have a head outside the phrase (circular??)
                          # then just take the first word
                          candidate_head = span[1]
                  # print("----> %d" % candidate_head)
                  candidate_head += word_total
                  span_clusters[span[0]].append((span_start, span_end))
                  word_clusters[span[0]].append(candidate_head)
                  head2span.append(
                      (candidate_head, span_start, span_end)
                  )
              word_total += len(ontonotes_sentence["words"])
          span_clusters = sorted(
              [sorted(values) for _, values in span_clusters.items()]
          )
          word_clusters = sorted(
              [sorted(values) for _, values in word_clusters.items()]
          )
          head2span = sorted(head2span)
  
          processed = {
              "document_id": doc_id,
              "cased_words": cased_words,
              "sent_id": sent_id,
              "part_id": part_id,
              "speaker": speaker,
              # "pos": pos,
              "deprel": deprel,
              "head": heads,
              "span_clusters": span_clusters,
              "word_clusters": word_clusters,
              "head2span": head2span,
          }
          processed_section.append(processed)
      return processed_section
  
file_path: stanza/stanza/utils/datasets/coref/convert_ontonotes.py
imports: from collections import defaultdict
  from functools import lru_cache
  import json
  import os
  import stanza
  from stanza.models.constituency import tree_reader
  from stanza.utils.default_paths import get_default_paths
  from stanza.utils.get_tqdm import get_tqdm
label_desc: Use image node_id 3 for calling a global function with example usage: convert_dataset_section(pipe, section) and returns: processed_section