Distance-based treatment effects#

Based on the figures from Holodinsky et al. 2017 - “Drip and Ship Versus Direct to Comprehensive Stroke Center”

Notebook admin#

# Import packages
import numpy as np 
import matplotlib.pyplot as plt
import pandas as pd
# Set up MatPlotLib
%matplotlib inline
# Keep notebook cleaner once finalised
import warnings
warnings.filterwarnings('ignore')

Assumptions#

Fixed times for additional delays:

# All fixed times have units of minutes
fixed_times = dict(
    onset_to_ambulance_arrival = 60,
    ivt_arrival_to_treatment = 30,
    transfer_additional_delay = 60,
    travel_ivt_to_mt = 50,
    mt_arrival_to_treatment = 90,
    )
pd.DataFrame(fixed_times.values(), index=fixed_times.keys(), 
             columns=['Fixed time (minutes)'])
Fixed time (minutes)
onset_to_ambulance_arrival 60
ivt_arrival_to_treatment 30
transfer_additional_delay 60
travel_ivt_to_mt 50
mt_arrival_to_treatment 90

Patient population:

patient_props = dict(
    lvo = 0.35,
    nlvo = 1.0-0.35, # 1-LVO
    lvo_mt_also_receiving_ivt = 0.85,
    lvo_treated_ivt_only = 0.0,
    lvo_treated_ivt_mt = 0.286,    # 0.286 gives 10% final MT if 35%LVO
    nlvo_treated_ivt_only = 0.155,    # 0.155 gives final 20% IVT
    )

treated_population = (
    patient_props['nlvo'] * patient_props['nlvo_treated_ivt_only'] +
    patient_props['lvo'] * patient_props['lvo_treated_ivt_mt'] +
    patient_props['lvo'] * patient_props['lvo_treated_ivt_only']
    )

patient_props['treated_population'] = treated_population
df_patients = pd.DataFrame(patient_props.values(), 
    index=patient_props.keys(), columns=['Proportion of patient population'])
df_patients['Comment'] = [
    'Proportion of LVO',
    'Proportion of nLVO', 
    'Proportion LVO MT also receiving IVT',
    'Proportion LVO admissions treated with IVT only',
    'Proportion LVO admissions treated with MT',
    'Proportion nLVO admissions treated with IVT',
    'Proportion all admissions treated'
    ]

df_patients
Proportion of patient population Comment
lvo 0.35000 Proportion of LVO
nlvo 0.65000 Proportion of nLVO
lvo_mt_also_receiving_ivt 0.85000 Proportion LVO MT also receiving IVT
lvo_treated_ivt_only 0.00000 Proportion LVO admissions treated with IVT only
lvo_treated_ivt_mt 0.28600 Proportion LVO admissions treated with MT
nlvo_treated_ivt_only 0.15500 Proportion nLVO admissions treated with IVT
treated_population 0.20085 Proportion all admissions treated

Define travel time grids#

For each point on a grid, find the travel time to a given coordinate (one of the treatment centres).

The treatment centres are located at the following coordinates:

  • IVT centre: (0, 0)

  • IVT/MT centre: (0, \(-t_{\mathrm{travel}}^{\mathrm{IVT~to~MT}}\))

ivt_coords = [0, 0]
mt_coords  = [0, -fixed_times['travel_ivt_to_mt']]

Change these parameters:

# Only calculate travel times up to this x or y displacement:
time_travel_max = 80

# Change how granular the grid is. 
grid_step = 1 # minutes

Define a helper function to build the time grid:

def make_time_grid(xy_max, step, x_offset=0, y_offset=0):
    # Times for each row....
    x_times = np.arange(-xy_max, xy_max + step, step) - x_offset
    # ... and each column.
    y_times = np.arange(-xy_max, xy_max + step, step) - y_offset
    # The offsets shift the position of (0,0) from the grid centre 
    # to (x_offset, y_offset). Distances will be calculated from the
    # latter point. 

    # Mesh to create new grids by stacking rows (xx) and columns (yy):
    xx, yy = np.meshgrid(x_times, y_times)

    # Then combine the two temporary grids to find distances: 
    radial_times = np.sqrt(xx**2.0 + yy**2.0)
    return radial_times

