text: from __future__ import absolute_import, division, print_function
  
  import sys
  
  from boost.python import streambuf
  from scitbx import matrix
  from scitbx.array_family import flex
  
  from dxtbx import IncorrectFormatError
  from dxtbx.ext import (
      is_big_endian,
      read_uint8,
      read_uint16,
      read_uint16_bs,
      read_uint32,
      read_uint32_bs,
  )
  from dxtbx.format.FormatBruker import FormatBruker
  
  
  class FormatBrukerPhotonII(FormatBruker):
      @staticmethod
      def understand(image_file):
  
          try:
              header_lines = FormatBruker.read_header_lines(image_file)
          except IOError:
              return False
  
          header_dic = FormatBruker.parse_header(header_lines)
  
          dettype = header_dic.get("DETTYPE")
          if dettype is None:
              return False
          if not dettype.startswith("CMOS-PHOTONII"):
              return False
  
          return True
  
      def _start(self):
  
          try:
              header_lines = FormatBruker.read_header_lines(self._image_file)
          except IOError:
              return False
  
          self.header_dict = FormatBrukerPhotonII.parse_header(header_lines)
  
          # The Photon II format can't currently use BrukerImage, see
          # https://github.com/cctbx/cctbx_project/issues/65
          # from iotbx.detectors.bruker import BrukerImage
          # self.detectorbase = BrukerImage(self._image_file)
  
      def _goniometer(self):
          # goniometer angles in ANGLES are 2-theta, omega, phi, chi (FIXED)
          # AXIS indexes into this list to define the scan axis (in FORTRAN counting)
          # START and RANGE define the start and step size for each image
  
          _, omega, phi, chi = map(float, self.header_dict["ANGLES"].split())
          scan_axis = ["NONE", "2THETA", "OMEGA", "PHI", "CHI", "X", "Y", "Z"]
          scan_axis = scan_axis[int(self.header_dict["AXIS"])]
          names = flex.std_string(("PHI", "CHI", "OMEGA"))
          scan_axis = flex.first_index(names, scan_axis)
          if scan_axis is None:
              scan_axis = "OMEGA"  # default
  
          # https://journals.iucr.org/d/issues/2014/10/00/dz5309/dz5309sup1.pdf
          axes = flex.vec3_double(((0, -1, 0), (0, 0, 1), (0, 1, 0)))
          omega -= 180
          angles = flex.double((phi, chi, omega))
  
          return self._goniometer_factory.make_multi_axis_goniometer(
              axes, angles, names, scan_axis
          )
  
      @staticmethod
      def _estimate_gain(wavelength):
          """Estimate the detector gain based on values provided by Bruker. Each ADU
          corresponds to 36.6 electrons. The X-ray conversion results in deposited
          charge according to the following table for typical home sources:
  
          In (0.5136 A): 359.6893 e/X-ray
          Ag (0.5609 A): 329.3748 e/X-ray
          Mo (0.7107 A): 259.9139 e/X-ray
          Ga (1.3414 A): 137.6781 e/X-ray
          Cu (1.5418 A): 119.8156 e/X-ray
  
          This fits the linear model (1/G) = -0.0000193358 + 0.1981607255 * wavelength
          extremely well.
          """
          inv_gain = -0.0000193358 + 0.1981607255 * wavelength
          assert inv_gain > 0.1
          return 1.0 / inv_gain
  
      def _detector(self):
          # goniometer angles in ANGLES are 2-theta, omega, phi, chi (FIXED)
          two_theta = float(self.header_dict["ANGLES"].split()[0])
  
          overload = float(self.header_dict["CCDPARM"].split()[-1])
          underload = -1
  
          fast = matrix.col((1, 0, 0))
          slow = matrix.col((0, 1, 0))
          beam = matrix.col((0, 0, 1))
          pixel_mm = 5.0 / float(self.header_dict["DETTYPE"].split()[1])
          beam_pixel = [float(bp) for bp in self.header_dict["CENTER"].split()[:-3:-1]]
          distance_mm = 10.0 * float(self.header_dict["DISTANC"].split()[1])
          origin = (
              -distance_mm * beam
              - fast * pixel_mm * beam_pixel[1]
              - slow * pixel_mm * beam_pixel[0]
          )
          # 2theta rotation appears to be around the slow axis
          origin = origin.rotate_around_origin(slow, two_theta, deg=True)
          fast = fast.rotate_around_origin(slow, two_theta, deg=True)
          pixel_size = pixel_mm, pixel_mm
          # ncols is nfast, nrows is nslow
          image_size = (
              int(self.header_dict["NCOLS"].split()[0]),
              int(self.header_dict["NROWS"].split()[0]),
          )
  
          # Not a CCD, but is an integrating detector. Photon II has a 90 um Gadox
          # scintillator.
          gain = self._estimate_gain(float(self.header_dict["WAVELEN"].split()[0]))
          return self._detector_factory.complex(
              "CCD",
              origin.elems,
              fast.elems,
              slow.elems,
              pixel_size,
              image_size,
              (underload, overload),
              gain=gain,
          )
  
      def _beam(self):
          wavelength = float(self.header_dict["WAVELEN"].split()[0])
  
          return self._beam_factory.simple(wavelength)
  
      def _scan(self):
  
          start = float(self.header_dict["START"].split()[0])
          incr = float(self.header_dict["INCREME"].split()[0])
          if incr < 0:
              start *= -1
              incr *= -1
  
          return self._scan_factory.single(
              filename=self._image_file,
              format="BrukerCCD",
              exposure_times=1,
              osc_start=start,
              osc_width=incr,
              epoch=None,
          )
  
      def get_raw_data(self):
          """Get the pixel intensities (i.e. read the image and return as a
          flex array of integers.)"""
  
          # It is better to catch FORMAT 86 here and fail with a sensible error msg
          # as soon as something is attempted with the image data rather than in
          # the understand method. Otherwise the user gets FormatBruker reading the
          # image improperly but without failing
          if self.header_dict["FORMAT"] != "100":
              raise NotImplementedError(
                  "Only FORMAT 100 images from the Photon II are currently supported"
              )
  
          f = self.open_file(self._image_file, "rb")
          header_size = int(self.header_dict["HDRBLKS"]) * 512
          f.read(header_size)
  
          if is_big_endian():
              read_2b = read_uint16_bs
              read_4b = read_uint32_bs
          else:
              read_2b = read_uint16
              read_4b = read_uint32
  
          # NPIXELB stores the number of bytes/pixel for the data and the underflow
          # table. We expect 1 byte for underflows and either 2 or 1 byte per pixel
          # for the data
          npixelb = [int(e) for e in self.header_dict["NPIXELB"].split()]
          assert npixelb[1] == 1
  
          if npixelb[0] == 1:
              read_data = read_uint8
          elif npixelb[0] == 2:
              read_data = read_2b
          else:
              raise IncorrectFormatError(
                  "{} bytes per pixel is not supported".format(npixelb[0])
              )
  
          nrows = int(self.header_dict["NROWS"].split()[0])
          ncols = int(self.header_dict["NCOLS"].split()[0])
  
          raw_data = read_data(streambuf(f), nrows * ncols)
  
          image_size = (nrows, ncols)
          raw_data.reshape(flex.grid(*image_size))
  
          (num_underflows, num_2b_overflows, num_4b_overflows) = [
              int(e) for e in self.header_dict["NOVERFL"].split()
          ]
  
          # read underflows
          if num_underflows > 0:
              # stored values are padded to a multiple of 16 bytes
              nbytes = num_underflows + 15 & ~(15)
              underflow_vals = read_uint8(streambuf(f), nbytes)[:num_underflows]
          else:
              underflow_vals = None
  
          # handle 2 byte overflows
          if num_2b_overflows > 0:
              # stored values are padded to a multiple of 16 bytes
              nbytes = num_2b_overflows * 2 + 15 & ~(15)
              overflow_vals = read_2b(streambuf(f), nbytes // 2)[:num_2b_overflows]
              overflow = flex.int(nrows * ncols, 0)
              sel = (raw_data == 255).as_1d()
              overflow.set_selected(sel, overflow_vals - 255)
              overflow.reshape(flex.grid(*image_size))
              raw_data += overflow
  
          # handle 4 byte overflows
          if num_4b_overflows > 0:
              # stored values are padded to a multiple of 16 bytes
              nbytes = num_4b_overflows * 4 + 15 & ~(15)
              overflow_vals = read_4b(streambuf(f), nbytes // 4)[:num_4b_overflows]
              overflow = flex.int(nrows * ncols, 0)
              sel = (raw_data == 65535).as_1d()
              overflow.set_selected(sel, overflow_vals - 65535)
              overflow.reshape(flex.grid(*image_size))
              raw_data += overflow
  
          # handle underflows
          if underflow_vals is not None:
              sel = (raw_data == 0).as_1d()
              underflow = flex.int(nrows * ncols, 0)
              underflow.set_selected(sel, underflow_vals)
              underflow.reshape(flex.grid(*image_size))
              raw_data += underflow
  
          # handle baseline. num_underflows == -1 means no baseline subtraction. See
          # https://github.com/cctbx/cctbx_project/files/1262952/BISFrameFileFormats.zip
          if num_underflows != -1:
              num_exposures = [int(e) for e in self.header_dict["NEXP"].split()]
              baseline = num_exposures[2]
              raw_data += baseline
  
          return raw_data
  
  
  if __name__ == "__main__":
      for arg in sys.argv[1:]:
          print(FormatBrukerPhotonII.understand(arg))
  