function trnsps, a
  return, n_elements(a) gt 1 ? transpose( a ) : a
end
;
;+
; PROJECT:
;	HESSI
; NAME:
;	HSI_CART_MODUL_PATTERN_1DET
;
; PURPOSE:
;	This function returns the cartesian image response building blocks.
;
; CATEGORY:
;	HESSI, IMAGE
;
; CALLING SEQUENCE:
;  mpat2 = hsi_cart_modul_pattern_1det( obj, cntrl, cbeptrarr=cbarr, this_det=2)
; CALLS:
;	HESSI OBJECTS
;
; INPUTS:
;      obj - object containing calibrated eventlist object.
; OUTPUTS:
;    IDL> help, *mp[2]
;    ** Structure <24acf930>, 27 tags, length=25240, data length=25234, refs=1:
;       COS_MAP0        POINTER   <PtrHeapVar1504>
;       COS_MAP1        POINTER   <PtrHeapVar1505>
;       SIN_MAP0        POINTER   <PtrHeapVar1506>
;       SIN_MAP1        POINTER   <PtrHeapVar1507>
;       VRATE0          FLOAT     Array[768]
;       CVRATE          FLOAT     Array[768]
;       SVRATE          FLOAT     Array[768]
;       SINSIGN         INT              1
;       CBE_ROLL_ANGLE  FLOAT     Array[768]
;       MPAT_ROLL_ANGLE FLOAT     Array[768]
;       REV_INDICES     LONG      Array[833]
;       VLD_INDICES     LONG      Array[16]
;       IXMPAT          LONG      Array[64]
;       WEIGHT_MAP_PTR  POINTER   <PtrHeapVar1532>
;       MACOSPHZ        FLOAT     Array[768]
;       MASINPHZ        FLOAT     Array[768]
;       IMAGE_DIM       INT       Array[2]
;       PIXEL_SIZE      FLOAT     Array[2]
;       PIXEL_SCALE     FLOAT           1.00000
;       NMAPS           INT              1
;       NMAP            LONG      Array[2]
;       NPHZ            LONG                12
;       DET_INDEX       INT              2
;       DET_EFF_REL     FLOAT          0.974749
;       DET_EFF_AVG     FLOAT          0.548151
;       ENERGY_BAND     FLOAT     Array[2]
;       XYOFFSET        FLOAT     Array[2];
; OPTIONAL OUTPUTS:
;	none
;
; KEYWORDS:
;	 weight - detector weights
;	 phz_nbins - if you want to override the control structure. Should be consistent
;	 eventually but if the phase stacker isn't set then phz_nbins should be 1
;	 roll_angle - if set, then use_phz_stacker should be 0. Relaxes the constraint
;	 that there is a roll_angle for every element of the calib_eventlist
;
; COMMON BLOCKS:
;	none
;
; SIDE EFFECTS:
;	none
;
; RESTRICTIONS:
;	none
;
; PROCEDURE:
;	Builds cosine and sine modulation pattern maps for phase stacked,
;	unstacked, or visibility-derived
;	calibrated eventlists for the rotation angle and
;	phase of the first element in a stack group (same rot angle for 12 phases)
;	originally built to concatenate multiple detector modulation patterns
;
; MODIFICATION HISTORY:
;	20-aug-2015, richard.schwartz@nasa.gov
; 10-jul-2017, ras, simplification of hsi_cart_modul_pattern to support 1 det at a time
; 17-jul-2017, ras, removes some pointers from output structure, pointer is still
; better for cos_map1 and sin_map1 because of how they are used for pixon smoothing
; 27-jul-2017, ras, added sign factor for sin terms to make cos and sin operations more
;   symmetric and simpler downstream for bproj and profile
; 18-aug-2017, ras, made provisions for using cbe structures derived from visibilities
; 29-aug-2017, ras, refactoring, added support for unstacked cbe
; 13-feb-2018, ras, changed the comment on when the weight map is filled in
; 15-feb-2018, ras, inhibit higher harmonics for now. Only compute the response of the first harmonic
; 21-feb-2018, ras, include max_harmonic and code to add higher harmonics at a later date
; 09-mar-2018, ras, suppress higher harmonics being written into cos_map0, sin_map0 because it
;   will break pixon without modifications there in smoothing the module patterns because of syntax
;-
function hsi_cart_modul_pattern_1det,  obj_in, cntrl, $
  cbeptrarr = cbeptrarr, $
  phz_nbins = phz_nbins, $
  weight = weight, det_index = det_index, $
  roll_angle = roll_angle, $
  max_harmonic = max_harmonic, $

  error = error, $
  _extra = _extra

  twopi = 2.0 * !pi
  default, max_harmonic, 1
  default, det_index, 0
  default, weight,  1.0
  default, hh, 0 ;harmonic index, added 15-feb-2018, RAS, 0 for first harm
  obj_passed = size(/tname, obj_in) eq 'OBJREF'
  obj = obj_passed ? obj_in : hsi_calib_eventlist()
  if keyword_set( _extra )  then obj->set, _extra = _extra

  error = 1
  have_control = is_struct( cntrl )
  if ~have_control then cntrl = obj->get(/xyoffset, /image_dim, $
    /pixel_size, /pixel_scale, /use_phz_stacker, /phz_n_phase_bins, $
    /cbe_det_eff, /energy_band, /time_bin_def, /time_bin_min, /absolute_time_range)
  confirm_these = ['image_dim','xyoffset','use_phz_stacker',$
    'energy_band','cbe_det_eff','time_bin_def','time_bin_min',$
    'pixel_size','pixel_scale']
  valid = have_tag( cntrl, confirm_these, count = count)
  valid = valid and count eq n_elements( confirm_these )


  if ~valid then $
    message,'Must provide a calib_eventlist object or control structure for ' + $
    'image_dim, xyoffset, use_phz_stacker, energy_band, cbe_det_eff, ' + $
    'time_bin_def, time_bin_min, pixel_size, pixel_scale'
  is_calibvis    = 0 ;unless proven otherwise, check the cbeptrarr
  need_data      = 0 ;let's see in the next whether we need data, ie,
  ;cbeptrarr not passed

  z = where( ptr_valid( cbeptrarr ), nz)
  if nz ge 1 then begin

    calib_str = *cbeptrarr[ det_index ]
    is_calibvis = stregex(/boo, /fold, tag_names(/str, calib_str[0]), 'calibvis')

  endif else need_data = 1
  if need_data then begin
    cobj =  obj->get(/obj,class='hsi_calib_eventlist')
    ;if a full cbeptrarr or if it's a
    cbeptrarr =  cobj->framework::getdata()
    need_data = total( ptr_valid( cbeptrarr)) eq 9 ?  0 : 1
    if ~need_data then calib_str = *cbeptrarr[ det_index ]
  endif

  pixel_size     = cntrl.pixel_size
  pixel_scale    = cntrl.pixel_scale
  stacked        = cntrl.use_phz_stacker
  nphz           = stacked ? cntrl.phz_n_phase_bins : 1
  det_eff        = cntrl.cbe_det_eff
  energy_band    = cntrl.energy_band
  image_dim      = cntrl.image_dim
  time_bin_def   = cntrl.time_bin_def
  time_bin_min   = cntrl.time_bin_min
  xyoffset       = cntrl.xyoffset
  abs_time       = cntrl.absolute_time_range
  if keyword_set( phz_nbins ) then nphz = phz_nbins


  pixel_size = pixel_size * pixel_scale[0]


  npix = product( image_dim )
  ;help, nbin
  nbins = n_elements( calib_str )
  ;help, nbins
  nmap  = nbins/nphz ;start index of valid maps (0-!pi only, cos and sin)


  time_bins = float( time_bin_def[ det_index ] * time_bin_min )/2.0^20

  duration = abs_time[1]-abs_time[0]

  det_eff_rel = det_eff.rel[det_index]
  vrate0 = calib_str.livetime * calib_str.gridtran * $
    calib_str.flux_var * det_eff_rel * time_bins

  macosphz = fltarr( nbins, max_harmonic )
  masinphz = macosphz
  for hh = 0, max_harmonic-1 do begin
    macosphz[*,hh] = cos( calib_str.phase_map_ctr[hh] ) * $
      calib_str[*].modamp[ hh] * $
      (1 - calib_str.gap) * weight ;used in bproj
    masinphz[*,hh] = sin( calib_str.phase_map_ctr[hh] ) * $
      calib_str.modamp[*, hh] * $
      (1 - calib_str.gap) * weight
  endfor
  cvrate = rebin( vrate0, nbins, 3) * macosphz

  sinsign = is_calibvis ? 0: 1
  if ~is_calibvis then begin ; factor that is -1 for roll_angle btwn pi and 2pi
    sinsign = sin( calib_str.roll_angle)
    sinminus = where( sinsign lt 0, comp = sinplus )
    sinsign[ sinplus ] = 1.0
    sinsign[ sinminus ] = -1.0
    masinphz *= sinsign
  endif else begin ;this is necessary for the calibvis construction
    masinphz *= -1.0
  endelse
  svrate = rebin( vrate0, nbins, 3) * masinphz
  pxsize = pixel_size * pixel_scale
  ra     = calib_str.roll_angle
  ra = ((ra mod twopi) + twopi) mod twopi
  ;compute the cos and sin maps of the phase maps
  ;for each pixel in the fov, cos_sin varies from -1 to 1
  if ~stacked and ~keyword_set( roll_angle ) then begin
    default, min_nroll, 32 ;32
    default, max_nroll, 512 ;similar to annsec modpat density
    nroll_angle = ( hsi_cart_pattern_roll( image_dim, pxsize, $
      min_nroll = min_nroll, max_nroll = max_nroll ) )[det_index]
    roll_angle  = ( findgen( nroll_angle ) + 0.5 ) / nroll_angle * !pi
  endif
  default, roll_angle, ra
  exact = 0 ;
  if have_tag( calib_str, 'countsum') then begin
    nphz = 1 ;Special cbe derived from visibility, RAS, 18-aug-2017
    exact = 1 ;for the cbevis use the exact roll_angles
  endif
  ;Fill values to indicate this is not the case of an
  ;unstacked non-vis derived calib_eventlist
  rev_indices = 0
  vld_indices = 0
  ixmpat      = 0
  unstacked   = 0

  cos_sin_str  = hsi_cart_mpati_stk( roll_angle, det_index, nphz, $
    image_dim, pxsize, max_harmonic = max_harmonic, exact = exact ) ;reuse pixel_coord

  ;Determine the roll_angle and calib_eventlist index relationship for
  ;the unstacked non-vis derived calib_eventlist
  if nphz eq 1 and ~is_calibvis then begin
    dmap = (size(/dimension, cos_sin_str.cos))[1]

    hsi_cart_modul_pattern_indices,  dmap, roll_angle[0:*:nphz], ra,$
      rev_indices, hist_cbe2mpat, ixmpat
    vld_indices = where( hist_cbe2mpat<1, nvld )
    unstacked   = 1
  endif

  ;Now concatenate all the cos maps across sub-colls and all the sin maps.
  ;These are only from 0 to !pi
  nmap = [0, nmap]
  css  = cos_sin_str
  if max_harmonic eq 1 then begin
    cos_map0 = ptr_new( css.cos )
    cos_map1 = ptr_new( trnsps( css.cos ) )
    sin_map0 = ptr_new( css.sin )
    sin_map1 = ptr_new( trnsps( css.sin ) )
  endif else begin
    cos_map0 = [ptr_new( css.cos ), ptr_new( css.cos2 ), ptr_new( css.cos3 )]
    cos_map1 = [ptr_new( trnsps( css.cos ) ), $
      ptr_new( trnsps( css.cos2 )), ptr_new( trnsps( css.cos3 ))]
    sin_map0 = [ptr_new( css.sin ), ptr_new( css.sin2 ), ptr_new( css.sin3 )]
    sin_map1 = [ptr_new( trnsps( css.sin ) ), $
      ptr_new( trnsps( css.sin2 )), ptr_new( trnsps( css.sin3 ))]
  endelse

  cmpat = {$
    cos_map0:   cos_map0, $ ptrarr( max_harmonic ) (npix*npix,2*npat)
    cos_map1:   cos_map1, $ ptrarr( max_harmonic )
    sin_map0:   sin_map0, $ ptrarr( max_harmonic )
    sin_map1:   sin_map1, $ ptrarr( max_harmonic )
    vrate0:  vrate0, $ fltarr(nbins)
    cvrate:  cvrate, $ fltarr(nbins, max_harmonic)
    svrate:  svrate, $ fltarr(nbins, max_harmonic)
    ; for roll_angle from pi to 2pi, sin term mult by -1
    sinsign: keyword_set( sinsign ), $
    cbe_roll_angle:  ra, $
    mpat_roll_angle: roll_angle, $
    ;The next three fields are normally set for the case of an
    ;unstacked calib_eventlist and they provide the relationship
    ;between the bins and the rotation angles in the modulation patterns
    ;29-aug-2017, we will set them all to 0 for the case of the
    ;calib_eventlist that comes back from visibilities
    ;
    ;for unstacked cbe, the index for each virtual roll_angle from 0 to 2pi
    rev_indices: rev_indices, $ ;
    vld_indices: vld_indices, $ ;for unstacked cbe,the set of valid virtual indices
    ixmpat: ixmpat, $ ; the actual mpat_roll_angle index on 0 to pi
    unstacked: unstacked, $
    is_calibvis: is_calibvis, $
    weight_map_ptr:  ptr_new( ), $ ;to be filled in later by
    ; hsi_cart_bproj_weight_map and only when used in bproj

    macosphz:    macosphz, $ fltarr(nbins, max_harmonic)
    masinphz:    masinphz, $ fltarr(nbins, max_harmonic)
    ;if sinsign is set, then masinphz been modified by sinsign to contain the
    ;sign change needed for the sin_map(0 or 1) for cbe roll angles from pi to 2pi
    image_dim:   image_dim, $
    pixel_size:  pixel_size, $
    pixel_scale: pixel_scale, $
    nmaps: 1, $
    nmap: nmap, $
    nphz: nphz, $
    max_harmonic: max_harmonic, $ ;max harmonic computed for these modpats
    det_index: det_index, $ ;if this is -1 then more then it's aggregated
    time_bin_sec: time_bins, $ ;RAS, 16-feb-2018
    duration_sec: duration, $
    det_eff_rel: det_eff_rel, $
    det_eff_avg: det_eff.avg, $
    energy_band: energy_band, $
    xyoffset: xyoffset  }   ;property of pointing, not modulation patterns

  error = 0
  return, cmpat
end
