qiprofile
Version:
Quantitative Imaging Profile ( QiPr) web application
1,004 lines (870 loc) • 38.6 kB
text/coffeescript
define ['angular', 'lodash', 'ngsanitize', 'ngnvd3', 'resources', 'modelingChart', 'breast',
'sliceDisplay', 'collection'],
(ng, _) ->
ctlrs = ng.module(
'qiprofile.controllers',
['ngSanitize', 'nvd3', 'qiprofile.resources', 'qiprofile.modelingchart',
'qiprofile.breast', 'qiprofile.slicedisplay', 'qiprofile.collection']
)
# The local controller helper methods.
ctlrs.factory 'ControllerHelper', [
'$location',
($location) ->
# Resets the browser URL search parameters to include at most
# a non-default project, since all other search parameters are
# redundant.
cleanBrowserUrl: (project) ->
searchParams = {}
# TODO - get the default project from a config file.
if project != 'QIN'
searchParams.project = project
$location.search(searchParams)
]
ctlrs.controller 'HomeCtrl', [
'$rootScope', '$scope', '$state',
($rootScope, $scope, $state) ->
$scope.goHome = () ->
project = $rootScope.project or 'QIN'
$state.go('quip.home', project: project)
]
ctlrs.controller 'HelpCtrl', [
'$rootScope', '$scope',
($rootScope, $scope) ->
# Toggles the root scope showHelp flag.
# This method and the showHelp flag are defined in the root
# scope in order to share them with the partial template
# home alert attribute directives.
$rootScope.toggleHelp = ->
$rootScope.showHelp = !$rootScope.showHelp
# Unconditionally hide help when the location changes.
$scope.$on '$locationChangeStart', (event, next, current) ->
# Set the showHelp flag on the parent scope, since the
# flag is shared with the sibling view scope.
$rootScope.showHelp = false
]
ctlrs.controller 'AccordionGroupCtrl', [
'$scope',
($scope) ->
# The accordion group is initially open.
$scope.isOpen = true
]
ctlrs.controller 'CollectionListCtrl', [
'$rootScope', '$scope', 'project', 'collections', '$window',
($rootScope, $scope, project, collections, $window) ->
# Capture the current project.
$rootScope.project = project
# Place the collections in the scope.
$scope.collections = collections
$scope.open = (url) ->
$window.location.href = url
# The help is always open to start with on this landing page.
$rootScope.showHelp = true
]
ctlrs.controller 'SubjectListCtrl', [
'$rootScope', '$scope', 'project', 'subjects', 'collections',
($rootScope, $scope, project, subjects, collections) ->
# Capture the current project.
$rootScope.project = project
# Place the subjects and collections in the scope.
$scope.subjects = subjects
$scope.collections = collections
]
ctlrs.controller 'CollectionDetailCtrl', [
'$rootScope', '$scope', 'project', 'collection', 'charting', 'Collection', 'ControllerHelper',
($rootScope, $scope, project, collection, charting, Collection, ControllerHelper) ->
# Capture the current project.
$rootScope.project = project
# Place the collection in the scope.
$scope.collection = collection
# Obtain the valid data series and labels for the current collection
# and put them in the scope. These comprise the X/Y axis dropdown
# choices. The X axis choices contain all valid data series. The Y axis
# choices may only include continuous data types.
$scope.choices = Collection.dataSeriesChoices(collection)
# Obtain the formatted scatterplot data.
data = Collection.chartData(charting, Object.keys $scope.choices.x)
# Obtain the chart padding for each data series - continuous data types
# only. The padding for categorical data series is configured in the
# correlation module chart rendering function.
padding = Collection.chartPadding(data, Object.keys $scope.choices.y)
# Put a copy of the default charts (X and Y axes) in the scope.
# This charts object changes when the user selects a different
# axis to chart.
$scope.axes = _.clone(Collection.DEFAULT_AXES[collection])
# Check the validity of the default chart axes.
#
# TODO - Suppress charts where no data exists for a particular data
# series throughout the collection.
#
checkAxes = (axis) ->
axis.x in xAxisChoices or
throw new Error "Invalid #{ collection } X-axis data series: " +
"#{ axis.x }"
axis.y in yAxisChoices or
throw new Error "Invalid #{ collection } Y-axis data series: " +
"#{ axis.y }"
xAxisChoices = Object.keys($scope.choices.x)
yAxisChoices = Object.keys($scope.choices.y)
checkAxes(axis) for axis in $scope.axes
# Place the chart configuration object in the scope.
$scope.config =
data: data
padding: padding
axes: $scope.axes
# If the user changes any X or Y axis selection, then re-render the
# charts. The watcher is invoked on initialization and whenever the
# user changes the axes. On initialization, then the watcher is called
# with new axes value argument identical (===) to the old value
# argument. If the user changes the axes, then the watcher is called
# with different new and old axes objects.
watcher = (newValue, oldValue) ->
# If the user changed the axes, then re-render the charts.
if newValue != oldValue
Collection.renderCharts($scope.config)
# Since charts is an object, the objectEquality flag is set to true.
# AngularJS then copies the object for later comparison and uses
# angular.equals to recursively compare the object properties rather
# than a simple === test, which is the default.
$scope.$watch('axes', watcher, true)
# If the project is the default, then remove it from the URL.
ControllerHelper.cleanBrowserUrl($rootScope.project)
]
## The Subject Detail page controllers.
ctlrs.controller 'SubjectDetailCtrl', [
'$rootScope', '$scope', 'subject', 'ControllerHelper',
($rootScope, $scope, subject, ControllerHelper) ->
# Capture the current project.
$rootScope.project = subject.project
# Place the subject in scope.
$scope.subject = subject
# If the project is the default, then remove it from the URL.
ControllerHelper.cleanBrowserUrl($rootScope.project)
]
ctlrs.controller 'ScanVolumeSelectCtrl', [
'$scope',
($scope) ->
# Place the scan protocol id in scope.
$scope.protocolId = $scope.scan.protocol
]
ctlrs.controller 'RegistrationVolumeSelectCtrl', [
'$scope',
($scope) ->
# Place the registration protocol id in scope.
$scope.protocolId = $scope.registration.protocol
]
ctlrs.controller 'ScanProtocolCtrl', [
'$scope', '$uibModal', 'Resources',
($scope, $uibModal, Resources) ->
# Open a modal window to display the scan procedure properties.
$scope.open = ->
$uibModal.open
controller: 'ProtocolModalCtrl'
templateUrl: '/partials/scan-protocol.html'
size: 'sm'
resolve:
# Fetch the scan protocol.
protocol: ->
Resources.ScanProtocol.find(id: $scope.protocolId)
]
ctlrs.controller 'RegistrationProtocolCtrl', [
'$scope', '$uibModal', 'Resources',
($scope, $uibModal, Resources) ->
# Open a modal window to display the registration properties.
$scope.open = ->
$uibModal.open
controller: 'ProtocolModalCtrl'
templateUrl: '/partials/registration-protocol.html'
size: 'sm'
resolve:
# Fetch the registration protocol.
protocol: ->
Resources.RegistrationProtocol.find(id: $scope.protocolId)
]
ctlrs.controller 'ProtocolModalCtrl', [
'$scope', '$uibModalInstance', 'protocol',
($scope, $uibModalInstance, protocol) ->
# Place the protocol object in the modal scope.
$scope.protocol = protocol
$scope.close = ->
$uibModalInstance.close()
]
## The Imaging Profile pane controllers.
ctlrs.controller 'SubjectModelingCtrl', [
'$scope',
($scope) ->
# Place the modelings in scope.
# Note: modelings is a subject property that is calculated
# on the fly. Since the partials reference the modelings in
# ng-switch and ng-repeat models, AngularJS sets up hidden
# watches on this variable and redigests the partials whenever
# the property value is calculated. If subject.modelings were
# used instead of modelings, then AngularJS would enter an
# infinite digest cycle. Placing the modelings variable in
# scope here avoids this trap by fixing the value for the
# course of the AngularJS page formatting.
$scope.modelings = $scope.subject.modelings
# The format button action.
$scope.toggleModelingFormat = ->
if $scope.modelingFormat is 'chart'
$scope.modelingFormat = 'table'
else if $scope.modelingFormat is 'table'
$scope.modelingFormat = 'chart'
else
throw new Error "Modeling format is not recognized:" +
" #{ $scope.modelingFormat }"
# The modeling format is 'chart' if the subject has
# more than one session, 'table' otherwise.
if $scope.subject.isMultiSession()
$scope.modelingFormat = 'chart'
else
$scope.modelingFormat = 'table'
# The default modeling results index is the first.
$scope.modelingIndex = 0
# Place the selected modeling in scope.
$scope.$watch 'modelingIndex', (modelingIndex) ->
$scope.selModeling = $scope.modelings[modelingIndex]
]
ctlrs.controller 'PkModelingHelpCtrl', [
'$scope', '$uibModal',
($scope, $uibModal) ->
$scope.open = ->
$uibModal.open
controller: 'PkModelingHelpModalCtrl'
templateUrl: '/partials/pk-modeling-help.html'
size: 'med'
]
ctlrs.controller 'PkModelingHelpModalCtrl', [
'$scope', '$uibModalInstance',
($scope, $uibModalInstance) ->
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'ModelingProtocolCtrl', [
'$scope',
($scope) ->
# Place the protocol id in scope.
$scope.protocolId = $scope.modeling.protocol
]
ctlrs.controller 'ModelingInfoCtrl', [
'$scope', '$uibModal', 'Resources',
($scope, $uibModal, Resources) ->
# Open a modal window to display the modeling input properties.
$scope.open = ->
$uibModal.open
controller: 'ModelingInfoModalCtrl'
templateUrl: '/partials/modeling-info.html'
size: 'sm'
resolve:
# Make modeling injectable in the modal controller.
# See the ModelingInfoModalCtrl comment.
modeling: ->
$scope.modeling
# Fetch the modeling protocol.
protocol: ->
Resources.ModelingProtocol.find(id: $scope.modeling.protocol)
]
ctlrs.controller 'ModelingInfoModalCtrl', [
'$scope', '$uibModalInstance', 'modeling', 'protocol',
($scope, $uibModalInstance, modeling, protocol) ->
# Since the modal is not contained in the application page, this
# modal controller scope does not inherit the application page
# scope. Therefore, the application scope modeling variable is
# not visible to the modal scope. Hence, it is necessary to
# transfer the modeling object as a $uibModal.open function resolve
# property into this controller's injection list.
# The assignment below then places the modeling object in the
# modal scope for use in the modal template partial.
$scope.modeling = modeling
$scope.protocol = protocol
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'ModelingSourceProtocolCtrl', [
'$scope',
($scope) ->
# The modeling source is either a scan or registration protocol.
if $scope.modeling.source.scan?
$scope.sourceType = 'scan'
$scope.protocolId = $scope.modeling.source.scan
else if $scope.modeling.source.registration?
$scope.sourceType = 'registration'
$scope.protocolId = $scope.modeling.source.registration
else
throw new Error("The modeling source has neither a scan" +
" nor a registration protocol reference")
]
ctlrs.controller 'TimelineCtrl', [
'$scope', '$state', '$compile', 'Timeline',
($scope, $state, $compile, Timeline) ->
# The chart {options, data} configuration.
chartConfig = Timeline.configure($scope.subject)
$scope.options = chartConfig.options
$scope.data = chartConfig.data
# The global configuration. Disable the data watcher since,
# once built, the data is not changed. Furthermore, the
# data is the modeling parameter objects, which are complex
# objects with a circular object graph reference
# (paramResult -> parent modelingResult -> child paramResult).
$scope.config = deepWatchData: false
# The chart decoration callback.
$scope.decorate = (scope, element) ->
# Note: since the nvd3 directive isolates the scope,
# it does not inherit to the parent $scope properties.
# Therefore, we must specify $scope.subject rather than
# scope.subject.
Timeline.decorate(element, $scope.subject, scope, $state)
# Note: an angular-nvd3 bug voids element changes made
# in this callback on refresh
# (cf. https://github.com/krispo/angular-nvd3/issues/316).
# The work-around is to surgically intervene in the
# defective ngnvd3 code.
# TODO - periodically check whether the ngnvd3 bug is
# fixed and remove the work-around if possible.
#scope.isReady = false
if _.isUndefined(scope.isBroken)
broken = scope.api.refresh
fixed = ->
# Note: setting scope.isReady should trigger the watcher
# but doesn't. The work-around to this work-around bug is
# to force the issue by calling the decorate function
# directly in the refresh rather than indirectly via the
# watcher.
# TODO - remove this work-around to a work-around bug when
# the original work-around is fixed.
broken()
$scope.decorate(scope, element)
scope.api.refresh = fixed
scope.isBroken = false
]
ctlrs.controller 'IntensityChartCtrl', [
'$scope', 'IntensityChart',
($scope, IntensityChart) ->
# The chart {options, data} configuration.
chartConfig = IntensityChart.configure($scope.scan)
$scope.options = chartConfig.options
$scope.data = chartConfig.data
# The global configuration. See ModelingChartCtrl comment.
$scope.config = deepWatchData: false
]
ctlrs.controller 'ModelingChartCtrl', [
'$scope', 'ModelingChart',
($scope, ModelingChart) ->
# selModeling is set to the modeling object to display.
# It is set if and only if there is at least one modeling
# object. Thus, the if test guards against there not being
# any modeling object to display.
if $scope.selModeling?
# The chart {options, data} configuration.
chartConfig = ModelingChart.configure($scope.selModeling.results,
$scope.paramKey)
$scope.options = chartConfig.options
$scope.data = chartConfig.data
# The global configuration. Disable the data watcher since,
# once built, the data is not changed. Furthermore, the
# data is the modeling parameter objects, which are complex
# objects with a circular object graph reference
# (paramResult -> parent modelingResult -> child paramResult).
$scope.config = deepWatchData: false
]
## The modeling parameter controllers. ##
#
# TODO - revise to beter fit ngnvd3. These controller's don't do
# much.
#
# Each controller is required to set the following scope variable:
# * dataSeriesConfig - the ModelingChart.configureD3 dataSeriesSpec
# argument
#
# Note: The modeling parameter controllers defined below somewhat abuse
# Controller-View separation of control, since the controller places
# formatting details like the label and color in a scope variable. This
# is necessary evil to accomodate d3 charting. A preferable solution is
# to specify the color in a style sheet and the data series label in a
# transcluded partial. However, that solution appears to go against the
# grain of d3. In general, the modeling chart partial/controller pattern
# should not be used as an example for building a non-d3 charting
# component.
ctlrs.controller 'KTransCtrl', [ '$scope', ($scope) ->
$scope.paramKey = 'kTrans'
]
ctlrs.controller 'VeCtrl', [ '$scope', ($scope) ->
$scope.paramKey = 'vE'
]
ctlrs.controller 'TauICtrl', [ '$scope', ($scope) ->
$scope.paramKey = 'tauI'
]
## The Clinical Profile pane controllers. ##
ctlrs.controller 'PathologyCtrl', [
'$scope',
($scope) ->
$scope.pathology = $scope.encounter.pathology
]
ctlrs.controller 'BreastHormoneReceptorsCtrl', [
'$scope',
($scope) ->
$scope.hormoneReceptors = $scope.tumor.hormoneReceptors
]
ctlrs.controller 'BreastGeneticExpressionCtrl', [
'$scope',
($scope) ->
$scope.geneticExpression = $scope.tumor.geneticExpression
]
ctlrs.controller 'BreastGeneAssayCtrl', [
'$scope', 'Breast',
($scope, Breast) ->
$scope.assay = $scope.geneticExpression.normalizedAssay
$scope.recurrenceScore = Breast.recurrenceScore($scope.assay)
]
ctlrs.controller 'TumorExtentCtrl', [
'$scope',
($scope) ->
$scope.extent = $scope.tumor.extent
]
ctlrs.controller 'ResidualCancerBurdenCtrl', [
'$scope', 'Breast',
($scope, Breast) ->
$scope.rcb = Breast.residualCancerBurden($scope.tumor)
]
ctlrs.controller 'RecurrenceScoreHelpCtrl', [
'$scope', '$uibModal',
($scope, $uibModal) ->
$scope.open = ->
$uibModal.open
controller: 'RecurrenceScoreHelpModalCtrl'
templateUrl: '/partials/recurrence-score-help.html'
size: 'med'
]
ctlrs.controller 'RecurrenceScoreHelpModalCtrl', [
'$scope', '$uibModalInstance',
($scope, $uibModalInstance) ->
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'DosageAmountHelpCtrl', [
'$scope', '$uibModal',
($scope, $uibModal) ->
$scope.open = ->
$uibModal.open
controller: 'DosageAmountHelpModalCtrl'
templateUrl: '/partials/dosage-amount-help.html'
size: 'med'
]
ctlrs.controller 'DosageAmountHelpModalCtrl', [
'$scope', '$uibModalInstance',
($scope, $uibModalInstance) ->
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'TNMCtrl', [
'$scope',
($scope) ->
# The parent can either be a pathology evaluation or a
# generic evaluation outcome iterator.
path = $scope.tumor
$scope.tnm = if path then path.tnm else $scope.outcome
]
ctlrs.controller 'BreastTNMStageHelpCtrl', [
'$scope', '$uibModal',
($scope, $uibModal) ->
$scope.open = ->
$uibModal.open
controller: 'BreastTNMStageHelpModalCtrl'
templateUrl: '/partials/breast-tnm-stage-help.html'
size: 'med'
]
ctlrs.controller 'BreastTNMStageHelpModalCtrl', [
'$scope', '$uibModalInstance',
($scope, $uibModalInstance) ->
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'SarcomaTNMStageHelpCtrl', [
'$scope', '$uibModal',
($scope, $uibModal) ->
$scope.open = ->
$uibModal.open
controller: 'SarcomaTNMStageHelpModalCtrl'
templateUrl: '/partials/sarcoma-tnm-stage-help.html'
size: 'med'
]
ctlrs.controller 'SarcomaTNMStageHelpModalCtrl', [
'$scope', '$uibModalInstance',
($scope, $uibModalInstance) ->
$scope.close = ->
$uibModalInstance.close()
]
ctlrs.controller 'GradeCtrl', [
'$scope',
($scope) ->
$scope.grade = $scope.tnm.grade
]
ctlrs.controller 'HormoneReceptorCtrl', [
'$scope',
($scope) ->
# The parent of a generic HormoneReceptor is a generic
# evaluation outcome iterator.
$scope.receptorStatus = $scope.outcome
]
ctlrs.controller 'EstrogenCtrl', [
'$scope',
($scope) ->
$scope.receptorStatus = _.find($scope.hormoneReceptors,
(hr) -> hr.hormone == 'estrogen')
]
ctlrs.controller 'ProgesteroneCtrl', [
'$scope',
($scope) ->
$scope.receptorStatus = _.find($scope.hormoneReceptors,
(hr) -> hr.hormone == 'progesterone')
]
ctlrs.controller 'ChemotherapyCtrl', [
'$scope',
($scope) ->
isChemo = (dosage) -> dosage.agent._cls == 'Drug'
chemo = $scope.treatment.dosages.filter(isChemo)
$scope.chemotherapy = {dosages: chemo} if chemo.length
]
ctlrs.controller 'RadiotherapyCtrl', [
'$scope',
($scope) ->
isRadio = (dosage) -> dosage.agent._cls == 'Radiation'
radio = $scope.treatment.dosages.filter(isRadio)
$scope.radiotherapy = {dosages: radio} if radio.length
]
ctlrs.controller 'NecrosisPercentCtrl', [
'$scope',
($scope) ->
$scope.necrosis_percent = $scope.tumor.necrosis_percent
]
## The Session Detail page controllers. ##
ctlrs.controller 'SessionDetailCtrl', [
'$rootScope', '$scope', '$state', 'session', 'ControllerHelper',
($rootScope, $scope, $state, session, ControllerHelper) ->
# Capture the current project.
$rootScope.project = session.subject.project
# Place the session in the scope.
$scope.session = session
# If the project is the default, then remove it from the URL.
ControllerHelper.cleanBrowserUrl($rootScope.project)
]
#
# TODO - retain on the xtk branch only.
#
ctlrs.controller 'VolumeDisplayCtrl', [
'$scope', '$state',
($scope, $state) ->
# Create the image object on demand.
$scope.image = $scope.volume.image
# Opens the series image display page.
#
# @param image the Image object
$scope.openImage = () ->
# The parent scan or registration image sequence.
imageSequence = $scope.volume.imageSequence
# The common parameters.
params =
project: imageSequence.session.subject.project
collection: imageSequence.session.subject.collection
subject: imageSequence.session.subject.number
session: imageSequence.session.number
detail: imageSequence.session.detail
volume: $scope.volume.number
# The target imageSequence route prefix.
route = 'quip.collection.subject.session.scan.'
# Add to the parameters and route based on the imageSequence type.
if imageSequence._cls == 'Scan'
params.scan = imageSequence.number
else if imageSequence._cls == 'Registration'
params.scan = imageSequence.scan.number
params.registration = imageSequence.resource
route += 'registration.'
else
throw new TypeError("Unsupported image sequence type:" +
" #{ imageSequence._cls }")
# Finish off the route.
route += 'volume.slice'
# Go to the volume page.
$state.go(route, params)
#
# TODO - Legacy controller partially repurposed for slice display.
# If volume display is reenabled, then borrow what is needed for
# the above controller and delete the rest.
#
# ctlrs.controller 'VolumeeDisplayCtrl', [
# '$rootScope', '$scope', '$location', '$sce', 'ModelingChart', 'SliceDisplay',
# 'ControllerHelper', 'session', 'imageSequence', 'volume', 'slice',
# ($rootScope, $scope, $location, $sce, ModelingChart, SliceDisplay,
# ControllerHelper, session, imageSequence, volume, slice) ->
# # Capture the current project.
# $rootScope.project = session.subject.project
#
# # @param key the modeling parameter key, e.g. 'deltaKTrans'
# # @returns the modeling parameter heading HTML span element,
# # e.g. '<span>ΔK<sub>trans</sub></span>'
# $scope.parameterHeading = (key) ->
# html = "<span>#{ Modeling.properties[key].html }</span>"
# $sce.trustAsHtml(html)
#
# # The session modelings which have an overlay.
# $scope.overlayModelings = (
# mdl for mdl in session.modelings when mdl.overlays.length?
# )
# # The overlay selection.
# $scope.overlayIndex = null
#
# # The initial saggital slice.
# $scope.saggitalView = slice: slice
#
# # The overlay opacity slider setting and CSS styles.
# $scope.overlayConfig =
# setting: 1
# style:
# "opacity": 1
# "z-index": -1
#
# # Watch for a change in the saggital slice control setting.
# # When the slice index changes, then update the image and,
# # if selected, the overlay.
# #
# # TODO - address the volume-controls.jade TODO items.
# #
# $scope.$watch 'saggitalView.slice', (index) ->
# SliceDisplay.updateSlice($scope.imageIds[index].dicomImageId)
# if $scope.overlayIndex?
# Slice.updateOverlay($scope.imageIds[index].overlayIds,
# $scope.overlayIndex)
#
# # Update the URL search parameters.
# $location.search('volume', volume)
# $location.search('slice', slice)
#
# # The overlayIndex scope variable is the overlay radio input
# # selection value in the format *modeling index*.*overlay index*,
# # e.g. the value '0.1' is the second overlay of the first modeling.
# # By default, no overlay is selected. When a radio button is
# # checked, then the overlayIndex variable is set and the watcher
# # below is triggered.
# #
# # If the overlayIndex scope variable is changed to a non-null value,
# # then parse the value and call the image selectOverlay(overlay)
# # function on the referenced overlay.
# # Otherwise, call the image deselectOverlay() function.
# $scope.$watch 'overlayIndex', (index) ->
# if index?
# # Parse and disaggregate the composite index.
# # Note: calling map with parseInt fails with a NaN second value
# # since both parseInt can include optional arguments
# # (cf. http://stackoverflow.com/questions/262427/javascript-arraymap-and-parseint).
# # The work-around is to call map with Number, which takes a
# # single string argument.
# #[mdlIndex, ovrIndex] = index.split('.').map(Number)
# # The selected modeling object.
# #modeling = $scope.overlayModelings[mdlIndex]
# # The select overlay label map in the selected modeling object.
# #overlay = modeling.overlays[ovrIndex]
# # Delegate to the image object.
# #$scope.volume.selectOverlay(overlay)
#
# # Move the overlay viewport to the front and update it -
# # Cornerstone prototype.
# $scope.overlayConfig.style['z-index'] = 1
# slice = $scope.saggitalView.slice
# $scope.slice.updateOverlay($scope.imageIds[slice].overlayIds,
# index)
# else
# #$scope.volume.deselectOverlay()
# # Move the overlay viewport to the back - Cornerstone prototype.
# $scope.overlayConfig.style['z-index'] = -1
#
#
# # If the project is the default, then remove it from the URL.
# ControllerHelper.cleanBrowserUrl($rootScope.project)
]
# The Image Slice Display controller.
ctlrs.controller 'SliceDisplayCtrl', [
'$rootScope', '$scope', '$location', 'SliceDisplay', 'ControllerHelper', 'timeSeries', 'volume', 'slice',
($rootScope, $scope, $location, SliceDisplay, ControllerHelper, timeSeries, volume, slice) ->
# Capture the current project.
$rootScope.project = timeSeries.imageSequence.session.subject.project
# Place the time series in scope.
$scope.timeSeries = timeSeries
# Initialize the volume number, if necessary.
if not volume?
# The default volume is at bolus arrival, if defined, otherwise the
# first volume.
bolusArvNdx = timeSeries.imageSequence.bolusArrivalIndex
volume = if bolusArvNdx? then bolusArvNdx + 1 else 1
# Update the URL search parameter.
$location.search('volume', volume)
# Place the volume in scope.
$scope.volume = volumeNbr: volume
# Initialize the slice number, if necessary.
if not slice?
# The default slice is the point of maximal ROI extent, if defined,
# otherwise the first slice.
# TODO - calculate the maximal ROI slice in the pipeline and put in
# the REST database.
maximalROISlice = undefined
slice = if maximalROISlice? then maximalROISlice + 1 else 1
# Update the URL search parameter.
$location.search('slice', slice)
# Place the slice in scope.
$scope.slice = sliceNbr: slice
# The slice dimensions.
$scope.dimensions = [
"Saggital"
"Coronal"
"Axial"
]
# The default slice dimension.
$scope.dimension = "Axial"
#
# TODO - add overlays. The REST data model is:
# Modeling {
# result {
# parameter: parameterResult{name, labelMap: {name, colorTable}}, ...}
# }
# }
# and:
# Scan {
# rois: [{name, labelMap: {name, colorTable}}, ...]
# }
# }
# with one ROI per tumor.
#
# The LabelMap objects need to be collected by Scan.extend into a
# Scan.overlays virtual property {key, image} object, where:
# * *key* is either a modeling parameter name or 'roi'
# * *image* is the LabelMap extended by the Image service, which
# should be done in Modeling.extend and Scan.extend.
#
# The composite ROI needs to be created in the pipeline from the
# tumor ROIs and placed into a new REST Scan.rois ROI object
# {composite, regions}, where:
# *composite* is a LabelMap object
# *regions* is the current Scan.rois [Region, ...] array. Since
# the Scan ROIs do not overlap, the masks can be combined in a
# ndarray operation by the pipeline.
#
# The Slice Display page should have an overlay selection control,
# one per modeling parameter and one for the composite ROI.
#
# A registration image sequence needs to delegate to the parent
# scan overlays. This can be done in Registration.extend by adding a
# Registration.overlays virtual property.
#
# Finally, TimeSeries.extend should create a TimeSeries.overlays
# virtual property which delegates to the parent overlays.
$scope.timeSeries.image.loader = {}
display = ->
SliceDisplay.display($scope.timeSeries, $scope.volume.volumeNbr
$scope.slice.sliceNbr)
watcher = (newValue, oldValue) ->
if newValue != oldValue
display()
# Display the image the first time and whenever the volume changes
# thereafter.
$scope.$watch('volume', display, true)
# Redisplay the image when the slice changes.
$scope.$watch('slice', watcher, true)
#
# TODO - implement the overlay watcher in conjunction with the
# changes described above.
#
# # Redisplay the overlay when the overlay changes.
# $scope.$watch 'overlayIndex', (overlayIndex, previous) ->
# # There must first be an overlay. This situation should never
# # occur by construction if the UI overlay selection properly
# # guards against it.
# if not data?
# throw new ReferenceError("The time series does not have an overlay")
# if overlayIndex != previous
# display()
#
# TODO - borrow whatever is useful from below--but don't copy it as is--then
# delete it.
#
# '$rootScope', '$scope', '$location', '$sce', 'ModelingChart', 'SliceDisplay',
# 'ControllerHelper', 'session', 'imageSequence', 'volume', 'slice',
# ($rootScope, $scope, $location, $sce, ModelingChart, SliceDisplay,
# ControllerHelper, session, imageSequence, volume, slice) ->
# # Capture the current project.
# $rootScope.project = session.subject.project
#
# # @param key the modeling parameter key, e.g. 'deltaKTrans'
# # @returns the modeling parameter heading HTML span element,
# # e.g. '<span>ΔK<sub>trans</sub></span>'
# $scope.parameterHeading = (key) ->
# html = "<span>#{ Modeling.properties[key].html }</span>"
# $sce.trustAsHtml(html)
#
# # The session modelings which have an overlay.
# $scope.overlayModelings = (
# mdl for mdl in session.modelings when mdl.overlays.length?
# )
# # The overlay selection.
# $scope.overlayIndex = null
#
# # The initial saggital slice.
# $scope.saggitalView = slice: slice
#
# # The overlay opacity slider setting and CSS styles.
# $scope.overlayConfig =
# setting: 1
# style:
# "opacity": 1
# "z-index": -1
#
# # Watch for a change in the saggital slice control setting.
# # When the slice index changes, then update the image and,
# # if selected, the overlay.
# #
# # TODO - address the volume-controls.jade TODO items.
# #
# $scope.$watch 'saggitalView.slice', (index) ->
# SliceDisplay.updateSlice($scope.imageIds[index].dicomImageId)
# if $scope.overlayIndex?
# Slice.updateOverlay($scope.imageIds[index].overlayIds,
# $scope.overlayIndex)
#
#
# # The overlayIndex scope variable is the overlay radio input
# # selection value in the format *modeling index*.*overlay index*,
# # e.g. the value '0.1' is the second overlay of the first modeling.
# # By default, no overlay is selected. When a radio button is
# # checked, then the overlayIndex variable is set and the watcher
# # below is triggered.
# #
# # If the overlayIndex scope variable is changed to a non-null value,
# # then parse the value and call the image selectOverlay(overlay)
# # function on the referenced overlay.
# # Otherwise, call the image deselectOverlay() function.
# $scope.$watch 'overlayIndex', (index) ->
# if index?
# # Parse and disaggregate the composite index.
# # Note: calling map with parseInt fails with a NaN second value
# # since both parseInt can include optional arguments
# # (cf. http://stackoverflow.com/questions/262427/javascript-arraymap-and-parseint).
# # The work-around is to call map with Number, which takes a
# # single string argument.
# #[mdlIndex, ovrIndex] = index.split('.').map(Number)
# # The selected modeling object.
# #modeling = $scope.overlayModelings[mdlIndex]
# # The select overlay label map in the selected modeling object.
# #overlay = modeling.overlays[ovrIndex]
# # Delegate to the image object.
# #$scope.volume.selectOverlay(overlay)
#
# # Move the overlay viewport to the front and update it -
# # Cornerstone prototype.
# $scope.overlayConfig.style['z-index'] = 1
# slice = $scope.saggitalView.slice
# $scope.slice.updateOverlay($scope.imageIds[slice].overlayIds,
# index)
# else
# #$scope.volume.deselectOverlay()
# # Move the overlay viewport to the back - Cornerstone prototype.
# $scope.overlayConfig.style['z-index'] = -1
# If the project is the default, then remove it from the URL.
ControllerHelper.cleanBrowserUrl($rootScope.project)
]