Attribute VB_Name = "modMeasure"
' Sample Routines
'
' handle everything from the movement and the selection
' the positioning, the measurement and the demag steps!


Option Explicit  ' enforce variable declaration!

' Time delay after ARC squid box command
Const Measure_ARCDelay = 2.5

' Sample Orientation
Global Const Magnet_SampleOrientationUp As Integer = -1
Global Const Magnet_SampleOrientationDown As Integer = 1

Type Measure_Unfolded
    s As Angular3D     ' Bedding coordinates
    C As Angular3D     ' Core coordinates
    g As Angular3D     ' Geog. coordinates
End Type

Public Type Measure_AvgStats
    ' This type is used to pass information from measurement
    ' of the sample to display on the screen, and output to
    ' a data file
    
    unfolded   As Measure_Unfolded  ' Unfolded measurement
    SigNoise   As Double            ' Signal/Noise ratio
    SigHolder  As Double            ' Signal/Holder ratio
    SigInduced As Double            ' Signal/Induced ratio
    momentvol  As Double            ' Moment/vol ratio
End Type

Public HolderMeasured As Boolean    ' Has the holder been measured?
                                    ' (frmMagnetometerControl.cmdManHolder)

Public modeFluxCount As Boolean     ' Flux Counting mode.  (Unimplemented)

Public Holder As MeasurementBlock     ' Holder measurements


Sub Measure_QueryLoad(sampname As String, SampleOrientation As Integer)
    ' Request the user to manually load the sample with 'SampName'
    ' into the magnetometer and set it to the correct orientation.
    ' We then automatically load frmMeasure and call the 'MeasureSample'
    ' routines
    
    Dim QueryStr As String

    If (sampname <> SampleNameCurrent) Then
        ' We changed the sample, time to re-measure
        
        Magnetometer_UnloadSample
        If (sampname <> "Holder") Then
            frmVacuum.ValveConnect True
            QueryStr = "Please load the sample " + sampname
            If (SampleOrientation = Magnet_SampleOrientationUp) Then
                QueryStr = QueryStr + " with the arrow pointing up."
            Else
                QueryStr = QueryStr + " with the arrow pointing down."
            End If
 '           Motor_WaitStop ("UPDOWN")         ' Wait for motor to stop?
            MsgBox QueryStr, vbOKOnly, "Load Sample..."
        Else
            MsgBox "Please remove sample from holder", vbOKOnly, _
                "Remove Sample..."
        End If
        SampleNameCurrent = sampname                 ' ?
        SampleOrientationCurrent = SampleOrientation   ' ?
    ElseIf (SampleOrientation <> SampleOrientationCurrent) Then
        MsgBox "Please turn the sample over.", vbOKOnly, "Flip Sample..."
        SampleOrientationCurrent = SampleOrientation   ' ?
    End If
End Sub

' Measure_TreatAndRead
'
' This is the routine for taking care of AF demagnetization,
' susceptibility measurements, etc.