Build the grids:

def make_grids_travel_time(time_travel_ivt_to_mt, time_travel_max, ivt_coords, mt_coords, grid_step=1):        
    # Make the grid a bit larger than the max travel time: 
    grid_xy_max = time_travel_max + grid_step*2

    grid_time_travel_directly_to_ivt = make_time_grid(
        grid_xy_max, grid_step, x_offset=ivt_coords[0], 
        y_offset=ivt_coords[1])

    grid_time_travel_directly_to_mt = make_time_grid(
        grid_xy_max, grid_step, x_offset=mt_coords[0], y_offset=mt_coords[1])

    grid_time_travel_directly_diff = (
        grid_time_travel_directly_to_ivt - grid_time_travel_directly_to_mt)

    extent = [-grid_xy_max - grid_step*0.5, 
              +grid_xy_max - grid_step*0.5, 
              -grid_xy_max - grid_step*0.5, 
              +grid_xy_max - grid_step*0.5] 
    
    return (grid_time_travel_directly_to_ivt, 
        grid_time_travel_directly_to_mt,
        grid_time_travel_directly_diff, 
        extent)
(grid_time_travel_directly_to_ivt, 
grid_time_travel_directly_to_mt,
grid_time_travel_directly_diff, 
extent) = make_grids_travel_time(
    fixed_times['travel_ivt_to_mt'], time_travel_max, ivt_coords, mt_coords, grid_step)

Plot travel times#

Grids#

from outcome_utilities.geography_plot import plot_two_grids_and_diff
titles = [
    'Travel time'+'\n'+'directly to IVT centre', 
    'Travel time'+'\n'+'directly to MT centre', 
    'Difference in travel time'+'\n'+'(IVT centre minus MT centre)'
    ]

cbar_labels = [
    'Travel time (mins)', 
    'Difference in travel time (mins)'
    ]

plot_two_grids_and_diff(
    grid_time_travel_directly_to_ivt, 
    grid_time_travel_directly_to_mt, 
    grid_time_travel_directly_diff,
    titles=titles, cbar_labels=cbar_labels, 
    extent=extent, cmaps=['viridis_r', 'RdBu'],
    ivt_coords=ivt_coords, mt_coords=mt_coords
    )
_images/0d93b691f4d31c0ff0521ea5cb5c0634fde61ae8c75619369a15bf0b4a8dda3d.png

On the difference grid, positive values are nearer the IVT/MT centre and negative nearer the IVT-only centre. There is a horizontal line halfway between the two treatment centres that marks where the travel times to the two treatment centres are equal.

Presumably the curves in the difference grid match the ones drawn in the Holodinsky et al. 2017 paper.

Circle plot#

To help define colour limits (vmin and vmax), gather the coordinates within the largest flattened radiating circle:

from outcome_utilities.geography_plot import find_mask_within_flattened_circle
grid_mask = find_mask_within_flattened_circle(
    grid_time_travel_directly_diff, 
    grid_time_travel_directly_to_ivt, 
    time_travel_max)

coords_valid = np.where(grid_mask<1)
vmin_time = np.nanmin(-grid_time_travel_directly_diff[coords_valid])
vmax_time = np.nanmax(-grid_time_travel_directly_diff[coords_valid])

Update plotting style:

time_step_circle = 5
circ_linewidth = 0.5
from outcome_utilities.geography_plot import circle_plot
circle_plot(
    -grid_time_travel_directly_diff, fixed_times['travel_ivt_to_mt'], 
    time_travel_max, time_step_circle, vmin_time, vmax_time, imshow=0, 
    cbar_label='Extra travel time to MT centre', extent=extent,
    ivt_coords=ivt_coords, mt_coords=mt_coords, cmap='Reds',
    n_contour_steps=5
    )
