package edu.stanford.rsl.tutorial.RotationalAngiography.ResidualMotionCompensation.ECG; import ij.ImagePlus; import ij.ImageStack; import ij.process.FloatProcessor; import edu.stanford.rsl.conrad.data.numeric.Grid3D; import edu.stanford.rsl.conrad.geometry.Projection; import edu.stanford.rsl.tutorial.RotationalAngiography.ResidualMotionCompensation.ECG.Angiogram; public class ECGGating { private double width = 0.4; //windowWith [0,1] private double exp = 4.0; // exponentA >= 0 private double refHeartPhase = 0.9; // refHeartPhase [0,1] public ECGGating(double width, double exp, double hp){ assert(width <= 1 && width >= 0) : new IllegalArgumentException("Width needs to be in [0,1]."); assert(exp >= 1) : new IllegalArgumentException("Exp needs to be >1."); assert(hp <= 1 && hp >= 0) : new IllegalArgumentException("Heart Phase needs to be in [0,1]."); this.width = width; this.exp = exp; this.refHeartPhase = hp; } /** * Calculates the ECG-gating based weights of projections corresponding to the ECG signal handled. * @param ecg * @return */ public double[] evaluate(double[] ecg){ double[] weights = new double[ecg.length]; for(int i = 0; i < ecg.length; i++){ weights[i] = getWeight(ecg[i]); } return weights; } private double getWeight(double currentHeartPhase){ double weight = 0; double distMeasure = getDistanceMeasure(currentHeartPhase); if(distMeasure > width/2){ return 0; }else{ double cos = Math.cos((distMeasure/width)*Math.PI); weight = Math.pow(cos, exp); } return weight; } private double getDistanceMeasure(double currentHeartPhase){ int[] j = new int[] {-1,0,+1}; double min = Math.abs(currentHeartPhase - refHeartPhase + j[0]); min = Math.min((Math.abs(currentHeartPhase - refHeartPhase + j[1])),min); min = Math.min((Math.abs(currentHeartPhase - refHeartPhase + j[2])),min); return min; } public Grid3D weightProjections(Grid3D g, double[] ecg){ double[] weights = evaluate(ecg); Grid3D weighted = new Grid3D(g); for(int k = 0; k < g.getSize()[2]; k++){ float weight = (float)weights[k]; for(int i = 0; i < g.getSize()[0]; i++){ for(int j = 0; j < g.getSize()[1]; j++){ weighted.setAtIndex(i, j, k, g.getAtIndex(i, j, k)*weight); } } } return weighted; } /** * Applies gating to an angiogram using the specified threshold for heart-phase weights. * Angiograms consist of projection matrices, projection images and the corresponding ECG signal. * @param a * @param threshold * @return */ public Angiogram applyGating(Angiogram a, double threshold){ Projection[] pMat = a.getPMatrices(); ImageStack ims = a.getProjections().getImageStack(); double[] primA = a.getPrimAngles(); double[] ecg = a.getEcg(); double[] weights = evaluate(ecg); int nAbove = 0; for(int i = 0; i < ecg.length; i++){ if(weights[i] > threshold){ nAbove++; } } double[] reducedEcg = new double[nAbove]; double[] reducedPrimA = new double[nAbove]; ImageStack reducedIms = new ImageStack(ims.getProcessor(1).getWidth(), ims.getProcessor(1).getHeight()); Projection[] reducedPMat = new Projection[nAbove]; int count = 0; for(int i = 0; i < weights.length; i++){ if(weights[i] > threshold){ reducedEcg[count] = ecg[i]; reducedIms.addSlice(ims.getProcessor(i+1)); reducedPrimA[count] = primA[i]; reducedPMat[count] = pMat[i]; count++; } } ImagePlus reducedImp = new ImagePlus(); reducedImp.setStack(reducedIms); return new Angiogram(reducedImp, reducedPMat, reducedPrimA, reducedEcg); } /** * Applies gating to an angiogram by multiplication of the projection with the corresponding weight. * Angiograms consist of projection matrices, projection images and the corresponding ECG signal. * @param a * @return */ public Angiogram applyGating(Angiogram a){ Projection[] pMat = a.getPMatrices(); ImageStack ims = a.getProjections().getImageStack(); double[] primA = a.getPrimAngles(); double[] ecg = a.getEcg(); double[] weights = evaluate(ecg); ImageStack weightedIms = new ImageStack(ims.getProcessor(1).getWidth(), ims.getProcessor(1).getHeight()); for(int i = 0; i < weights.length; i++){ FloatProcessor current = (FloatProcessor) ims.getProcessor(i+1); current.multiply(weights[i]); weightedIms.addSlice(current); } ImagePlus reducedImp = new ImagePlus(); reducedImp.setStack(weightedIms); return new Angiogram(reducedImp, pMat, primA, ecg); } }