diff --git a/Dockerfile b/Dockerfile index fb0ce0b..ec41acd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,15 @@ -FROM rockylinux:8 +FROM rockylinux:9 # install some base necessities -RUN dnf install -y git vim python39 python39-devel && \ - alternatives --set python /usr/bin/python3 +RUN dnf install -y git vim +RUN dnf install -y epel-release +RUN dnf install -y conda +RUN conda create -y -n python3.13t --override-channels -c conda-forge python-freethreading +RUN conda env config vars set PYTHON_GIL=0 -n python3.13t +RUN conda run -n python3.13t --no-capture-output python3 -m pip install git+https://github.com/harvard-nrg/scanbuddy.git +RUN conda run -n python3.13t start.py --help # ENTRYPOINT ["conda", "run", "-n", "python3.13t", "start.py"] +#RUN dnf install -y git vim python3.13 && \ +# alternatives --set python /usr/bin/python3 # create a home directory RUN mkdir -p /home/scanbuddy @@ -13,7 +20,7 @@ ARG AFNI_PREFIX="/sw/apps/afni" ARG AFNI_URI="https://github.com/afni/afni" WORKDIR /tmp RUN dnf install -y epel-release && \ - dnf install -y curl tcsh python2-devel libpng15 motif && \ + dnf install -y --allowerasing curl tcsh libpng15 motif && \ dnf install -y make clang zlib-devel libXt-devel libXext-devel expat-devel motif-devel f2c && \ git clone "${AFNI_URI}" WORKDIR afni/src @@ -29,7 +36,8 @@ ARG D2N_VERSION="1.0.20220720" ARG D2N_URI="https://github.com/rordenlab/dcm2niix/archive/refs/tags/v${D2N_VERSION}.zip" WORKDIR "/tmp" RUN dnf install -y unzip cmake gcc-c++ && \ - dnf --enablerepo=powertools install -y libstdc++-static && \ + dnf config-manager --set-enabled crb && \ + dnf install -y libstdc++-static && \ curl -sL "${D2N_URI}" -o "dcm2niix_src.zip" && \ unzip "dcm2niix_src.zip" && \ rm "dcm2niix_src.zip" && \ @@ -43,15 +51,15 @@ RUN cmake .. && \ # install scanbuddy ARG SB_PREFIX="/sw/apps/scanbuddy" -ARG SB_VERSION="v0.1.7" +ARG SB_VERSION="v0.1.10" RUN python3 -m venv "${SB_PREFIX}" && \ - dnf install -y gcc zlib-devel libjpeg-devel python39-tkinter && \ + dnf install -y gcc zlib-devel libjpeg-devel python3-tkinter && \ "${SB_PREFIX}/bin/pip" install "git+https://github.com/harvard-nrg/scanbuddy.git@${SB_VERSION}" # set up afni environment ENV PATH="${AFNI_PREFIX}:${PATH}" -# set up dcm2niix environment +# set up dcm2niix environment f ENV PATH="${D2N_PREFIX}:${PATH}" # set up scanbuddy diff --git a/scanbuddy/proc/__init__.py b/scanbuddy/proc/__init__.py index 487f88c..3577374 100644 --- a/scanbuddy/proc/__init__.py +++ b/scanbuddy/proc/__init__.py @@ -1,7 +1,13 @@ +import os +import sys +import pdb import time import math import json +import shutil import logging +import datetime +import threading import numpy as np from pubsub import pub from sortedcontainers import SortedDict @@ -16,23 +22,42 @@ def __init__(self): def reset(self): self._instances = SortedDict() + self._slice_means = SortedDict() + pub.sendMessage('plot_snr', snr_metric=str(0.0)) logger.debug('received message to reset') def listener(self, ds, path): key = int(ds.InstanceNumber) self._instances[key] = { 'path': path, - 'volreg': None + 'volreg': None, + 'nii_path': None + } + self._slice_means[key] = { + 'path': path, + 'slice_means': None, + 'mask_threshold': None, + 'mask': None } logger.debug('current state of instances') logger.debug(json.dumps(self._instances, default=list, indent=2)) + + tasks = self.check_volreg(key) + snr_tasks = self.check_snr(key) logger.debug('publishing message to volreg topic with the following tasks') logger.debug(json.dumps(tasks, indent=2)) pub.sendMessage('volreg', tasks=tasks) logger.debug(f'publishing message to params topic') pub.sendMessage('params', ds=ds) + logger.debug(f'publishing message to snr_fdata topic') + logger.debug(f'snr task sorted dict: {snr_tasks}') + pub.sendMessage('snr', nii_path=self._instances[key]['nii_path'], tasks=snr_tasks) + logger.debug('after snr calculation') + + logger.debug(json.dumps(self._instances, indent=2)) + logger.debug(f'after volreg') logger.debug(json.dumps(self._instances, indent=2)) @@ -43,14 +68,64 @@ def listener(self, ds, path): subtitle_string = f'{project} • {session} • {scandesc} • {scannum}' pub.sendMessage('plot', instances=self._instances, subtitle_string=subtitle_string) + if key < 5: + self._num_vols = ds[(0x0020, 0x0105)].value + self._mask_threshold, self._decrement = self.get_mask_threshold(ds) + x, y, self._z, _ = self._slice_means[key]['slice_means'].shape + + self._fdata_array = np.empty((x, y, self._z, self._num_vols)) + self._slice_intensity_means = np.zeros( (self._z, self._num_vols) ) + + + + logger.info(f'shape of zeros: {self._fdata_array.shape}') + logger.info(f'shape of first slice means: {self._slice_means[key]['slice_means'].shape}') + + if key >= 5: + insert_position = key - 5 + self._fdata_array[:, :, :, insert_position] = self._slice_means[key]['slice_means'].squeeze() + + if key > 53 and (key % 4 == 0) and key < self._num_vols: + logger.info('launching calculate and publish snr thread') + + snr_thread = threading.Thread(target=self.calculate_and_publish_snr, args=(key,)) + snr_thread.start() + + if key == self._num_vols: + time.sleep(2) + data_path = os.path.dirname(self._instances[key]['path']) + logger.info(f'removing dicom dir: {data_path}') + shutil.rmtree(data_path) + + #if key == self._num_vols: + # logger.info('RUNNING FINAL SNR CALCULATION') + # snr_metric = round(self.calc_snr(key), 2) + # logger.info(f'final snr metric: {snr_metric}') + # pub.sendMessage('plot_snr', snr_metric=snr_metric) + + + + def calculate_and_publish_snr(self, key): + start = time.time() + snr_metric = round(self.calc_snr(key), 2) + elapsed = time.time() - start + #self._plot_dict[self._key] = elapsed + logger.info(f'snr calculation took {elapsed} seconds') + logger.info(f'running snr metric: {snr_metric}') + if np.isnan(snr_metric): + logger.info(f'snr is a nan, decrementing mask threshold by {self._decrement}') + self._mask_threshold = self._mask_threshold - self._decrement + logger.info(f'new threshold: {self._mask_threshold}') + self._slice_intensity_means = np.zeros( (self._z, self._num_vols) ) + else: + pub.sendMessage('plot_snr', snr_metric=snr_metric) + def check_volreg(self, key): tasks = list() current = self._instances[key] - # get numerical index of key O(log n) i = self._instances.bisect_left(key) - # always register current node to left node try: left_index = max(0, i - 1) left = self._instances.values()[left_index] @@ -59,7 +134,6 @@ def check_volreg(self, key): except IndexError: pass - # if there is a right node, re-register to current node try: right_index = i + 1 right = self._instances.values()[right_index] @@ -70,3 +144,203 @@ def check_volreg(self, key): return tasks + def calc_snr(self, key): + + slice_intensity_means, slice_voxel_counts, data = self.get_mean_slice_intensitites(key) + + non_zero_columns = ~np.all(slice_intensity_means == 0, axis=0) + + slice_intensity_means_2 = slice_intensity_means[:, non_zero_columns] + + slice_count = slice_intensity_means_2.shape[0] + volume_count = slice_intensity_means_2.shape[1] + + slice_weighted_mean_mean = 0 + slice_weighted_stdev_mean = 0 + slice_weighted_snr_mean = 0 + slice_weighted_max_mean = 0 + slice_weighted_min_mean = 0 + outlier_count = 0 + total_voxel_count = 0 + + for slice_idx in range(slice_count): + slice_data = slice_intensity_means_2[slice_idx] + slice_voxel_count = slice_voxel_counts[slice_idx] + slice_mean = slice_data.mean() + slice_stdev = slice_data.std(ddof=1) + slice_snr = slice_mean / slice_stdev + + slice_weighted_mean_mean += (slice_mean * slice_voxel_count) + slice_weighted_stdev_mean += (slice_stdev * slice_voxel_count) + slice_weighted_snr_mean += (slice_snr * slice_voxel_count) + + total_voxel_count += slice_voxel_count + + logger.debug(f"Slice {slice_idx}: Mean={slice_mean}, StdDev={slice_stdev}, SNR={slice_snr}") + + + return slice_weighted_snr_mean / total_voxel_count + + + def get_mean_slice_intensitites(self, key): + + data = self.generate_mask(key) + mask = np.ma.getmask(data) + dim_x, dim_y, dim_z, _ = data.shape + + dim_t = key - 4 + + ''' + if key > 55: + start = time.time() + differing_slices = self.find_mask_differences(key) + logger.info(f'finding mask differences took {time.time() - start}') + ''' + + + slice_voxel_counts = np.zeros( (dim_z), dtype='uint32' ) + slice_size = dim_x * dim_y + + for slice_idx in range(dim_z): + slice_voxel_counts[slice_idx] = slice_size - mask[:,:,slice_idx,0].sum() + + zero_columns = np.where(np.all(self._slice_intensity_means[:,:dim_t] == 0, axis=0))[0].tolist() + + logger.info(f'volumes being calculated: {zero_columns}') + + + if len(zero_columns) > 20: + for volume_idx in range(dim_t): + for slice_idx in range(dim_z): + slice_data = data[:,:,slice_idx,volume_idx] + self._slice_intensity_means[slice_idx,volume_idx] = slice_data.mean() + + else: + + for volume_idx in zero_columns: + for slice_idx in range(dim_z): + slice_data = data[:,:,slice_idx,volume_idx] + slice_vol_mean = slice_data.mean() + self._slice_intensity_means[slice_idx,volume_idx] = slice_vol_mean + + #logger.info(f'recalculating slice means at the following slices: {differing_slices}') + #logger.info(f'total of {len(differing_slices)} new slices being computed') + + #if differing_slices: + + if key == self._num_vols: + start = time.time() + differing_slices = self.find_mask_differences(key) + logger.info(f'finding mask differences took {time.time() - start}') + logger.info(f'recalculating slice means at the following slices: {differing_slices}') + logger.info(f'total of {len(differing_slices)} new slices being computed') + for volume_idx in range(dim_t): + for slice_idx in differing_slices: + slice_data = data[:,:,slice_idx,volume_idx] + slice_vol_mean = slice_data.mean() + self._slice_intensity_means[slice_idx,volume_idx] = slice_vol_mean + + elif key % 6 == 0: + logger.info(f'inside the even calculation') + start = time.time() + differing_slices = self.find_mask_differences(key) + logger.info(f'finding mask differences took {time.time() - start}') + logger.info(f'recalculating slice means at the following slices: {differing_slices}') + logger.info(f'total of {len(differing_slices)} new slices being computed') + for volume_idx in range(0, dim_t, 8): + for slice_idx in differing_slices: + slice_data = data[:,:,slice_idx,volume_idx] + slice_vol_mean = slice_data.mean() + self._slice_intensity_means[slice_idx,volume_idx] = slice_vol_mean + + elif key % 5 == 0: + logger.info(f'inside the odd calculation') + start = time.time() + differing_slices = self.find_mask_differences(key) + logger.info(f'finding mask differences took {time.time() - start}') + logger.info(f'recalculating slice means at the following slices: {differing_slices}') + logger.info(f'total of {len(differing_slices)} new slices being computed') + for volume_idx in range(5, dim_t, 8): + for slice_idx in differing_slices: + slice_data = data[:,:,slice_idx,volume_idx] + slice_vol_mean = slice_data.mean() + self._slice_intensity_means[slice_idx,volume_idx] = slice_vol_mean + + + return self._slice_intensity_means[:, :dim_t], slice_voxel_counts, data + + + def generate_mask(self, key): + + mean_data = np.mean(self._fdata_array[...,:key-4], axis=3) + + numpy_3d_mask = np.zeros(mean_data.shape, dtype=bool) + + to_mask = (mean_data <= self._mask_threshold) + + mask_lower_count = int(to_mask.sum()) + + numpy_3d_mask = numpy_3d_mask | to_mask + + numpy_4d_mask = np.zeros(self._fdata_array[..., :key-4].shape, dtype=bool) + + numpy_4d_mask[numpy_3d_mask] = 1 + + masked_data = np.ma.masked_array(self._fdata_array[..., :key-4], mask=numpy_4d_mask) + + mask = np.ma.getmask(masked_data) + + self._slice_means[key]['mask'] = mask + + return masked_data + + def find_mask_differences(self, key): + num_old_vols = key - 8 + last_50 = num_old_vols - 50 + prev_mask = self._slice_means[key-4]['mask'] + current_mask = self._slice_means[key]['mask'] + differences = prev_mask[:,:,:,-50:] != current_mask[:,:,:,last_50:num_old_vols] + diff_indices = np.where(differences) + differing_slices = [] + for index in zip(*diff_indices): + if int(index[2]) not in differing_slices: + differing_slices.append(int(index[2])) + return differing_slices + + + def get_mask_threshold(self, ds): + bits_stored = ds.get('BitsStored', None) + receive_coil = self.find_coil(ds) + + if bits_stored == 12: + logger.debug(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 150.0') + return 150.0, 10 + if bits_stored == 16: + if receive_coil in ['Head_32']: + logger.debug(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 1500.0') + return 1500.0, 100 + if receive_coil in ['Head_64', 'HeadNeck_64']: + logger.debug(f'scan has "{bits_stored}" bits and receive coil "{receive_coil}", setting mask threshold to 3000.0') + return 3000.0, 300 + raise MaskThresholdError(f'unexpected bits stored "{bits_stored}" + receive coil "{receive_coil}"') + + def find_coil(self, ds): + seq = ds[(0x5200, 0x9229)][0] + seq = seq[(0x0018, 0x9042)][0] + return seq[(0x0018, 0x1250)].value + + + def check_snr(self, key): + tasks = list() + current = self._slice_means[key] + + current_idx = self._slice_means.bisect_left(key) + + try: + value = self._slice_means.values()[current_idx] + tasks.append(value) + except IndexError: + pass + + return tasks + diff --git a/scanbuddy/proc/snr.py b/scanbuddy/proc/snr.py new file mode 100644 index 0000000..898ab41 --- /dev/null +++ b/scanbuddy/proc/snr.py @@ -0,0 +1,82 @@ +import os +import sys +import pdb +import glob +import json +import time +import shutil +import random +import logging +import pydicom +import subprocess +import numpy as np +import nibabel as nib +from pubsub import pub +import collections as c +from pathlib import Path + +logger = logging.getLogger(__name__) + +class SNR: + def __init__(self): + pub.subscribe(self.listener, 'snr') + + + def listener(self, nii_path, tasks): + logger.info('received tasks for fdata extraction') + self.snr_tasks = tasks + self._nii_path = nii_path + + self.run() + + def run(self): + self.get_num_tasks() + + start = time.time() + + dcm = self.read_dicoms(self._num_tasks-1) + + instance_num = int(dcm.InstanceNumber) + + logger.info(f'extracting fdata for volume {instance_num}') + + data_array = self.get_nii_array() + + self.insert_snr(data_array, self.snr_tasks[0], None) + + self.clean_dir(instance_num) + + elapsed = time.time() - start + + logger.info(f'extracting fdata from volume {instance_num} took {elapsed} seconds') + + + def get_nii_array(self): + return nib.load(self._nii_path).get_fdata() + + + def insert_snr(self, slice_means, task, mask): + logger.debug(f'state of tasks when inserting {self.snr_tasks}') + x, y, z = slice_means.shape + data_array_4d = slice_means.reshape(x, y, z, 1) + task['slice_means'] = data_array_4d + + def read_dicoms(self, last_idx): + logger.debug(f'state of tasks when reading dicom: {self.snr_tasks}') + dcm1 = self.snr_tasks[0]['path'] + + ds1 = pydicom.dcmread(dcm1, force=True, stop_before_pixels=True) + + return ds1 + + def clean_dir(self, instance_num): + if instance_num == 1: + return + for file in glob.glob(f'{os.path.dirname(self._nii_path)}/*.json'): + os.remove(file) + for file in glob.glob(f'{os.path.dirname(self._nii_path)}/*.nii'): + os.remove(file) + + def get_num_tasks(self): + self._num_tasks = len(self.snr_tasks) + diff --git a/scanbuddy/proc/volreg.py b/scanbuddy/proc/volreg.py index 103cc09..94398b6 100644 --- a/scanbuddy/proc/volreg.py +++ b/scanbuddy/proc/volreg.py @@ -1,19 +1,22 @@ import os import glob import json +import time import logging import random import pydicom import subprocess import numpy as np from pubsub import pub -import time +from pathlib import Path logger = logging.getLogger(__name__) class VolReg: def __init__(self, mock=False): self._mock = mock + self._dcm1_instance_num = None + self._dcm2_instance_num = None pub.subscribe(self.listener, 'volreg') def listener(self, tasks): @@ -48,9 +51,9 @@ def run(self): arr = self.run_volreg(nii1, nii2, self.out_dir) - self.clean_dir(nii1, nii2, self.out_dir) + #self.clean_dir(nii1, nii2, self.out_dir) - logger.info(f'volreg array from registering volume {int(pydicom.dcmread(dcm2, force=True, stop_before_pixels=True).InstanceNumber)} to volume {int(pydicom.dcmread(dcm1, force=True, stop_before_pixels=True).InstanceNumber)}: {arr}') + logger.info(f'volreg array from registering volume {self._dcm2_instance_num} to volume {self._dcm1_instance_num}: {arr}') self.insert_array(arr, task_idx) @@ -63,11 +66,20 @@ def get_num_tasks(self): self.num_tasks = len(self.tasks) def create_niis(self, task_idx): + dcm1 = self.tasks[task_idx][1]['path'] nii1 = self.run_dcm2niix(dcm1, 1) + self._dcm1_instance_num = int(pydicom.dcmread(dcm1, force=True, stop_before_pixels=True).InstanceNumber) + nii1 = self.run_dcm2niix(dcm1, self._dcm1_instance_num) + if self.tasks[task_idx][1]['nii_path'] is None: + self.tasks[task_idx][1]['nii_path'] = nii1 dcm2 = self.tasks[task_idx][0]['path'] nii2 = self.run_dcm2niix(dcm2, 2) + self._dcm2_instance_num = int(pydicom.dcmread(dcm2, force=True, stop_before_pixels=True).InstanceNumber) + nii2 = self.run_dcm2niix(dcm2, self._dcm2_instance_num) + if self.tasks[task_idx][0]['nii_path'] is None: + self.tasks[task_idx][0]['nii_path'] = nii2 return nii1, nii2, dcm1, dcm2 diff --git a/scanbuddy/view/dash.py b/scanbuddy/view/dash.py index c316c24..c23d981 100644 --- a/scanbuddy/view/dash.py +++ b/scanbuddy/view/dash.py @@ -34,6 +34,7 @@ def __init__(self, host='127.0.0.1', port=8080, config=None, debug=False): self._subtitle = 'Ready' self._num_warnings = 0 self._instances = dict() + self._current_snr = 0.0 self._redis_client = redis.StrictRedis( host=self._config.find_one('$.broker.host', default='127.0.0.1'), port=self._config.find_one('$.broker.port', default=6379), @@ -43,6 +44,7 @@ def __init__(self, host='127.0.0.1', port=8080, config=None, debug=False): self.init_page() self.init_callbacks() pub.subscribe(self.listener, 'plot') + pub.subscribe(self.plot_snr, 'plot_snr') def init_app(self): self._app = Dash( @@ -144,6 +146,7 @@ def init_page(self): } ) + ''' metrics_card = dbc.Card( [ dbc.CardBody( @@ -276,8 +279,39 @@ def init_page(self): ], style={"margin": "0px"} ), + dbc.Row( + [ + dbc.Col( + "SNR", + width=8, + style={ + "borderRight": "1px solid black", + "textAlign": "center", + "display": "flex", + "alignItems": "center", + "justifyContent": "flex-end", + "paddingRight": "5px", + "fontSize": "1.5vw", + "borderBottom": "1px solid black" + } + ), + dbc.Col( + id='snr', + children="0", + width=4, + style={ + "borderBottom": "1px solid black", + "textAlign": "center", + "padding": "1rem", + "fontSize": "1.25vw" + } + ) + ], + style={"margin": "0px"} + ), ], style={"border": "1px solid black", "padding": "0"} + '' ) ], style={ @@ -289,11 +323,82 @@ def init_page(self): } ) + ''' + LEFT_COLUMN_WIDTH = "85%" + RIGHT_COLUMN_WIDTH = "85%" + BG_COLOR = "#e0f7fa" # Light blue background + PADDING = "10px" + BORDER_STYLE = "1px solid black" + table_header = [ + html.Thead( + html.Tr( + html.Th( + "Motion Metrics", + colSpan=2, + style={ + 'textAlign': 'center', + 'border': BORDER_STYLE + } + ) + ) + ) + ] + # Metric table rows with ids for each value + row1 = html.Tr([ + html.Td("Number of Volumes", style={'width': LEFT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}), + html.Td(id='number-of-vols', children="0", style={'width': RIGHT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}) + ]) + + row2 = html.Tr([ + html.Td("Movements > .5 mm", style={'width': LEFT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}), + html.Td(id='movements-05mm', children="0", style={'width': RIGHT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}) + ]) + + row3 = html.Tr([ + html.Td("Movements > 1 mm", style={'width': LEFT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}), + html.Td(id='movements-1mm', children="0", style={'width': RIGHT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}) + ]) + + row4 = html.Tr([ + html.Td("Max Abs Motion", style={'width': LEFT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}), + html.Td(id='max-abs-motion', children="0", style={'width': RIGHT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}) + ]) + + row5 = html.Tr([ + html.Td("SNR", style={'width': LEFT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE}), + html.Td(id='snr', children="0.0", style={'width': RIGHT_COLUMN_WIDTH, 'padding': PADDING, 'border': BORDER_STYLE, 'textAlign': 'center'}) + ]) + + table_body = [ + html.Tbody([ + row1, + row2, + row3, + row4, + row5 + ]) + ] + + metrics_table = dbc.Table( + table_header + table_body, + bordered=True, + striped=True, + style={ + 'fontSize': '3.0vh', + 'backgroundColor': BG_COLOR + } + ) + self._app.layout = html.Div([ navbar, dbc.Row( [ - dbc.Col(metrics_card, width=2, style={"marginTop": "50px"}), + dbc.Col( + metrics_table, + style={ + 'margin': '10px' + } + ), dbc.Col( [ displacements_graph, @@ -393,6 +498,7 @@ def init_callbacks(self): Output('movements-05mm', 'children'), Output('movements-1mm', 'children'), Output('max-abs-motion', 'children'), + Output('snr', 'children'), Input('plot-interval-component', 'n_intervals'), )(self.update_metrics) @@ -429,7 +535,15 @@ def update_metrics(self, n): else: max_abs_motion = 0 - return str(num_vols), str(movements_05mm), str(movements_1mm), str(max_abs_motion) + snr = self.get_snr() + + return str(num_vols), str(movements_05mm), str(movements_1mm), str(max_abs_motion), str(snr) + + def get_snr(self): + if not self._current_snr: + return 0.0 + else: + return self._current_snr def get_subtitle(self): return self._subtitle @@ -643,6 +757,7 @@ def todataframe(self): arr = list() for i,instance in enumerate(self._instances.values(), start=1): volreg = instance['volreg'] + #snr = instance.get('snr', '0.0') if volreg: arr.append([i] + volreg) df = pd.DataFrame(arr, columns=['N', 'roll', 'pitch', 'yaw', 'x', 'y', 'z']) @@ -659,5 +774,8 @@ def listener(self, instances, subtitle_string): self._instances = instances self._subtitle = subtitle_string + def plot_snr(self, snr_metric): + self._current_snr = snr_metric + class AuthError(Exception): pass diff --git a/scripts/start.py b/scripts/start.py index 72ef336..5566999 100755 --- a/scripts/start.py +++ b/scripts/start.py @@ -10,6 +10,7 @@ from scanbuddy.proc import Processor from scanbuddy.proc.volreg import VolReg from scanbuddy.proc.params import Params +from scanbuddy.proc.snr import SNR from scanbuddy.view.dash import View from scanbuddy.broker.redis import MessageBroker from scanbuddy.config import Config @@ -25,6 +26,8 @@ def main(): parser.add_argument('--port', type=int, default=8080) parser.add_argument('--folder', type=Path, required=True) parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('--snr-interval', default=10, + help='Every N volumes snr should be calculated') args = parser.parse_args() config = Config(args.config) @@ -37,6 +40,7 @@ def main(): config=config ) volreg = VolReg(mock=args.mock) + snr = SNR() view = View( host=args.host, port=args.port, diff --git a/setup.py b/setup.py index 9217fb7..72eae9a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,8 @@ 'redis', 'pyyaml', 'jsonpath-ng', - 'dash_auth' + 'dash_auth', + 'nibabel' ] about = dict()