def run(args):
  if ("--help" in args or "-h" in args) :
    print "Write a CBF header from a SLAC metrology file. Parameters:"
    master_phil.show(attributes_level=2)
    return

  user_phil = []
  for arg in args:
    if (os.path.isfile(arg)) :
      user_phil.append(libtbx.phil.parse("""metrology_file=\"%s\"""" % arg))
    else :
      try :
        user_phil.append(libtbx.phil.parse(arg))
      except RuntimeError as e :
        raise Sorry("Unrecognized argument '%s' (error: %s)" % (arg, str(e)))

  params = master_phil.fetch(sources=user_phil).extract()
  if (params.metrology_file is None) :
    master_phil.show()
    raise Usage("metrology_file must be defined (either metrology_file=XXX, or the file path alone).")
  assert params.plot is not None
  assert params.out is not None

  print params.metrology_file

  metro = read_slac_metrology(params.metrology_file, plot=params.plot)

  write_cspad_cbf(None, metro, 'cbf', None, params.out, None, 0, header_only=True)
  def run(self):
    params, options = self.parser.parse_args(show_diff_phil=True)
    experiments = flatten_experiments(params.input.experiments)

    detector = experiments[0].detector

    metro = map_detector_to_basis_dict(detector)
    write_cspad_cbf(None, metro, 'cbf', None, params.output_def_file, None, detector.hierarchy().get_distance(), header_only=True)

    print "Done"
Exemplo n.º 3
0
def run(args):
    command_line = (option_parser().option(
        "-o",
        "--output_filename",
        action="store",
        type="string",
        help="Filename for the output cbf file",
        default="gain_map.cbf"
    ).option(
        "-m",
        "--metrology",
        action="store",
        type="string",
        help="CBF or DEF file",
        default=None
    ).option(
        "-d",
        "--distance",
        action="store",
        type="int",
        help=
        "Detector distance put into the gain cbf file. Not needed for processing.",
        default="0"
    ).option(
        "-w",
        "--wavelength",
        action="store",
        type="float",
        help=
        "Incident beam wavelength put into the gain cbf file. Not needed for processing.",
        default="0")).process(args=args)

    output_filename = command_line.options.output_filename
    metrology = command_line.options.metrology
    assert metrology is not None and os.path.isfile(metrology)

    args = command_line.args

    assert len(args) == 1
    if args[0].endswith('.txt') or args[0].endswith('.gain'):
        raw_data = numpy.loadtxt(args[0])
        assert raw_data.shape in [(5920, 388), (11840, 194)]
        tiles = convert_detector(raw_data)
    else:
        raise Usage(
            "Gain input file should be a text file with extension .txt or .gain"
        )

    metro = cbf_file_to_basis_dict(metrology)
    write_cspad_cbf(tiles, metro, 'cbf', None, output_filename,
                    command_line.options.wavelength,
                    command_line.options.distance)
