Skip to content

Hounsfield conversion

Simple class for conversion from HU to the provided units through a piecewise curve fit

Source code in pygrpm/material/hounsfield_conversion.py
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
class FitHUCurve:
    """Simple class for conversion from HU to the provided units through a piecewise curve fit"""

    def __init__(self) -> None:
        self.__fit_params = None
        self.dataframe = None

    # Black magic function courtesy of
    # https://stackoverflow.com/a/29384899/1991715
    @staticmethod
    def _piecewise_linear(x, x0, y0, k1, k2):
        """
        Method representing the piecewise function
        """
        # pylint: disable=C0103
        # Disable snake_case variable name rule for this block
        return np.piecewise(
            x,
            [x < x0],
            [lambda a: k1 * a + y0 - k1 * x0, lambda a: k2 * a + y0 - k2 * x0],
        )
        # pylint: enable=C0103

    @staticmethod
    def _validate_csv(dataframe: pd.DataFrame) -> None:
        """
        Internal method to provide some basic validation to the CSV file
        :param dataframe: Pandas dataframe
        :return: None
        """

        # Ensure we have just 2 columns
        assert dataframe.shape[1] == 2, "More than two columns present in the csv file."

        # Ensure there are no nan
        assert (
            not dataframe.isnull().values.any()
        ), "Provided CSV Should not contain null values."

        # Ensure same length of both columns (sort of redundant due to previous checks)
        assert (
            dataframe.iloc[:, 0].shape == dataframe.iloc[:, 0].shape
        ), "Provided CSV data columns are not of the same length"

    def get_fit_params(self) -> tuple:
        """
        Returns a tuple of fit parameters as returned by scipy's curve_fit()
        https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
        :return: Tuple
        """
        return self.__fit_params

    def load_curve_csv(self, filepath: Union[str, bytes, os.PathLike]) -> pd.DataFrame:
        """
        Simple method to load csv, acts as a soft wrapper to pandas read_csv
        :param filepath: String
        :return: DataFrame
        """
        if filepath is None:
            raise ValueError("Did not receive a filepath for the curve")

        curve_file = Path(filepath)

        # Raise an error if the file is missing, technically also if it's a directory
        assert curve_file.is_file(), f"{filepath} could not be opened."

        # Read the actual csv
        curve_dataframe = pd.read_csv(curve_file)

        self._validate_csv(curve_dataframe)
        self.dataframe = curve_dataframe

        return curve_dataframe

    def fit_curve(
        self, curve_dataframe: pd.DataFrame = None, plot: bool = False
    ) -> tuple:
        """
        Method used to apply the curve fit on the provided filepath data
        :param curve_dataframe: Dataframe containing the conversion data
        :param plot: Whether to display a preview plot of the fit
        :return :A tuple of 3 tuples
            x and y data from the loaded data
            parameters and covariance of the fit
            x and y labels from the loaded data
        """
        if curve_dataframe is None:
            curve_dataframe = self.dataframe

        assert (
            curve_dataframe is not None
        ), "No dataframe was provided and none exist in cache"

        # Unpack our data frame into x, y and grab labels
        (data_x, data_y) = (
            curve_dataframe.iloc[:, 0].values,
            curve_dataframe.iloc[:, 1].values,
        )
        (x_label, y_label) = curve_dataframe.columns.values

        # Perform the actual curve fitting
        # pylint: disable=unbalanced-tuple-unpacking
        params, covariances = optimize.curve_fit(self._piecewise_linear, data_x, data_y)
        # pylint: enable=unbalanced-tuple-unpacking
        self.__fit_params = (params, covariances)

        # Small subtask for plotting
        if plot is True:
            fit_x = np.linspace(data_x[0], data_x[-1], data_x.shape[0])
            fit_y = self._piecewise_linear(fit_x, *params)

            self.plot_fit((data_x, data_y), (fit_x, fit_y))

        return (data_x, data_y), (params, covariances), (x_label, y_label)

    @staticmethod
    def plot_fit(plot_data: tuple, fit_data: tuple) -> None:
        """
        Internal method to quickly preview the performed fit
        :param plot_data: A tuple/array containing x and y lists/arrays
        :param fit_data: A tuple/array containing the x and y fit parameters
        :return : None
        """
        try:
            # pylint: disable=C0415
            import matplotlib.pyplot as plt
        except ModuleNotFoundError as exc_:
            raise ModuleNotFoundError(
                "This method requires the `matplotlib` optional dependency. Install with: "
                "`pip install pygrpm[matplotlib]` or `pip install pygrpm[all]`"
            ) from exc_

        data_x, data_y = plot_data
        fit_x, fit_y = fit_data

        plt.plot(data_x, data_y, "o")
        plt.plot(fit_x, fit_y)
        plt.show()