Public Sub Measure_TreatAndRead(targetSample As Sample, Optional ByVal useChanger = False)

    Dim RockmagMode As Boolean
    Dim doMeasure As Boolean
    Dim labelString As String


    frmDCMotors.TurningMotorAngleOffset TrayOffsetAngle

    With targetSample.Parent
        RockmagMode = .RockmagMode
        
        .measurementSteps.CurrentStepIndex = 1
        doMeasure = .measurementSteps.CurrentStep.Measure
        If .measurementSteps.Count = 1 And Not .RockmagMode Then doMeasure = True
    
    If RockmagMode Then
            targetSample.WriteRockmagInfoLine "Instrument: " & MailFromName
            targetSample.WriteRockmagInfoLine "Time: " & Format(Now, "yyyy-mm-dd hh:mm")
    End If
    
    Do While .measurementSteps.CurrentStepIndex > 0
        labelString = targetSample.Samplename & " @ " & .curDemagLong
        If .measurementSteps.Count > 1 Then labelString = labelString & " [" & Format$(.measurementSteps.CurrentStepIndex, "0") & "/" & Format$(.measurementSteps.Count, "0") & "]"
        frmProgram.StatusBar "Measuring samples... (" & labelString & ")", 1
        
        SampleNameCurrent = targetSample.Samplename
        SampleStepCurrent = .measurementSteps.CurrentStep.DemagStepLabelLong
        If .doUp Then SampleOrientationCurrent = Magnet_SampleOrientationUp Else SampleOrientationCurrent = Magnet_SampleOrientationDown
        
        If (.measurementSteps.CurrentStep.MeasureSusceptibility) And (.doUp Or (Not .doBoth)) Then
            Susceptibility_Measure targetSample, (targetSample.Samplename = "Holder")
        End If
        .measurementSteps.CurrentStep.PerformStep targetSample

        frmMeasure.SetFields .avgSteps, .curDemagLong, .doUp, .doBoth, .Filename
        frmMeasure.clearData
        frmMeasure.HideStats
        frmMeasure.clearStats
        If doMeasure Then Measure_Read targetSample, .measurementSteps.CurrentStep, RockmagMode
        .measurementSteps.AdvanceStep
    Loop
    End With
    frmProgram.StatusBar "Measuring samples...", 1
    
    frmDCMotors.TurningMotorAngleOffset -TrayOffsetAngle
    
    SampleNameCurrent = vbNullString
    SampleStepCurrent = vbNullString
    SampleOrientationCurrent = 0

End Sub