Exemplo n.º 4
0
def run(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    command_line = libtbx.option_parser.option_parser(
        usage="%s files" % libtbx.env.dispatcher_name).process(args=argv)

    paths = command_line.args
    if len(paths) <= 0:
        raise Usage("No files specified")

    for path in paths:
        # Load the metrology dictionary, containting basis shifts for each item in the hierarchy
        metro = cbf_file_to_basis_dict(path)

        # Remove from the hiearchy all but the central sensors (sensor 1 of each quadrant).
        # Need to remove the sesnor basis shifts and the corresponding asic shifts
        for quad in range(4):
            for sensor in [0, 2, 3, 4, 5, 6, 7]:
                metro.pop((0, quad, sensor))
                for asic in range(2):
                    metro.pop((0, quad, sensor, asic))

        # Renumber the sensors to 0 instead of 1
        for key in metro:
            if len(key) == 3:
                detector, quad, sensor = key
                metro[(detector, quad, 0)] = metro.pop(key)
            elif len(key) == 4:
                detector, quad, sensor, asic = key
                metro[(detector, quad, 0, asic)] = metro.pop(key)

        # Build the tiles dictionary for only sensor 1 of each quadrant.  Rename that sensor to zero.
        img = dxtbx.load(path)
        tiles = {}
        for quad in range(4):
            src_sensor = 1
            dest_sensor = 0
            for asic in range(2):
                tiles[(0, quad, dest_sensor, asic)] = img.get_raw_data()[
                    (quad * 16) + (src_sensor * 2) +
                    asic]  # FIXME get the panel ID from dxtbx

        destpath = os.path.splitext(path)[0] + "_pinwheel.cbf"

        hierarchy = img.get_detector().hierarchy()
        beam = img.get_beam()

        # write the result.  Have to call abs on the root distance because of a bug with the hierarchy matrix.
        write_cspad_cbf(tiles, metro, 'cbf', None, destpath,
                        beam.get_wavelength(), hierarchy.get_distance())
Exemplo n.º 5
0
def run(argv=None):
  if argv is None:
    argv = sys.argv[1:]

  command_line = libtbx.option_parser.option_parser(
    usage="%s files" % libtbx.env.dispatcher_name).process(args=argv)

  paths = command_line.args
  if len(paths) <= 0:
    raise Usage("No files specified")

  for path in paths:
    # Load the metrology dictionary, containting basis shifts for each item in the hierarchy
    metro = cbf_file_to_basis_dict(path)

    # Remove from the hiearchy all but the central sensors (sensor 1 of each quadrant).
    # Need to remove the sesnor basis shifts and the corresponding asic shifts
    for quad in range(4):
      for sensor in [0,2,3,4,5,6,7]:
        metro.pop((0,quad,sensor))
        for asic in range(2):
          metro.pop((0,quad,sensor,asic))

    # Renumber the sensors to 0 instead of 1
    for key in metro:
      if len(key) == 3:
        detector, quad, sensor = key
        metro[(detector,quad,0)] = metro.pop(key)
      elif len(key) == 4:
        detector, quad, sensor, asic = key
        metro[(detector,quad,0,asic)] = metro.pop(key)

    # Build the tiles dictionary for only sensor 1 of each quadrant.  Rename that sensor to zero.
    img = dxtbx.load(path)
    tiles = {}
    for quad in range(4):
      src_sensor = 1
      dest_sensor = 0
      for asic in range(2):
        tiles[(0,quad,dest_sensor,asic)] = img.get_raw_data()[(quad*16)+(src_sensor*2)+asic] # FIXME get the panel ID from dxtbx

    destpath = os.path.splitext(path)[0] + "_pinwheel.cbf"

    hierarchy = img.get_detector().hierarchy()
    beam = img.get_beam()

    # write the result.  Have to call abs on the root distance because of a bug with the hierarchy matrix.
    write_cspad_cbf(tiles, metro, 'cbf', None, destpath, beam.get_wavelength(), hierarchy.get_distance())
Exemplo n.º 6
0
def run(args):
    command_line = (
        option_parser()
        .option(
            "-o",
            "--output_filename",
            action="store",
            type="string",
            help="Filename for the output cbf file",
            default="gain_map.cbf",
        )
        .option("-m", "--metrology", action="store", type="string", help="CBF or DEF file", default=None)
        .option(
            "-d",
            "--distance",
            action="store",
            type="int",
            help="Detector distance put into the gain cbf file. Not needed for processing.",
            default="0",
        )
        .option(
            "-w",
            "--wavelength",
            action="store",
            type="float",
            help="Incident beam wavelength put into the gain cbf file. Not needed for processing.",
            default="0",
        )
    ).process(args=args)

    output_filename = command_line.options.output_filename
    metrology = command_line.options.metrology
    assert metrology is not None and os.path.isfile(metrology)

    args = command_line.args

    assert len(args) == 1
    if args[0].endswith(".txt") or args[0].endswith(".gain"):
        raw_data = numpy.loadtxt(args[0])
        assert raw_data.shape in [(5920, 388), (11840, 194)]
        tiles = convert_detector(raw_data)
    else:
        raise Usage("Gain input file should be a text file with extension .txt or .gain")

    metro = cbf_file_to_basis_dict(metrology)
    write_cspad_cbf(
        tiles, metro, "cbf", None, output_filename, command_line.options.wavelength, command_line.options.distance
    )
Exemplo n.º 7
0
    def run(self):
        params, options = self.parser.parse_args(show_diff_phil=True)
        experiments = flatten_experiments(params.input.experiments)

        detector = experiments[0].detector

        metro = map_detector_to_basis_dict(detector)
        write_cspad_cbf(None,
                        metro,
                        'cbf',
                        None,
                        params.output_def_file,
                        None,
                        detector.hierarchy().get_distance(),
                        header_only=True)

        print("Done")
Exemplo n.º 8
0
    def run(self):
        params, options = self.parser.parse_args(show_diff_phil=True)
        datablocks = flatten_datablocks(params.input.datablock)
        experiments = flatten_experiments(params.input.experiments)

        if len(datablocks) > 0:
            detector = datablocks[0].unique_detectors()[0]
        else:
            detector = experiments[0].detector

        metro = map_detector_to_basis_dict(detector)
        write_cspad_cbf(None,
                        metro,
                        'cbf',
                        None,
                        params.output_def_file,
                        None,
                        detector.hierarchy().get_distance(),
                        header_only=True)

        print "Done"
  .help = Directory with optical metrology information posistioning quadrants and sensors, corrected for
  .help = rectangularity
  .optional = False
out = None
  .type = str
  .help = Output file name
  .optional = False
""")

if (__name__ == "__main__") :
  user_phil = []
  for arg in sys.argv[1:]:
    if (os.path.isdir(arg)) :
      user_phil.append(libtbx.phil.parse("""metrology_dir=\"%s\"""" % arg))
    else :
      try :
        user_phil.append(libtbx.phil.parse(arg))
      except RuntimeError, e :
        raise Sorry("Unrecognized argument '%s' (error: %s)" % (arg, str(e)))

  params = master_phil.fetch(sources=user_phil).extract()
  if (params.metrology_dir is None) :
    master_phil.show()
    raise Usage("metrology_dir must be defined (either metrology_dir=XXX, or the directory path alone).")
  assert params.out is not None

  metro_phil = metrology2phil(params.metrology_dir, verbose=True)

  write_cspad_cbf(None, metro_phil, 'calibdir', None, params.out, None, 0, header_only=True)