_images/66031a5773f73267e3c5a38bf889bacd775606e0b6aa386037035d2be79a224e.png

Define total time grids (including fixed times for delays)#

pd.DataFrame(fixed_times.values(), index=fixed_times.keys(), 
             columns=['Fixed time (minutes)'])
Fixed time (minutes)
onset_to_ambulance_arrival 60
ivt_arrival_to_treatment 30
transfer_additional_delay 60
travel_ivt_to_mt 50
mt_arrival_to_treatment 90
def make_grids_treatment_time(
    grid_time_travel_directly_to_ivt,
    grid_time_travel_directly_to_mt,
    times_dir):
    """
    Define new treatment time grids. 
    """
    grid_time_ivt_at_ivtcentre = (
        times_dir['onset_to_ambulance_arrival'] + 
        grid_time_travel_directly_to_ivt + 
        times_dir['ivt_arrival_to_treatment']
        )

    grid_time_ivt_at_ivt_then_mt_at_mtcentre = (
        times_dir['onset_to_ambulance_arrival'] + 
        grid_time_travel_directly_to_ivt + 
        times_dir['ivt_arrival_to_treatment'] + 
        times_dir['transfer_additional_delay'] + 
        times_dir['travel_ivt_to_mt'] + 
        times_dir['mt_arrival_to_treatment']
        )

    grid_time_ivt_at_mtcentre = (
        times_dir['onset_to_ambulance_arrival'] + 
        grid_time_travel_directly_to_mt + 
        times_dir['ivt_arrival_to_treatment']
        )

    grid_time_ivt_then_mt_at_mtcentre = (
        times_dir['onset_to_ambulance_arrival'] + 
        grid_time_travel_directly_to_mt + 
        times_dir['ivt_arrival_to_treatment'] + 
        times_dir['mt_arrival_to_treatment']
        )
    return (grid_time_ivt_at_ivtcentre,
            grid_time_ivt_at_ivt_then_mt_at_mtcentre,
            grid_time_ivt_at_mtcentre,
            grid_time_ivt_then_mt_at_mtcentre)

Use this function to define the treatment time grids:

(grid_time_ivt_at_ivtcentre,
grid_time_ivt_at_ivt_then_mt_at_mtcentre,
grid_time_ivt_at_mtcentre,
grid_time_ivt_then_mt_at_mtcentre) = \
make_grids_treatment_time(grid_time_travel_directly_to_ivt,
    grid_time_travel_directly_to_mt, fixed_times)

check the assumption of time between IVT and MT at the same centre above


Outcome model#

Imports for the clinical outcome model:

from outcome_utilities.clinical_outcome import Clinical_outcome

mrs_dists = pd.read_csv(
    './outcome_utilities/mrs_dist_probs_cumsum.csv', index_col='Stroke type')

# Set up outcome model
outcome_model = Clinical_outcome(mrs_dists)
pd.DataFrame(patient_props.values(), index=patient_props.keys(), 
             columns=['Proportion of patient population'])
Proportion of patient population
lvo 0.35000
nlvo 0.65000
lvo_mt_also_receiving_ivt 0.85000
lvo_treated_ivt_only 0.00000
lvo_treated_ivt_mt 0.28600
nlvo_treated_ivt_only 0.15500
treated_population 0.20085

Method to find the added utility (c.f. the matrix notebook):

