//******************************************************************************
//
// File:    PixelAnalysis.java
// Package: edu.rit.mri
// Unit:    Class edu.rit.mri.PixelAnalysis
//
// This Java source file is copyright (C) 2008 by Alan Kaminsky. All rights
// reserved. For further information, contact the author, Alan Kaminsky, at
// ark@cs.rit.edu.
//
// This Java source file is part of the Parallel Java Library ("PJ"). PJ 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.
//
// PJ 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.
//
// A copy of the GNU General Public License is provided in the file gpl.txt. You
// may also obtain a copy of the GNU General Public License on the World Wide
// Web at http://www.gnu.org/licenses/gpl.html.
//
//******************************************************************************

package edu.rit.mri;

import edu.rit.mri.SpinSignal;
import edu.rit.mri.SpinSignalDifference;

import edu.rit.numeric.NonLinearLeastSquares;
import edu.rit.numeric.NonNegativeLeastSquares;
import edu.rit.numeric.Series;
import edu.rit.numeric.TooManyIterationsException;

import java.util.ArrayList;
import java.util.List;

/**
 * Class PixelAnalysis provides a routine for doing a spin relaxometry analysis
 * on one pixel of a magnetic resonance image.
 * 

* The input to the analysis is a measured spin signal expressed as two * {@linkplain edu.rit.numeric.Series Series} objects, a time series * ti and a spin signal series * S(ti). Another input is a series of fixed * spin-lattice relaxation rates R1j. These rates are * chosen to cover the range of likely rates for the magnetic resonance image * being analyzed. *

* The routine first does a nonnegative, linear least squares fit of the * input data to a model consisting of a group of tissues with the input fixed * spin-lattice relaxation rates R1j. Peaks in the * linear least squares fit determine the number of tissues and the approximate * spin density and spin-lattice relaxation rate for each tissue. *

* The routine then does a nonlinear least squares fit of the input data * to a model consisting of the number of tissues determined in the previous * step. The nonlinear least squares fit "polishes up" the spin densities and * spin-lattice relaxation rates determined in the previous step, which are only * approximate. *

* The routine checks the nonlinear least squares fit for plausibility. To be * plausible: *

* If the nonlinear least squares fit is not plausible, the routine decides it * is trying to fit too many tissues. The routine eliminates the tissue with the * smallest spin density and repeats the nonlinear least squares fit. This * continues until the fit is plausible or until all the tissues have been * eliminated, in which case the routine reports that it could not find a * solution. *

* The output of the analysis is a list of the tissues' computed spin densities * and a list of the tissues' computed spin-lattice relaxation rates. * * @author Alan Kaminsky * @version 16-Jun-2008 */ public class PixelAnalysis { // Prevent construction. private PixelAnalysis() { } // Exported operations. /** * Do a spin relaxometry analysis. * * @param t_series * Series of measured time values, of length M (input). * @param S_series * Series of measured spin signal values, of length M (input). * @param R1_series * Series of fixed spin-lattice relaxation rates for the linear part of * the analysis, of length N (input). * @param A * Design matrix for the linear part of the analysis (input). This must * be an M×N-element matrix such that * Ai,j = 1 − 2 * exp(−R1j ti). * (The design matrix is supplied as an argument because the same design * matrix is typically used for every pixel in an image, and calculating * the design matrix just once outside this routine saves time.) * @param rho_list * List in which to store the computed spin densities (output). The size * of the list is the number of tissues. If the routine could not find a * solution, the size of the list is 0. * @param R1_list * List in which to store the computed spin-lattice relaxation rates * (output). The size of the list is the number of tissues. If the * routine could not find a solution, the size of the list is 0. */ public static void analyze (Series t_series, Series S_series, Series R1_series, double[][] A, List rho_list, List R1_list) { int M = t_series.length(); int N = R1_series.length(); // Do a spin relaxometry analysis using nonnegative linear least // squares. // Create nonnegative linear least squares solver. NonNegativeLeastSquares linsolver = new NonNegativeLeastSquares (M, N); // Find the solution. for (int i = 0; i < M; ++ i) { System.arraycopy (A[i], 0, linsolver.a[i], 0, N); linsolver.b[i] = S_series.x(i); } linsolver.solve(); double[] rho_series = linsolver.x; // Find peaks in the solution. A peak occurs at index i if // rho[i] > rho[i-1] and rho[i] > rho[i+1]. ArrayList approx_rho_list = new ArrayList(); ArrayList approx_R1_list = new ArrayList(); for (int j = 0; j < N; ++ j) { if (rho_series[j] > (j == 0 ? 0.0 : rho_series[j-1]) && rho_series[j] > (j == N-1 ? 0.0 : rho_series[j+1])) { approx_rho_list.add (rho_series[j]); approx_R1_list.add (R1_series.x(j)); } } // Do a spin relaxometry analysis using nonlinear least squares. Peaks // in the linear analysis give the initial vector of densities and // rates. // Repeat until the solution is plausible. boolean plausible = false; int L = approx_rho_list.size(); rho_list.clear(); R1_list.clear(); while (L > 0 && ! plausible) { // Create spin signal difference function. L = number of tissues. SpinSignalDifference fcn = new SpinSignalDifference (t_series, S_series, L); // Create nonlinear least squares solver. NonLinearLeastSquares nonlinsolver = new NonLinearLeastSquares (fcn); // Find the solution. for (int i = 0; i < L; ++ i) { nonlinsolver.x[(i<<1)] = approx_rho_list.get(i); nonlinsolver.x[(i<<1)+1] = approx_R1_list.get(i); } try { nonlinsolver.solve(); for (int i = 0; i < L; ++ i) { rho_list.add (nonlinsolver.x[(i<<1)]); R1_list.add (nonlinsolver.x[(i<<1)+1]); } // Decide if solution is plausible. plausible = checkPlausibility (S_series, rho_list, R1_list); } // Couldn't find a solution. catch (TooManyIterationsException exc) { plausible = false; } // If solution is not plausible, eliminate tissue with smallest // density and try again. if (! plausible) { double minrho = Double.MAX_VALUE; int mini = 0; for (int i = 0; i < L; ++ i) { if (approx_rho_list.get(i) < minrho) { minrho = approx_rho_list.get(i); mini = i; } } approx_rho_list.remove (mini); approx_R1_list.remove (mini); L = approx_rho_list.size(); rho_list.clear(); R1_list.clear(); } } } // Hidden operations. /** * Decide if the given solution is plausible. */ private static boolean checkPlausibility (Series S_series, List rho_list, List R1_list) { int M = S_series.length(); int L = rho_list.size(); // If any density or rate is negative, solution is not plausible. for (int i = 0; i < L; ++ i) { if (rho_list.get(i) < 0.0) { return false; } if (R1_list.get(i) < 0.0) { return false; } } // If relative difference between any two rates is too small, solution // is not plausible. for (int i = 0; i < L-1; ++ i) { double R_i = R1_list.get(i); for (int j = i+1; j < L; ++ j) { double R_j = R1_list.get(j); double reldiff = 2.0*Math.abs(R_i-R_j)/Math.abs(R_i+R_j); if (reldiff <= 0.001) { return false; } } } // If sum of densities is too far from asymptotic measurement for large // t, solution is not plausible. double sumrho = 0.0; for (int i = 0; i < L; ++ i) { sumrho += rho_list.get(i); } double S_last = 0.0; int n = 0; for (int i = M-1; i >=0 && n < 7; -- i) { S_last += S_series.x(i); ++ n; } S_last /= n; double reldiff = Math.abs(sumrho-S_last)/Math.abs(S_last); if (reldiff >= 0.2) { return false; } // Solution is plausible. return true; } }

Ads help maintain this website.