Exemplo n.º 10
0
      for p in xrange(num_sections):
        for s in xrange(section_len):
          for a in xrange(2):
            x1,y1,x2,y2 = active_areas[tile_id]
            block = data[x1:x2,y1:y2]
            asics[(0, p, s, a)] = block.matrix_rot90(-rotations[tile_id])
            tile_id += 1

            if not 'asic_focus' in locals():
              asic_focus = asics[(0, p, s, a)].focus()
            else:
              assert asic_focus == asics[(0, p, s, a)].focus()

    # make the tiles dictionary
    for p in xrange(num_sections):
      tiles[(0,p)] = type(data)(flex.grid(asic_focus[0]*section_len,asic_focus[1]*2))
      for s in xrange(section_len):
        tiles[(0,p)].matrix_paste_block_in_place(asics[(0,p,s,0)],
                                                 i_row = s*asic_focus[0],
                                                 i_column = 0)
        tiles[(0,p)].matrix_paste_block_in_place(asics[(0,p,s,1)],
                                                 i_row = s*asic_focus[0],
                                                 i_column = asic_focus[1])
      tiles[(0,p)].reshape(flex.grid((section_len,asic_focus[0],asic_focus[1]*2)))

    # Write the cbf file
    destpath = os.path.splitext(filename)[0] + ".cbf"

    write_cspad_cbf(tiles, metro, metro_style, img['TIMESTAMP'], destpath,
                    img['WAVELENGTH'], img['DISTANCE'])
