UNPKG

qiprofile

Version:

Quantitative Imaging Profile ( QiPr) web application

1,004 lines (870 loc) 38.6 kB
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>&Delta;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>&Delta;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) ]