Sub Measure_Read(targetSample As Sample, _
    RMStep As RockmagStep, _
    Optional RockmagMode As Boolean = False)
    ' This function starts the averaging cycles for the up
    ' direction.  It measures two +X, two -X, two +Y, two -y,
    ' and four +Z components
    
    Dim i As Integer
    Dim j As Integer
    Dim readDats As MeasurementBlocks
    Dim unfolded As Measure_Unfolded
    
    Set readDats = New MeasurementBlocks
    
    Dim isHolder As Boolean
    Dim isUp As Boolean
    Dim doBoth As Boolean
    Dim numAvgSteps As Integer
    Dim curDemag As String
    
    Dim sdvect As Cartesian3D
    
    isHolder = (targetSample.Samplename = "Holder")
    
    If DEBUG_MODE Then frmDebug.Msg "Reading: " + targetSample.Samplename + " isUp: " + Str(isUp) + " doBoth: " + Str(doBoth)
    
    
    SampleNameCurrent = targetSample.Samplename
    SampleStepCurrent = RMStep.DemagStepLabelLong
    
    Dim vectUMag   As Double                  ' Magnitude of "Up" vector
    Dim vectDMag   As Double                  ' Magnitude of "Down" vector
    
    Dim fInd As Double
    Dim UpToDn As Double
    Dim ErrorAngle As Double, errorHoriz As Double
    Dim filepath As String
    Dim filepathbackup As String
    Dim avstats As Measure_AvgStats
    Dim avg As Cartesian3D
    
    Dim msgret As VbMsgBoxResult
    
    ' Initialize variables
    
    If Prog_halted Then Exit Sub
    
    If isHolder Then
        ' Do initializations necessary for holder
        Set Holder = Nothing
        Set Holder = New MeasurementBlock
        frmSQUID.ChangeRange "A", "1" ' 1x read mode
        numAvgSteps = SampQueue.maxAvgSteps
    Else
        With targetSample.Parent
            isUp = .doUp
            doBoth = .doBoth
            curDemag = .curDemag
            numAvgSteps = .avgSteps
            If numAvgSteps < 1 Then numAvgSteps = 1
        End With
    End If
    
    ' Begin
    
    For i = 1 To numAvgSteps
        '  Do the initial zero measurement here
        readDats.Add Measure_ReadSample(targetSample, isHolder, isUp)
        For j = 1 To 4
            readDats.Last.SetHolder j, Holder.Sample(j)
        Next j
        readDats.Last.isUp = isUp
        
        Set avg = readDats.VectAvg
        unfolded = Measure_Unfold(targetSample, avg.x, avg.y, avg.z)
        frmMeasure.ShowStats avg.x, avg.y, avg.z, unfolded.s.dec, unfolded.s.inc, _
                             readDats.SigDrift, _
                             readDats.SigHolder, _
                             readDats.SigInduced, _
                             readDats.FischerSD
                             
        
        Set avg = Nothing
                    
    Next i

    ' Now we've done the measurements the avgSteps number of times
    
    If isHolder Then
        Set Holder = readDats.AverageBlock
    Else
        ' Not a holder measurement
    
        If (isUp And doBoth) Then
            ' We've measured the up direction, so save it to a temp file and leave
            targetSample.WriteUpMeasurements readDats, curDemag
            If DumpRawDataStats Then targetSample.WriteStatsTable readDats, curDemag
            avstats = Measure_CalcStats(targetSample, readDats)
            Set sdvect = readDats.VectSD
            frmStats.ShowErrors readDats.FischerSD, 0, 0
            frmStats.ShowAvgStats sdvect.x, sdvect.y, sdvect.z, _
                avstats.unfolded.C.dec, avstats.unfolded.C.inc, _
                avstats.unfolded.g.dec, avstats.unfolded.g.inc, _
                avstats.unfolded.s.dec, avstats.unfolded.s.inc, _
                avstats.momentvol, avstats.SigNoise, _
                avstats.SigHolder, avstats.SigInduced
            Set sdvect = Nothing
            Exit Sub
        End If
        
        If doBoth And Not isUp Then
            readDats.Assimilate targetSample.ReadUpMeasurements
            UpToDn = readDats.UpToDown
        End If
        
        ErrorAngle = readDats.FischerSD
        
        ' THE HORIZONTAL ERROR ANGLE, EH, IS NEGATIVE IF HOLDER SHOULD BE
        ' ROTATED TO THE LEFT, AND POSITIVE IF IT SHOULD GO TO THE RIGHT
        errorHoriz = readDats.ErrorHorizontal
        
        frmStats.ShowErrors ErrorAngle, errorHoriz, UpToDn
        
        Set sdvect = readDats.VectSD
        
        avstats = Measure_CalcStats(targetSample, readDats)
        frmStats.ShowAvgStats sdvect.x, sdvect.y, sdvect.z, _
            avstats.unfolded.C.dec, avstats.unfolded.C.inc, _
            avstats.unfolded.g.dec, avstats.unfolded.g.inc, _
            avstats.unfolded.s.dec, avstats.unfolded.s.inc, _
            avstats.momentvol, avstats.SigNoise, _
            avstats.SigHolder, avstats.SigInduced
    
        unfolded = avstats.unfolded
        
        
       
        ' Save the measurement if we're not measuring the holder
        
        targetSample.WriteData curDemag, unfolded.g.dec, _
            unfolded.g.inc, unfolded.s.dec, unfolded.s.inc, _
            unfolded.C.dec, unfolded.C.inc, avstats.momentvol, _
            ErrorAngle, sdvect.x, sdvect.y, sdvect.z, readDats.UpToDown
        
        If RockmagMode Or RMStep.MeasureSusceptibility Then
            targetSample.WriteRockmagData RMStep, readDats.MomentVector.z, RangeFact * sdvect.z, readDats.MomentVector.x, RangeFact * sdvect.x, readDats.MomentVector.y, RangeFact * sdvect.y
            ' multiply by rangefact to convert to emu
        End If
        
        If DumpRawDataStats Then
            targetSample.WriteUpMeasurements readDats, curDemag
            targetSample.WriteStatsTable readDats, curDemag
        End If
        
        targetSample.BackupSpecFile
    End If
    
    If NOCOMM_MODE Then DelayTime 5
    
    
    Set sdvect = Nothing
    Set readDats = Nothing
    
        
    SampleNameCurrent = vbNullString
    SampleStepCurrent = vbNullString

End Sub




