From c611eab1e666040636151f9099f851a08121e618 Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 11 Sep 2023 13:24:57 +0200 Subject: [PATCH 01/12] modify gammaPerf --- setup.bat | 4 +- tests/performance/gammaPerf.cpp | 94 ++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/setup.bat b/setup.bat index a42dbde..64e2a7a 100644 --- a/setup.bat +++ b/setup.bat @@ -18,8 +18,8 @@ set BUILD_EXAMPLES=ON set BUILD_TESTING=OFF set BUILD_PERFORMANCE_TESTING=OFF -set REF_IMG=original_dose_beam_4.dcm -set EVAL_IMG=logfile_dose_beam_4.dcm +set REF_IMG=img_reference.dcm +set EVAL_IMG=img_evaluated.dcm set INSTALL=OFF set INSTALL_DIR=./yagit diff --git a/tests/performance/gammaPerf.cpp b/tests/performance/gammaPerf.cpp index 1f489be..112696a 100644 --- a/tests/performance/gammaPerf.cpp +++ b/tests/performance/gammaPerf.cpp @@ -33,6 +33,9 @@ const auto GLOBAL = yagit::GammaNormalization::Global; const auto LOCAL = yagit::GammaNormalization::Local; +const float MAX_REF_DOSE = -1; // set automatically max ref dose later +const float DCO = -1; // set automatically 5% of max ref dose later + using GammaFunc = std::function; @@ -44,49 +47,49 @@ struct Config{ }; const std::vector configs = { - {"classic", "2D", {3, 3, GLOBAL, 0, 0, 0, 0}, 10}, - {"classic", "2D", {2, 2, GLOBAL, 0, 0, 0, 0}, 10}, - {"classic", "2D", {3, 3, LOCAL, 0, 0, 0, 0}, 10}, - {"classic", "2D", {2, 2, LOCAL, 0, 0, 0, 0}, 10}, - {"classic", "2.5D", {3, 3, GLOBAL, 0, 0, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, GLOBAL, 0, 0, 0, 0}, 1}, - {"classic", "2.5D", {3, 3, LOCAL, 0, 0, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, LOCAL, 0, 0, 0, 0}, 1}, - - {"classic", "2D", {3, 3, GLOBAL, 0, 1e-6, 0, 0}, 10}, - {"classic", "2D", {2, 2, GLOBAL, 0, 1e-6, 0, 0}, 10}, - {"classic", "2D", {3, 3, LOCAL, 0, 1e-6, 0, 0}, 10}, - {"classic", "2D", {2, 2, LOCAL, 0, 1e-6, 0, 0}, 10}, - {"classic", "2.5D", {3, 3, GLOBAL, 0, 1e-6, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, GLOBAL, 0, 1e-6, 0, 0}, 1}, - {"classic", "2.5D", {3, 3, LOCAL, 0, 1e-6, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, LOCAL, 0, 1e-6, 0, 0}, 1}, - - {"wendling", "2D", {3, 3, GLOBAL, 0, 0, 10, 0.3}, 100}, - {"wendling", "2D", {2, 2, GLOBAL, 0, 0, 10, 0.2}, 100}, - {"wendling", "2D", {3, 3, LOCAL, 0, 0, 10, 0.3}, 100}, - {"wendling", "2D", {2, 2, LOCAL, 0, 0, 10, 0.2}, 100}, - {"wendling", "2.5D", {3, 3, GLOBAL, 0, 0, 10, 0.3}, 10}, - {"wendling", "2.5D", {2, 2, GLOBAL, 0, 0, 10, 0.2}, 10}, - {"wendling", "2.5D", {3, 3, LOCAL, 0, 0, 10, 0.3}, 3}, - {"wendling", "2.5D", {2, 2, LOCAL, 0, 0, 10, 0.2}, 3}, - {"wendling", "3D", {3, 3, GLOBAL, 0, 0, 10, 0.3}, 10}, - {"wendling", "3D", {2, 2, GLOBAL, 0, 0, 10, 0.2}, 10}, - {"wendling", "3D", {3, 3, LOCAL, 0, 0, 10, 0.3}, 1}, - {"wendling", "3D", {2, 2, LOCAL, 0, 0, 10, 0.2}, 1}, - - {"wendling", "2D", {3, 3, GLOBAL, 0, 1e-6, 10, 0.3}, 100}, - {"wendling", "2D", {2, 2, GLOBAL, 0, 1e-6, 10, 0.2}, 100}, - {"wendling", "2D", {3, 3, LOCAL, 0, 1e-6, 10, 0.3}, 100}, - {"wendling", "2D", {2, 2, LOCAL, 0, 1e-6, 10, 0.2}, 100}, - {"wendling", "2.5D", {3, 3, GLOBAL, 0, 1e-6, 10, 0.3}, 10}, - {"wendling", "2.5D", {2, 2, GLOBAL, 0, 1e-6, 10, 0.2}, 10}, - {"wendling", "2.5D", {3, 3, LOCAL, 0, 1e-6, 10, 0.3}, 3}, - {"wendling", "2.5D", {2, 2, LOCAL, 0, 1e-6, 10, 0.2}, 3}, - {"wendling", "3D", {3, 3, GLOBAL, 0, 1e-6, 10, 0.3}, 10}, - {"wendling", "3D", {2, 2, GLOBAL, 0, 1e-6, 10, 0.2}, 10}, - {"wendling", "3D", {3, 3, LOCAL, 0, 1e-6, 10, 0.3}, 1}, - {"wendling", "3D", {2, 2, LOCAL, 0, 1e-6, 10, 0.2}, 1} + {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 10}, + {"classic", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 10}, + {"classic", "2D", {3, 3, LOCAL, 0, 0, 0, 0}, 10}, + {"classic", "2D", {2, 2, LOCAL, 0, 0, 0, 0}, 10}, + {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 1}, + {"classic", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 1}, + {"classic", "2.5D", {3, 3, LOCAL, 0, 0, 0, 0}, 1}, + {"classic", "2.5D", {2, 2, LOCAL, 0, 0, 0, 0}, 1}, + + {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 10}, + {"classic", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 10}, + {"classic", "2D", {3, 3, LOCAL, 0, DCO, 0, 0}, 10}, + {"classic", "2D", {2, 2, LOCAL, 0, DCO, 0, 0}, 10}, + {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 1}, + {"classic", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 1}, + {"classic", "2.5D", {3, 3, LOCAL, 0, DCO, 0, 0}, 1}, + {"classic", "2.5D", {2, 2, LOCAL, 0, DCO, 0, 0}, 1}, + + {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 100}, + {"wendling", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 100}, + {"wendling", "2D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 100}, + {"wendling", "2D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 100}, + {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 10}, + {"wendling", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 10}, + {"wendling", "2.5D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 3}, + {"wendling", "2.5D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 3}, + {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 10}, + {"wendling", "3D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 10}, + {"wendling", "3D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 1}, + {"wendling", "3D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 1}, + + {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 100}, + {"wendling", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 100}, + {"wendling", "2D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 100}, + {"wendling", "2D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 100}, + {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 10}, + {"wendling", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 10}, + {"wendling", "2.5D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 3}, + {"wendling", "2.5D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 3}, + {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 10}, + {"wendling", "3D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 10}, + {"wendling", "3D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 1}, + {"wendling", "3D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 1} }; std::string csvHeader(){ @@ -189,9 +192,12 @@ int main(int argc, char** argv){ std::cout << i+1 << "/" << configs.size(); auto [method, dims, gammaParams, nrOfTests] = configs[i]; - if(gammaParams.normalization == GLOBAL){ + if(gammaParams.globalNormDose == MAX_REF_DOSE){ gammaParams.globalNormDose = (dims == "2D" ? refMaxDose2D : refMaxDose3D); } + if(gammaParams.doseCutoff == DCO){ + gammaParams.doseCutoff = 0.05 * (dims == "2D" ? refMaxDose2D : refMaxDose3D); + } csvFile << configToCsv({method, dims, gammaParams, nrOfTests}) << ","; From b747aa7e9e6020d8a475e00cfc0f863ce2dd62ee Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 11 Sep 2023 13:58:01 +0200 Subject: [PATCH 02/12] add fred and pymedphys performance tests --- tests/performance/other/gamma_perf_fred.py | 221 ++++++++++++++++++ .../performance/other/gamma_perf_pymedphys.py | 202 ++++++++++++++++ 2 files changed, 423 insertions(+) create mode 100644 tests/performance/other/gamma_perf_fred.py create mode 100644 tests/performance/other/gamma_perf_pymedphys.py diff --git a/tests/performance/other/gamma_perf_fred.py b/tests/performance/other/gamma_perf_fred.py new file mode 100644 index 0000000..4ecf983 --- /dev/null +++ b/tests/performance/other/gamma_perf_fred.py @@ -0,0 +1,221 @@ +############################################################################################# +# Copyright (C) 2023 'Yet Another Gamma Index Tool' Developers. +# +# This file is part of 'Yet Another Gamma Index Tool'. +# +# 'Yet Another Gamma Index Tool' 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. +# +# 'Yet Another Gamma Index Tool' 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. +# +# You should have received a copy of the GNU General Public License +# along with 'Yet Another Gamma Index Tool'. If not, see . +############################################################################################# + +# works only on Linux +# when using WSL, run this script within WSL location and not Windows location (don't use /mnt/c/), +# because running it within Windows location is slower + +# I additionaly use maxSearchDist parameter that is absent in fredtools +# to add this, you need to do the following: +# 1. add this parameter to calcGammaIndex function in fredtools +# maxSearchDist=None +# 2. add this code to calcGammaIndex function body (it sets gammaSearchMax based on maxSearchDist) +# if maxSearchDist is not None: +# gammaSearchMax = maxSearchDist / DTA +# libFredGI.fredGI_setGammaSearchMax(ctypes.c_float(gammaSearchMax)) + +import time +import statistics +import csv + +import numpy as np +import SimpleITK as sitk +import fredtools as ft + + +def read_dcm_rtdose(filepath): + img = sitk.ReadImage(filepath, imageIO="GDCMImageIO") + + # convert dicom int pixels to dose value pixels + dose_grid_scaling = float(img.GetMetaData("3004|000e")) + img_dose_arr = dose_grid_scaling * sitk.GetArrayFromImage(img) + img_dose = sitk.GetImageFromArray(img_dose_arr) + + img_dose.SetOrigin(img.GetOrigin()) + img_dose.SetSpacing(img.GetSpacing()) + + return img_dose + +def print_dcm_info(image): + size = image.GetSize() + offset = image.GetOrigin() + spacing = image.GetSpacing() + print(f"Size: ({size[2]}, {size[1]}, {size[0]})") + print(f"Offset: ({offset[2]}, {offset[1]}, {offset[0]})") + print(f"Spacing: ({spacing[2]}, {spacing[1]}, {spacing[0]})") + + +def save_gamma_to_file(gamma, filepath): + gamma_arr = sitk.GetArrayFromImage(gamma) + + # convert negative numbers to NaN + gamma_arr[gamma_arr < 0] = np.NaN + gamma2 = sitk.GetImageFromArray(gamma_arr) + + # copy offset and spacing + gamma2.SetOrigin(gamma.GetOrigin()) + gamma2.SetSpacing(gamma.GetSpacing()) + + # copy all additional metadata + for key in gamma.GetMetaDataKeys(): + gamma2.SetMetaData(key, gamma.GetMetaData(key)) + + ft.writeMHD(gamma2, filepath, singleFile=False, overwrite=True, displayInfo=False) + + +def csv_header(): + return [ + "method", "dims", + "dd[%]", "dta[mm]", "norm", "normDose", "dco", + "maxSearchDist[mm]", "stepSize[mm]", + "nrOfTests", + "meanTime[ms]", "stdTime[ms]", "minTime[ms]", "maxTime[ms]", + "GIPR[%]", "meanGamma", "minGamma", "maxGamma", "gammaSize", "NaNvalues" + ] + +def config_to_csv(gamma_options, max_ref_val, nr_of_tests): + dd = gamma_options["DD"] + dta = gamma_options["DTA"] + normalization = "G" if gamma_options["DDType"] == "global" else "L" + global_norm_dose = max_ref_val if gamma_options["globalNorm"] is None else gamma_options["globalNorm"] + dose_cutoff = gamma_options["DCO"] * max_ref_val + max_search_dist = gamma_options["maxSearchDist"] + step_size = dta / gamma_options["stepSize"] if gamma_options["fractionalStepSize"] else gamma_options["stepSize"] + + return [ + "wendling", "3D", + round(dd, 6), round(dta, 6), normalization, round(global_norm_dose, 6), round(dose_cutoff, 6), + round(max_search_dist, 6), round(step_size, 6), + nr_of_tests + ] + +def time_stats_to_csv(times_ms): + mean = statistics.mean(times_ms) + standard_deviation = statistics.pstdev(times_ms) # population sd + min_time = min(times_ms) + max_time = max(times_ms) + + return [ + f"{mean:.6f}", f"{standard_deviation:.6f}", + f"{min_time:.3f}", f"{max_time:.3f}" + ] + +def gamma_result_to_csv(gamma_res): + gamma_stat = ft.getGIstat(gamma_res) + gamma_arr = sitk.GetArrayFromImage(gamma_res) + nan_values = np.count_nonzero(gamma_arr == -1) + + return [ + round(gamma_stat["passRate"], 4), + round(gamma_stat["mean"], 6), gamma_stat["min"], gamma_stat["max"], + gamma_arr.size, nan_values + ] + + +# =========================================================================== + +DATA_REF = "img_reference.dcm" +DATA_EVAL = "img_evaluated.dcm" + +CSV_FILENAME = "output_fred.csv" +MHA_FILENAME = "result_fred.mha" + +GLOBAL = "global" +LOCAL = "local" + +DCO = 0.05 # 5% of max ref dose +STEP_SIZE = 10 # 1/10 of DTA + +CPU_NO = "auto" +# CPU_NO = None # sequential +# CPU_NO = 8 + +# FRED has only Wendling 3D +# FRED requires DCO to be greater than 0 +# config: dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests +configs = [ + (3, 3, GLOBAL, DCO, 9, STEP_SIZE, 10), + (2, 2, GLOBAL, DCO, 6, STEP_SIZE, 10), + (3, 3, LOCAL, DCO, 9, STEP_SIZE, 10), + (2, 2, LOCAL, DCO, 6, STEP_SIZE, 10) +] + + +# =========================================================================== + +img_ref = read_dcm_rtdose(DATA_REF) +img_eval = read_dcm_rtdose(DATA_EVAL) + +max_ref_val = sitk.GetArrayFromImage(img_ref).max() + +print("Reference image") +print_dcm_info(img_ref) +print("--------------") +print("Evaluated image") +print_dcm_info(img_eval) + +print("==================================") + +csv_file = open(CSV_FILENAME, "w") +csv_writer = csv.writer(csv_file) +csv_writer.writerow(csv_header()) + +for i, config in enumerate(configs): + print(f"{i+1}/{len(configs)}", end="") + + dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests = config + + # https://www.fredtools.ifj.edu.pl/Documentation/gammaIndexAnalyse.html + gamma_options = { + "DD": dd, # DD [%] + "DTA": dta, # DTA [mm] + "DDType": norm, # global or local + "globalNorm": None, # if None then use max value of ref image + "DCO": dco, # dose cutoff (given as fraction of max ref dose) + + "maxSearchDist": max_search_dist, # my custom parameter!!! you need to modify fredtools source code for this to work + "fractionalStepSize": True, # if True then stepSize is fraction of dta, else stepSize is absolute value + "stepSize": step_size, + + "CPUNo": CPU_NO, + "displayInfo": False + } + + times_ms = [] + gamma_res = None + for _ in range(nr_of_tests): + start = time.time() + gamma_res = ft.calcGammaIndex(img_ref, img_eval, **gamma_options) + end = time.time() + + time_ms = (end - start) * 1000 + times_ms.append(time_ms) + + mean = statistics.mean(times_ms) + print(f" - mean time: {mean:.6f} ms") + + record = config_to_csv(gamma_options, max_ref_val, nr_of_tests) +\ + time_stats_to_csv(times_ms) +\ + gamma_result_to_csv(gamma_res) + csv_writer.writerow(record) + + # if i == 0: + # save_gamma_to_file(gamma_res, MHA_FILENAME) + +csv_file.close() diff --git a/tests/performance/other/gamma_perf_pymedphys.py b/tests/performance/other/gamma_perf_pymedphys.py new file mode 100644 index 0000000..94a2310 --- /dev/null +++ b/tests/performance/other/gamma_perf_pymedphys.py @@ -0,0 +1,202 @@ +############################################################################################# +# Copyright (C) 2023 'Yet Another Gamma Index Tool' Developers. +# +# This file is part of 'Yet Another Gamma Index Tool'. +# +# 'Yet Another Gamma Index Tool' 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. +# +# 'Yet Another Gamma Index Tool' 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. +# +# You should have received a copy of the GNU General Public License +# along with 'Yet Another Gamma Index Tool'. If not, see . +############################################################################################# + +import time +import statistics +import csv + +import numpy as np +import pydicom +import pymedphys +import SimpleITK as sitk + + +def get_z_spacing(image): + if "SliceThickness" in image and image.SliceThickness is not None: + return image.SliceThickness + + gridFrameOffsetVector = image.GridFrameOffsetVector + if isinstance(gridFrameOffsetVector, pydicom.multival.MultiValue): + slices_diff = np.diff(gridFrameOffsetVector) + if all(slices_diff == slices_diff[0]): + return slices_diff[0] + raise ValueError("z-spacing is not evenly spaced") + else: + return gridFrameOffsetVector + +def print_dcm_info(image): + size = (image.NumberOfFrames, image.Rows, image.Columns) + offset = (image.ImagePositionPatient[2], image.ImagePositionPatient[1], image.ImagePositionPatient[0]) + spacing = (get_z_spacing(image), image.PixelSpacing[0], image.PixelSpacing[1]) + print(f"Size: ({size[0]}, {size[1]}, {size[2]})") + print(f"Offset: ({offset[0]}, {offset[1]}, {offset[2]})") + print(f"Spacing: ({spacing[0]}, {spacing[1]}, {spacing[2]})") + + +def save_gamma_to_file(gamma_arr, img_ref, filepath): + gamma_img = sitk.GetImageFromArray(gamma_arr) + gamma_img.SetOrigin(img_ref.ImagePositionPatient) + gamma_img.SetSpacing((get_z_spacing(img_ref), img_ref.PixelSpacing[0], img_ref.PixelSpacing[1])) + sitk.WriteImage(gamma_img, filepath) + + +def csv_header(): + return [ + "method", "dims", + "dd[%]", "dta[mm]", "norm", "normDose", "dco", + "maxSearchDist[mm]", "stepSize[mm]", + "nrOfTests", + "meanTime[ms]", "stdTime[ms]", "minTime[ms]", "maxTime[ms]", + "GIPR[%]", "meanGamma", "minGamma", "maxGamma", "gammaSize", "NaNvalues" + ] + +def config_to_csv(gamma_options, max_ref_val, nr_of_tests): + dd = gamma_options["dose_percent_threshold"] + dta = gamma_options["distance_mm_threshold"] + normalization = "L" if gamma_options["local_gamma"] else "G" + global_norm_dose = max_ref_val if gamma_options["global_normalisation"] is None else gamma_options["global_normalisation"] + dose_cutoff = gamma_options["lower_percent_dose_cutoff"] / 100 * max_ref_val + max_search_dist = gamma_options["max_gamma"] * gamma_options["distance_mm_threshold"] + step_size = dta / gamma_options["interp_fraction"] + + return [ + "wendling", "3D", + round(dd, 6), round(dta, 6), normalization, round(global_norm_dose, 6), round(dose_cutoff, 6), + round(max_search_dist, 6), round(step_size, 6), + nr_of_tests + ] + +def time_stats_to_csv(times_ms): + mean = statistics.mean(times_ms) + standard_deviation = statistics.pstdev(times_ms) # population sd + min_time = min(times_ms) + max_time = max(times_ms) + + return [ + f"{mean:.6f}", f"{standard_deviation:.6f}", + f"{min_time:.3f}", f"{max_time:.3f}" + ] + +def gamma_result_to_csv(gamma_res): + valid_gamma = gamma_res[~np.isnan(gamma_res)] + pass_ratio = np.sum(valid_gamma <= 1) / len(valid_gamma) * 100 + nan_values = gamma_res.size - valid_gamma.size + + return [ + round(pass_ratio, 4), + round(np.nanmean(gamma_res), 6), np.nanmin(gamma_res), np.nanmax(gamma_res), + gamma_res.size, nan_values + ] + + +# =========================================================================== + +DATA_REF = "img_reference.dcm" +DATA_EVAL = "img_evaluated.dcm" + +CSV_FILENAME = "output_pymedphys.csv" +MHA_FILENAME = "result_pymedphys.mha" + +GLOBAL = False +LOCAL = True + +DCO = 5 # 5% of max ref dose +STEP_SIZE = 10 # 1/10 of DTA + +# PyMedPhys has only Wendling 3D +# config: dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests +configs = [ + (3, 3, GLOBAL, 0, 9, STEP_SIZE, 3), + (2, 2, GLOBAL, 0, 6, STEP_SIZE, 3), + (3, 3, LOCAL, 0, 9, STEP_SIZE, 3), + (2, 2, LOCAL, 0, 6, STEP_SIZE, 3), + + (3, 3, GLOBAL, DCO, 9, STEP_SIZE, 3), + (2, 2, GLOBAL, DCO, 6, STEP_SIZE, 3), + (3, 3, LOCAL, DCO, 9, STEP_SIZE, 3), + (2, 2, LOCAL, DCO, 6, STEP_SIZE, 3) +] + + +# =========================================================================== + +img_ref = pydicom.read_file(DATA_REF, force=True) +img_eval = pydicom.read_file(DATA_EVAL, force=True) + +axes_ref, dose_ref = pymedphys.dicom.zyx_and_dose_from_dataset(img_ref) +axes_eval, dose_eval = pymedphys.dicom.zyx_and_dose_from_dataset(img_eval) + +max_ref_val = dose_ref.max() + +print("Reference image") +print_dcm_info(img_ref) +print("--------------") +print("Evaluated image") +print_dcm_info(img_eval) + +print("==================================") + +csv_file = open(CSV_FILENAME, "w") +csv_writer = csv.writer(csv_file) +csv_writer.writerow(csv_header()) + +for i, config in enumerate(configs): + print(f"{i+1}/{len(configs)}", end="") + + dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests = config + + # https://docs.pymedphys.com/en/latest/users/ref/lib/gamma.html + gamma_options = { + "dose_percent_threshold": dd, # DD [%] + "distance_mm_threshold": dta, # DTA [mm] + "local_gamma": norm, # local or global + "global_normalisation": None, # if None then use max value of ref image + "lower_percent_dose_cutoff": dco, # dose cutoff (given as percent of max ref dose) + + "max_gamma": max_search_dist / dta, + "interp_fraction": step_size, # fraction of dta + + "random_subset": None, + "ram_available": 2**32 # 4 GB + } + + times_ms = [] + gamma_res = None + for _ in range(nr_of_tests): + start = time.time() + gamma_res = pymedphys.gamma(axes_ref, dose_ref, + axes_eval, dose_eval, + **gamma_options) + end = time.time() + + time_ms = (end - start) * 1000 + times_ms.append(time_ms) + + mean = statistics.mean(times_ms) + print(f" - mean time: {mean:.6f} ms") + + record = config_to_csv(gamma_options, max_ref_val, nr_of_tests) +\ + time_stats_to_csv(times_ms) +\ + gamma_result_to_csv(gamma_res) + csv_writer.writerow(record) + + # if i == 0: + # save_gamma_to_file(gamma_res, img_ref, MHA_FILENAME) + +csv_file.close() From 2ef20eff838fa9c2bf8de5762071c08ae46eaba6 Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 11 Sep 2023 23:26:11 +0200 Subject: [PATCH 03/12] add plotting data from multiple files and improve plot appearance --- tests/performance/plot_gamma_times.py | 198 ++++++++++++++++++-------- 1 file changed, 136 insertions(+), 62 deletions(-) diff --git a/tests/performance/plot_gamma_times.py b/tests/performance/plot_gamma_times.py index 9c69a19..a3c732d 100644 --- a/tests/performance/plot_gamma_times.py +++ b/tests/performance/plot_gamma_times.py @@ -17,15 +17,16 @@ # along with 'Yet Another Gamma Index Tool'. If not, see . ############################################################################################# -# Python script that reads two csv files that are output from gammaPerf program and compares them using plots. -# 'TIMES' mode plots times of first and second versions. -# 'SPEEDUP' mode plots speedup of the second version compared to the first. +# Python script that reads multiple csv files that are output from gammaPerf program and compares them using plots. +# 'TIMES' mode plots times from all files. +# 'SPEEDUP' mode plots speedup compared to the first file. # There is also the possibility to set filters to show only part of the data (e.g. only classic method). import pandas as pd import matplotlib.pyplot as plt -plt.rcParams["figure.figsize"] = (8,5) +plt.rcParams["figure.figsize"] = (10, 5) +# plt.rcParams["figure.figsize"] = (7, 5) def human_readable_time(time_ms): MSEC_IN_SEC = 1000 @@ -49,22 +50,42 @@ def human_readable_time(time_ms): # ============================================== # CONFIG -file1 = "gammaTimesSequential.csv" -file2 = "gammaTimesThreaded.csv" + +title = "Comparison of gamma index implementations" + +files = [ + "gammaTimes_seq.csv", + "gammaTimes_thr.csv", + "gammaTimes_simd.csv", + "gammaTimes_thr_simd.csv" +] +bars_labels = [ + "Sequential", + "Multithreaded", + "SIMD (AVX2)", + "Multithreaded \nand SIMD (AVX2)" +] mode = "TIMES" # mode = "SPEEDUP" -title = "Comparison of sequential and threaded gamma index" -bars1_label = "Sequential" -bars2_label = "Threaded" - # filters method = "" dims = "" norm = "" min_dco = 0 + +# check if config is correct +if len(files) == 0: + print("ERROR: no input files were provided") + exit(1) + +if len(bars_labels) != len(files) and len(bars_labels) != 0: + print("ERROR: number of bars labels must be 0 or equal to number of files") + exit(1) + +# print filters if method == "" and dims == "" and norm == "" and min_dco == 0: print("filters: none") else: @@ -73,76 +94,129 @@ def human_readable_time(time_ms): # ============================================== # READ CSV FILES AND PREPARE DATA -df1 = pd.read_csv(file1) -df2 = pd.read_csv(file2) - -if method != "": - df1 = df1[df1["method"] == method] - df2 = df2[df2["method"] == method] -if dims != "": - df1 = df1[df1["dims"] == dims] - df2 = df2[df2["dims"] == dims] -if norm != "": - df1 = df1[df1["norm"] == norm] - df2 = df2[df2["norm"] == norm] -if min_dco > 0: - df1 = df1[df1["dco"] >= min_dco] - df2 = df2[df2["dco"] >= min_dco] - -if df1.shape != df2.shape: - print("ERROR: dataframes have different shapes") - exit(1) -df1 = df1.reset_index(drop=True) -df2 = df2.reset_index(drop=True) +dfs = [pd.read_csv(file) for file in files] + +for i in range(len(dfs)): + if method != "": + dfs[i] = dfs[i][dfs[i]["method"] == method] + if dims != "": + dfs[i] = dfs[i][dfs[i]["dims"] == dims] + if norm != "": + dfs[i] = dfs[i][dfs[i]["norm"] == norm] + if min_dco > 0: + dfs[i] = dfs[i][dfs[i]["dco"] >= min_dco] + + dfs[i] = dfs[i].reset_index(drop=True) + +for df in dfs: + if df.shape != dfs[0].shape: + print("ERROR: dataframes have different shapes") + exit(1) # check if columns containing test config are equal -for col in ["method", "dims", "dd[%]", "dta[mm]", "norm", "normDose", "dco", "maxSearchDist[mm]", "stepSize[mm]"]: - if (df1[col] != df2[col]).any(): - print(f"WARNING: column {col} in two dataframes are not equal") +test_config_cols = ["method", "dims", "dd[%]", "dta[mm]", "norm", "normDose", "dco", "maxSearchDist[mm]", "stepSize[mm]"] +for i, df in enumerate(dfs): + for col in test_config_cols: + if (df[col] != dfs[0][col]).any(): + print(f"WARNING: column {col} in dataframe {i+1} is not the same as in the first dataframe") + + +times = [df["meanTime[ms]"] for df in dfs] +speedups = [times[0] / time for time in times[1:]] -meanTime_col = "meanTime[ms]" -time1 = df1[meanTime_col] -time2 = df2[meanTime_col] +# summed_times = [(df["nrOfTests"] * df["meanTime[ms]"]).sum() for df in dfs] +# for i, stime in enumerate(summed_times): +# print(f"summed time for file {i+1}: {stime:.3f} ms ({human_readable_time(stime)})") -summed_time1 = (df1["nrOfTests"] * df1[meanTime_col]).sum() -summed_time2 = (df2["nrOfTests"] * df2[meanTime_col]).sum() -print(f"summed time for file1: {summed_time1:.3f} ms ({human_readable_time(summed_time1)})") -print(f"summed time for file2: {summed_time2:.3f} ms ({human_readable_time(summed_time2)})") +summed_mean_times = [times_in_df.sum() for times_in_df in times] +for i, smtime in enumerate(summed_mean_times): + print(f"summed mean time for file {i+1}: {smtime:.3f} ms ({human_readable_time(smtime)})") -speedup = df1[meanTime_col] / df2[meanTime_col] -labels = [f'{method[0].upper()}{dims}: {dd}%{norm}/{dta}mm{" DCO" if dco > 0 else ""}\nspeedup: {sp:.2f}' \ - for method, dims, dd, norm, dta, dco, sp in \ - zip(df1["method"], df1["dims"], df1["dd[%]"], df1["norm"], df1["dta[mm]"], df1["dco"], speedup)] +x_labels = [ + f'{method} {dims}\n{dd}%{norm}/{dta}mm{" DCO" if dco > 0 else ""}' + for method, dims, dd, norm, dta, dco in zip( + dfs[0]["method"], dfs[0]["dims"], + dfs[0]["dd[%]"], dfs[0]["norm"], dfs[0]["dta[mm]"], dfs[0]["dco"] + ) +] # ============================================== # PLOT -fig, ax = plt.subplots() -ax.set_title(title) -indices = range(len(time1)) +def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): + group_count = len(values[0]) + bars_per_group_count = len(values) + + indices = range(group_count) + few_groups = (group_count <= 4) + + width = min(0.2, 0.84 / bars_per_group_count) + half_width = width / 2 + offset = (1 - bars_per_group_count * width) / 2 + + # set colors of bars + if color_cycle is not None: + ax.set_prop_cycle("color", color_cycle) + + # add bars + all_bars = [] + for i in range(bars_per_group_count): + pos = [index + offset + i * width + half_width for index in indices] + + bar = ax.bar(pos, values[i], width, zorder=3) + if len(bars_labels) > 0: + bar.set_label(bars_labels[i]) + + all_bars.append(bar) + + # add text with value above bars + if few_groups: + for bars_container in all_bars: + for bar in bars_container: + pos = bar.get_x() + bar.get_width() / 2.0 + height = bar.get_height() + plt.text(pos, height, f'{height:.2f}', ha='center', va='bottom') + + # add more space at the top + ax.margins(y = 0.1) + + # set x labels + # ax.set_xlim(0, group_count) + ax.set_xlim(-offset, group_count + offset) + ax.set_xticks([index + offset + (bars_per_group_count - 1) * width / 2 + half_width for index in indices]) + if few_groups: + ax.set_xticklabels(x_labels) + else: + ax.set_xticklabels(x_labels, rotation=90) + + # double the y-tick density + # mul = 2 + # current_yticks = plt.yticks()[0] + # new_diff = (current_yticks[1] - current_yticks[0]) / mul + # new_yticks = [current_yticks[0] + i * new_diff for i in range(len(current_yticks) * mul - 1)] + # plt.yticks(new_yticks) + + +color_cycle = ["#1f77b4", "#ff7f0e", "#2ca02c", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"] + +fig, ax = plt.subplots() if mode == "TIMES": ax.set_ylabel("Time [ms]") - width = 0.35 - bars1 = ax.bar(indices, time1, width, zorder=3, label=bars1_label) - bars2 = ax.bar([index + width for index in indices], time2, width, zorder=3, label=bars2_label) - ax.set_xticks([index + width/2 for index in indices]) + plot_bars(ax, times, bars_labels, x_labels, color_cycle) elif mode == "SPEEDUP": ax.set_ylabel("Speedup") - width = 0.5 - bars = ax.bar(indices, speedup, width, zorder=3, label="Speedup") - ax.set_xticks(indices) + plot_bars(ax, speedups, bars_labels[1:], x_labels, color_cycle[1:]) -if len(time1) <= 4: - ax.set_xticklabels(labels) -else: - ax.set_xticklabels(labels, rotation=90) - -ax.grid() -ax.legend() +ax.set_title(title) +ax.grid(axis="y", linestyle=(0, (5, 5))) +if len(bars_labels) > 0: + ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.12), ncol=5) + # ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)) + # ax.legend() plt.tight_layout() plt.show() From 88b65e7f8bc51b3294c70ef9dc4220c0b342bae0 Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 11 Sep 2023 23:41:31 +0200 Subject: [PATCH 04/12] add saving plot to file --- tests/performance/plot_gamma_times.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/performance/plot_gamma_times.py b/tests/performance/plot_gamma_times.py index a3c732d..40451b3 100644 --- a/tests/performance/plot_gamma_times.py +++ b/tests/performance/plot_gamma_times.py @@ -75,6 +75,11 @@ def human_readable_time(time_ms): norm = "" min_dco = 0 +# save to file +save_to_png = False +save_to_svg = False +save_to_pdf = False + # check if config is correct if len(files) == 0: @@ -219,4 +224,22 @@ def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): # ax.legend() plt.tight_layout() + + +# ============================================== +# SAVE TO FILE + +if save_to_png: + plt.savefig("plot.png", format="png") + +if save_to_svg: + plt.savefig("plot.svg", format="svg") + +if save_to_pdf: + plt.savefig("plot.pdf", format="pdf") + + +# ============================================== +# SHOW PLOT + plt.show() From 1132c0be7c2962d88312325be60187e56e549b1d Mon Sep 17 00:00:00 2001 From: michal367 Date: Thu, 12 Oct 2023 09:24:25 +0200 Subject: [PATCH 05/12] allow eval img to have more frames than ref img in classic 2.5d --- src/gamma/Gamma.cpp | 4 ++-- src/gamma/GammaSimd.cpp | 4 ++-- src/gamma/GammaThreads.cpp | 4 ++-- src/gamma/GammaThreadsSimd.cpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gamma/Gamma.cpp b/src/gamma/Gamma.cpp index 6d1d4ee..77b2547 100644 --- a/src/gamma/Gamma.cpp +++ b/src/gamma/Gamma.cpp @@ -129,8 +129,8 @@ GammaResult gammaIndex2DClassic(const ImageData& refImg2D, const ImageData& eval GammaResult gammaIndex2_5DClassic(const ImageData& refImg3D, const ImageData& evalImg3D, const GammaParameters& gammaParams){ - if(refImg3D.getSize().frames != evalImg3D.getSize().frames){ - throw std::invalid_argument("reference image and evaluated image don't have the same number of frames"); + if(evalImg3D.getSize().frames < refImg3D.getSize().frames){ + throw std::invalid_argument("evaluated image must have at least the same number of frames as the reference image"); } validateGammaParameters(gammaParams); diff --git a/src/gamma/GammaSimd.cpp b/src/gamma/GammaSimd.cpp index 55e205a..bf81ae9 100644 --- a/src/gamma/GammaSimd.cpp +++ b/src/gamma/GammaSimd.cpp @@ -163,8 +163,8 @@ GammaResult gammaIndex2DClassic(const ImageData& refImg2D, const ImageData& eval GammaResult gammaIndex2_5DClassic(const ImageData& refImg3D, const ImageData& evalImg3D, const GammaParameters& gammaParams){ - if(refImg3D.getSize().frames != evalImg3D.getSize().frames){ - throw std::invalid_argument("reference image and evaluated image don't have the same number of frames"); + if(evalImg3D.getSize().frames < refImg3D.getSize().frames){ + throw std::invalid_argument("evaluated image must have at least the same number of frames as the reference image"); } validateGammaParameters(gammaParams); diff --git a/src/gamma/GammaThreads.cpp b/src/gamma/GammaThreads.cpp index 520f43d..d37313c 100644 --- a/src/gamma/GammaThreads.cpp +++ b/src/gamma/GammaThreads.cpp @@ -254,8 +254,8 @@ GammaResult gammaIndex2DClassic(const ImageData& refImg2D, const ImageData& eval GammaResult gammaIndex2_5DClassic(const ImageData& refImg3D, const ImageData& evalImg3D, const GammaParameters& gammaParams){ - if(refImg3D.getSize().frames != evalImg3D.getSize().frames){ - throw std::invalid_argument("reference image and evaluated image don't have the same number of frames"); + if(evalImg3D.getSize().frames < refImg3D.getSize().frames){ + throw std::invalid_argument("evaluated image must have at least the same number of frames as the reference image"); } validateGammaParameters(gammaParams); diff --git a/src/gamma/GammaThreadsSimd.cpp b/src/gamma/GammaThreadsSimd.cpp index 2055fcf..b1e42d6 100644 --- a/src/gamma/GammaThreadsSimd.cpp +++ b/src/gamma/GammaThreadsSimd.cpp @@ -353,8 +353,8 @@ GammaResult gammaIndex2DClassic(const ImageData& refImg2D, const ImageData& eval GammaResult gammaIndex2_5DClassic(const ImageData& refImg3D, const ImageData& evalImg3D, const GammaParameters& gammaParams){ - if(refImg3D.getSize().frames != evalImg3D.getSize().frames){ - throw std::invalid_argument("reference image and evaluated image don't have the same number of frames"); + if(evalImg3D.getSize().frames < refImg3D.getSize().frames){ + throw std::invalid_argument("evaluated image must have at least the same number of frames as the reference image"); } validateGammaParameters(gammaParams); From 01d11c02b70668c0515e4f54a47cd8889dca3a43 Mon Sep 17 00:00:00 2001 From: michal367 Date: Thu, 12 Oct 2023 09:40:20 +0200 Subject: [PATCH 06/12] performance tests refactor --- tests/performance/gammaPerf.cpp | 89 +++++++++---------- tests/performance/other/gamma_perf_fred.py | 5 +- .../performance/other/gamma_perf_pymedphys.py | 9 +- 3 files changed, 51 insertions(+), 52 deletions(-) diff --git a/tests/performance/gammaPerf.cpp b/tests/performance/gammaPerf.cpp index 112696a..e03802a 100644 --- a/tests/performance/gammaPerf.cpp +++ b/tests/performance/gammaPerf.cpp @@ -33,8 +33,10 @@ const auto GLOBAL = yagit::GammaNormalization::Global; const auto LOCAL = yagit::GammaNormalization::Local; -const float MAX_REF_DOSE = -1; // set automatically max ref dose later -const float DCO = -1; // set automatically 5% of max ref dose later +const float MAX_REF_DOSE = -1; // set automatically max reference dose +const float DCO1 = -1; // set automatically 1% of max ref dose +const float DCO5 = -5; // set automatically 5% of max ref dose +const float DCO10 = -10; // set automatically 10% of max ref dose using GammaFunc = std::function; @@ -47,49 +49,37 @@ struct Config{ }; const std::vector configs = { - {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 10}, - {"classic", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 10}, - {"classic", "2D", {3, 3, LOCAL, 0, 0, 0, 0}, 10}, - {"classic", "2D", {2, 2, LOCAL, 0, 0, 0, 0}, 10}, - {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 1}, - {"classic", "2.5D", {3, 3, LOCAL, 0, 0, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, LOCAL, 0, 0, 0, 0}, 1}, - - {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 10}, - {"classic", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 10}, - {"classic", "2D", {3, 3, LOCAL, 0, DCO, 0, 0}, 10}, - {"classic", "2D", {2, 2, LOCAL, 0, DCO, 0, 0}, 10}, - {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 0, 0}, 1}, - {"classic", "2.5D", {3, 3, LOCAL, 0, DCO, 0, 0}, 1}, - {"classic", "2.5D", {2, 2, LOCAL, 0, DCO, 0, 0}, 1}, - - {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 100}, - {"wendling", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 100}, - {"wendling", "2D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 100}, - {"wendling", "2D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 100}, - {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 10}, - {"wendling", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 10}, - {"wendling", "2.5D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 3}, - {"wendling", "2.5D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 3}, - {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 10}, - {"wendling", "3D", {2, 2, GLOBAL, MAX_REF_DOSE, 0, 6, 0.2}, 10}, - {"wendling", "3D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 1}, - {"wendling", "3D", {2, 2, LOCAL, 0, 0, 6, 0.2}, 1}, - - {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 100}, - {"wendling", "2D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 100}, - {"wendling", "2D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 100}, - {"wendling", "2D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 100}, - {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 10}, - {"wendling", "2.5D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 10}, - {"wendling", "2.5D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 3}, - {"wendling", "2.5D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 3}, - {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO, 9, 0.3}, 10}, - {"wendling", "3D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO, 6, 0.2}, 10}, - {"wendling", "3D", {3, 3, LOCAL, 0, DCO, 9, 0.3}, 1}, - {"wendling", "3D", {2, 2, LOCAL, 0, DCO, 6, 0.2}, 1} + // classic method + {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 20}, + {"classic", "2D", {3, 3, LOCAL, 0, 0, 0, 0}, 20}, + {"classic", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 0, 0}, 20}, + {"classic", "2D", {3, 3, LOCAL, 0, DCO5, 0, 0}, 20}, + + {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 0, 0}, 4}, + {"classic", "2.5D", {3, 3, LOCAL, 0, 0, 0, 0}, 8}, + {"classic", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 0, 0}, 10}, + {"classic", "2.5D", {3, 3, LOCAL, 0, DCO5, 0, 0}, 10}, + + // wendling method + {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 1000}, + {"wendling", "2D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 200}, + {"wendling", "2D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 9, 0.3}, 1000}, + {"wendling", "2D", {3, 3, LOCAL, 0, DCO5, 9, 0.3}, 1000}, + + {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 15}, + {"wendling", "2.5D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 6}, + {"wendling", "2.5D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 9, 0.3}, 15}, + {"wendling", "2.5D", {3, 3, LOCAL, 0, DCO5, 9, 0.3}, 6}, + + {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, 0, 9, 0.3}, 10}, + {"wendling", "3D", {3, 3, LOCAL, 0, 0, 9, 0.3}, 3}, + {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 9, 0.3}, 10}, + {"wendling", "3D", {3, 3, LOCAL, 0, DCO5, 9, 0.3}, 10}, + + // {"wendling", "3D", {3, 3, GLOBAL, MAX_REF_DOSE, DCO5, 9, 0.3}, 50}, + // {"wendling", "3D", {2, 2, GLOBAL, MAX_REF_DOSE, DCO5, 6, 0.2}, 20}, + // {"wendling", "3D", {3, 3, LOCAL, 0, DCO5, 9, 0.3}, 50}, + // {"wendling", "3D", {2, 2, LOCAL, 0, DCO5, 6, 0.2}, 20} }; std::string csvHeader(){ @@ -195,9 +185,16 @@ int main(int argc, char** argv){ if(gammaParams.globalNormDose == MAX_REF_DOSE){ gammaParams.globalNormDose = (dims == "2D" ? refMaxDose2D : refMaxDose3D); } - if(gammaParams.doseCutoff == DCO){ + + if(gammaParams.doseCutoff == DCO1){ + gammaParams.doseCutoff = 0.01 * (dims == "2D" ? refMaxDose2D : refMaxDose3D); + } + else if(gammaParams.doseCutoff == DCO5){ gammaParams.doseCutoff = 0.05 * (dims == "2D" ? refMaxDose2D : refMaxDose3D); } + else if(gammaParams.doseCutoff == DCO10){ + gammaParams.doseCutoff = 0.10 * (dims == "2D" ? refMaxDose2D : refMaxDose3D); + } csvFile << configToCsv({method, dims, gammaParams, nrOfTests}) << ","; diff --git a/tests/performance/other/gamma_perf_fred.py b/tests/performance/other/gamma_perf_fred.py index 4ecf983..8b3ddfb 100644 --- a/tests/performance/other/gamma_perf_fred.py +++ b/tests/performance/other/gamma_perf_fred.py @@ -150,9 +150,9 @@ def gamma_result_to_csv(gamma_res): # FRED requires DCO to be greater than 0 # config: dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests configs = [ - (3, 3, GLOBAL, DCO, 9, STEP_SIZE, 10), + (3, 3, GLOBAL, DCO, 9, STEP_SIZE, 20), (2, 2, GLOBAL, DCO, 6, STEP_SIZE, 10), - (3, 3, LOCAL, DCO, 9, STEP_SIZE, 10), + (3, 3, LOCAL, DCO, 9, STEP_SIZE, 20), (2, 2, LOCAL, DCO, 6, STEP_SIZE, 10) ] @@ -214,6 +214,7 @@ def gamma_result_to_csv(gamma_res): time_stats_to_csv(times_ms) +\ gamma_result_to_csv(gamma_res) csv_writer.writerow(record) + csv_file.flush() # if i == 0: # save_gamma_to_file(gamma_res, MHA_FILENAME) diff --git a/tests/performance/other/gamma_perf_pymedphys.py b/tests/performance/other/gamma_perf_pymedphys.py index 94a2310..ce442a4 100644 --- a/tests/performance/other/gamma_perf_pymedphys.py +++ b/tests/performance/other/gamma_perf_pymedphys.py @@ -122,10 +122,10 @@ def gamma_result_to_csv(gamma_res): # PyMedPhys has only Wendling 3D # config: dd, dta, norm, dco, max_search_dist, step_size, nr_of_tests configs = [ - (3, 3, GLOBAL, 0, 9, STEP_SIZE, 3), - (2, 2, GLOBAL, 0, 6, STEP_SIZE, 3), - (3, 3, LOCAL, 0, 9, STEP_SIZE, 3), - (2, 2, LOCAL, 0, 6, STEP_SIZE, 3), + # (3, 3, GLOBAL, 0, 9, STEP_SIZE, 3), + # (2, 2, GLOBAL, 0, 6, STEP_SIZE, 3), + # (3, 3, LOCAL, 0, 9, STEP_SIZE, 3), + # (2, 2, LOCAL, 0, 6, STEP_SIZE, 3), (3, 3, GLOBAL, DCO, 9, STEP_SIZE, 3), (2, 2, GLOBAL, DCO, 6, STEP_SIZE, 3), @@ -195,6 +195,7 @@ def gamma_result_to_csv(gamma_res): time_stats_to_csv(times_ms) +\ gamma_result_to_csv(gamma_res) csv_writer.writerow(record) + csv_file.flush() # if i == 0: # save_gamma_to_file(gamma_res, img_ref, MHA_FILENAME) From f24e660d452cae7b182d89437f774ebc6f208083 Mon Sep 17 00:00:00 2001 From: michal367 Date: Fri, 13 Oct 2023 15:43:07 +0200 Subject: [PATCH 07/12] improve plot script --- .gitignore | 5 + tests/performance/plot_gamma_times.py | 192 ++++++++++++++++---------- 2 files changed, 127 insertions(+), 70 deletions(-) diff --git a/.gitignore b/.gitignore index 85313bc..7e062cb 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,8 @@ docs/build # tests data files !tests/unit/data/*.dcm !tests/unit/data/*.mha + +# plot output files +*plot*.png +*plot*.svg +*plot*.pdf diff --git a/tests/performance/plot_gamma_times.py b/tests/performance/plot_gamma_times.py index 40451b3..a4fbf8f 100644 --- a/tests/performance/plot_gamma_times.py +++ b/tests/performance/plot_gamma_times.py @@ -25,55 +25,45 @@ import pandas as pd import matplotlib.pyplot as plt -plt.rcParams["figure.figsize"] = (10, 5) -# plt.rcParams["figure.figsize"] = (7, 5) - -def human_readable_time(time_ms): - MSEC_IN_SEC = 1000 - SEC_IN_MIN = 60 - MIN_IN_HOUR = 60 - - s, ms = divmod(time_ms, MSEC_IN_SEC) - m, s = divmod(s, SEC_IN_MIN) - h, m = divmod(m, MIN_IN_HOUR) - - result = str(int(ms)) + "ms" - if h > 0 or m > 0 or s > 0: - result = str(int(s)) + "s " + result - if h > 0 or m > 0: - result = str(int(m)) + "min " + result - if h > 0: - result = str(int(h)) + "h " + result - - return result - # ============================================== # CONFIG -title = "Comparison of gamma index implementations" +# plot_size = (11, 5) +plot_size = (10, 5) +# plot_size = (8, 5) +# plot_size = (7, 5) +plt.rcParams["figure.figsize"] = plot_size + +title = None files = [ - "gammaTimes_seq.csv", - "gammaTimes_thr.csv", - "gammaTimes_simd.csv", - "gammaTimes_thr_simd.csv" + "output_seq.csv", + "output_thr.csv", + "output_simd.csv", + "output_thr_simd.csv" ] bars_labels = [ "Sequential", "Multithreaded", - "SIMD (AVX2)", - "Multithreaded \nand SIMD (AVX2)" + "SIMD", + "Multithreaded \nand SIMD" ] mode = "TIMES" # mode = "SPEEDUP" +add_bar_text = False +rotate_xtick_labels = False + # filters method = "" dims = "" +dd = None +dta = None norm = "" -min_dco = 0 +min_dco = None +max_dco = None # save to file save_to_png = False @@ -81,20 +71,35 @@ def human_readable_time(time_ms): save_to_pdf = False +# ============================================== + # check if config is correct if len(files) == 0: print("ERROR: no input files were provided") exit(1) if len(bars_labels) != len(files) and len(bars_labels) != 0: - print("ERROR: number of bars labels must be 0 or equal to number of files") + print("ERROR: number of bars labels must be equal to number of files or 0") + exit(1) + +if mode == "SPEEDUP" and len(files) < 2: + print("ERROR: SPEEDUP mode requires at least 2 input files") exit(1) # print filters -if method == "" and dims == "" and norm == "" and min_dco == 0: +filters = [] +if method != "": filters.append(f"method={method}") +if dims != "": filters.append(f"dims={dims}") +if dd is not None: filters.append(f"dd={dd}") +if dta is not None: filters.append(f"dta={dta}") +if norm != "": filters.append(f"norm={norm}") +if min_dco is not None: filters.append(f"min_dco={min_dco}") +if max_dco is not None: filters.append(f"max_dco={max_dco}") + +if len(filters) == 0: print("filters: none") else: - print(f"filters: method={method}, dims={dims}, norm={norm}, min_dco={min_dco}") + print(f"filters: {', '.join(filters)}") # ============================================== @@ -103,18 +108,20 @@ def human_readable_time(time_ms): dfs = [pd.read_csv(file) for file in files] for i in range(len(dfs)): - if method != "": - dfs[i] = dfs[i][dfs[i]["method"] == method] - if dims != "": - dfs[i] = dfs[i][dfs[i]["dims"] == dims] - if norm != "": - dfs[i] = dfs[i][dfs[i]["norm"] == norm] - if min_dco > 0: - dfs[i] = dfs[i][dfs[i]["dco"] >= min_dco] - + if method != "": dfs[i] = dfs[i][dfs[i]["method"] == method] + if dims != "": dfs[i] = dfs[i][dfs[i]["dims"] == dims] + if dd is not None: dfs[i] = dfs[i][dfs[i]["dd[%]"] == dd] + if dta is not None: dfs[i] = dfs[i][dfs[i]["dta[mm]"] == dta] + if norm != "": dfs[i] = dfs[i][dfs[i]["norm"] == norm] + if min_dco is not None: dfs[i] = dfs[i][dfs[i]["dco"] >= min_dco] + if max_dco is not None: dfs[i] = dfs[i][dfs[i]["dco"] <= max_dco] + dfs[i] = dfs[i].reset_index(drop=True) for df in dfs: + if df.shape[0] == 0: + print("ERROR: at least one dataframe is empty") + exit(1) if df.shape != dfs[0].shape: print("ERROR: dataframes have different shapes") exit(1) @@ -130,6 +137,36 @@ def human_readable_time(time_ms): times = [df["meanTime[ms]"] for df in dfs] speedups = [times[0] / time for time in times[1:]] +xtick_labels = [ + f"{method.capitalize()} {dims}\n{dd}%{norm}/{dta}mm{chr(10) + 'DCO' if dco > 0 else ''}" + for method, dims, dd, norm, dta, dco in zip( + dfs[0]["method"], dfs[0]["dims"], + dfs[0]["dd[%]"], dfs[0]["norm"], dfs[0]["dta[mm]"], dfs[0]["dco"] + ) +] + + +# ============================================== + +def human_readable_time(time_ms): + MSEC_IN_SEC = 1000 + SEC_IN_MIN = 60 + MIN_IN_HOUR = 60 + + s, ms = divmod(time_ms, MSEC_IN_SEC) + m, s = divmod(s, SEC_IN_MIN) + h, m = divmod(m, MIN_IN_HOUR) + + result = str(int(ms)) + "ms" + if h > 0 or m > 0 or s > 0: + result = str(int(s)) + "s " + result + if h > 0 or m > 0: + result = str(int(m)) + "min " + result + if h > 0: + result = str(int(h)) + "h " + result + + return result + # summed_times = [(df["nrOfTests"] * df["meanTime[ms]"]).sum() for df in dfs] # for i, stime in enumerate(summed_times): # print(f"summed time for file {i+1}: {stime:.3f} ms ({human_readable_time(stime)})") @@ -139,24 +176,14 @@ def human_readable_time(time_ms): print(f"summed mean time for file {i+1}: {smtime:.3f} ms ({human_readable_time(smtime)})") -x_labels = [ - f'{method} {dims}\n{dd}%{norm}/{dta}mm{" DCO" if dco > 0 else ""}' - for method, dims, dd, norm, dta, dco in zip( - dfs[0]["method"], dfs[0]["dims"], - dfs[0]["dd[%]"], dfs[0]["norm"], dfs[0]["dta[mm]"], dfs[0]["dco"] - ) -] - - # ============================================== # PLOT -def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): +def plot_bars(ax, values, bars_labels, xtick_labels, add_bar_text=False, rotate_xtick_labels=False, color_cycle=None): group_count = len(values[0]) bars_per_group_count = len(values) indices = range(group_count) - few_groups = (group_count <= 4) width = min(0.2, 0.84 / bars_per_group_count) half_width = width / 2 @@ -178,31 +205,55 @@ def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): all_bars.append(bar) # add text with value above bars - if few_groups: + if add_bar_text: for bars_container in all_bars: for bar in bars_container: pos = bar.get_x() + bar.get_width() / 2.0 height = bar.get_height() - plt.text(pos, height, f'{height:.2f}', ha='center', va='bottom') + ax.text(pos, height, f"{height:.1f}", ha="center", va="bottom") # add more space at the top - ax.margins(y = 0.1) + ax.margins(y=0.1) # set x labels # ax.set_xlim(0, group_count) ax.set_xlim(-offset, group_count + offset) ax.set_xticks([index + offset + (bars_per_group_count - 1) * width / 2 + half_width for index in indices]) - if few_groups: - ax.set_xticklabels(x_labels) + if not rotate_xtick_labels: + ax.set_xticklabels(xtick_labels) else: - ax.set_xticklabels(x_labels, rotation=90) + ax.set_xticklabels(xtick_labels, rotation=90) # double the y-tick density # mul = 2 - # current_yticks = plt.yticks()[0] + # current_yticks = ax.get_yticks() # new_diff = (current_yticks[1] - current_yticks[0]) / mul # new_yticks = [current_yticks[0] + i * new_diff for i in range(len(current_yticks) * mul - 1)] - # plt.yticks(new_yticks) + # ax.set_yticks(new_yticks) + + +def add_legend(pos="default", rotated_xtick_labels=False): + def get_lines_count(str): + if str == "": + return 0 + else: + return str.count("\n") + 1 + + if pos == "default": + ax.legend() + elif pos == "top": + x, y = (0.5, 1.03) + ax.legend(loc="lower center", bbox_to_anchor=(x, y), ncol=6) + elif pos == "bottom": + if not rotated_xtick_labels: + xtl_lines_count = max([get_lines_count(label.get_text()) for label in ax.get_xticklabels()]) + x, y = (0.5, -0.04 * (xtl_lines_count + 1)) + else: + x, y = (0.5, -0.28) + ax.legend(loc="upper center", bbox_to_anchor=(x, y), ncol=6) + elif pos == "right": + x, y = (1.01, 0.5) + ax.legend(loc="center left", bbox_to_anchor=(x, y), ncol=1) color_cycle = ["#1f77b4", "#ff7f0e", "#2ca02c", "#9467bd", "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"] @@ -211,17 +262,20 @@ def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): if mode == "TIMES": ax.set_ylabel("Time [ms]") - plot_bars(ax, times, bars_labels, x_labels, color_cycle) + plot_bars(ax, times, bars_labels, xtick_labels, + add_bar_text, rotate_xtick_labels, color_cycle) elif mode == "SPEEDUP": ax.set_ylabel("Speedup") - plot_bars(ax, speedups, bars_labels[1:], x_labels, color_cycle[1:]) + plot_bars(ax, speedups, bars_labels[1:], xtick_labels, + add_bar_text, rotate_xtick_labels, color_cycle[1:]) + +if title is not None: + ax.set_title(title) -ax.set_title(title) -ax.grid(axis="y", linestyle=(0, (5, 5))) if len(bars_labels) > 0: - ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.12), ncol=5) - # ax.legend(loc='center left', bbox_to_anchor=(1, 0.5)) - # ax.legend() + add_legend("bottom", rotate_xtick_labels) + +ax.grid(axis="y", linestyle=(0, (5, 5))) plt.tight_layout() @@ -231,10 +285,8 @@ def plot_bars(ax, values, bars_labels, x_labels, color_cycle = None): if save_to_png: plt.savefig("plot.png", format="png") - if save_to_svg: plt.savefig("plot.svg", format="svg") - if save_to_pdf: plt.savefig("plot.pdf", format="pdf") From 6b0cfac48b5226378df0151495a3a69500c560ec Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 16 Oct 2023 21:58:20 +0200 Subject: [PATCH 08/12] improve tolerance in interpolation --- src/Interpolation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Interpolation.cpp b/src/Interpolation.cpp index 6a12c55..866640a 100644 --- a/src/Interpolation.cpp +++ b/src/Interpolation.cpp @@ -27,13 +27,13 @@ namespace yagit::Interpolation{ namespace{ // absolute tolerance that is useful for floating-point computations // currently it is absolute tolerance, but it can be changed to relative tolerance if it turns out to work better -constexpr double Tolerance{5e-6}; +constexpr double Tolerance{5e-7}; float calcNewOffset(float oldOffset, float gridOffset, float spacing){ // calculate closest point to oldOffset that is greater than or equal to oldOffset and also lies on grid. // Tolerance here is useful in cases where value passed to ceil function is e.g. 12.0000001 due to computer // floating-point errors and will be evaluated as 13 which is actually an incorrect result (correct is 12) - int n = std::ceil((oldOffset - gridOffset) / static_cast(spacing) - Tolerance); + int n = std::ceil((oldOffset - gridOffset - Tolerance) / static_cast(spacing)); float newOffset = gridOffset + n * spacing; return newOffset; } @@ -43,7 +43,7 @@ constexpr uint32_t calcNewSize(uint32_t oldSize, float oldSpacing, float offsetR // Tolerance here is useful in cases where value passed to truncation function (cast to integer) // is e.g. 15.9999999 due to computer floating-point errors and will be evaluated as 15 which is actually // an incorrect result (correct is 16) - return static_cast((oldSpacing * (oldSize - 1) - offsetRel) / newSpacing + 1 + Tolerance); + return static_cast((oldSpacing * (oldSize - 1) - offsetRel + Tolerance) / newSpacing + 1); } } From 362de576afa8d918b3e0b3086b463890cc0f020c Mon Sep 17 00:00:00 2001 From: michal367 Date: Sun, 22 Oct 2023 21:22:39 +0200 Subject: [PATCH 09/12] remove forwarding --- src/gamma/GammaThreadsUtils.hpp | 10 +++++----- tests/performance/interpPerf.cpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gamma/GammaThreadsUtils.hpp b/src/gamma/GammaThreadsUtils.hpp index 02a9b0f..bfcda1d 100644 --- a/src/gamma/GammaThreadsUtils.hpp +++ b/src/gamma/GammaThreadsUtils.hpp @@ -98,7 +98,7 @@ std::vector multithreadedGammaIndex(const ImageData& refImg, const GammaP threads.reserve(nrOfThreads); for(const auto& range : generateCalcRanges(nrOfThreads, nrOfCalcs, gammaVals)){ - threads.emplace_back(std::forward(func), std::forward(args)..., + threads.emplace_back(func, std::cref(args)..., range.first, range.second, std::ref(gammaVals)); } for(auto& thread : threads){ @@ -106,7 +106,7 @@ std::vector multithreadedGammaIndex(const ImageData& refImg, const GammaP } } else{ // single-threaded - func(std::forward(args)..., 0, refImg.size(), gammaVals); + func(args..., 0, refImg.size(), gammaVals); } return gammaVals; @@ -131,7 +131,7 @@ void loadBalancingMultithreadedGammaIndexInternal(Function&& func, Args&&... arg while(true){ if(auto task = tasks.safePop(); task.has_value()){ const auto [start, end] = *task; - func(std::forward(args)..., start, end, gammaVals); + func(args..., start, end, gammaVals); } else{ break; @@ -155,14 +155,14 @@ std::vector loadBalancingMultithreadedGammaIndex(size_t refImgSize, Funct for(uint32_t i = 0; i < nrOfThreads; i++){ threads.emplace_back(loadBalancingMultithreadedGammaIndexInternal, - std::cref(func), std::forward(args)..., std::ref(gammaVals), std::ref(tasks)); + std::cref(func), std::cref(args)..., std::ref(gammaVals), std::ref(tasks)); } for(auto& thread : threads){ thread.join(); } } else{ // single-threaded - func(std::forward(args)..., 0, refImgSize, gammaVals); + func(args..., 0, refImgSize, gammaVals); } return gammaVals; diff --git a/tests/performance/interpPerf.cpp b/tests/performance/interpPerf.cpp index 7a9e836..a539a5f 100644 --- a/tests/performance/interpPerf.cpp +++ b/tests/performance/interpPerf.cpp @@ -59,7 +59,7 @@ void measureInterp(uint32_t nrOfTests, Function&& func, Args&&... args){ for(uint32_t i = 0; i < nrOfTests; i++){ auto begin = std::chrono::steady_clock::now(); - result = func(std::forward(args)...); + result = func(args...); auto end = std::chrono::steady_clock::now(); auto timeMs = std::chrono::duration_cast(end - begin).count() / 1000.0; @@ -81,7 +81,7 @@ void measureInterpAtPoint(uint32_t nrOfTests, Function&& func, Args&&... args){ // in each test do it 100000 times, because 1 time takes too little time for(uint32_t j = 0; j < 100000; j++){ - result = func(std::forward(args)...); + result = func(args...); } auto end = std::chrono::steady_clock::now(); From b79cc10a136d6559f2cc235c2345d68434c4c34d Mon Sep 17 00:00:00 2001 From: michal367 Date: Sun, 22 Oct 2023 21:25:46 +0200 Subject: [PATCH 10/12] fix cast type in passingRate --- examples/gamma3D.cpp | 2 +- src/GammaResult.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/gamma3D.cpp b/examples/gamma3D.cpp index 62d6996..e17f637 100644 --- a/examples/gamma3D.cpp +++ b/examples/gamma3D.cpp @@ -39,7 +39,7 @@ std::string floatToString(float d){ std::ostringstream oss; oss << d; - return oss.str(); + return oss.str(); } std::string gammaParametersToString(const yagit::GammaParameters& gammaParams){ diff --git a/src/GammaResult.cpp b/src/GammaResult.cpp index 187320f..95f933f 100644 --- a/src/GammaResult.cpp +++ b/src/GammaResult.cpp @@ -25,7 +25,7 @@ namespace yagit{ double GammaResult::passingRate() const{ - return static_cast(std::count_if(m_data.begin(), m_data.end(), [](value_type el) { + return static_cast(std::count_if(m_data.begin(), m_data.end(), [](value_type el) { return !std::isnan(el) && el <= 1; })) / nansize(); } From 3ab2efcf1c6949744e77ea415f03916a1a6c6e7f Mon Sep 17 00:00:00 2001 From: michal367 Date: Mon, 23 Oct 2023 20:15:31 +0200 Subject: [PATCH 11/12] add setup.sh script --- .github/workflows/test.yml | 14 ++-- conanfile.txt | 2 +- setup.bat | 6 +- setup.sh | 160 +++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+), 11 deletions(-) create mode 100755 setup.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7a322af..37eb6a3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,21 +43,21 @@ jobs: sudo apt install libgdcm-dev # install from source, because apt has old version - - name: Install GoogleTest and GoogleMock + - name: Install xsimd + if: ${{ endsWith(matrix.gamma-version, 'SIMD') }} run: | - git clone https://github.com/google/googletest.git -b v1.13.0 - cd googletest + git clone https://github.com/xtensor-stack/xsimd.git -b 11.1.0 + cd xsimd mkdir build && cd build cmake .. make sudo make install # install from source, because apt has old version - - name: Install xsimd - if: ${{ endsWith(matrix.gamma-version, 'SIMD') }} + - name: Install GoogleTest and GoogleMock run: | - git clone https://github.com/xtensor-stack/xsimd.git -b 11.1.0 - cd xsimd + git clone https://github.com/google/googletest.git -b v1.13.0 + cd googletest mkdir build && cd build cmake .. make diff --git a/conanfile.txt b/conanfile.txt index 15f5762..9b4964b 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,7 +1,7 @@ [requires] gdcm/3.0.20 -gtest/1.13.0 xsimd/11.1.0 +gtest/1.13.0 [generators] CMakeDeps diff --git a/setup.bat b/setup.bat index 64e2a7a..78b7c98 100644 --- a/setup.bat +++ b/setup.bat @@ -30,7 +30,7 @@ if not exist build\CMakeCache.txt ( echo CONFIGURING CMAKE FIRST TIME... mkdir build cd build - conan install .. + conan install .. --output-folder . --build missing cmake .. -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake cd .. ) @@ -95,10 +95,10 @@ if %INSTALL% == ON ( echo: echo INSTALLING... IF "%INSTALL_DIR%" NEQ "" ( - echo INSTALLING TO %INSTALL_DIR% + echo INSTALLING IN %INSTALL_DIR% cmake --install build --prefix %INSTALL_DIR% ) else ( - echo INSTALLING TO SYSTEM DIRECTORY + echo INSTALLING IN SYSTEM DIRECTORY echo MAKE SURE YOU RUN THIS AS ADMINISTRATOR cmake --install build ) diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..85f97f7 --- /dev/null +++ b/setup.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +BUILD_TYPE=Release +BUILD_SHARED_LIBS=OFF + +# INSTALL_DEPENDENCIES=OFF +INSTALL_DEPENDENCIES=LOCAL +# INSTALL_DEPENDENCIES=GLOBAL # requires root privileges +# INSTALL_DEPENDENCIES=CONAN + +# GAMMA_VERSION=SEQUENTIAL +GAMMA_VERSION=THREADS +# GAMMA_VERSION=SIMD +# GAMMA_VERSION=THREADS_SIMD + +SIMD_EXTENSION=DEFAULT +# SIMD_EXTENSION=AVX2 + +ENABLE_FMA=OFF + +BUILD_EXAMPLES=ON +BUILD_TESTING=OFF +BUILD_PERFORMANCE_TESTING=OFF + +REF_IMG=img_reference.dcm +EVAL_IMG=img_evaluated.dcm + +INSTALL=OFF +INSTALL_DIR=./yagit + + +# ============================================================ +mkdir -p build +cd build + +# ============================================================ + +install () { + # $1 - path to library to install + + cd $1 + mkdir -p build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release -j + + if [ $INSTALL_DEPENDENCIES == LOCAL ]; then + cmake --install . --prefix ./installed + elif [ $INSTALL_DEPENDENCIES == GLOBAL ]; then + sudo cmake --install . + fi + + cd ../.. +} + +DEPENDENCIES_PATHS="" +TOOLCHAIN_FILE="" + +if [[ $INSTALL_DEPENDENCIES == LOCAL || $INSTALL_DEPENDENCIES == GLOBAL ]]; then + echo "INSTALLING DEPENDENCIES..." + mkdir -p deps && cd deps + + # GDCM + if [ ! -d GDCM ]; then + git clone https://github.com/malaterre/GDCM.git -b v3.0.22 + install GDCM + fi + + # xsimd + if [ ! -d xsimd ]; then + git clone https://github.com/xtensor-stack/xsimd.git -b 11.1.0 + install xsimd + fi + + # GoogleTest + if [ ! -d googletest ]; then + git clone https://github.com/google/googletest.git -b v1.13.0 + install googletest + fi + + if [ $INSTALL_DEPENDENCIES == LOCAL ]; then + GDCM_PATH="$(pwd)/GDCM/build/installed" + XSIMD_PATH="$(pwd)/xsimd/build/installed" + GTEST_PATH="$(pwd)/googletest/build/installed" + DEPENDENCIES_PATHS="$GDCM_PATH;$XSIMD_PATH;$GTEST_PATH" + fi + + cd .. +elif [ $INSTALL_DEPENDENCIES == CONAN ]; then + conan install .. --output-folder . --build missing + TOOLCHAIN_FILE=conan_toolchain.cmake +fi + + +# ============================================================ +echo "" +echo "CONFIGURING CMAKE..." +cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DBUILD_SHARED_LIBS=$BUILD_SHARED_LIBS \ + -DGAMMA_VERSION=$GAMMA_VERSION \ + -DSIMD_EXTENSION=$SIMD_EXTENSION \ + -DENABLE_FMA=$ENABLE_FMA \ + -DBUILD_EXAMPLES=$BUILD_EXAMPLES \ + -DBUILD_TESTING=$BUILD_TESTING \ + -DBUILD_PERFORMANCE_TESTING=$BUILD_PERFORMANCE_TESTING \ + -DCMAKE_PREFIX_PATH="$DEPENDENCIES_PATHS" \ + -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" + + +# ============================================================ +echo "" +echo "COMPILING..." +cmake --build . --config $BUILD_TYPE -j +COMPILE_RESULT=$? +cd .. + +if [ $COMPILE_RESULT -ne 0 ]; then + exit $COMPILE_RESULT +fi + + +# ============================================================ +if [ $BUILD_EXAMPLES == ON ]; then + echo "" + echo "RUNNING EXAMPLES..." + ./build/examples/gamma2DInterp "$REF_IMG" "$EVAL_IMG" + echo "" + ./build/examples/gamma25D "$REF_IMG" "$EVAL_IMG" + echo "" + ./build/examples/gamma3D "$REF_IMG" "$EVAL_IMG" + echo "" + ./build/examples/gammaImage +fi + +if [ $BUILD_TESTING == ON ]; then + echo "" + echo "RUNNING UNIT TESTS..." + ctest -C $BUILD_TYPE --test-dir build --output-on-failure +fi + +if [ $BUILD_PERFORMANCE_TESTING == ON ]; then + echo "" + echo "RUNNING PERFORMANCE TEST..." + ./build/tests/performance/gammaPerf "$REF_IMG" "$EVAL_IMG" gammaTimes.csv + echo "" + ./build/tests/performance/interpPerf "$EVAL_IMG" +fi + + +# ============================================================ +if [ $INSTALL == ON ]; then + echo "" + echo "INSTALLING..." + if [ -n "$INSTALL_DIR" ]; then + echo "INSTALLING IN $INSTALL_DIR" + cmake --install build --prefix "$INSTALL_DIR" + else + echo "INSTALLING IN SYSTEM DIRECTORY" + sudo cmake --install build + fi +fi From 3cb7bed7eba92a5bb058e11ad66a55229c7b1f0b Mon Sep 17 00:00:00 2001 From: michal367 Date: Tue, 24 Oct 2023 15:10:12 +0200 Subject: [PATCH 12/12] add github actions job running setup.sh --- .github/workflows/test.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 37eb6a3..502fa95 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -86,6 +86,28 @@ jobs: run: | cmake --install build --prefix ./yagit_install_dir + run-setup-linux: + if: > + github.repository == 'DataMedSci/yagit' && + !contains(github.event.head_commit.message, '[ci skip]') && + !contains(github.event.head_commit.message, '[skip ci]') + name: Run setup.sh on Linux + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Check versions + run: | + gcc --version + g++ --version + cmake --version + + - name: Run setup.sh script + run: | + ./setup.sh + build-windows: if: > github.repository == 'DataMedSci/yagit' &&