code: /*******************************************************************************
   * Copyright (c) 2019 Attila Kovacs <attila[AT]sigmyne.com>.
   * All rights reserved. 
   * 
   * This file is part of crush.
   * 
   *     crush is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU General Public License as published by
   *     the Free Software Foundation, either version 3 of the License, or
   *     (at your option) any later version.
   * 
   *     crush is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU General Public License for more details.
   * 
   *     You should have received a copy of the GNU General Public License
   *     along with crush.  If not, see <http://www.gnu.org/licenses/>.
   * 
   * Contributors:
   *     Attila Kovacs <attila[AT]sigmyne.com> - initial API and implementation
   ******************************************************************************/
  
  package crush.instrument.hirmes;
  
  
  import crush.Channel;
  import crush.Frame;
  import crush.Instrument;
  import crush.telescope.sofia.SofiaFrame;
  import jnum.astro.GeodeticCoordinates;
  import jnum.math.Vector2D;
  
  
  class HirmesFrame extends SofiaFrame {
      /**
       * 
       */
      private static final long serialVersionUID = 2015348452508480568L;
      
      float LOS, roll;
      
      boolean qclState;
      long mceSerial;
      byte[] jumpCounter;
      
      
      int status;
  
      boolean isComplete = false;
      
      HirmesFrame(HirmesIntegration parent) {
          super(parent);
          create(getInstrument().size());
      }
  
      @Override
      public HirmesScan getScan() { return (HirmesScan) super.getScan(); }
      
      @Override
      public HirmesFrame copy(boolean withContents) {
          HirmesFrame copy = (HirmesFrame) super.copy(withContents);
          
          if(jumpCounter != null) {
              copy.jumpCounter = new byte[jumpCounter.length];
              if(withContents) System.arraycopy(jumpCounter, 0, copy.jumpCounter, 0, jumpCounter.length);
          }
          
          return copy;
      }
      
      @Override
      public Hirmes getInstrument() { return (Hirmes) super.getInstrument(); }
  
      void parseData(int frameIndex, int[] DAC, short[] jump) {   
          parseData(DAC, jump, frameIndex * FITS_CHANNELS);
      }
      
      // Parses data for valid pixels only...   
      private void parseData(int[] DAC, short[] jump, int from) {  
          Hirmes hirmes = getScan().getInstrument();
          
          if(jump != null) jumpCounter = new byte[data.length];
          
          for(final HirmesPixel pixel : hirmes) {
              data[pixel.getIndex()] = DAC[from + pixel.getFixedIndex()];
              if(jump != null) jumpCounter[pixel.getIndex()] = (byte) jump[from + pixel.getFixedIndex()];
          }
      }
      
      void parseData(int[][] DAC, short[][] jump) {  
          Hirmes hirmes = getScan().getInstrument();
          
          if(jump != null) jumpCounter = new byte[data.length];
          
          for(final HirmesPixel pixel : hirmes) {
              data[pixel.getIndex()] = DAC[pixel.fitsRow][pixel.fitsCol];
              if(jump != null) jumpCounter[pixel.getIndex()] = (byte) jump[pixel.fitsRow][pixel.fitsCol];
          }
      }
  
  
      @Override
      public void cloneReadout(Frame from) {
          super.cloneReadout(from);
          
          HirmesFrame frame = (HirmesFrame) from;
          jumpCounter = frame.jumpCounter;
          chopperPosition = frame.chopperPosition;
          MJD = frame.MJD;
          mceSerial = frame.mceSerial;
      }
      
      
      @Override
      public void slimTo(Instrument<?> instrument) {
          super.slimTo(instrument);
  
          if(jumpCounter == null) return;
  
          final byte[] newJumpCounter = new byte[instrument.size()];
          for(int k=instrument.size(); --k >= 0; ) newJumpCounter[k] = jumpCounter[instrument.get(k).getIndex()];      
  
          jumpCounter = newJumpCounter;
      }
    
      @Override
      public GeodeticCoordinates getSite() { return site; }
      
      @Override
      public boolean validate() {
          HirmesScan hirmesScan = getScan();
          Hirmes hirmes = hirmesScan.getInstrument();
          
          if(!isComplete) return false;
           
          // Skip data that is not normal observing
          if(status != FITS_FLAG_NORMAL_OBSERVING || (hirmesScan.useBetweenScans && status == FITS_FLAG_BETWEEN_SCANS)) 
              return false;
          
          if(!Double.isNaN(hirmesScan.transitTolerance)) if(hirmesScan.chopper != null) {
              double dev = Math.abs(chopperPosition.length()) - hirmesScan.chopper.amplitude;
              if(Math.abs(dev) > hirmesScan.transitTolerance) return false;
          }
          
          
          if(hasTelescopeInfo) {
              if(equatorial == null) return false;
              if(equatorial.isNull()) return false;
              if(hirmesScan.isNonSidereal) if(objectEq.isNull()) return false;
              
              if(equatorial.isNaN()) return false;
              if(horizontal.isNaN()) return false;
           
              if(Double.isNaN(LST)) return false;
              if(Double.isNaN(site.longitude())) return false;
              if(Double.isNaN(site.latitude())) return false;
              if(Double.isNaN(telescopeVPA)) return false;
              if(Double.isNaN(instrumentVPA)) return false;
              
              if(chopperPosition != null) {
                  if(Double.isNaN(chopVPA)) return false;
                  
                  horizontalOffset.add(chopperPosition);
                  horizontal.addOffset(chopperPosition);
  
                  Vector2D offset = chopperPosition.copy();
                  
                  horizontalToNativeEquatorial(offset);
                  
                  equatorial.addNativeOffset(offset);
              }
  
          }
        
          
          if(hirmes.darkSquidCorrection) darkCorrect();
  
          return super.validate();
         
      }
      
  
      void darkCorrect() {
          Hirmes hirmes = getScan().getInstrument();
  
          for(HirmesPixel pixel : hirmes) if(!pixel.isFlagged(Channel.FLAG_BLIND))
              data[pixel.getIndex()] -= data[hirmes.darkSquidLookup[pixel.col]];
      }
    
      void instrumentToEquatorial(Vector2D offset) {
          offset.rotate(-instrumentVPA);
      }
  
      void equatorialToInstrument(Vector2D offset) {
          offset.rotate(instrumentVPA);
      }
      
      @Override
      public double getRollAngle() {
          return roll;
      }
  
      @Override
      public double getLOSAngle() {
          return LOS;
      }
     
      
  
      public final static int JUMP_RANGE = 1<<7;  // Jumps are 7-bit signed values...
      
      public final static int FITS_FLAG_NORMAL_OBSERVING = 0;
      public final static int FITS_FLAG_LOS_REWIND = 1;
      public final static int FITS_FLAG_IVCURVES = 2;
      public final static int FITS_FLAG_BETWEEN_SCANS = 3;
  
      public final static int FITS_CHANNELS = 1188;
  
      public static byte SAMPLE_PHI0_JUMP = sampleFlags.next('j', "phi0 jump").value();
      public static byte SAMPLE_TRANSIENT_NOISE = sampleFlags.next('T', "transient noise").value();
     
  }
  