Private Function Measure_ReadSample(specimen As Sample, _
    Optional isHolder As Boolean = False, _
    Optional isUp As Boolean = True, Optional AllowRemeasure As Boolean = True) As MeasurementBlock
    ' This procedure goes forward and measures the sample that
    ' is currently loaded in the magnetometer.  It starts with
    ' the sample in the zero position, and ends with the sample
    ' in the zero position
    
    Dim msgret As VbMsgBoxResult
    Dim curMeas As Cartesian3D
    Dim x, y, z As Double
    Dim unfolded As Measure_Unfolded
    Dim SampleCenterPosition As Long
    Dim j As Integer
    
    Dim blocks As MeasurementBlocks
    
    Dim avstats As Measure_AvgStats
    Dim avg As Cartesian3D
    

    
    Set curMeas = New Cartesian3D
    Set Measure_ReadSample = New MeasurementBlock
    
    SampleNameCurrent = specimen.Samplename
    
    Measure_ReadSample.isUp = isUp
    If Not isHolder Then
        For j = 1 To 4
            Measure_ReadSample.SetHolder j, Holder.Sample(j)
        Next j
    End If
    
    frmDCMotors.UpDownMove Int(ZeroPos + specimen.SampleHeight / 2), 2
    
    ' Before the first zero, reset and zero counter
    ' then wait for numbers to settle.
    frmSQUID.CLP "A"
    frmSQUID.ResetCount "A"
    frmProgram.StatusBar "Resetting...", 3
    DelayTime (Measure_ARCDelay)    ' Briefly pause
    
    ' First zero measurement
    
    ' latch data from zero position
    frmSQUID.latchVal "A", False
    
    ' start move into SampleCenterPosition
    SampleCenterPosition = Int(MeasPos + specimen.SampleHeight / 2)
    frmDCMotors.UpDownMove SampleCenterPosition, 0, False
    
    Set curMeas = frmSQUID.getData(True)
    Measure_ReadSample.SetBaseline 1, curMeas
    
    frmMeasure.showData curMeas.x, curMeas.y, curMeas.z, 0

    ' Lower to sense region and take first measurement
    ' remember to center the sample in the sense region ... SampleBottom is in the INI
    ' file, and the SampleTop value was set when the system picked it up initially.
    ' Note that both positions are measured with the TestAll function homing down, so
    ' the small distance that the turning rod moves up before the limit switch clicks
    ' should not influence the pushbutton position.
    
    '
    frmDCMotors.UpDownMove SampleCenterPosition, 0
    frmSQUID.latchVal "A", True
    frmDCMotors.TurningMotorRotate 90, False

    Set curMeas = frmSQUID.getData(True)
    Measure_ReadSample.SetSample 1, curMeas
   
    
    ' Adjust to baseline - just for the display
    x = curMeas.x - Measure_ReadSample.Baselines(1).x
    y = curMeas.y - Measure_ReadSample.Baselines(1).y
    z = curMeas.z - Measure_ReadSample.Baselines(1).z
    ' Adjust to direction
    If isUp Then
        ' +X, -Y, +Z direction
        y = -y
    Else
        ' +X, +Y, -Z direction
        z = -z
    End If
    unfolded = Measure_Unfold(specimen, x, y, z)
    frmMeasure.showData x, y, z, 1
    frmMeasure.ShowAngDat unfolded.s.dec, unfolded.s.inc, 1

    ' Move to +Y, +X Orientation and take measurement
    'Motor_Turn90
    MotorTurn_90
    '
    frmSQUID.latchVal "A", True
    frmDCMotors.TurningMotorRotate 180, False
    Set curMeas = frmSQUID.getData(True)

    Measure_ReadSample.SetSample 2, curMeas
    
    
    ' Adjust to baseline - just for the display
    x = curMeas.y - Measure_ReadSample.Baselines(1).y
    y = curMeas.x - Measure_ReadSample.Baselines(1).x
    z = curMeas.z - Measure_ReadSample.Baselines(1).z    ' Adjust to direction
    If isUp Then
        ' +Y, +X, +Z direction
    Else
        ' -Y, +X, -Z direction
        y = -y
        z = -z
    End If
    unfolded = Measure_Unfold(specimen, x, y, z)
    frmMeasure.showData x, y, z, 2
    frmMeasure.ShowAngDat unfolded.s.dec, unfolded.s.inc, 2

    ' Move to -X, +Y Orientation and measure
    'Motor_Turn90
    MotorTurn_180
    '
    
    frmSQUID.latchVal "A", True
    frmDCMotors.TurningMotorRotate 270, False
    Set curMeas = frmSQUID.getData(True)
    
    Measure_ReadSample.SetSample 3, curMeas
    
    
    ' Adjust to baseline - just for the display
    x = curMeas.x - Measure_ReadSample.Baselines(1).x
    y = curMeas.y - Measure_ReadSample.Baselines(1).y
    z = curMeas.z - Measure_ReadSample.Baselines(1).z
    ' Adjust to direction
    If isUp Then
        ' -X, +Y, +Z direction
        x = -x
    Else
        ' -X, -Y, -Z direction
        x = -x
        y = -y
        z = -z
    End If
    unfolded = Measure_Unfold(specimen, x, y, z)
    frmMeasure.showData x, y, z, 3
    frmMeasure.ShowAngDat unfolded.s.dec, unfolded.s.inc, 3

    ' Move to -Y, -X Orientation and measure
    'Motor_Turn90
    MotorTurn_270
    '
    
    frmSQUID.latchVal "A", True
    frmDCMotors.UpDownMove Int(ZeroPos + specimen.SampleHeight / 2), 0, False
    Set curMeas = frmSQUID.getData(True)
    MotorTurn_360   ' remember, on the new systems we keep the tube spinning in the same direction
    
    
    Measure_ReadSample.SetSample 4, curMeas
    
    
    ' Adjust to baseline - just for the display
    x = curMeas.y - Measure_ReadSample.Baselines(1).y
    y = curMeas.x - Measure_ReadSample.Baselines(1).x
    z = curMeas.z - Measure_ReadSample.Baselines(1).z
    ' Adjust to direction
    If isUp Then
        ' -Y, -X, +Z direction
        x = -x
        y = -y
    Else
        ' +Y, -X, -Z direction
        x = -x
        z = -z
    End If
    unfolded = Measure_Unfold(specimen, x, y, z)
    frmMeasure.showData x, y, z, 4
    frmMeasure.ShowAngDat unfolded.s.dec, unfolded.s.inc, 4

    ' Lift to zero and measure
    ' Rotate the sample back to start direction
    'Motor_MoveMeasdownToZero
    frmDCMotors.UpDownMove Int(ZeroPos + specimen.SampleHeight / 2), 0
    
    frmSQUID.latchVal "A", True
    Set curMeas = frmSQUID.getData(True)
    
    Measure_ReadSample.SetBaseline 2, curMeas
    frmMeasure.showData curMeas.x, curMeas.y, curMeas.z, 5
    
    Set blocks = New MeasurementBlocks
    blocks.Add Measure_ReadSample
    avstats = Measure_CalcStats(specimen, blocks)
    Set blocks = Nothing
    
    If AllowRemeasure And (specimen.Vol * avstats.momentvol) > 0.0000005 And ((avstats.SigNoise < 1) Or (avstats.SigInduced < 1) Or (Measure_ReadSample.FischerSD > RemeasureCSDThreshold)) Then
        frmDCMotors.UpDownMove Int(ZeroPos + specimen.SampleHeight / 2), 0
        Set Measure_ReadSample = Measure_ReadSample(specimen, isHolder, isUp, False)
    End If
    
    
    '
    ' We've finished the measuring cycle
    ' So, now calculate the components, etc.
    If DEBUG_MODE Then frmDebug.Msg specimen.Samplename & ": " & Measure_ReadSample.Average.x & ", " & Measure_ReadSample.Average.y & "," & Measure_ReadSample.Average.x
        
    ' ADD Range switch code here if necessary. !!
    
    Set curMeas = Nothing
        
    SampleNameCurrent = vbNullString