fit_curve(curve_dataframe=None, plot=False)

Method used to apply the curve fit on the provided filepath data :param curve_dataframe: Dataframe containing the conversion data :param plot: Whether to display a preview plot of the fit :return :A tuple of 3 tuples x and y data from the loaded data parameters and covariance of the fit x and y labels from the loaded data

Source code in pygrpm/material/hounsfield_conversion.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def fit_curve(
    self, curve_dataframe: pd.DataFrame = None, plot: bool = False
) -> tuple:
    """
    Method used to apply the curve fit on the provided filepath data
    :param curve_dataframe: Dataframe containing the conversion data
    :param plot: Whether to display a preview plot of the fit
    :return :A tuple of 3 tuples
        x and y data from the loaded data
        parameters and covariance of the fit
        x and y labels from the loaded data
    """
    if curve_dataframe is None:
        curve_dataframe = self.dataframe

    assert (
        curve_dataframe is not None
    ), "No dataframe was provided and none exist in cache"

    # Unpack our data frame into x, y and grab labels
    (data_x, data_y) = (
        curve_dataframe.iloc[:, 0].values,
        curve_dataframe.iloc[:, 1].values,
    )
    (x_label, y_label) = curve_dataframe.columns.values

    # Perform the actual curve fitting
    # pylint: disable=unbalanced-tuple-unpacking
    params, covariances = optimize.curve_fit(self._piecewise_linear, data_x, data_y)
    # pylint: enable=unbalanced-tuple-unpacking
    self.__fit_params = (params, covariances)

    # Small subtask for plotting
    if plot is True:
        fit_x = np.linspace(data_x[0], data_x[-1], data_x.shape[0])
        fit_y = self._piecewise_linear(fit_x, *params)

        self.plot_fit((data_x, data_y), (fit_x, fit_y))

    return (data_x, data_y), (params, covariances), (x_label, y_label)

get_fit_params()

Returns a tuple of fit parameters as returned by scipy's curve_fit() https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html :return: Tuple

Source code in pygrpm/material/hounsfield_conversion.py
68
69
70
71
72
73
74
def get_fit_params(self) -> tuple:
    """
    Returns a tuple of fit parameters as returned by scipy's curve_fit()
    https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.curve_fit.html
    :return: Tuple
    """
    return self.__fit_params

load_curve_csv(filepath)

Simple method to load csv, acts as a soft wrapper to pandas read_csv :param filepath: String :return: DataFrame

Source code in pygrpm/material/hounsfield_conversion.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
def load_curve_csv(self, filepath: Union[str, bytes, os.PathLike]) -> pd.DataFrame:
    """
    Simple method to load csv, acts as a soft wrapper to pandas read_csv
    :param filepath: String
    :return: DataFrame
    """
    if filepath is None:
        raise ValueError("Did not receive a filepath for the curve")

    curve_file = Path(filepath)

    # Raise an error if the file is missing, technically also if it's a directory
    assert curve_file.is_file(), f"{filepath} could not be opened."

    # Read the actual csv
    curve_dataframe = pd.read_csv(curve_file)

    self._validate_csv(curve_dataframe)
    self.dataframe = curve_dataframe

    return curve_dataframe

plot_fit(plot_data, fit_data) staticmethod

Internal method to quickly preview the performed fit :param plot_data: A tuple/array containing x and y lists/arrays :param fit_data: A tuple/array containing the x and y fit parameters :return : None

Source code in pygrpm/material/hounsfield_conversion.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
@staticmethod
def plot_fit(plot_data: tuple, fit_data: tuple) -> None:
    """
    Internal method to quickly preview the performed fit
    :param plot_data: A tuple/array containing x and y lists/arrays
    :param fit_data: A tuple/array containing the x and y fit parameters
    :return : None
    """
    try:
        # pylint: disable=C0415
        import matplotlib.pyplot as plt
    except ModuleNotFoundError as exc_:
        raise ModuleNotFoundError(
            "This method requires the `matplotlib` optional dependency. Install with: "
            "`pip install pygrpm[matplotlib]` or `pip install pygrpm[all]`"
        ) from exc_

    data_x, data_y = plot_data
    fit_x, fit_y = fit_data

    plt.plot(data_x, data_y, "o")
    plt.plot(fit_x, fit_y)
    plt.show()