def find_grid_outcomes(outcome_model, grid_time_ivt, grid_time_mt, 
                       patient_props):
    """
    For all pairs of treatment times, calculate the changes in utility
    and mRS for the patient population. 
    
    Inputs:
    
    Returns:
    
    """
    grid_shape = grid_time_ivt.shape
    utility_grid = np.empty(grid_shape)
    mRS_grid = np.empty(grid_shape)

    for row in range(grid_shape[0]):
        for col in range(int(grid_shape[1]*0.5)+1):
            # ^ weird range is for symmetry later when filling grids.
            # Expect col_opp, the opposite column as reflected in the 
            # x=0 axis, to contain the same values as col:
            col_opp = grid_shape[1]-1-col
            
            time_to_ivt = grid_time_ivt[row, col]
            time_to_mt = grid_time_mt[row, col]
            outcomes = outcome_model.calculate_outcomes(
                time_to_ivt, time_to_mt, patients=1000)

            # Find the change in utility:
            added_utility = find_weighted_change(
                outcomes['lvo_ivt_added_utility'],
                outcomes['lvo_mt_added_utility'],
                outcomes['nlvo_ivt_added_utility'],
                patient_props
                )
            # Add this value to the grid:
            utility_grid[row,col] = added_utility
            utility_grid[row,col_opp] = added_utility

            # Find the change in mRS:
            reduced_mRS = find_weighted_change(
                outcomes['lvo_ivt_mean_shift'], 
                outcomes['lvo_mt_mean_shift'],
                outcomes['nlvo_ivt_mean_shift'], 
                patient_props
                )
            # Add this value to the grid:
            mRS_grid[row,col] = reduced_mRS
            mRS_grid[row,col_opp] = reduced_mRS
            
    # Adjust outcome for just treated population
    utility_grid = utility_grid / patient_props['treated_population']
    mRS_grid = mRS_grid / patient_props['treated_population']
    return utility_grid, mRS_grid
def find_weighted_change(change_lvo_ivt, change_lvo_mt, change_nlvo_ivt, 
                         patient_props):
    """
    Take the total changes for each category and calculate their
    weighted sum, where weights are from the proportions of the 
    patient population.
    
    (originally from matrix notebook)
    
    Inputs:
    
    Returns:
    
    """
    # If LVO-IVT is greater change than LVO-MT then adjust MT for 
    # proportion of patients receiving IVT:
    if change_lvo_ivt > change_lvo_mt:
        diff = change_lvo_ivt - change_lvo_mt
        change_lvo_mt += diff * patient_props['lvo_mt_also_receiving_ivt']

    # Calculate changes multiplied by proportions (cp):
    cp_lvo_mt = (
        change_lvo_mt * 
        patient_props['lvo'] * 
        patient_props['lvo_treated_ivt_mt']
        )
    cp_lvo_ivt = (
        change_lvo_ivt * 
        patient_props['lvo'] * 
        patient_props['lvo_treated_ivt_only']
        )
    cp_nlvo_ivt = (
        change_nlvo_ivt * 
        patient_props['nlvo'] * 
        patient_props['nlvo_treated_ivt_only']
        )
        
    total_change = cp_lvo_mt + cp_lvo_ivt + cp_nlvo_ivt
    return total_change

Grids of changed outcomes#

Case 1: IVT at the IVT centre, then MT at the IVT/MT centre

grid_utility_case1, grid_mRS_case1 = find_grid_outcomes(
    outcome_model,
    grid_time_ivt_at_ivtcentre, 
    grid_time_ivt_at_ivt_then_mt_at_mtcentre, 
    patient_props
    )

Case 2: IVT at the IVT/MT centre, then MT at the IVT/MT centre

grid_utility_case2, grid_mRS_case2 = find_grid_outcomes(
    outcome_model,
    grid_time_ivt_at_mtcentre, 
    grid_time_ivt_then_mt_at_mtcentre, 
    patient_props
    )

Difference between them:

grid_utility_diff = grid_utility_case2 - grid_utility_case1
grid_mRS_diff     = grid_mRS_case2     - grid_mRS_case1

Plot the added utility grids#

titles = [
    'IVT at IVT centre'+'\n'+'then MT at IVT/MT centre',
    'IVT and MT at IVT/MT centre',
    'Difference in added utility'+'\n'+'(IVT centre minus MT centre)'
    ]

cbar_labels = [
    'Mean population added utility', 
    'Change in population added utility by attending MT unit directly'
    ]

vmin = 0.0 
vmax = np.max([grid_utility_case1, grid_utility_case2])