End Function

Public Function Measure_Unfold(specimen As Sample, _
    ByVal x As Double, ByVal y As Double, ByVal z As Double, _
    Optional HO As Double = 0) As Measure_Unfolded
    '  A subroutine to feed in a direction in sample coordinates, and
    '  to unfold w.r.t. fold axes, bedding orientation, and sample
    '  orientation, to spit out a declination and inclination.  UNFOLD
    '  wants to be fed directions in sample coordinates, in variables
    '  XTEMP, YTEMP, and ZTEMP.

    '  COMPUTE DECL. AND INCL. IN SPECIMEN COORDINATES AS POSITIVE FROM +X

    Dim ret As Measure_Unfolded
    Dim aX As Double, aY As Double, aZ As Double
    Dim DD As Double, DP As Double, SD As Double, CD As Double
    Dim BB As Double, CC As Double, XP As Double
    Dim MT As Double
    Dim magDec As Double
    
    Set ret.C = New Angular3D
    Set ret.g = New Angular3D
    Set ret.s = New Angular3D
    
    MT = Sqr(Abs(x ^ 2 + y ^ 2 + z ^ 2))
    
    ret.C.dec = RadToDeg(atan2(x, y), True)
    ret.C.inc = RadToDeg(Atn(z / (HO + 0.00001)))
    
    '   COORDINATE TRANSFORM FOR SAMPLE ORIENTATION IN FIELD
    '   CORRECT DIP DIRECTION FOR MAGNETIC DECLINATION,
    '   AND USE CALTECH ORIENTATION SYSTEM
    
    With specimen
        magDec = .Parent.magDec
        DD = .CorePlateStrike + magDec - 90#
        DP = DegToRad((90 - .CorePlateDip))
        SD = Sin(DP)
        CD = Cos(DP)
    End With

    If MT = 0 Then MT = 0.00001
        
    aX = x / MT
    aY = y / MT
    aZ = z / MT

    XP = aX * SD + aZ * CD
    BB = Sqr(XP ^ 2 + aY ^ 2)
    CC = aZ * SD - aX * CD

    If BB = 0 Then BB = 0.00001

    ret.g.inc = RadToDeg(Atn(CC / BB))
    ' COMPUTE DEC = ARCTAN(Y/X)
    ret.g.dec = RadToDeg(atan2(XP, aY) + DegToRad(DD), True)

    Set ret.s = Measure_Bedding(specimen, ret.g.inc, ret.g.dec)
    Measure_Unfold = ret