Exemplo n.º 11
0
from __future__ import division
from __future__ import print_function

# script to convert the output of refined_quadrants to a cbf header that
# can be applied to a cspad cbf with cxi.apply_metrology
# Note the hardcoded wavelength and distance.  For development only.

from dials.util.command_line import Importer

importer = Importer(["refined_experiments.json"], check_format=False)
experiment = importer.experiments[0]
detector = experiment.detector

from xfel.cftbx.detector.cspad_cbf_tbx import (
    write_cspad_cbf,
    map_detector_to_basis_dict,
)

metro = map_detector_to_basis_dict(detector)
write_cspad_cbf(None,
                metro,
                "cbf",
                None,
                "quad_refined.def",
                1.81587,
                105,
                header_only=True)

print("Done")
    print "Write a CBF header from a SLAC metrology file. Parameters:"
    master_phil.show(attributes_level=2)
    return

  user_phil = []
  for arg in args:
    if (os.path.isfile(arg)) :
      user_phil.append(libtbx.phil.parse("""metrology_file=\"%s\"""" % arg))
    else :
      try :
        user_phil.append(libtbx.phil.parse(arg))
      except RuntimeError, e :
        raise Sorry("Unrecognized argument '%s' (error: %s)" % (arg, str(e)))

  params = master_phil.fetch(sources=user_phil).extract()
  if (params.metrology_file is None) :
    master_phil.show()
    raise Usage("metrology_file must be defined (either metrology_file=XXX, or the file path alone).")
  assert params.plot is not None
  assert params.out is not None

  print params.metrology_file

  metro = read_slac_metrology(params.metrology_file, plot=params.plot)

  write_cspad_cbf(None, metro, 'cbf', None, params.out, None, 0, header_only=True)

if (__name__ == "__main__") :
  args = sys.argv[1:]
  run(args)
Exemplo n.º 13
0
                        asics[(0, p, s,
                               a)] = block.matrix_rot90(-rotations[tile_id])
                        tile_id += 1

                        if not 'asic_focus' in locals():
                            asic_focus = asics[(0, p, s, a)].focus()
                        else:
                            assert asic_focus == asics[(0, p, s, a)].focus()

        # make the tiles dictionary
        for p in range(num_sections):
            tiles[(0, p)] = type(data)(flex.grid(asic_focus[0] * section_len,
                                                 asic_focus[1] * 2))
            for s in range(section_len):
                tiles[(0,
                       p)].matrix_paste_block_in_place(asics[(0, p, s, 0)],
                                                       i_row=s * asic_focus[0],
                                                       i_column=0)
                tiles[(0,
                       p)].matrix_paste_block_in_place(asics[(0, p, s, 1)],
                                                       i_row=s * asic_focus[0],
                                                       i_column=asic_focus[1])
            tiles[(0, p)].reshape(
                flex.grid((section_len, asic_focus[0], asic_focus[1] * 2)))

        # Write the cbf file
        destpath = os.path.splitext(filename)[0] + ".cbf"

        write_cspad_cbf(tiles, metro, metro_style, img['TIMESTAMP'], destpath,
                        img['WAVELENGTH'], img['DISTANCE'])
            except RuntimeError, e:
                raise Sorry("Unrecognized argument '%s' (error: %s)" %
                            (arg, str(e)))

    params = master_phil.fetch(sources=user_phil).extract()
    if (params.metrology_file is None):
        master_phil.show()
        raise Usage(
            "metrology_file must be defined (either metrology_file=XXX, or the file path alone)."
        )
    assert params.plot is not None
    assert params.out is not None

    print params.metrology_file

    metro = read_slac_metrology(params.metrology_file, plot=params.plot)

    write_cspad_cbf(None,
                    metro,
                    'cbf',
                    None,
                    params.out,
                    None,
                    0,
                    header_only=True)


if (__name__ == "__main__"):
    args = sys.argv[1:]
    run(args)