plot_two_grids_and_diff(
    grid_utility_case1, 
    grid_utility_case2, 
    grid_utility_diff,
    vlims = [[vmin,vmax],[]],
    titles=titles, cbar_labels=cbar_labels, 
    extent=extent, cmaps=['gnuplot','gnuplot'],
    ivt_coords=ivt_coords, mt_coords=mt_coords, plot_contours=1
    )
_images/52ff8843810b3c1d27585df84e6dddbb27b39831cf8a06f42bd83b9fd975b58c.png

Getting another horizontal line halfway between the two centres.

Select colour limits for the circle plot:

def round_to_next(vmin, vmax, r=0.005):
    """Round vmin and vmax to nicer values"""
    vmax_r = np.sign(vmax)*np.ceil(np.abs(vmax)/r)*r
    vmin_r = np.floor(vmin/r)*r
    return vmin_r, vmax_r
# Find the actual minumum and maximum values in the grid:
vmin_total = np.nanmin(grid_utility_diff[coords_valid])
vmax_total = np.nanmax(grid_utility_diff[coords_valid])

vmin_util, vmax_util = round_to_next(vmin_total, vmax_total, 0.005)

print('Max. value in grid:', vmax_total)
print('Chosen vmax:       ', vmax_util)
print('Min. value in grid:', vmin_total)
print('Chosen vmin:       ', vmin_util)
Max. value in grid: 0.03292809061488673
Chosen vmax:        0.035
Min. value in grid: 0.007052200647249179
Chosen vmin:        0.005
circle_plot(
    grid_utility_diff, fixed_times['travel_ivt_to_mt'], 
    time_travel_max, time_step_circle, vmin_util, vmax_util, imshow=0, 
    cmap='gnuplot', extent=extent,
    cbar_label=('Change in population added utility\n'+
                'by attending MT unit directly'),
    n_contour_steps = 5, 
    # cbar_format_str='{:3.3f}',
    ivt_coords=ivt_coords, mt_coords=mt_coords
    )
_images/43ad5ca26cfa5f66b083cbb756e4be191d02ee96b5c0b5eefda324ffab665b5c.png

Plot the reduced mRS grids#

titles = [
    'IVT at IVT centre'+'\n'+'then MT at IVT/MT centre',
    'IVT and MT at IVT/MT centre',
    'Difference in reduced mRS'+'\n'+'(IVT centre minus MT centre)'
    ]

cbar_labels = [
    'Mean population reduced mRS',
    'Change in mean population reduced mRS by attending MT unit directly',
    ]

plot_two_grids_and_diff(
    grid_mRS_case1, 
    grid_mRS_case2, 
    grid_mRS_diff,
    vlims = [[],[]],
    titles=titles, cbar_labels=cbar_labels, 
    extent=extent, cmaps=['cividis_r','cividis_r'],
    ivt_coords=ivt_coords, mt_coords=mt_coords
    )
_images/2d4426e388233d31e5098dfeccc80fceb58df3cd88c3d0ce09a60540ab41221b.png

Getting another horizontal line halfway between the two centres.

Select colour limits for the circle plot:

# Find the actual maximum value in the grid:
vmin_total = np.nanmin(grid_mRS_diff[coords_valid])
vmax_total = np.nanmax(grid_mRS_diff[coords_valid])

# Round this up to the nearest nicer thing of our choice:
vmin_mRS, vmax_mRS = round_to_next(vmin_total, vmax_total, 0.005)

print('Max. value in grid:', vmax_total)
print('Chosen vmax:       ', vmax_mRS)
print('Min. value in grid:', vmin_total)
print('Chosen vmin:       ', vmin_mRS)
Max. value in grid: 0.06490938511326869
Chosen vmax:        0.065
Min. value in grid: -0.02601553398058254
Chosen vmin:        -0.03
circle_plot(
    grid_mRS_diff, fixed_times['travel_ivt_to_mt'], 
    time_travel_max, time_step_circle, vmin_mRS, vmax_mRS, imshow=0, 
    cmap='cividis_r', extent=extent,
    cbar_label=('Change in mean population reduced mRS\n'+
               'by attending MT unit directly'),
    # cbar_format_str='{:3.2f}', 
    n_contour_steps=5,
    ivt_coords=ivt_coords, mt_coords=mt_coords
    )
