docs: def _to_process(self, pipeline, required_outputs, prqs_to_process_array,
                      to_skip_array, filter_array, subject_inds, visit_inds,
                      force):
          """
          Check whether the outputs of the pipeline are present in all sessions
          in the project repository and were generated with matching provenance.
          Return an 2D boolean array (subjects: rows, visits: cols) with the
          sessions to process marked True.
  
          Parameters
          ----------
          pipeline : Pipeline
              The pipeline to determine the sessions to process
          required_ouputs : set[str]
              The names of the pipeline outputs that are required. If None all
              are deemed to be required
          prqs_to_process_array : 2-D numpy.array[bool]
              A two-dimensional boolean array, where rows and columns correspond
              correspond to subjects and visits in the repository tree. True
              values represent a subject/visit ID pairs that will be
              (re)processed in prerequisite pipelines and therefore need to be
              included in the returned array.
          to_skip_array : 2-D numpy.array[bool]
              Similar to prqs_to_process_array, except denote the subject/visits
              that are to be skipped due to missing inputs
          filter_array : 2-D numpy.array[bool]
              A two-dimensional boolean array, where rows and columns correspond
              correspond to subjects and visits in the repository tree. True
              values represent a subject/visit ID pairs to include
              in the current round of processing. Note that if the 'force'
              flag is not set, sessions won't be reprocessed unless the
              parameters and pipeline version saved in the provenance doesn't
              match that of the given pipeline.
          subject_inds : dict[str,int]
              Mapping from subject ID to index in filter|to_process arrays
          visit_inds : dict[str,int]
              Mapping from visit ID to index in filter|to_process arrays
          force : bool
              Whether to force reprocessing of all (filtered) sessions or not.
              Note that if 'force' is true we can't just return the filter array
              as it might be dilated by summary outputs (i.e. of frequency
              'per_visit', 'per_subject' or 'per_study'). So we still loop
              through all outputs and treat them like they don't exist
  
          Returns
          -------
          to_process_array : 2-D numpy.array[bool]
              A two-dimensional boolean array, where rows correspond to
              subjects and columns correspond to visits in the repository. True
              values represent subject/visit ID pairs to run the pipeline for
          """
          # Reference the study tree in local variable for convenience
          tree = self.study.tree
          # Check to see if the pipeline has any low frequency outputs, because
          # if not then each session can be processed indepdently. Otherwise,
          # the "session matrix" (as defined by subject_ids and visit_ids
          # passed to the Study class) needs to be complete, i.e. a session
          # exists (with the full complement of requird inputs) for each
          # subject/visit ID pair.
          summary_outputs = [
              o.name for o in pipeline.outputs if o.frequency != 'per_session']
          # Set of frequencies present in pipeline outputs
          output_freqs = pipeline.output_frequencies
          if summary_outputs:
              if list(tree.incomplete_subjects):
                  raise ArcanaUsageError(
                      "Can't process '{}' pipeline as it has low frequency "
                      " outputs (i.e. outputs that aren't of 'per_session' "
                      "frequency) ({}) and subjects ({}) that are missing one "
                      "or more visits ({}). Please restrict the subject/visit "
                      "IDs in the study __init__ to continue the analysis"
                      .format(
                          self.name,
                          ', '.join(summary_outputs),
                          ', '.join(s.id for s in tree.incomplete_subjects),
                          ', '.join(v.id for v in tree.incomplete_visits)))
  
          def array_inds(x):
              """
              Get the array index corresponding to an item or node. If it doesn't
              have a subject and/or visit ID (i.e. frequency != 'per_session')
              then the first index in the row/column is selected as it will be
              dialated to the whole row/column later
              """
              return (subject_inds.get(x.subject_id, 0),
                      visit_inds.get(x.visit_id, 0))
  
          # Initalise array to represent which sessions need to be reprocessed
          to_process_array = np.zeros((len(subject_inds), len(visit_inds)),
                                      dtype=bool)
          # An array to mark outputs that have been altered outside of Arcana
          # and therefore protect from over-writing
          to_protect_array = np.copy(to_process_array)
          # Mark the sessions that we should check to see if the configuration
          # saved in the provenance record matches that of the current pipeline
          to_check_array = np.copy(to_process_array)
          # As well as the the sessions that need to be protected or skipped,
          # keep track of the items in those sessions that are protected or
          # missing for more informative error messages
          to_protect = defaultdict(list)
          to_skip = defaultdict(list)
          # Check data tree for missing inputs
          for input in pipeline.inputs:  # @ReservedAssignment
              # NB: Study inputs that don't have skip_missing set and have
              # missing data should raise an error before this point
              if input.skip_missing:
                  for item in input.collection:
                      if not item.exists:
                          to_skip_array[array_inds(item)] = True
                          to_skip[array_inds(item)].append(item)
          # Dialate array over all iterators that are joined by the pipeline
          to_skip_array = self._dialate_array(to_skip_array, pipeline.joins)
          # Check data tree for missing required outputs
          for output in pipeline.outputs:
              # Check to see if output is required by downstream processing
              required = (required_outputs is None or
                          output.name in required_outputs)
              for item in output.collection:
                  if item.exists:
                      # Check to see if checksums recorded when derivative
                      # was generated by previous run match those of current file
                      # set. If not we assume they have been manually altered and
                      # therefore should not be overridden
                      if item.checksums != item.recorded_checksums:
                          logger.warning(
                              "Checksums for {} do not match those recorded in "
                              "provenance. Assuming it has been manually "
                              "corrected outside of Arcana and will therefore "
                              "not overwrite. Please delete manually if this "
                              "is not intended".format(repr(item)))
                          to_protect_array[array_inds(item)] = True
                          to_protect[array_inds(item)].append(item)
                      elif required:
                          if force:
                              to_process_array[array_inds(item)] = True
                          else:
                              to_check_array[array_inds(item)] = True
                  elif required:
                      to_process_array[array_inds(item)] = True
          # Filter sessions to process by those requested
          to_process_array *= filter_array
          to_check_array *= (filter_array * np.invert(to_process_array))
          if to_check_array.any() and self.prov_check:
              # Get list of sessions, subjects, visits, tree objects to check
              # their provenance against that of the pipeline
              to_check = []
              if 'per_session' in output_freqs:
                  to_check.extend(
                      s for s in tree.sessions
                      if to_check_array[subject_inds[s.subject_id],
                                        visit_inds[s.visit_id]])
              if 'per_subject' in output_freqs:
                  # We can just test the first col of outputs_exist as rows
                  # should be either all True or all False
                  to_check.extend(s for s in tree.subjects
                                  if to_check_array[subject_inds[s.id], 0])
              if 'per_visit' in output_freqs:
                  # We can just test the first row of outputs_exist as cols
                  # should be either all True or all False
                  to_check.extend(v for v in tree.visits
                                  if to_check_array[0, visit_inds[v.id]])
              if 'per_study' in output_freqs:
                  to_check.append(tree)
              for node in to_check:
                  # Generated expected record from current pipeline/repository-
                  # state
                  requires_reprocess = False
                  try:
                      # Retrieve record stored in tree node
                      record = node.record(pipeline.name, pipeline.study.name)
                      expected_record = pipeline.expected_record(node)
  
                      # Compare record with expected
                      mismatches = record.mismatches(expected_record,
                                                     self.prov_check,
                                                     self.prov_ignore)
                      if mismatches:
                          msg = ("mismatch in provenance:\n{}\n Add mismatching "
                                 "paths (delimeted by '/') to 'prov_ignore' "
                                 "argument of Processor to ignore"
                                 .format(
                                     pformat(mismatches)))
                          requires_reprocess = True
                  except ArcanaNameError as e:
                      msg = "missing provenance record"
                      requires_reprocess = False
                      to_protect_array[array_inds(node)] = True
                  except ArcanaDataNotDerivedYetError as e:
                      msg = ("missing input '{}' and therefore cannot check "
                             "provenance".format(e.name))
                      requires_reprocess = True
                  if requires_reprocess:
                      if self.reprocess:
                          to_process_array[array_inds(node)] = True
                          logger.info(
                              "Reprocessing {} with '{}' due to {}"
                              .format(node, pipeline.name, msg))
                      else:
                          raise ArcanaReprocessException(
                              "Cannot use derivatives for '{}' pipeline stored "
                              "in {} due to {}, set 'reprocess' "
                              "flag of Processor to overwrite".format(
                                  pipeline.name, node, msg))
  
          def inds_to_ids(inds):
              subject_id = next(k for k, v in subject_inds.items()
                                if v == inds[0])
              visit_id = next(k for k, v in visit_inds.items() if v == inds[1])
              return (subject_id, visit_id)
  
          # Dialate to process array
          to_process_array = self._dialate_array(to_process_array,
                                                 pipeline.joins)
          intersection = to_process_array * to_skip_array
          if intersection.any():
              missing_inputs_msg = ''
              missing_prq_inputs_msg = ''
              for sess_inds in zip(*np.nonzero(intersection)):
                  subject_id, visit_id = inds_to_ids(sess_inds)
                  if sess_inds in to_skip:
                      missing_inputs_msg += (
                          "\n(subject={}, visit={}): [{}]".format(
                              subject_id, visit_id,
                              ', '.join(i.name for i in to_skip[sess_inds])))
                  else:
                      missing_prq_inputs_msg += (
                          "\n(subject={}, visit={})".format(subject_id,
                                                            visit_id))
              warning_msg = ("Skipping the following nodes for '{}' pipeline due"
                             " to ".format(pipeline))
              if missing_inputs_msg:
                  warning_msg += "missing inputs:{}".format(missing_inputs_msg)
              if missing_prq_inputs_msg:
                  if missing_inputs_msg:
                      warning_msg += "\nand the following due to "
                  warning_msg += ("missing inputs to prerequisite pipelines:{}"
                                  .format(missing_prq_inputs_msg))
              logger.warning(warning_msg)
          # Remove nodes that are to be skipped due to missing inputs
          to_process_array *= np.invert(to_skip_array)
          # Check for conflicts between nodes to process and nodes to protect
          conflicting = to_process_array * to_protect_array
          if conflicting.any():
              error_msg = ''
              for sess_inds in zip(*np.nonzero(conflicting)):
                  subject_id, visit_id = inds_to_ids(sess_inds)
                  if required_outputs is None:
                      conflict_outputs = pipeline.outputs
                  else:
                      conflict_outputs = [pipeline.study.bound_spec(r)
                                          for r in required_outputs]
                  items = [
                      o.collection.item(subject_id=subject_id, visit_id=visit_id)
                      for o in conflict_outputs]
                  missing = [i for i in items if i not in to_protect[sess_inds]]
                  error_msg += (
                      "\n({}, {}): protected=[{}], missing=[{}]"
                      .format(
                          subject_id, visit_id,
                          ', '.join(i.name for i in to_protect[sess_inds]),
                          ', '.join(i.name for i in missing)))
              raise ArcanaProtectedOutputConflictError(
                  "Cannot process {} as there are nodes with both protected "
                  "outputs (ones modified externally to Arcana) and missing "
                  "required outputs. Either delete protected outputs or provide "
                  "missing required outputs to continue:{}".format(pipeline,
                                                                   error_msg))
          # Add in any prerequisites to process that aren't explicitly protected
          to_process_array |= (prqs_to_process_array *
                               filter_array *
                               np.invert(to_protect_array))
          to_process_array = self._dialate_array(to_process_array,
                                                 pipeline.joins)
          return to_process_array, to_protect_array, to_skip_array