Exemplo n.º 15
0
def run(argv=None):
  """Compute mean, standard deviation, and maximum projection images
  from a set of CSPAD cbf images given on the command line.

  @param argv Command line argument list
  @return     @c 0 on successful termination, @c 1 on error, and @c 2
              for command line syntax errors
  """

  import libtbx.load_env

  from libtbx import option_parser
  from scitbx.array_family import flex
  from dxtbx.format.Registry import Registry
  from xfel.cftbx.detector.cspad_cbf_tbx import cbf_file_to_basis_dict, write_cspad_cbf
#  from xfel.cxi.cspad_ana import cspad_tbx
#  from iotbx.detectors.cspad_detector_formats import reverse_timestamp

  if argv is None:
    argv = sys.argv
  command_line = (option_parser.option_parser(
    usage="%s [-v] [-a PATH] [-m PATH] [-s PATH] " \
    "image1 image2 [image3 ...]" % libtbx.env.dispatcher_name)
                  .option(None, "--average-path", "-a",
                          type="string",
                          default=None,
                          dest="avg_path",
                          metavar="PATH",
                          help="Write average image to PATH")
                  .option(None, "--maximum-path", "-m",
                          type="string",
                          default=None,
                          dest="max_path",
                          metavar="PATH",
                          help="Write maximum projection image to PATH")
                  .option(None, "--stddev-path", "-s",
                          type="string",
                          default=None,
                          dest="stddev_path",
                          metavar="PATH",
                          help="Write standard deviation image to PATH")
                  .option(None, "--verbose", "-v",
                          action="store_true",
                          default=False,
                          dest="verbose",
                          help="Print more information about progress")
                  ).process(args=argv[1:])

  # Note that it is not an error to omit the output paths, because
  # certain statistics could still be printed, e.g. with the verbose
  # option.
  paths = command_line.args
  if len(paths) == 0:
    command_line.parser.print_usage(file=sys.stderr)
    return 2

  # Loop over all images and accumulate statistics.
  nfail = 0
  nmemb = 0
  for path in paths:
    if command_line.options.verbose:
      sys.stdout.write("Processing %s...\n" % path)

    try:
      # Promote the image to double-precision floating point type.
      # All real-valued flex arrays have the as_double() function.
      # Warn if the header items across the set of images do not match
      # up.  Note that discrepancies regarding the image size are
      # fatal.
      if not 'reader' in locals():
        reader = Registry.find(path)
      img = reader(path)
      if 'detector' in locals():
        test_detector = img.get_detector()
        if len(test_detector) != len(detector):
          sys.stderr.write("Detectors do not have the same number of panels\n")
          return 1
        for t, d in zip(test_detector, detector):
          if t.get_image_size() != d.get_image_size():
            sys.stderr.write("Panel sizes do not match\n")
            return 1
          if t.get_pixel_size() != d.get_pixel_size():
            sys.stderr.write("Pixel sizes do not match\n")
            return 1
          if t.get_d_matrix() != d.get_d_matrix():
            sys.stderr.write("Detector panels are not all in the same location. The average will use the positions of the first image.\n")
        detector = test_detector
      else:
        detector = img.get_detector()

      data = [img.get_raw_data()[i].as_1d().as_double() for i in xrange(len(detector))]
      wavelength = img.get_beam().get_wavelength()
      distance = flex.mean(flex.double([d.get_directed_distance() for d in detector]))

    except Exception:
      nfail += 1
      continue

    # The sum-of-squares image is accumulated using long integers, as
    # this delays the point where overflow occurs.  But really, this
    # is just a band-aid...
    if nmemb == 0:
      max_img = copy.deepcopy(data)
      sum_distance = distance
      sum_img = copy.deepcopy(data)
      ssq_img = [flex.pow2(d) for d in data]
      sum_wavelength = wavelength
      metro = cbf_file_to_basis_dict(path)

    else:
      sel = [(d > max_d).as_1d() for d, max_d in zip(data, max_img)]
      for d, max_d, s in zip(data, max_img, sel): max_d.set_selected(s, d.select(s))

      sum_distance += distance
      for d, sum_d in zip(data, sum_img): sum_d += d
      for d, ssq_d in zip(data, ssq_img): ssq_d += flex.pow2(d)
      sum_wavelength += wavelength

    nmemb += 1

  # Early exit if no statistics were accumulated.
  if command_line.options.verbose:
    sys.stderr.write("Processed %d images (%d failed)\n" % (nmemb, nfail))
  if nmemb == 0:
    return 0

  # Calculate averages for measures where other statistics do not make
  # sense.  Note that avg_img is required for stddev_img.
  avg_img = [sum_d.as_double() / nmemb for sum_d in sum_img]
  avg_distance = sum_distance / nmemb
  avg_wavelength = sum_wavelength / nmemb

  def make_tiles(data, detector):
    """
    Assemble a tiles dictionary as required by write_cspad_cbf, consisting of 4 arrays of shape 8x185x388.
    Assumes the order in the data array matches the order of the enumerated detector panels.
    """
    assert len(data) == 64
    tiles = {}
    s, f = 185, 194

    for q_id in xrange(4):
      tiles[0,q_id] = flex.double((flex.grid(s*8, f*2)))
      for s_id in xrange(8):
        for a_id in xrange(2):
          asic_idx = (q_id*16) + (s_id*2) + a_id
          asic = data[asic_idx]
          asic.reshape(flex.grid((s, f)))

          tiles[0, q_id].matrix_paste_block_in_place(asic, s_id*s, a_id*f)
      tiles[0, q_id].reshape(flex.grid((8, s, f*2)))

    return tiles


  # Output the average image, maximum projection image, and standard
  # deviation image, if requested.
  if command_line.options.avg_path is not None:
    tiles = make_tiles(avg_img, detector)
    write_cspad_cbf(tiles, metro, 'cbf', None, command_line.options.avg_path, avg_wavelength, avg_distance)

  if command_line.options.max_path is not None:
    tiles = make_tiles(max_img, detector)
    write_cspad_cbf(tiles, metro, 'cbf', None, command_line.options.max_path, avg_wavelength, avg_distance)

  if command_line.options.stddev_path is not None:
    stddev_img = [ssq_d.as_double() - sum_d.as_double() * avg_d for ssq_d, sum_d, avg_d in zip(ssq_img, sum_img, avg_img)]

    # Accumulating floating-point numbers introduces errors, which may
    # cause negative variances.  Since a two-pass approach is
    # unacceptable, the standard deviation is clamped at zero.
    for stddev_d in stddev_img:
      stddev_d.set_selected(stddev_d < 0, 0)

    if nmemb == 1:
      stddev_img = [flex.sqrt(stddev_d) for stddev_d in stddev_img]
    else:
      stddev_img = [flex.sqrt(stddev_d / (nmemb - 1)) for stddev_d in stddev_img]

    tiles = make_tiles(stddev_img, detector)
    write_cspad_cbf(tiles, metro, 'cbf', None, command_line.options.stddev_path, avg_wavelength, avg_distance)

  return 0