_images/ae2b01519dbb4eaaee29bffe208c143aa6097416f345ef58e2201f1eec235869.png

Combined circle plot#

Condense this notebook into one cell and make a separate figure for each combination of fixed times:

  • Travel time from IVT to MT centre: 30, 60, or 90 mins

  • Onset to ambulance arrival time: 30, 60, or 90 mins

time_list_travel_ivt_to_mt = [30, 60, 90]
time_list_onset_to_ambulance_arrival = [30, 60, 90]

Share these colour limits between all figures:

vmin_time = 0 
vmax_time = np.max(time_list_travel_ivt_to_mt)
vmin_util = -0.014 # -0.045
vmax_util = 0.049 # 0.035
vmin_mRS = -0.090 # -0.250 
vmax_mRS = 0.150 # 0.175

Make new colour maps that are based on a diverging colour map but have the zero (white) off-centre:

from outcome_utilities.geography_plot import make_new_cmap_diverging

cmap_util = make_new_cmap_diverging(
    vmin_util, vmax_util, cmap_base='bwr_r', cmap_name='bwr_util')
cmap_mRS = make_new_cmap_diverging(
    vmin_mRS, vmax_mRS, cmap_base='bwr', cmap_name='bwr_mRS')

# For the time colours, take only the red half of 'bwr':
from matplotlib import colors
colours_time = plt.get_cmap('bwr')(np.linspace(0.5, 1.0, 256))
cmap_time = colors.LinearSegmentedColormap.from_list('bwr_time', colours_time)

Define contour levels for plotting:

from outcome_utilities.geography_plot import make_levels_with_zeroish

level_step_util = 0.007 # 0.005
levels_util = make_levels_with_zeroish(
    level_step_util, vmax_util, zeroish=1e-3, vmin=vmin_util)

level_step_mRS = 0.03 # 0.025
levels_mRS = make_levels_with_zeroish(
    level_step_mRS, vmax_mRS, zeroish=4e-3, vmin=vmin_mRS)

The following big cell calculates the new grids and then makes the plots.

