/// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // WhichBin to match pMCROfRho which matches ROfRho
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            if (ir != -1)
            {
                double weightFactor = _absorbAction(
                    photon.History.SubRegionInfoList.Select(c => c.NumberOfCollisions).ToList(),
                    photon.History.SubRegionInfoList.Select(p => p.PathLength).ToList(),
                    _perturbedOps);

                Mean[ir] += photon.DP.Weight * weightFactor;
                if (TallySecondMoment)
                {
                    SecondMoment[ir] += photon.DP.Weight * weightFactor * photon.DP.Weight * weightFactor;
                }
                TallyCount++;
            }
        }
        /// <summary>
        /// method to tally reflected photon by determining cumulative MT in each tissue subregion and binning in MT
        /// </summary>
        /// <param name="photon">Photon (includes HistoryData)</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // calculate the radial and time bin of reflected photon
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            for (int i = 0; i < NumSubregions; i++)
            {
                var timeInSubRegion = DetectorBinning.GetTimeDelay(photon.History.SubRegionInfoList[i].PathLength,
                                                                   _tissue.Regions[i].RegionOP.N);
                // make sure floating point round in Photon's update to S and subsequently to PathLength in SRIL doesn't get tallied
                if (timeInSubRegion > 1e-14)
                {
                    var it = DetectorBinning.WhichBin(timeInSubRegion, Time.Count - 1, Time.Delta, Time.Start);
                    // tally Continuous Absorption Weighting (CAW)
                    var tally = Math.Exp(-_tissue.Regions[i].RegionOP.Mua * photon.History.SubRegionInfoList[i].PathLength);
                    Mean[ir, i, it] += tally;
                    if (TallySecondMoment)
                    {
                        SecondMoment[ir, i, it] += tally * tally;
                    }
                }
            }
            TallyCount++;
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // ray trace exit location and direction to location at ZPlane
            var positionAtZPlane = LayerTissueRegionToolbox.RayExtendToInfinitePlane(
                photon.DP.Position, photon.DP.Direction, ZPlane);

            // WhichBin to match ROfRhoAndTimeDetector
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(positionAtZPlane.X, positionAtZPlane.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var it = DetectorBinning.WhichBin(photon.DP.TotalTime, Time.Count - 1, Time.Delta, Time.Start);

            if ((ir != -1) && (it != -1))
            {
                double weightFactor = _absorbAction(
                    photon.History.SubRegionInfoList.Select(c => c.NumberOfCollisions).ToList(),
                    photon.History.SubRegionInfoList.Select(p => p.PathLength).ToList(),
                    _perturbedOps, _referenceOps, _perturbedRegionsIndices);

                Mean[ir, it] += photon.DP.Weight * weightFactor;
                if (TallySecondMoment)
                {
                    SecondMoment[ir, it] += photon.DP.Weight * weightFactor * photon.DP.Weight * weightFactor;
                }
                TallyCount++;
            }
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // WhichBin to match ROfRhoAndTimeDetector
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var it = DetectorBinning.WhichBin(photon.DP.TotalTime, Time.Count - 1, Time.Delta, Time.Start);

            if ((ir != -1) && (it != -1))
            {
                double weightFactor = _absorbAction(
                    photon.History.SubRegionInfoList.Select(c => c.NumberOfCollisions).ToList(),
                    photon.History.SubRegionInfoList.Select(p => p.PathLength).ToList(),
                    _perturbedOps, _referenceOps, _perturbedRegionsIndices);

                Mean[ir, it] += photon.DP.Weight * weightFactor;
                if (TallySecondMoment)
                {
                    SecondMoment[ir, it] += photon.DP.Weight * weightFactor * photon.DP.Weight * weightFactor;
                }
                TallyCount++;
            }
        }
        public void verify_that_GetFinalPositionAndWeight_samples_from_UnifOfRhoAndZ_sampling_correctly()
        {
            ITissueInput tissueInput = new SingleInfiniteCylinderTissueInput();
            ITissue      tissue      = tissueInput.CreateTissue(AbsorptionWeightingType.Discrete,
                                                                PhaseFunctionType.HenyeyGreenstein, 0);

            tissue.Regions[1] = _xyzLoaderUnif.FluorescentTissueRegion;
            for (int i = 0; i < 100; i++)
            {
                var photon = _fluorEmissionAOfRhoAndZSourceUnif.GetNextPhoton(tissue);
                // verify that photons start within range of midpoints of voxels in infinite cylinder
                Assert.IsTrue(photon.DP.Position.X <= 3.5);
                Assert.AreEqual(photon.DP.Position.Y, 0);
                Assert.IsTrue((photon.DP.Position.Z >= 0.5) && (photon.DP.Position.Z <= 1.5));
                // verify sampling is proceeding in coded sequence
                // detector center=(0,0,1) radius=4, rho=[0 4] 4 bins, z=[0 2] 2 bins
                var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(
                                                      photon.DP.Position.X, photon.DP.Position.Y),
                                                  _rhozLoaderUnif.Rho.Count - 1, _rhozLoaderUnif.Rho.Delta, _rhozLoaderUnif.Rho.Start);
                var iz       = DetectorBinning.WhichBin(photon.DP.Position.Z, _rhozLoaderUnif.Z.Count - 1, _rhozLoaderUnif.Z.Delta, _rhozLoaderUnif.Z.Start);
                var rhozNorm = (_rhozLoaderUnif.Rho.Start + (ir + 0.5) * _rhozLoaderUnif.Rho.Delta) * 2.0 * Math.PI * _rhozLoaderUnif.Rho.Delta * _rhozLoaderUnif.Z.Delta;
                // verify weight at location is equal to AOfRhoAndZ note: setup with single y bin
                // expected: Map [ 1 1 1 1; 1 1 1 1] row major
                // expected: A   [ 1 3 5 7; 2 4 6 8] row major
                Assert.IsTrue(Math.Abs(photon.DP.Weight -
                                       _rhozLoaderUnif.AOfRhoAndZ[ir, iz] * rhozNorm) < 1e-6);
            }
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            var ir        = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var totalTime = photon.DP.TotalTime;

            for (int iw = 0; iw < Omega.Count; iw++)
            {
                double freq = Omega.AsEnumerable().ToArray()[iw];
                Mean[ir, iw] += photon.DP.Weight * (Math.Cos(-2 * Math.PI * freq * totalTime) +
                                                    Complex.ImaginaryOne * Math.Sin(-2 * Math.PI * freq * totalTime));

                if (TallySecondMoment) // 2nd moment is E[xx*]=E[xreal^2]+E[ximag^2]
                {
                    SecondMoment[ir, iw] +=
                        photon.DP.Weight * (Math.Cos(-2 * Math.PI * freq * totalTime)) *
                        photon.DP.Weight * (Math.Cos(-2 * Math.PI * freq * totalTime)) +
                        photon.DP.Weight * (Math.Sin(-2 * Math.PI * freq * totalTime)) *
                        photon.DP.Weight * (Math.Sin(-2 * Math.PI * freq * totalTime));
                }
            }
            TallyCount++;
        }