Exemplo n.º 16
0
from __future__ import division
# script to convert the output of refined_quadrants to a cbf header that
# can be applied to a cspad cbf with cxi.apply_metrology
# Note the hardcoded wavelength and distance.  For development only.

from dials.util.command_line import Importer

importer = Importer(['refined_experiments.json'], check_format=False)
experiment = importer.experiments[0]
detector = experiment.detector

from xfel.cftbx.detector.cspad_cbf_tbx import write_cspad_cbf, map_detector_to_basis_dict

metro = map_detector_to_basis_dict(detector)
write_cspad_cbf(None, metro, 'cbf', None, 'quad_refined.def', 1.81587, 105, header_only=True)

print "Done"
Exemplo n.º 17
0
    user_phil = []
    for arg in sys.argv[1:]:
        if (os.path.isdir(arg)):
            user_phil.append(
                libtbx.phil.parse("""metrology_dir=\"%s\"""" % arg))
        else:
            try:
                user_phil.append(libtbx.phil.parse(arg))
            except RuntimeError, e:
                raise Sorry("Unrecognized argument '%s' (error: %s)" %
                            (arg, str(e)))

    params = master_phil.fetch(sources=user_phil).extract()
    if (params.metrology_dir is None):
        master_phil.show()
        raise Usage(
            "metrology_dir must be defined (either metrology_dir=XXX, or the directory path alone)."
        )
    assert params.out is not None

    metro_phil = metrology2phil(params.metrology_dir, verbose=True)

    write_cspad_cbf(None,
                    metro_phil,
                    'calibdir',
                    None,
                    params.out,
                    None,
                    0,
                    header_only=True)