End Function

Public Function Measure_Bedding(specimen As Sample, _
    ByVal ginc As Double, ByVal gdec As Double) As Angular3D
    '  Subroutine to make the structural and fold corrections.
    
    '  This uses the strike of bedding, not the dip direction, given in
    '  a right-handed sense. If fold corrections are also going to be done,
    '  both the remanence direction and a normal vector to the local bedding
    '  planes are rotated such that the fold axis is horizontal. The new
    '  bedding direction is then used to tilt-correct the rotated remanence
    '  direction to the final structurally-corrected orientation, SDEC and
    '  SINC.
    
    Dim bA As Double, bD As Double, magDec As Double
    
    With specimen
    magDec = .Parent.magDec
    If Not .FoldRotation Then
        ' Do the simple garden-variety bedding correction.
        bA = DegToRad(.BeddingStrike + magDec + 90)
        bD = DegToRad(.BeddingDip)
        
        Set Measure_Bedding = Measure_Rotate(ginc, gdec, bA, bD)
    Else
        Dim inc As Double, dec As Double
        Dim firstval As Angular3D, secondval As Angular3D
        
        ' First, rotate the remanence direction through the amount
        ' necessary to make the fold axis horizontal.
        bA = DegToRad(.FoldAxis + magDec)
        bD = DegToRad(.FoldPlunge)
        Set firstval = Measure_Rotate(ginc, gdec, bA, bD)
        
        ' Now we must find the new orientation of the bedding planes after
        ' rotating the fold axis up to horizontal.  To do this, rotate the
        ' direction of the normal vector through the same matrix.  The
        ' DEC and INC values calculated in the next two statements should
        ' be a normal vector to the untilted plane, and the DEC and INC
        ' returned from ROTATE should correspond to the fold-corrected
        ' plane direction.
        With specimen
            dec = .BeddingStrike + magDec - 90#
            inc = 90# - .BeddingDip
        End With
        Set secondval = Measure_Rotate(inc, dec, bA, bD)
            
        ' Now we need to take the new normal vector to the bedding plane,
        ' DEC,INC, and compute the dip direction (BA) and plunge (BD,
        ' both in radians).  We can then re-generate the rotation matrix
        ' and finish the tilt correction process on SDEC and SINC.  Note
        ' that the rotated directions are given with respect to TRUE NORTH,
        ' and only the measurements taken in the field (with the Magnetic
        ' Declination offset on the compass set at ZERO) require the MAGDEC
        ' correction.
        bA = DegToRad((secondval.dec + 180#))
        bD = DegToRad(90# - secondval.inc)
        
        ' Put the intermediate direction back for the final rotation
        Set Measure_Bedding = Measure_Rotate(firstval.inc, firstval.dec, bA, bD)
    End If
    End With
End Function

Public Function Measure_Rotate(ByVal inc As Double, ByVal dec As Double, _
                       ByVal bA As Double, ByVal bD As Double) _
                       As Angular3D
    ' Subroutine to perform the bedding-style rotations.  The direction
    ' to be rotated should be given in polar coordinates of DEC and INC
    ' (in degrees), while the direction of bedding dip (BA, not the
    ' strike!) and bedding dip (BD) are in radians.  The routine returns
    ' a new DEC and INC corresponding to the tilt-corrected directions.

    Dim x, y, z As Double
    Dim SA, CA, CDP, SDP As Double
    Dim xC, yC, zC As Double
    
    Set Measure_Rotate = New Angular3D
    
    z = -Sin(DegToRad(inc))
    x = Cos(DegToRad(inc)) * Cos(DegToRad(dec))
    y = Cos(DegToRad(inc)) * Sin(DegToRad(dec))
    SA = -Sin(bA)
    CA = Cos(bA)
    CDP = Cos(bD)
    SDP = Sin(bD)
    xC = x * (SA * SA + CA * CA * CDP) + y * (CA * SA * (1 - CDP)) - z * SDP * CA
    yC = x * CA * SA * (1 - CDP) + y * (CA * CA + SA * SA * CDP) + z * SA * SDP
    zC = x * CA * SDP - y * SDP * SA + z * CDP

    ' Corrected incl and decl
    Measure_Rotate.inc = -RadToDeg(Atn(zC / Sqr(xC ^ 2 + yC ^ 2)))
    Measure_Rotate.dec = RadToDeg(atan2(xC, yC), True)
End Function

Public Function Format5Char(d As Double)
    ' This function formats the given number to a 5 character string
    If d >= 100 Then
        Format5Char = Format(d, "000.0")
    ElseIf d >= 10 Then
        Format5Char = " " + Format(d, "00.0")
    End If
    
End Function


Public Function Measure_CalcStats(specimen As Sample, measblock As MeasurementBlocks) As Measure_AvgStats
    
    Dim vect As Cartesian3D
    Dim workingVol As Double
    
    Set vect = measblock.VectAvg
    
    If specimen.Vol > 0 Then workingVol = specimen.Vol Else workingVol = 1
    
    Measure_CalcStats.momentvol = measblock.Moment / workingVol
    
    ' Generate signal/noise ratios - Large values imply good data

    Measure_CalcStats.SigNoise = measblock.SigNoise
    Measure_CalcStats.SigHolder = measblock.SigHolder
    Measure_CalcStats.SigInduced = measblock.SigInduced
    

    '  Calls subroutine to complete all unfolding, to give a
    '  structurally corrected declination and inclination.
    Measure_CalcStats.unfolded = Measure_Unfold(specimen, _
        vect.x, vect.y, vect.z, Sqr(vect.x ^ 2 + vect.y ^ 2))
        
    Set vect = Nothing
End Function