for time_travel_ivt_to_mt in time_list_travel_ivt_to_mt:
    # Recalculate some values: 
    ivt_coords = [0, 0]
    mt_coords  = [0, -time_travel_ivt_to_mt] 
    # Only calculate travel times up to this x or y displacement:
    time_travel_max = time_travel_ivt_to_mt 
    # Change how granular the grid is. 
    grid_step = 1 # minutes

    # Make the travel time grids: 
    (grid_time_travel_directly_to_ivt, 
    grid_time_travel_directly_to_mt,
    grid_time_travel_directly_diff, 
    extent) = \
    make_grids_travel_time(time_travel_ivt_to_mt, time_travel_max, 
                           ivt_coords, mt_coords, grid_step=1)
    
    # Find grid coordinates within the largest circle: 
    grid_mask = find_mask_within_flattened_circle(
        grid_time_travel_directly_diff, 
        grid_time_travel_directly_to_ivt, 
        time_travel_max)
    coords_valid = np.where(grid_mask<1)

    for time_onset_to_ambulance_arrival in (
        time_list_onset_to_ambulance_arrival):

        # Build a new dictionary of fixed times: 
        times_dir = {}
        for key in fixed_times:
            times_dir[key] = fixed_times[key]
        times_dir['onset_to_ambulance_arrival'] = time_onset_to_ambulance_arrival
        times_dir['travel_ivt_to_mt'] = time_travel_ivt_to_mt
        
        # Grids of times to treatment:
        (grid_time_ivt_at_ivtcentre,
        grid_time_ivt_at_ivt_then_mt_at_mtcentre,
        grid_time_ivt_at_mtcentre,
        grid_time_ivt_then_mt_at_mtcentre) = \
        make_grids_treatment_time(grid_time_travel_directly_to_ivt,
            grid_time_travel_directly_to_mt, times_dir)

        # Grids of changed outcomes
        # Case 1: IVT at the IVT centre, then MT at the IVT/MT centre
        grid_utility_case1, grid_mRS_case1 = find_grid_outcomes(
            outcome_model,
            grid_time_ivt_at_ivtcentre, 
            grid_time_ivt_at_ivt_then_mt_at_mtcentre, 
            patient_props
            )
        # Case 2: IVT at the IVT/MT centre, then MT at IVT/MT centre
        grid_utility_case2, grid_mRS_case2 = find_grid_outcomes(
            outcome_model,
            grid_time_ivt_at_mtcentre, 
            grid_time_ivt_then_mt_at_mtcentre, 
            patient_props
            )
        # Difference between them:
        grid_utility_diff = grid_utility_case2 - grid_utility_case1
        grid_mRS_diff     = grid_mRS_case2     - grid_mRS_case1


        print('-'*50)
        print(f'Time of travel from IVT to MT: {time_travel_ivt_to_mt:2d}')
        print(f'Time from onset to ambulance arrival: ',
              f'{time_onset_to_ambulance_arrival:2d}')
        
        print(f'Utility {np.min(grid_utility_diff[coords_valid]):7.4f} '+
              f'{np.max(grid_utility_diff[coords_valid]):7.4f}')
        print(f'mRS {np.min(grid_mRS_diff[coords_valid]):7.4f} '+
              f'{np.max(grid_mRS_diff[coords_valid]):7.4f}')
        

        # Plot setup:  
        fig, axs = plt.subplots(2, 3, figsize=(15,4), 
        gridspec_kw={'wspace':0.2, 'hspace':0.0, 'height_ratios':[20,1]})
        ax_time = axs[0,0]
        ax_time_cbar = axs[1,0]
        ax_util = axs[0,1]
        ax_util_cbar = axs[1,1]
        ax_mRS = axs[0,2]
        ax_mRS_cbar = axs[1,2]

        # Travel time:
        circle_plot(
            -grid_time_travel_directly_diff, 
            time_travel_ivt_to_mt, 
            time_travel_max, 
            time_step_circle, 
            vmin_time, 
            vmax_time, 
            ivt_coords=ivt_coords, 
            mt_coords=mt_coords,             
            extent=extent,
            imshow=0, 
            ax=ax_time, 
            cax=ax_time_cbar, 
            cmap=cmap_time,
            cbar_label='Extra travel time to MT unit\n(minutes)', 
            cbar_orientation='horizontal',
            n_contour_steps=6)

        # Draw legend now:
        fig.legend(loc='upper center', bbox_to_anchor=[0.5,1.0], ncol=2)

        # Utility:
        circle_plot(
            grid_utility_diff, 
            time_travel_ivt_to_mt, 
            time_travel_max, 
            time_step_circle, 
            vmin_util, 
            vmax_util, 
            ivt_coords=ivt_coords, 
            mt_coords=mt_coords,
            extent=extent,
            imshow=0, 
            ax=ax_util, 
            cax=ax_util_cbar, 
            cmap=cmap_util, 
            cbar_label=('Change in population added utility\n'+
                        'by attending MT unit directly'),
            cbar_orientation='horizontal',
            levels=levels_util,
            # cbar_format_str='{:3.3f}',
            cbar_ticks=np.arange(
                vmin_util, vmax_util+level_step_util, level_step_util))

        # mRS:
        circle_plot(
            grid_mRS_diff, 
            time_travel_ivt_to_mt, 
            time_travel_max, 
            time_step_circle, 
            vmin_mRS, 
            vmax_mRS, 
            ivt_coords=ivt_coords, 
            mt_coords=mt_coords,
            extent=extent,
            imshow=0, 
            ax=ax_mRS, 
            cax=ax_mRS_cbar, 
            cmap=cmap_mRS, 
            cbar_label='Change in mean population reduced mRS\n'+
                        'by attending MT unit directly',
            cbar_orientation='horizontal',
            # cbar_format_str='{:3.2f}', 
            levels=levels_mRS, 
            cbar_ticks=np.arange(
                vmin_mRS, vmax_mRS+level_step_mRS, level_step_mRS))
        

        for ax in [ax_time, ax_util, ax_mRS]:
            ax.set_xlabel('')
            ax.set_ylabel('')
            ax.set_xticks([])
            ax.set_yticks([])
            for spine in ['top', 'bottom', 'left', 'right']:
                ax.spines[spine].set_color('None')
            ax.set_ylim(-max(time_list_travel_ivt_to_mt)-10, 
                        +max(time_list_travel_ivt_to_mt)+10)

        # Rotate colourbar tick labels to prevent overlapping:
        for ax in [ax_util_cbar, ax_mRS_cbar]:
            for tick in ax.get_xticklabels():
                tick.set_rotation(30)
                
        filename = ('circle_plots'+
            f'_t-IVT-to-MT={time_travel_ivt_to_mt:2d}'+
            f'_t-onset-to-ambo={time_onset_to_ambulance_arrival:2d}')
        plt.savefig('./images/'+filename+'.jpg', dpi=300, bbox_inches='tight')
        plt.show()