Exemplo n.º 18
0
def run(argv=None):
    """Compute mean, standard deviation, and maximum projection images
  from a set of CSPAD cbf images given on the command line.

  @param argv Command line argument list
  @return     @c 0 on successful termination, @c 1 on error, and @c 2
              for command line syntax errors
  """

    import libtbx.load_env

    from libtbx import option_parser
    from scitbx.array_family import flex
    from dxtbx.format.Registry import Registry
    from xfel.cftbx.detector.cspad_cbf_tbx import cbf_file_to_basis_dict, write_cspad_cbf
    #  from xfel.cxi.cspad_ana import cspad_tbx
    #  from iotbx.detectors.cspad_detector_formats import reverse_timestamp

    if argv is None:
        argv = sys.argv
    command_line = (option_parser.option_parser(
      usage="%s [-v] [-a PATH] [-m PATH] [-s PATH] " \
      "image1 image2 [image3 ...]" % libtbx.env.dispatcher_name)
                    .option(None, "--average-path", "-a",
                            type="string",
                            default=None,
                            dest="avg_path",
                            metavar="PATH",
                            help="Write average image to PATH")
                    .option(None, "--maximum-path", "-m",
                            type="string",
                            default=None,
                            dest="max_path",
                            metavar="PATH",
                            help="Write maximum projection image to PATH")
                    .option(None, "--stddev-path", "-s",
                            type="string",
                            default=None,
                            dest="stddev_path",
                            metavar="PATH",
                            help="Write standard deviation image to PATH")
                    .option(None, "--verbose", "-v",
                            action="store_true",
                            default=False,
                            dest="verbose",
                            help="Print more information about progress")
                    ).process(args=argv[1:])

    # Note that it is not an error to omit the output paths, because
    # certain statistics could still be printed, e.g. with the verbose
    # option.
    paths = command_line.args
    if len(paths) == 0:
        command_line.parser.print_usage(file=sys.stderr)
        return 2

    # Loop over all images and accumulate statistics.
    nfail = 0
    nmemb = 0
    for path in paths:
        if command_line.options.verbose:
            sys.stdout.write("Processing %s...\n" % path)

        try:
            # Promote the image to double-precision floating point type.
            # All real-valued flex arrays have the as_double() function.
            # Warn if the header items across the set of images do not match
            # up.  Note that discrepancies regarding the image size are
            # fatal.
            if not 'reader' in locals():
                reader = Registry.find(path)
            img = reader(path)
            if 'detector' in locals():
                test_detector = img.get_detector()
                if len(test_detector) != len(detector):
                    sys.stderr.write(
                        "Detectors do not have the same number of panels\n")
                    return 1
                for t, d in zip(test_detector, detector):
                    if t.get_image_size() != d.get_image_size():
                        sys.stderr.write("Panel sizes do not match\n")
                        return 1
                    if t.get_pixel_size() != d.get_pixel_size():
                        sys.stderr.write("Pixel sizes do not match\n")
                        return 1
                    if t.get_d_matrix() != d.get_d_matrix():
                        sys.stderr.write(
                            "Detector panels are not all in the same location. The average will use the positions of the first image.\n"
                        )
                detector = test_detector
            else:
                detector = img.get_detector()

            data = [
                img.get_raw_data()[i].as_1d().as_double()
                for i in range(len(detector))
            ]
            wavelength = img.get_beam().get_wavelength()
            distance = flex.mean(
                flex.double([d.get_directed_distance() for d in detector]))

        except Exception:
            nfail += 1
            continue

        # The sum-of-squares image is accumulated using long integers, as
        # this delays the point where overflow occurs.  But really, this
        # is just a band-aid...
        if nmemb == 0:
            max_img = copy.deepcopy(data)
            sum_distance = distance
            sum_img = copy.deepcopy(data)
            ssq_img = [flex.pow2(d) for d in data]
            sum_wavelength = wavelength
            metro = cbf_file_to_basis_dict(path)

        else:
            sel = [(d > max_d).as_1d() for d, max_d in zip(data, max_img)]
            for d, max_d, s in zip(data, max_img, sel):
                max_d.set_selected(s, d.select(s))

            sum_distance += distance
            for d, sum_d in zip(data, sum_img):
                sum_d += d
            for d, ssq_d in zip(data, ssq_img):
                ssq_d += flex.pow2(d)
            sum_wavelength += wavelength

        nmemb += 1

    # Early exit if no statistics were accumulated.
    if command_line.options.verbose:
        sys.stderr.write("Processed %d images (%d failed)\n" % (nmemb, nfail))
    if nmemb == 0:
        return 0

    # Calculate averages for measures where other statistics do not make
    # sense.  Note that avg_img is required for stddev_img.
    avg_img = [sum_d.as_double() / nmemb for sum_d in sum_img]
    avg_distance = sum_distance / nmemb
    avg_wavelength = sum_wavelength / nmemb

    def make_tiles(data, detector):
        """
    Assemble a tiles dictionary as required by write_cspad_cbf, consisting of 4 arrays of shape 8x185x388.
    Assumes the order in the data array matches the order of the enumerated detector panels.
    """
        assert len(data) == 64
        tiles = {}
        s, f = 185, 194

        for q_id in range(4):
            tiles[0, q_id] = flex.double((flex.grid(s * 8, f * 2)))
            for s_id in range(8):
                for a_id in range(2):
                    asic_idx = (q_id * 16) + (s_id * 2) + a_id
                    asic = data[asic_idx]
                    asic.reshape(flex.grid((s, f)))

                    tiles[0, q_id].matrix_paste_block_in_place(
                        asic, s_id * s, a_id * f)
            tiles[0, q_id].reshape(flex.grid((8, s, f * 2)))

        return tiles

    # Output the average image, maximum projection image, and standard
    # deviation image, if requested.
    if command_line.options.avg_path is not None:
        tiles = make_tiles(avg_img, detector)
        write_cspad_cbf(tiles, metro, 'cbf', None,
                        command_line.options.avg_path, avg_wavelength,
                        avg_distance)

    if command_line.options.max_path is not None:
        tiles = make_tiles(max_img, detector)
        write_cspad_cbf(tiles, metro, 'cbf', None,
                        command_line.options.max_path, avg_wavelength,
                        avg_distance)

    if command_line.options.stddev_path is not None:
        stddev_img = [
            ssq_d.as_double() - sum_d.as_double() * avg_d
            for ssq_d, sum_d, avg_d in zip(ssq_img, sum_img, avg_img)
        ]

        # Accumulating floating-point numbers introduces errors, which may
        # cause negative variances.  Since a two-pass approach is
        # unacceptable, the standard deviation is clamped at zero.
        for stddev_d in stddev_img:
            stddev_d.set_selected(stddev_d < 0, 0)

        if nmemb == 1:
            stddev_img = [flex.sqrt(stddev_d) for stddev_d in stddev_img]
        else:
            stddev_img = [
                flex.sqrt(stddev_d / (nmemb - 1)) for stddev_d in stddev_img
            ]

        tiles = make_tiles(stddev_img, detector)
        write_cspad_cbf(tiles, metro, 'cbf', None,
                        command_line.options.stddev_path, avg_wavelength,
                        avg_distance)

    return 0