Esempio n. 7
0
        /// <summary>
        /// method to tally given two consecutive photon data points
        /// </summary>
        /// <param name="previousDP">previous data point</param>
        /// <param name="dp">current data point</param>
        /// <param name="currentRegionIndex">index of region photon current is in</param>
        public void TallySingle(PhotonDataPoint previousDP, PhotonDataPoint dp, int currentRegionIndex)
        {
            var totalTime = dp.TotalTime;
            var ir        = DetectorBinning.WhichBin(DetectorBinning.GetRho(dp.Position.X, dp.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var iz        = DetectorBinning.WhichBin(dp.Position.Z, Z.Count - 1, Z.Delta, Z.Start);

            var weight = _absorptionWeightingMethod(previousDP, dp, currentRegionIndex);
            // Note: GetVolumeAbsorptionWeightingMethod in Initialize method determines the *absorbed* weight
            //  so for fluence this weight is divided by Mua

            var regionIndex = currentRegionIndex;

            if (weight != 0.0)
            {
                for (int iw = 0; iw < Omega.Count; iw++)
                {
                    double freq = Omega.AsEnumerable().ToArray()[iw];

                    Mean[ir, iz, iw] += (weight / _ops[regionIndex].Mua) *
                                        (Math.Cos(-2 * Math.PI * freq * totalTime) +
                                         Complex.ImaginaryOne * Math.Sin(-2 * Math.PI * freq * totalTime));
                    if (TallySecondMoment)
                    {
                        _tallyForOnePhoton[ir, iz, iw] += (weight / _ops[regionIndex].Mua) *
                                                          (Math.Cos(-2 * Math.PI * freq * totalTime) +
                                                           Complex.ImaginaryOne * Math.Sin(-2 * Math.PI * freq * totalTime));
                    }
                }
                TallyCount++;
            }
        }
Esempio n. 8
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            Mean[ir] += photon.DP.Weight;
            if (TallySecondMoment)
            {
                SecondMoment[ir] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
Esempio n. 9
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            // if exiting tissue top surface, Uz < 0 => Acos in [pi/2, pi]
            var ia = DetectorBinning.WhichBin(Math.Acos(photon.DP.Direction.Uz), Angle.Count - 1, Angle.Delta, Angle.Start);
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            Mean[ir, ia] += photon.DP.Weight;
            if (TallySecondMoment)
            {
                SecondMoment[ir, ia] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
Esempio n. 10
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            Mean[ir] += photon.DP.Weight / photon.DP.Direction.Uz;
            if (TallySecondMoment)
            {
                SecondMoment[ir] += (photon.DP.Weight / photon.DP.Direction.Uz) * (photon.DP.Weight / photon.DP.Direction.Uz);
            }
            TallyCount++;
        }
Esempio n. 11
0
        /// <summary>
        /// method to tally given two consecutive photon data points
        /// </summary>
        /// <param name="previousDP">previous data point</param>
        /// <param name="dp">current data point</param>
        /// <param name="currentRegionIndex">index of region photon current is in</param>
        public void TallySingle(PhotonDataPoint previousDP, PhotonDataPoint dp, int currentRegionIndex)
        {
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(dp.Position.X, dp.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var iz = DetectorBinning.WhichBin(dp.Position.Z, Z.Count - 1, Z.Delta, Z.Start);

            var weight = _absorptionWeightingMethod(previousDP, dp, currentRegionIndex);

            if (weight != 0.0)
            {
                Mean[ir, iz] += weight;
                if (TallySecondMoment)
                {
                    _tallyForOnePhoton[ir, iz] += weight;
                }
                TallyCount++;
            }
        }
Esempio n. 12
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var it = DetectorBinning.WhichBin(photon.DP.TotalTime, Time.Count - 1, Time.Delta, Time.Start);

            Mean[ir, it] += photon.DP.Weight;
            if (TallySecondMoment)
            {
                SecondMoment[ir, it] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            var    ir       = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            double maxDepth = photon.History.HistoryData.Max(d => d.Position.Z);
            var    id       = DetectorBinning.WhichBin(maxDepth, MaxDepth.Count - 1, MaxDepth.Delta, MaxDepth.Start);

            Mean[ir, id] += photon.DP.Weight; // mean integrated over max depth = R(rho)
            if (TallySecondMoment)
            {
                SecondMoment[ir, id] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
Esempio n. 14
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);

            if (ir != -1)
            {
                double weightFactor = _absorbAction(
                    photon.History.SubRegionInfoList.Select(c => c.NumberOfCollisions).ToList(),
                    photon.History.SubRegionInfoList.Select(p => p.PathLength).ToList(),
                    _perturbedOps, _referenceOps, _perturbedRegionsIndices);

                Mean[ir] += photon.DP.Weight * weightFactor;
                if (TallySecondMoment)
                {
                    SecondMoment[ir] += photon.DP.Weight * weightFactor * photon.DP.Weight * weightFactor;
                }
                TallyCount++;
            }
        }
Esempio n. 15
0
        /// <summary>
        /// method to tally given two consecutive photon data points
        /// </summary>
        /// <param name="previousDP">previous data point</param>
        /// <param name="dp">current data point</param>
        /// <param name="currentRegionIndex">index of region photon current is in</param>
        public void TallySingle(PhotonDataPoint previousDP, PhotonDataPoint dp, int currentRegionIndex)
        {
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(dp.Position.X, dp.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var iz = DetectorBinning.WhichBin(dp.Position.Z, Z.Count - 1, Z.Delta, Z.Start);

            var weight = _absorptionWeightingMethod(previousDP, dp, currentRegionIndex);
            // Note: GetVolumeAbsorptionWeightingMethod in Initialize method determines the *absorbed* weight
            //  so for fluence this weight is divided by Mua

            var regionIndex = currentRegionIndex;

            if (weight != 0.0)
            {
                Mean[ir, iz] += weight / _ops[regionIndex].Mua;
                if (TallySecondMoment)
                {
                    _tallyForOnePhoton[ir, iz] += weight / _ops[regionIndex].Mua;
                }
                TallyCount++;
            }
        }
        /// <summary>
        /// method to tally given two consecutive photon data points
        /// </summary>
        /// <param name="previousDP">previous data point</param>
        /// <param name="dp">current data point</param>
        /// <param name="currentRegionIndex">index of region photon current is in</param>
        public void TallySingle(PhotonDataPoint previousDP, PhotonDataPoint dp, int currentRegionIndex)
        {
            var ir = DetectorBinning.WhichBin(DetectorBinning.GetRho(dp.Position.X, dp.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var iz = DetectorBinning.WhichBin(dp.Position.Z, Z.Count - 1, Z.Delta, Z.Start);
            // using Acos, -1<Uz<1 goes to pi<theta<0, so first bin is most forward directed angle
            var ia = DetectorBinning.WhichBin(Math.Acos(dp.Direction.Uz), Angle.Count - 1, Angle.Delta, Angle.Start);

            var weight = _absorptionWeightingMethod(previousDP, dp, currentRegionIndex);

            var regionIndex = currentRegionIndex;

            if (weight != 0.0)
            {
                Mean[ir, iz, ia] += weight / _ops[regionIndex].Mua;
                if (TallySecondMoment)
                {
                    _tallyForOnePhoton[ir, iz, ia] += weight / _ops[regionIndex].Mua;
                }
                TallyCount++;
            }
        }
Esempio n. 17
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // ray trace exit location and direction to location at ZPlane in air
            var positionAtHeight = LayerTissueRegionToolbox.RayExtendToInfinitePlane(
                photon.DP.Position, photon.DP.Direction, ZPlane);

            var    ir       = DetectorBinning.WhichBin(DetectorBinning.GetRho(positionAtHeight.X, positionAtHeight.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            double maxDepth = photon.History.HistoryData.Max(d => d.Position.Z);
            var    id       = DetectorBinning.WhichBin(maxDepth, MaxDepth.Count - 1, MaxDepth.Delta, MaxDepth.Start);

            Mean[ir, id] += photon.DP.Weight; // mean integrated over max depth = R(rho)
            if (TallySecondMoment)
            {
                SecondMoment[ir, id] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
Esempio n. 18
0
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            if (!IsWithinDetectorAperture(photon))
            {
                return;
            }

            // ray trace exit location and direction to location at ZPlane
            var positionAtZPlane = LayerTissueRegionToolbox.RayExtendToInfinitePlane(
                photon.DP.Position, photon.DP.Direction, ZPlane);

            var ir = DetectorBinning.WhichBin(
                DetectorBinning.GetRho(positionAtZPlane.X, positionAtZPlane.Y),
                Rho.Count - 1,
                Rho.Delta,
                Rho.Start);

            Mean[ir] += photon.DP.Weight;
            if (TallySecondMoment)
            {
                SecondMoment[ir] += photon.DP.Weight * photon.DP.Weight;
            }
            TallyCount++;
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            // calculate the radial bin to attribute the deposition
            var  irho        = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var  subregionMT = new double[NumSubregions];
            bool talliedMT   = false;

            // go through photon history and claculate momentum transfer
            // assumes that no MT tallied at pseudo-collisions (reflections and refractions)
            // this algorithm needs to look ahead to angle of next DP, but needs info from previous to determine whether real or pseudo-collision
            PhotonDataPoint previousDP = photon.History.HistoryData.First();
            PhotonDataPoint currentDP  = photon.History.HistoryData.Skip(1).Take(1).First();

            foreach (PhotonDataPoint nextDP in photon.History.HistoryData.Skip(2))
            {
                if (previousDP.Weight != currentDP.Weight)                // only for true collision points
                {
                    var csr = _tissue.GetRegionIndex(currentDP.Position); // get current region index
                    // get angle between current and next
                    double cosineBetweenTrajectories = Direction.GetDotProduct(currentDP.Direction, nextDP.Direction);
                    var    momentumTransfer          = 1 - cosineBetweenTrajectories;
                    subregionMT[csr] += momentumTransfer;
                    talliedMT         = true;
                }
                previousDP = currentDP;
                currentDP  = nextDP;
            }
            // tally total MT
            double totalMT = subregionMT.Sum();

            if (totalMT > 0.0)  // only tally if momentum transfer accumulated
            {
                var imt = DetectorBinning.WhichBin(totalMT, MTBins.Count - 1, MTBins.Delta, MTBins.Start);
                Mean[irho, imt] += photon.DP.Weight;
                if (TallySecondMoment)
                {
                    SecondMoment[irho, imt] += photon.DP.Weight * photon.DP.Weight;
                }

                if (talliedMT)
                {
                    TallyCount++;
                }

                // tally fractional MT in each subregion
                int ifrac;
                for (int isr = 0; isr < NumSubregions; isr++)
                {
                    // add 1 to ifrac to offset bin 0 added for =0 only tallies
                    ifrac = DetectorBinning.WhichBin(subregionMT[isr] / totalMT,
                                                     FractionalMTBins.Count - 1, FractionalMTBins.Delta, FractionalMTBins.Start) + 1;
                    // put identically 0 fractional MT into separate bin at index 0
                    if (subregionMT[isr] / totalMT == 0.0)
                    {
                        ifrac = 0;
                    }
                    // put identically 1 fractional MT into separate bin at index Count+1 -1
                    if (subregionMT[isr] / totalMT == 1.0)
                    {
                        ifrac = FractionalMTBins.Count;
                    }
                    FractionalMT[irho, imt, isr, ifrac] += photon.DP.Weight;
                }
            }
        }
        /// <summary>
        /// method to tally to detector
        /// </summary>
        /// <param name="photon">photon data needed to tally</param>
        public void Tally(Photon photon)
        {
            // calculate the radial bin to attribute the deposition
            var    irho      = DetectorBinning.WhichBin(DetectorBinning.GetRho(photon.DP.Position.X, photon.DP.Position.Y), Rho.Count - 1, Rho.Delta, Rho.Start);
            var    tissueMT  = new double[2]; // 2 is for [static, dynamic] tally separation
            bool   talliedMT = false;
            double totalMT   = 0;
            var    totalMTOfZForOnePhoton   = new double[Rho.Count - 1, Z.Count - 1];
            var    dynamicMTOfZForOnePhoton = new double[Rho.Count - 1, Z.Count - 1];

            // go through photon history and claculate momentum transfer
            // assumes that no MT tallied at pseudo-collisions (reflections and refractions)
            // this algorithm needs to look ahead to angle of next DP, but needs info from previous to determine whether real or pseudo-collision
            PhotonDataPoint previousDP = photon.History.HistoryData.First();
            PhotonDataPoint currentDP  = photon.History.HistoryData.Skip(1).Take(1).First();

            foreach (PhotonDataPoint nextDP in photon.History.HistoryData.Skip(2))
            {
                if (previousDP.Weight != currentDP.Weight)                // only for true collision points
                {
                    var csr = _tissue.GetRegionIndex(currentDP.Position); // get current region index
                    // get z bin of current position
                    var iz = DetectorBinning.WhichBin(currentDP.Position.Z, Z.Count - 1, Z.Delta, Z.Start);
                    // get angle between current and next
                    double cosineBetweenTrajectories = Direction.GetDotProduct(currentDP.Direction, nextDP.Direction);
                    var    momentumTransfer          = 1 - cosineBetweenTrajectories;
                    totalMT += momentumTransfer;
                    TotalMTOfZ[irho, iz]             += photon.DP.Weight * momentumTransfer;
                    totalMTOfZForOnePhoton[irho, iz] += photon.DP.Weight * momentumTransfer;
                    if (_rng.NextDouble() < _bloodVolumeFraction[csr]) // hit blood
                    {
                        tissueMT[1]                        += momentumTransfer;
                        DynamicMTOfZ[irho, iz]             += photon.DP.Weight * momentumTransfer;
                        dynamicMTOfZForOnePhoton[irho, iz] += photon.DP.Weight * momentumTransfer;
                        SubregionCollisions[csr, 1]        += 1; // add to dynamic collision count
                    }
                    else // index 0 captures static events
                    {
                        tissueMT[0] += momentumTransfer;
                        SubregionCollisions[csr, 0] += 1; // add to static collision count
                    }
                    talliedMT = true;
                }
                previousDP = currentDP;
                currentDP  = nextDP;
            }
            if (totalMT > 0.0)  // only tally if momentum transfer accumulated
            {
                var imt = DetectorBinning.WhichBin(totalMT, MTBins.Count - 1, MTBins.Delta, MTBins.Start);
                Mean[irho, imt] += photon.DP.Weight;
                if (TallySecondMoment)
                {
                    SecondMoment[irho, imt] += photon.DP.Weight * photon.DP.Weight;
                    for (int i = 0; i < Rho.Count - 1; i++)
                    {
                        for (int j = 0; j < Z.Count - 1; j++)
                        {
                            TotalMTOfZSecondMoment[i, j]   += totalMTOfZForOnePhoton[i, j] * totalMTOfZForOnePhoton[i, j];
                            DynamicMTOfZSecondMoment[i, j] += dynamicMTOfZForOnePhoton[i, j] * dynamicMTOfZForOnePhoton[i, j];
                        }
                    }
                }

                if (talliedMT)
                {
                    TallyCount++;
                }

                // tally DYNAMIC fractional MT in each subregion
                int ifrac;
                for (int isr = 0; isr < NumSubregions; isr++)
                {
                    // add 1 to ifrac to offset bin 0 added for =0 only tallies
                    ifrac = DetectorBinning.WhichBin(tissueMT[1] / totalMT,
                                                     FractionalMTBins.Count - 1, FractionalMTBins.Delta, FractionalMTBins.Start) + 1;
                    // put identically 0 fractional MT into separate bin at index 0
                    if (tissueMT[1] / totalMT == 0.0)
                    {
                        ifrac = 0;
                    }
                    // put identically 1 fractional MT into separate bin at index Count+1 -1
                    if (tissueMT[1] / totalMT == 1.0)
                    {
                        ifrac = FractionalMTBins.Count;
                    }
                    FractionalMT[irho, imt, ifrac] += photon.DP.Weight;
                }
            }
        }