--------------------------------------------------
Time of travel from IVT to MT: 30
Time from onset to ambulance arrival:  30
Utility  0.0120  0.0274
mRS -0.0220  0.0331
_images/139ca93fcc2da8dbe2b8d46f429abeeb9ebe84617e4457af13c73cd46f3c2b6f.png
--------------------------------------------------
Time of travel from IVT to MT: 30
Time from onset to ambulance arrival:  60
Utility  0.0118  0.0272
mRS -0.0216  0.0335
_images/cc24a166d533dcdf7189a128c3ac3c1a09a06e23d85832a4989001453f28bcfa.png
--------------------------------------------------
Time of travel from IVT to MT: 30
Time from onset to ambulance arrival:  90
Utility  0.0114  0.0269
mRS -0.0212  0.0335
_images/995c54a64345489be2bbaeafafa30f4c937583d0cae24629184fe81f24fec441.png
--------------------------------------------------
Time of travel from IVT to MT: 60
Time from onset to ambulance arrival:  30
Utility  0.0058  0.0362
mRS -0.0288  0.0796
_images/5220ce93e7e211b1ab807502910b3de0f10293f98a04823947286a7e81d5488f.png
--------------------------------------------------
Time of travel from IVT to MT: 60
Time from onset to ambulance arrival:  60
Utility  0.0053  0.0357
mRS -0.0282  0.0801
_images/90720c234e8e7c8cf4d2839537f211c93541b0ea627f52c871cf33439d68d685.png
--------------------------------------------------
Time of travel from IVT to MT: 60
Time from onset to ambulance arrival:  90
Utility  0.0045  0.0353
mRS -0.0276  0.0810
_images/92f6f20b732d3722eb28d95a0add293c319de837d07f6893bf44ea9757b8281a.png
--------------------------------------------------
Time of travel from IVT to MT: 90
Time from onset to ambulance arrival:  30
Utility -0.0012  0.0447
mRS -0.0353  0.1278
_images/ee4c2c62b206a96c699fc58bd402c9e408ed7cc3979ce4097fc007aeb5bc1904.png
--------------------------------------------------
Time of travel from IVT to MT: 90
Time from onset to ambulance arrival:  60
Utility -0.0039  0.0440
mRS -0.0487  0.1278
_images/5b279edb42b50ce2694366fb632678bfeb444db15b26485e8930bd2d7351a28e.png
--------------------------------------------------
Time of travel from IVT to MT: 90
Time from onset to ambulance arrival:  90
Utility -0.0090  0.0435
mRS -0.0713  0.1278
_images/90109ef746290192c49391e4294352bcff2084685a3dfb0afce452d4d28651b8.png