var LocalViewer;
(function (LocalViewer) {
    LocalViewer.settings;
    LocalViewer.study;

    /**
    * Returns true if the viewer is running locally
    */
    function isLocalViewer() {
        return this.isNodeWebkitViewer() || this.isFileSystemViewer() || this.isPersonalAccelerator();
    }
    LocalViewer.isLocalViewer = isLocalViewer;

    /**
    * Returns true if the viewer is running in node-webkit
    */
    function isNodeWebkitViewer() {
        return typeof process !== "undefined";
    }
    LocalViewer.isNodeWebkitViewer = isNodeWebkitViewer;

    /**
    * Returns true if the viewer is running from the filesystem
    */
    function isFileSystemViewer() {
        return !isNodeWebkitViewer() && window.location.protocol.toLowerCase().indexOf("file") === 0;
    }
    LocalViewer.isFileSystemViewer = isFileSystemViewer;

    /**
    * Returns true if the viewer is running from the personal accelerator
    */
    function isPersonalAccelerator() {
        return this.personalAccelerator === true;
    }
    LocalViewer.isPersonalAccelerator = isPersonalAccelerator;

    /**
    * Returns true if the viewer is running as a local viewer, but not the personal accelerator
    */
    function isStandardLocalViewer() {
        return this.isLocalViewer() && !this.isPersonalAccelerator();
    }
    LocalViewer.isStandardLocalViewer = isStandardLocalViewer;

    /**
    * A helper function to mock the getStudyList call
    */
    function makePrior(study) {
        return {
            study_uid: study.studyAttributes.queryObject.studyUid.value,
            phi_namespace: study.studyAttributes.queryObject.phiNamespace.value,
            storage_namespace: study.studyAttributes.queryObject.storageNamespace.value,
            accession_number: study.studyAttributes.accessionNumber.value,
            study_description: study.studyAttributes.studyDescription,
            study_date: "",
            study_date_value: study.studyAttributes.studyCreateDate,
            prior_number: 0,
            thin: 0,
            uuid: ""
        };
    }
    LocalViewer.makePrior = makePrior;
})(LocalViewer || (LocalViewer = {}));
///<reference path="LocalViewer.ts" />
///<reference path="Query.ts" />
var Rendering;
(function (Rendering) {
    /**
    * Rendering mode types
    */
    (function (RenderingMode) {
        RenderingMode[RenderingMode["Canvas"] = 0] = "Canvas";
        RenderingMode[RenderingMode["WebGL"] = 1] = "WebGL";
        RenderingMode[RenderingMode["Simple"] = 2] = "Simple";
    })(Rendering.RenderingMode || (Rendering.RenderingMode = {}));
    var RenderingMode = Rendering.RenderingMode;

    /**
    * Get the rendering mode based on the option in the URL hash
    */
    function getRenderingMode(size) {
        if (typeof size === "undefined") { size = 0; }
        var mode = Query.findParameter(window.location, "mode");

        if (!mode) {
            return getDefaultRenderingMode(size);
        } else if (mode === "webgl" && !isWebGLSupported(size)) {
            return getDefaultRenderingMode();
        }

        return toRenderingMode(mode);
    }
    Rendering.getRenderingMode = getRenderingMode;

    function toRenderingMode(s) {
        switch (s) {
            case "webgl":
                return 1 /* WebGL */;
            case "canvas":
                return 0 /* Canvas */;
            case "simple":
                return 2 /* Simple */;
            default:
                return getDefaultRenderingMode();
        }
    }
    Rendering.toRenderingMode = toRenderingMode;

    /**
    * Get a default rendering mode for the environment
    */
    function getDefaultRenderingMode(size) {
        if (typeof size === "undefined") { size = 0; }
        if (LocalViewer.isLocalViewer()) {
            if (LocalViewer.isNodeWebkitViewer()) {
                return 0 /* Canvas */;
            } else if (LocalViewer.isPersonalAccelerator()) {
                // removing personal accelerator webgl support until issues resolved
                // if (Browser.isIE() || Browser.isEdge()) {  // performance issues in IE11 & Edge
                return 0 /* Canvas */;
                // } else {
                //     return isWebGLSupported(size) ? RenderingMode.WebGL : RenderingMode.Canvas;
                // }
            } else {
                return 2 /* Simple */;
            }
        } else {
            return 0 /* Canvas */;
        }
    }
    Rendering.getDefaultRenderingMode = getDefaultRenderingMode;

    var webGLSupported;

    /**
    * Check if WebGL is supported
    */
    function isWebGLSupported(size) {
        if (typeof size === "undefined") { size = 0; }
        if (size > 0) {
            return isWebGLSupportedImpl(size);
        }

        if (webGLSupported === undefined) {
            return webGLSupported = isWebGLSupportedImpl(size);
        }

        return webGLSupported;
    }
    Rendering.isWebGLSupported = isWebGLSupported;

    function isWebGLSupportedImpl(size) {
        var canvas = document.createElement("canvas");

        var gl = null;

        try  {
            gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");

            if (gl) {
                if (gl.getParameter(gl.MAX_TEXTURE_SIZE) >= size) {
                    // Destroy webgl context explicitly. Not wait for GC cleaning up.
                    var ext = gl.getExtension('WEBGL_lose_context');
                    if (ext) {
                        ext.loseContext();
                    }

                    gl = null;
                    canvas = null;
                    return true;
                } else {
                    console.error("MAX_TEXTURE_SIZE too small, cannot use WebGL mode.");
                }
            }
        } catch (e) {
        }
        gl = null;
        canvas = null;
        return false;
    }
    Rendering.isWebGLSupportedImpl = isWebGLSupportedImpl;
})(Rendering || (Rendering = {}));
/**
* This module defines optional values
*/
var Maybe;
(function (_Maybe) {
    

    /**
    * A missing value
    */
    function Nothing() {
        return {
            ctor: "Maybe.Nothing"
        };
    }
    _Maybe.Nothing = Nothing;

    /**
    * Nothing if the argument is null, Just otherwise
    */
    function fromNull(t) {
        return t !== null ? Just(t) : Nothing();
    }
    _Maybe.fromNull = fromNull;

    /**
    * A provided value
    */
    function Just(t) {
        return {
            ctor: "Maybe.Just",
            value: t
        };
    }
    _Maybe.Just = Just;

    /**
    * Deconstruct an optional value
    */
    function maybe(m, r, f) {
        if (m.ctor === "Maybe.Nothing") {
            return r;
        } else {
            return f(m.value);
        }
    }
    _Maybe.maybe = maybe;

    /**
    * Test if an optional value is specified
    */
    function hasValue(m) {
        return maybe(m, false, function (_) {
            return true;
        });
    }
    _Maybe.hasValue = hasValue;

    /**
    * Map a function over optional values
    */
    function fmap(m, f) {
        return maybe(m, Nothing(), function (s) {
            return Just(f(s));
        });
    }
    _Maybe.fmap = fmap;

    /**
    * Unwrap an optional value, providing a default in case the value is missing
    */
    function fromMaybe(m, t) {
        return maybe(m, t, function (value) {
            return value;
        });
    }
    _Maybe.fromMaybe = fromMaybe;

    /**
    * Unwrap an optional value unsafely
    */
    function fromJust(m) {
        if (m.ctor === "Maybe.Nothing") {
            throw new Error("fromJust(Nothing)");
        }
        return m.value;
    }
    _Maybe.fromJust = fromJust;

    /**
    * Collect Justs from an array of Maybes.
    */
    function catMaybes(ms) {
        var result = [];
        _.each(ms, function (m) {
            if (hasValue(m)) {
                result.push(m.value);
            }
        });
        return result;
    }
    _Maybe.catMaybes = catMaybes;
})(Maybe || (Maybe = {}));
///<reference path="../libs/Maybe.ts" />
var HangingProtocols;
(function (HangingProtocols) {
    (function (USFilter) {
        USFilter[USFilter["TwoDImaging"] = 0x001] = "TwoDImaging";
        USFilter[USFilter["MMode"] = 0x002] = "MMode";
        USFilter[USFilter["CWDoppler"] = 0x004] = "CWDoppler";
        USFilter[USFilter["PWDoppler"] = 0x008] = "PWDoppler";
        USFilter[USFilter["ColorDoppler"] = 0x010] = "ColorDoppler";
        USFilter[USFilter["ColorMMode"] = 0x020] = "ColorMMode";
        USFilter[USFilter["ThreeDRendering"] = 0x040] = "ThreeDRendering";
        USFilter[USFilter["ColorPowerMode"] = 0x100] = "ColorPowerMode";
        USFilter[USFilter["TissueCharacterization"] = 0x20] = "TissueCharacterization";
        USFilter[USFilter["SpatiallyRelatedFrames"] = 0x400] = "SpatiallyRelatedFrames";
    })(HangingProtocols.USFilter || (HangingProtocols.USFilter = {}));
    var USFilter = HangingProtocols.USFilter;

    HangingProtocols.CURRENT_HP_VERSION = 2;

    HangingProtocols.MIN_HP_VERSION = 1;
})(HangingProtocols || (HangingProtocols = {}));
///<reference path="../libs/LocalViewer.ts" />
///<reference path="../libs/RenderingMode.ts" />
///<reference path="HangingProtocols.ts" />
var __extends = this.__extends || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
/**
* Types used throughout the application. Note many single values are wrapped in their own types. This is to
* prevent errors like passing a study UID when a series UID is expected. Methods using these values should
* try to use the strongly-typed versions of the data.
*/
var Classes;
(function (Classes) {
    /**
    * Owner ID, identifies the owning window
    */
    var OwnerId = (function () {
        function OwnerId(value) {
            this.value = value;
        }
        return OwnerId;
    })();
    Classes.OwnerId = OwnerId;

    /**
    * Session ID, aka. authentication token
    */
    var SessionId = (function () {
        function SessionId(value) {
            this.value = value;
        }
        return SessionId;
    })();
    Classes.SessionId = SessionId;

    /**
    * Instance UID
    */
    var InstanceUid = (function () {
        function InstanceUid(value) {
            this.value = value;
        }
        return InstanceUid;
    })();
    Classes.InstanceUid = InstanceUid;

    /**
    * Series UID
    */
    var SeriesUid = (function () {
        function SeriesUid(value) {
            this.value = value;
        }
        return SeriesUid;
    })();
    Classes.SeriesUid = SeriesUid;

    /**
    * Attachment ID
    */
    var AttachmentID = (function () {
        function AttachmentID(value) {
            this.value = value;
        }
        return AttachmentID;
    })();
    Classes.AttachmentID = AttachmentID;

    var AttachmentVersion = (function () {
        function AttachmentVersion(value) {
            this.value = value;
        }
        return AttachmentVersion;
    })();
    Classes.AttachmentVersion = AttachmentVersion;

    var AttachmentPhiNamespace = (function () {
        function AttachmentPhiNamespace(value) {
            this.value = value;
        }
        return AttachmentPhiNamespace;
    })();
    Classes.AttachmentPhiNamespace = AttachmentPhiNamespace;

    /**
    * Radiology Report ID
    */
    var ReportID = (function () {
        function ReportID(value) {
            this.value = value;
        }
        return ReportID;
    })();
    Classes.ReportID = ReportID;

    /**
    * UUID for a study action (routing rule)
    */
    var StudyActionID = (function () {
        function StudyActionID(value) {
            this.value = value;
        }
        return StudyActionID;
    })();
    Classes.StudyActionID = StudyActionID;

    /**
    * An email address
    */
    var EmailAddress = (function () {
        function EmailAddress(value) {
            this.value = value;
        }
        return EmailAddress;
    })();
    Classes.EmailAddress = EmailAddress;

    /**
    * Key Image ID
    */
    var KeyImageId = (function () {
        function KeyImageId(value) {
            this.value = value;
        }
        return KeyImageId;
    })();
    Classes.KeyImageId = KeyImageId;

    /**
    * Study UID
    */
    var StudyUid = (function () {
        function StudyUid(value) {
            this.value = value;
        }
        return StudyUid;
    })();
    Classes.StudyUid = StudyUid;

    /**
    * Study UUID
    */
    var StudyUUID = (function () {
        function StudyUUID(value) {
            this.value = value;
        }
        return StudyUUID;
    })();
    Classes.StudyUUID = StudyUUID;

    /**
    * Storage Namespace
    */
    var StorageNamespace = (function () {
        function StorageNamespace(value) {
            this.value = value;
        }
        return StorageNamespace;
    })();
    Classes.StorageNamespace = StorageNamespace;

    /**
    * PHI Namespace
    */
    var PhiNamespace = (function () {
        function PhiNamespace(value) {
            this.value = value;
        }
        return PhiNamespace;
    })();
    Classes.PhiNamespace = PhiNamespace;

    /**
    * Image Version
    */
    var ImageVersion = (function () {
        function ImageVersion(value) {
            this.value = value;
        }
        return ImageVersion;
    })();
    Classes.ImageVersion = ImageVersion;

    /**
    * Frame Number
    */
    var FrameNumber = (function () {
        function FrameNumber(value) {
            this.value = value;
        }
        return FrameNumber;
    })();
    Classes.FrameNumber = FrameNumber;

    /**
    * Patient ID
    */
    var PatientID = (function () {
        function PatientID(value) {
            this.value = value;
        }
        return PatientID;
    })();
    Classes.PatientID = PatientID;

    /**
    * Accession Number
    */
    var AccessionNumber = (function () {
        function AccessionNumber(value) {
            this.value = value;
        }
        return AccessionNumber;
    })();
    Classes.AccessionNumber = AccessionNumber;

    /**
    * A key image
    */
    var KeyImage = (function () {
        function KeyImage() {
        }
        return KeyImage;
    })();
    Classes.KeyImage = KeyImage;

    /**
    * Terminology code
    */
    var Term = (function () {
        function Term(code, defaultValue) {
            this.code = code;
            this.defaultValue = defaultValue;
        }
        return Term;
    })();
    Classes.Term = Term;

    /**
    * Language code
    */
    var Language = (function () {
        function Language(code) {
            this.code = code;
        }
        return Language;
    })();
    Classes.Language = Language;

    /**
    * Services annotation id
    */
    var AnnotationId = (function () {
        function AnnotationId(value) {
            this.value = value;
        }
        return AnnotationId;
    })();
    Classes.AnnotationId = AnnotationId;

    /**
    * Base type for all exceptions used in the application
    */
    var Exception = (function () {
        function Exception(message) {
            this.message = message;
        }
        return Exception;
    })();
    Classes.Exception = Exception;

    /**
    * The query string was invalid
    */
    var InvalidQueryStringException = (function (_super) {
        __extends(InvalidQueryStringException, _super);
        function InvalidQueryStringException() {
            _super.apply(this, arguments);
        }
        return InvalidQueryStringException;
    })(Exception);
    Classes.InvalidQueryStringException = InvalidQueryStringException;

    /**
    * No SID was found
    */
    var ExpectedSIDException = (function (_super) {
        __extends(ExpectedSIDException, _super);
        function ExpectedSIDException() {
            _super.apply(this, arguments);
        }
        return ExpectedSIDException;
    })(Exception);
    Classes.ExpectedSIDException = ExpectedSIDException;

    /**
    * An argument was invalid
    */
    var InvalidArgumentException = (function (_super) {
        __extends(InvalidArgumentException, _super);
        function InvalidArgumentException() {
            _super.apply(this, arguments);
        }
        return InvalidArgumentException;
    })(Exception);
    Classes.InvalidArgumentException = InvalidArgumentException;

    /**
    * Represents a study as described by storage - namespaces and UID.
    */
    var QueryObject = (function () {
        function QueryObject(storageNamespace, phiNamespace, studyUid) {
            this.storageNamespace = storageNamespace;
            this.phiNamespace = phiNamespace;
            this.studyUid = studyUid;
        }
        QueryObject.prototype.toString = function () {
            return this.storageNamespace.value + "/" + this.studyUid.value + "/" + this.phiNamespace.value;
        };
        return QueryObject;
    })();
    Classes.QueryObject = QueryObject;

    /**
    * The various document types supported by storage
    */
    (function (DocumentType) {
        DocumentType[DocumentType["PDF"] = 0] = "PDF";
        DocumentType[DocumentType["Video"] = 1] = "Video";
    })(Classes.DocumentType || (Classes.DocumentType = {}));
    var DocumentType = Classes.DocumentType;

    /**
    * The various image types supported by storage
    */
    (function (ImageType) {
        ImageType[ImageType["Thumbnail"] = 0] = "Thumbnail";
        ImageType[ImageType["FullResolution"] = 1] = "FullResolution";
        ImageType[ImageType["Diagnostic"] = 2] = "Diagnostic";
        ImageType[ImageType["FullResolutionHD"] = 3] = "FullResolutionHD";
    })(Classes.ImageType || (Classes.ImageType = {}));
    var ImageType = Classes.ImageType;

    (function (ImageTypeLoaded) {
        ImageTypeLoaded[ImageTypeLoaded["None"] = 0] = "None";
        ImageTypeLoaded[ImageTypeLoaded["Attributes"] = 1] = "Attributes";
        ImageTypeLoaded[ImageTypeLoaded["Thumbnail"] = 2] = "Thumbnail";
        ImageTypeLoaded[ImageTypeLoaded["Diagnostic"] = 3] = "Diagnostic";
    })(Classes.ImageTypeLoaded || (Classes.ImageTypeLoaded = {}));
    var ImageTypeLoaded = Classes.ImageTypeLoaded;

    /**
    * Helper methods for working with image types
    */
    var ImageTypes = (function () {
        function ImageTypes() {
        }
        /**
        * Convert an image type into a value which can be used in a storage request URI
        */
        ImageTypes.toUriComponent = function (type) {
            switch (type) {
                case 0 /* Thumbnail */:
                    return "/thumbnail";
                case 2 /* Diagnostic */:
                    return "/diagnostic";
                case 3 /* FullResolutionHD */:
                    return "/diagnostic";
                case 1 /* FullResolution */:
                default:
                    if (LocalViewer.isLocalViewer() && Rendering.getRenderingMode() === 2 /* Simple */) {
                        return "/hd";
                    } else {
                        return "";
                    }
            }
        };
        return ImageTypes;
    })();
    Classes.ImageTypes = ImageTypes;

    Classes.CURRENT_SETTINGS_VERSION = 1;

    Classes.MIN_SETTINGS_VERSION = 1;

    

    

    /**
    * Toolbar button types
    */
    (function (ToolbarButton) {
        ToolbarButton[ToolbarButton["Transform"] = 0] = "Transform";
        ToolbarButton[ToolbarButton["Zoom"] = 1] = "Zoom";
        ToolbarButton[ToolbarButton["Move"] = 2] = "Move";
        ToolbarButton[ToolbarButton["Flip"] = 3] = "Flip";
        ToolbarButton[ToolbarButton["Rotate"] = 4] = "Rotate";
        ToolbarButton[ToolbarButton["Scroll"] = 5] = "Scroll";
        ToolbarButton[ToolbarButton["WindowLevel"] = 6] = "WindowLevel";
        ToolbarButton[ToolbarButton["Annotations"] = 7] = "Annotations";
        ToolbarButton[ToolbarButton["Probe"] = 8] = "Probe";
        ToolbarButton[ToolbarButton["Fit"] = 9] = "Fit";
        ToolbarButton[ToolbarButton["Reset"] = 10] = "Reset";
        ToolbarButton[ToolbarButton["ThreeD"] = 11] = "ThreeD";
        ToolbarButton[ToolbarButton["Layout"] = 12] = "Layout";
        ToolbarButton[ToolbarButton["Print"] = 13] = "Print";
        ToolbarButton[ToolbarButton["Thumbs"] = 14] = "Thumbs";
        ToolbarButton[ToolbarButton["TextAnnotations"] = 15] = "TextAnnotations";
        ToolbarButton[ToolbarButton["Measurements"] = 16] = "Measurements";
        ToolbarButton[ToolbarButton["Invert"] = 17] = "Invert";
        ToolbarButton[ToolbarButton["Export"] = 18] = "Export";
        ToolbarButton[ToolbarButton["Cine"] = 19] = "Cine";
        ToolbarButton[ToolbarButton["Record"] = 20] = "Record";
        ToolbarButton[ToolbarButton["KeyImage"] = 21] = "KeyImage";
        ToolbarButton[ToolbarButton["Settings"] = 22] = "Settings";
        ToolbarButton[ToolbarButton["Actions"] = 23] = "Actions";
        ToolbarButton[ToolbarButton["SavePreset"] = 24] = "SavePreset";
        ToolbarButton[ToolbarButton["DeleteImage"] = 25] = "DeleteImage";
        ToolbarButton[ToolbarButton["DeleteSeries"] = 26] = "DeleteSeries";
        ToolbarButton[ToolbarButton["FlipV"] = 27] = "FlipV";
        ToolbarButton[ToolbarButton["ResetWindowLevel"] = 28] = "ResetWindowLevel";
        ToolbarButton[ToolbarButton["WindowLevelPresets"] = 29] = "WindowLevelPresets";
        ToolbarButton[ToolbarButton["SelectAnnotation"] = 30] = "SelectAnnotation";
        ToolbarButton[ToolbarButton["DeleteAnnotation"] = 31] = "DeleteAnnotation";
        ToolbarButton[ToolbarButton["FillAnnotation"] = 32] = "FillAnnotation";
        ToolbarButton[ToolbarButton["ExportGSPS"] = 33] = "ExportGSPS";
        ToolbarButton[ToolbarButton["Line"] = 34] = "Line";
        ToolbarButton[ToolbarButton["Arrow"] = 35] = "Arrow";
        ToolbarButton[ToolbarButton["Cobb"] = 36] = "Cobb";
        ToolbarButton[ToolbarButton["Rectangle"] = 37] = "Rectangle";
        ToolbarButton[ToolbarButton["Ellipse"] = 38] = "Ellipse";
        ToolbarButton[ToolbarButton["Text"] = 39] = "Text";
        ToolbarButton[ToolbarButton["ReferenceLines"] = 40] = "ReferenceLines";
        ToolbarButton[ToolbarButton["LinkedSeries"] = 41] = "LinkedSeries";
        ToolbarButton[ToolbarButton["PlaneLocalization"] = 42] = "PlaneLocalization";
        ToolbarButton[ToolbarButton["Maximize"] = 43] = "Maximize";
        ToolbarButton[ToolbarButton["NewWindow"] = 44] = "NewWindow";
        ToolbarButton[ToolbarButton["LayoutButtons"] = 45] = "LayoutButtons";
        ToolbarButton[ToolbarButton["ExportPNG"] = 46] = "ExportPNG";
        ToolbarButton[ToolbarButton["SecondaryCapture"] = 47] = "SecondaryCapture";
        ToolbarButton[ToolbarButton["Metadata"] = 48] = "Metadata";
        ToolbarButton[ToolbarButton["Play"] = 49] = "Play";
        ToolbarButton[ToolbarButton["Faster"] = 50] = "Faster";
        ToolbarButton[ToolbarButton["Slower"] = 51] = "Slower";
        ToolbarButton[ToolbarButton["FPSLabel"] = 52] = "FPSLabel";
        ToolbarButton[ToolbarButton["Angle"] = 53] = "Angle";
        ToolbarButton[ToolbarButton["FreeRotate"] = 54] = "FreeRotate";
        ToolbarButton[ToolbarButton["GSPSLayers"] = 55] = "GSPSLayers";
        ToolbarButton[ToolbarButton["Magnify"] = 56] = "Magnify";
        ToolbarButton[ToolbarButton["Anonymize"] = 57] = "Anonymize";
        ToolbarButton[ToolbarButton["Radius"] = 58] = "Radius";
        ToolbarButton[ToolbarButton["AnonymizeStudy"] = 59] = "AnonymizeStudy";
        ToolbarButton[ToolbarButton["AnonymizeImage"] = 60] = "AnonymizeImage";
        ToolbarButton[ToolbarButton["PreviousSeriesSet"] = 61] = "PreviousSeriesSet";
        ToolbarButton[ToolbarButton["NextSeriesSet"] = 62] = "NextSeriesSet";
        ToolbarButton[ToolbarButton["CineAll"] = 63] = "CineAll";
        ToolbarButton[ToolbarButton["DuplicateAnnotation"] = 64] = "DuplicateAnnotation";
        ToolbarButton[ToolbarButton["CopyAnnotation"] = 65] = "CopyAnnotation";
        ToolbarButton[ToolbarButton["PasteAnnotation"] = 66] = "PasteAnnotation";
        ToolbarButton[ToolbarButton["StartMeeting"] = 67] = "StartMeeting";
        ToolbarButton[ToolbarButton["UseDiagnosticQuality"] = 68] = "UseDiagnosticQuality";
        ToolbarButton[ToolbarButton["RecordAudio"] = 69] = "RecordAudio";
        ToolbarButton[ToolbarButton["MPR"] = 70] = "MPR";
        ToolbarButton[ToolbarButton["Enhance"] = 71] = "Enhance";
        ToolbarButton[ToolbarButton["ExportAllPNG"] = 72] = "ExportAllPNG";
        ToolbarButton[ToolbarButton["DetectWindowLevel"] = 73] = "DetectWindowLevel";
        ToolbarButton[ToolbarButton["AnnotationsDetailToggle"] = 74] = "AnnotationsDetailToggle";
        ToolbarButton[ToolbarButton["Blank"] = 75] = "Blank";
        ToolbarButton[ToolbarButton["WindowLevelDirect"] = 76] = "WindowLevelDirect";
        ToolbarButton[ToolbarButton["MouseToolSettings"] = 77] = "MouseToolSettings";
        ToolbarButton[ToolbarButton["ShowOnlyKeyImages"] = 78] = "ShowOnlyKeyImages";
        ToolbarButton[ToolbarButton["ExportAVI"] = 79] = "ExportAVI";
        ToolbarButton[ToolbarButton["ExportMP4"] = 80] = "ExportMP4";
        ToolbarButton[ToolbarButton["ExportSeries"] = 81] = "ExportSeries";
        ToolbarButton[ToolbarButton["NextStudy"] = 82] = "NextStudy";
        ToolbarButton[ToolbarButton["PreviousStudy"] = 83] = "PreviousStudy";
        ToolbarButton[ToolbarButton["Ruler"] = 84] = "Ruler";
        ToolbarButton[ToolbarButton["Circle"] = 85] = "Circle";
        ToolbarButton[ToolbarButton["Square"] = 86] = "Square";
        ToolbarButton[ToolbarButton["CoLocalization"] = 87] = "CoLocalization";
        ToolbarButton[ToolbarButton["OrthoAxes"] = 88] = "OrthoAxes";
        ToolbarButton[ToolbarButton["Label"] = 89] = "Label";
        ToolbarButton[ToolbarButton["NextStudyByMRN"] = 90] = "NextStudyByMRN";
        ToolbarButton[ToolbarButton["PreviousStudyByMRN"] = 91] = "PreviousStudyByMRN";
        ToolbarButton[ToolbarButton["StorePNG"] = 92] = "StorePNG";
        ToolbarButton[ToolbarButton["FemoralHead"] = 93] = "FemoralHead";
        ToolbarButton[ToolbarButton["ArrowAnnotate"] = 94] = "ArrowAnnotate";
        ToolbarButton[ToolbarButton["CircleAnnotate"] = 95] = "CircleAnnotate";
        ToolbarButton[ToolbarButton["EllipseAnnotate"] = 96] = "EllipseAnnotate";
        ToolbarButton[ToolbarButton["LineAnnotate"] = 97] = "LineAnnotate";
        ToolbarButton[ToolbarButton["RectangleAnnotate"] = 98] = "RectangleAnnotate";
        ToolbarButton[ToolbarButton["SquareAnnotate"] = 99] = "SquareAnnotate";
        ToolbarButton[ToolbarButton["PreviousImage"] = 100] = "PreviousImage";
        ToolbarButton[ToolbarButton["NextImage"] = 101] = "NextImage";
        ToolbarButton[ToolbarButton["LayoutSingleSeries"] = 102] = "LayoutSingleSeries";
        ToolbarButton[ToolbarButton["ColorTablePresets"] = 103] = "ColorTablePresets";
        ToolbarButton[ToolbarButton["UltrasoundRegions"] = 104] = "UltrasoundRegions";
        ToolbarButton[ToolbarButton["Polygon"] = 105] = "Polygon";
        ToolbarButton[ToolbarButton["Trace"] = 106] = "Trace";
        ToolbarButton[ToolbarButton["CalibrateLine"] = 107] = "CalibrateLine";
        ToolbarButton[ToolbarButton["SplitStudy"] = 108] = "SplitStudy";
        ToolbarButton[ToolbarButton["PlayRecording"] = 109] = "PlayRecording";
        ToolbarButton[ToolbarButton["StopPlayback"] = 110] = "StopPlayback";
        ToolbarButton[ToolbarButton["RewindPlayback"] = 111] = "RewindPlayback";
        ToolbarButton[ToolbarButton["FastForwardPlayback"] = 112] = "FastForwardPlayback";
        ToolbarButton[ToolbarButton["ExportStudy"] = 113] = "ExportStudy";
        ToolbarButton[ToolbarButton["ExportISO"] = 114] = "ExportISO";
        ToolbarButton[ToolbarButton["ExportLocalViewer"] = 115] = "ExportLocalViewer";
        ToolbarButton[ToolbarButton["ShowRecordings"] = 116] = "ShowRecordings";
        ToolbarButton[ToolbarButton["LoadReport"] = 117] = "LoadReport";
        ToolbarButton[ToolbarButton["Subtraction"] = 118] = "Subtraction";
        ToolbarButton[ToolbarButton["Propagate"] = 119] = "Propagate";
        ToolbarButton[ToolbarButton["PropagateAll"] = 120] = "PropagateAll";
        ToolbarButton[ToolbarButton["Stamp"] = 121] = "Stamp";
        ToolbarButton[ToolbarButton["PixelSpacingUser"] = 122] = "PixelSpacingUser";
        ToolbarButton[ToolbarButton["SliceSpacingUser"] = 123] = "SliceSpacingUser";
        ToolbarButton[ToolbarButton["RemoveImages"] = 124] = "RemoveImages";
        ToolbarButton[ToolbarButton["CropSeries"] = 125] = "CropSeries";
        ToolbarButton[ToolbarButton["SaveKeyImageLayout"] = 126] = "SaveKeyImageLayout";
        ToolbarButton[ToolbarButton["Area"] = 127] = "Area";
        ToolbarButton[ToolbarButton["ThresholdToArea"] = 128] = "ThresholdToArea";
        ToolbarButton[ToolbarButton["ShrinkWrapToArea"] = 129] = "ShrinkWrapToArea";
        ToolbarButton[ToolbarButton["ColorButtons"] = 130] = "ColorButtons";
        ToolbarButton[ToolbarButton["ShowAttachments"] = 131] = "ShowAttachments";
        ToolbarButton[ToolbarButton["ThresholdRangeToArea"] = 132] = "ThresholdRangeToArea";
        ToolbarButton[ToolbarButton["ReverseSeries"] = 133] = "ReverseSeries";
        ToolbarButton[ToolbarButton["UnweaveSeries"] = 134] = "UnweaveSeries";
        ToolbarButton[ToolbarButton["RearrangeSeries"] = 135] = "RearrangeSeries";
        ToolbarButton[ToolbarButton["PartSeries"] = 136] = "PartSeries";
        ToolbarButton[ToolbarButton["ResetStudy"] = 137] = "ResetStudy";
        ToolbarButton[ToolbarButton["MergeSeries"] = 138] = "MergeSeries";
        ToolbarButton[ToolbarButton["AnnotationsCreatedByOthersToggle"] = 139] = "AnnotationsCreatedByOthersToggle";
        ToolbarButton[ToolbarButton["ProstateTool"] = 140] = "ProstateTool";
        ToolbarButton[ToolbarButton["ShowAllGSPS"] = 141] = "ShowAllGSPS";
    })(Classes.ToolbarButton || (Classes.ToolbarButton = {}));
    var ToolbarButton = Classes.ToolbarButton;

    /**
    * Types of item which can appear on the toolbar
    */
    (function (ToolbarItemType) {
        ToolbarItemType[ToolbarItemType["Button"] = 0] = "Button";
        ToolbarItemType[ToolbarItemType["Group"] = 1] = "Group";
    })(Classes.ToolbarItemType || (Classes.ToolbarItemType = {}));
    var ToolbarItemType = Classes.ToolbarItemType;

    

    

    

    

    /**
    * Types of text annotations
    */
    (function (TextAnnotationType) {
        TextAnnotationType[TextAnnotationType["PatientName"] = 0] = "PatientName";
        TextAnnotationType[TextAnnotationType["PatientDOB"] = 1] = "PatientDOB";
        TextAnnotationType[TextAnnotationType["StudyDate"] = 2] = "StudyDate";
        TextAnnotationType[TextAnnotationType["PriorNumber"] = 3] = "PriorNumber";
        TextAnnotationType[TextAnnotationType["ImageNumber"] = 4] = "ImageNumber";
        TextAnnotationType[TextAnnotationType["ImageType"] = 5] = "ImageType";
        TextAnnotationType[TextAnnotationType["MeasurementCreator"] = 6] = "MeasurementCreator";
        TextAnnotationType[TextAnnotationType["ReferringPhysician"] = 7] = "ReferringPhysician";
        TextAnnotationType[TextAnnotationType["StudyDescription"] = 8] = "StudyDescription";
        TextAnnotationType[TextAnnotationType["SeriesDescription"] = 9] = "SeriesDescription";
        TextAnnotationType[TextAnnotationType["Zoom"] = 10] = "Zoom";
        TextAnnotationType[TextAnnotationType["Quality"] = 11] = "Quality";
        TextAnnotationType[TextAnnotationType["WindowLevel"] = 12] = "WindowLevel";
        TextAnnotationType[TextAnnotationType["RadiationMachineName"] = 13] = "RadiationMachineName";
        TextAnnotationType[TextAnnotationType["KVP"] = 14] = "KVP";
        TextAnnotationType[TextAnnotationType["Exposure"] = 15] = "Exposure";
        TextAnnotationType[TextAnnotationType["SliceThickness"] = 16] = "SliceThickness";
        TextAnnotationType[TextAnnotationType["PixelSpacingMeaning"] = 17] = "PixelSpacingMeaning";
        TextAnnotationType[TextAnnotationType["AccessionNumber"] = 18] = "AccessionNumber";
        TextAnnotationType[TextAnnotationType["CustomField"] = 19] = "CustomField";
        TextAnnotationType[TextAnnotationType["ImageLaterality"] = 20] = "ImageLaterality";
        TextAnnotationType[TextAnnotationType["ViewPosition"] = 21] = "ViewPosition";
        TextAnnotationType[TextAnnotationType["StationName"] = 22] = "StationName";
        TextAnnotationType[TextAnnotationType["OperatorsName"] = 23] = "OperatorsName";
        TextAnnotationType[TextAnnotationType["InstitutionName"] = 24] = "InstitutionName";
        TextAnnotationType[TextAnnotationType["InstitutionAddress"] = 25] = "InstitutionAddress";
        TextAnnotationType[TextAnnotationType["DetectorID"] = 26] = "DetectorID";
        TextAnnotationType[TextAnnotationType["PatientMRN"] = 27] = "PatientMRN";
        TextAnnotationType[TextAnnotationType["TagSeries"] = 28] = "TagSeries";
        TextAnnotationType[TextAnnotationType["TagImage"] = 29] = "TagImage";
        TextAnnotationType[TextAnnotationType["Subtraction"] = 30] = "Subtraction";
        TextAnnotationType[TextAnnotationType["CureMetrix"] = 31] = "CureMetrix";
        TextAnnotationType[TextAnnotationType["CADSRDensityFindings"] = 32] = "CADSRDensityFindings";
        TextAnnotationType[TextAnnotationType["CADSRCalcificationFindings"] = 33] = "CADSRCalcificationFindings";
        TextAnnotationType[TextAnnotationType["PaintMeasurementArea"] = 34] = "PaintMeasurementArea";
        TextAnnotationType[TextAnnotationType["PaintMeasurementMin"] = 35] = "PaintMeasurementMin";
        TextAnnotationType[TextAnnotationType["PaintMeasurementMax"] = 36] = "PaintMeasurementMax";
        TextAnnotationType[TextAnnotationType["PaintMeasurementMean"] = 37] = "PaintMeasurementMean";
        TextAnnotationType[TextAnnotationType["PaintMeasurementStDev"] = 38] = "PaintMeasurementStDev";
        TextAnnotationType[TextAnnotationType["PaintMeasurementLabel"] = 39] = "PaintMeasurementLabel";
        TextAnnotationType[TextAnnotationType["ProstateToolMeasurementLabel"] = 40] = "ProstateToolMeasurementLabel";
        TextAnnotationType[TextAnnotationType["ProstateToolMeasurementLength"] = 41] = "ProstateToolMeasurementLength";
        TextAnnotationType[TextAnnotationType["ProstateToolMeasurementWidth"] = 42] = "ProstateToolMeasurementWidth";
        TextAnnotationType[TextAnnotationType["ProstateToolMeasurementHeight"] = 43] = "ProstateToolMeasurementHeight";
        TextAnnotationType[TextAnnotationType["ProstateToolGroupMeasurement"] = 44] = "ProstateToolGroupMeasurement";
    })(Classes.TextAnnotationType || (Classes.TextAnnotationType = {}));
    var TextAnnotationType = Classes.TextAnnotationType;

    /**
    * Meaning of the pixel spacing
    */
    (function (PixelSpacingMeaning) {
        PixelSpacingMeaning[PixelSpacingMeaning["PatientGeometry"] = 0] = "PatientGeometry";
        PixelSpacingMeaning[PixelSpacingMeaning["AtImagingPlate"] = 1] = "AtImagingPlate";
    })(Classes.PixelSpacingMeaning || (Classes.PixelSpacingMeaning = {}));
    var PixelSpacingMeaning = Classes.PixelSpacingMeaning;

    /**
    * Positions of text annotations
    */
    (function (TextAnnotationPosition) {
        TextAnnotationPosition[TextAnnotationPosition["TopLeft"] = 0] = "TopLeft";
        TextAnnotationPosition[TextAnnotationPosition["BottomLeft"] = 1] = "BottomLeft";
        TextAnnotationPosition[TextAnnotationPosition["BottomRight"] = 2] = "BottomRight";
        TextAnnotationPosition[TextAnnotationPosition["TopRight"] = 3] = "TopRight";
    })(Classes.TextAnnotationPosition || (Classes.TextAnnotationPosition = {}));
    var TextAnnotationPosition = Classes.TextAnnotationPosition;

    Classes.CURRENT_MODALITY_SETTINGS_VERSION = 1;

    Classes.MIN_MODALITY_SETTINGS_VERSION = 1;

    /**
    * Positions of stacked series
    */
    (function (StackSeriesPosition) {
        StackSeriesPosition[StackSeriesPosition["None"] = 0] = "None";
        StackSeriesPosition[StackSeriesPosition["First"] = 1] = "First";
        StackSeriesPosition[StackSeriesPosition["Last"] = 2] = "Last";
    })(Classes.StackSeriesPosition || (Classes.StackSeriesPosition = {}));
    var StackSeriesPosition = Classes.StackSeriesPosition;

    

    

    

    

    

    

    

    

    

    

    

    /**
    * Left mouse button tools
    */
    (function (MouseTool) {
        MouseTool[MouseTool["Move"] = 0] = "Move";
        MouseTool[MouseTool["Scroll"] = 1] = "Scroll";
        MouseTool[MouseTool["Zoom"] = 2] = "Zoom";
        MouseTool[MouseTool["Window"] = 3] = "Window";
        MouseTool[MouseTool["Select"] = 4] = "Select";
        MouseTool[MouseTool["Measure"] = 5] = "Measure";
        MouseTool[MouseTool["Rectangle"] = 6] = "Rectangle";
        MouseTool[MouseTool["CobbAngle"] = 7] = "CobbAngle";
        MouseTool[MouseTool["Ellipse"] = 8] = "Ellipse";
        MouseTool[MouseTool["Localization"] = 9] = "Localization";
        MouseTool[MouseTool["Text"] = 10] = "Text";
        MouseTool[MouseTool["Probe"] = 11] = "Probe";
        MouseTool[MouseTool["Arrow"] = 12] = "Arrow";
        MouseTool[MouseTool["Angle"] = 13] = "Angle";
        MouseTool[MouseTool["FreeRotate"] = 14] = "FreeRotate";
        MouseTool[MouseTool["Magnify"] = 15] = "Magnify";
        MouseTool[MouseTool["Circle"] = 16] = "Circle";
        MouseTool[MouseTool["MPRRotate"] = 17] = "MPRRotate";
        MouseTool[MouseTool["DropCircle"] = 18] = "DropCircle";
        MouseTool[MouseTool["DropSquare"] = 19] = "DropSquare";
        MouseTool[MouseTool["OrthoAxes"] = 20] = "OrthoAxes";
        MouseTool[MouseTool["FemoralHead"] = 21] = "FemoralHead";
        MouseTool[MouseTool["ArrowAnnotate"] = 22] = "ArrowAnnotate";
        MouseTool[MouseTool["CircleAnnotate"] = 23] = "CircleAnnotate";
        MouseTool[MouseTool["EllipseAnnotate"] = 24] = "EllipseAnnotate";
        MouseTool[MouseTool["LineAnnotate"] = 25] = "LineAnnotate";
        MouseTool[MouseTool["RectangleAnnotate"] = 26] = "RectangleAnnotate";
        MouseTool[MouseTool["SquareAnnotate"] = 27] = "SquareAnnotate";
        MouseTool[MouseTool["Polygon"] = 28] = "Polygon";
        MouseTool[MouseTool["Trace"] = 29] = "Trace";
        MouseTool[MouseTool["CalibrateLine"] = 30] = "CalibrateLine";
        MouseTool[MouseTool["Stamp"] = 31] = "Stamp";
        MouseTool[MouseTool["Area"] = 32] = "Area";
        MouseTool[MouseTool["ProstateTool"] = 33] = "ProstateTool";
        MouseTool[MouseTool["None"] = 34] = "None";
    })(Classes.MouseTool || (Classes.MouseTool = {}));
    var MouseTool = Classes.MouseTool;

    /**
    * Helper methods for working with mouse tools
    */
    var MouseTools = (function () {
        function MouseTools() {
        }
        /**
        * Check if a tool is a measurement tool
        */
        MouseTools.isMeasurementTool = function (tool) {
            switch (tool) {
                case 4 /* Select */:
                case 5 /* Measure */:
                case 7 /* CobbAngle */:
                case 6 /* Rectangle */:
                case 8 /* Ellipse */:
                case 10 /* Text */:
                case 31 /* Stamp */:
                case 12 /* Arrow */:
                case 13 /* Angle */:
                case 16 /* Circle */:
                case 18 /* DropCircle */:
                case 19 /* DropSquare */:
                case 20 /* OrthoAxes */:
                case 21 /* FemoralHead */:
                case 22 /* ArrowAnnotate */:
                case 23 /* CircleAnnotate */:
                case 24 /* EllipseAnnotate */:
                case 25 /* LineAnnotate */:
                case 30 /* CalibrateLine */:
                case 26 /* RectangleAnnotate */:
                case 27 /* SquareAnnotate */:
                case 28 /* Polygon */:
                case 29 /* Trace */:
                case 32 /* Area */:
                case 33 /* ProstateTool */:
                    return true;
            }

            return false;
        };

        MouseTools.isAnnotationTool = function (tool, showTextOnDirected) {
            switch (tool) {
                case 10 /* Text */:
                case 31 /* Stamp */:
                case 22 /* ArrowAnnotate */:
                case 23 /* CircleAnnotate */:
                case 24 /* EllipseAnnotate */:
                case 25 /* LineAnnotate */:
                case 26 /* RectangleAnnotate */:
                case 27 /* SquareAnnotate */:
                    return true;
            }

            if (tool == 12 /* Arrow */ && !showTextOnDirected) {
                return true;
            }

            return false;
        };

        MouseTools.isCalibrationTool = function (tool) {
            switch (tool) {
                case 30 /* CalibrateLine */:
                    return true;
            }

            return false;
        };

        MouseTools.isGroupMeasurementTool = function (tool) {
            switch (tool) {
                case 33 /* ProstateTool */:
                    return true;
            }

            return false;
        };

        MouseTools.isPaintTool = function (tool) {
            switch (tool) {
                case 32 /* Area */:
                    return true;
            }

            return false;
        };

        /**
        * These tools "drop" a ROI at a clicked location
        * @param {Classes.MouseTool} tool
        * @returns {boolean}
        */
        MouseTools.isDropMeasurementTool = function (tool) {
            switch (tool) {
                case 18 /* DropCircle */:
                case 23 /* CircleAnnotate */:
                case 19 /* DropSquare */:
                case 27 /* SquareAnnotate */:
                    return true;
            }

            return false;
        };

        /**
        * These tools are sensitive to a click after an initial stroke
        * @param {Classes.MouseTool} tool
        * @returns {boolean}
        */
        MouseTools.isMultiClickMeasurementTool = function (tool) {
            switch (tool) {
                case 13 /* Angle */:
                case 21 /* FemoralHead */:
                case 28 /* Polygon */:
                    return true;
            }

            return false;
        };
        return MouseTools;
    })();
    Classes.MouseTools = MouseTools;

    /**
    * An immutable semaphore
    *
    * Useful in combination with a Subject when multiple parts of the application affect whether a condition is true.
    *
    * @see Subjects.Subject
    */
    var Resource = (function () {
        function Resource(value) {
            this.value = value;
        }
        /**
        * A resource with no consumers
        */
        Resource.unused = function () {
            return new Resource(0);
        };

        /**
        * Increment the number of consumers, and return the new resource
        */
        Resource.prototype.take = function () {
            return new Resource(this.value + 1);
        };

        /**
        * Decrement the number of consumers, and return the new resource
        */
        Resource.prototype.release = function () {
            return new Resource(this.value - 1);
        };

        /**
        * Test if the resource is being used
        */
        Resource.prototype.used = function () {
            return this.value > 0;
        };
        return Resource;
    })();
    Classes.Resource = Resource;
})(Classes || (Classes = {}));
///<reference path="../classes/Types.ts" />
/**
* Helper methods for working with query strings
*/
var Query;
(function (Query) {
    /**
    * Find a parameter in the hash or query portions of a URL
    *
    * @param {key} The name of the parameter to find
    * @returns The corresponding URI-decoded value, or null if the key is not found
    */
    function findParameter(location, key) {
        return findParameterIn(location.hash, key) || findParameterIn(location.search, key);
    }
    Query.findParameter = findParameter;

    /**
    * Find a parameter in a query string
    *
    * @param {key} The name of the parameter to find
    * @returns The corresponding URI-decoded value, or null if the key is not found
    */
    function findParameterIn(search, key) {
        var index = Math.max(search.indexOf("?" + key + "="), search.indexOf("&" + key + "="));

        if (index < 0) {
            return null;
        }

        var lastIndex = search.indexOf("&", index + 1);

        if (lastIndex < 0) {
            lastIndex = search.length;
        }

        return decodeURIComponent(search.substr(index + 2 + key.length, lastIndex - index - 2 - key.length));
    }
    Query.findParameterIn = findParameterIn;

    /**
    * Get the session ID from the query string, or null if the "sid" parameter is missing.
    */
    function getSessionId(location) {
        var querySid = Query.findParameter(location, "sid");

        if (querySid !== null) {
            return new Classes.SessionId(querySid);
        }

        return null;
    }
    Query.getSessionId = getSessionId;

    /**
    * Parse a hash into a route and its parameters
    *
    * The hash is of the form {route}/{storageNamespace}/{studyUid}/{phiNamespace}?param1=value&param2=value...
    * where {route} is one of "study" or "print".
    */
    function parseQueryString(location) {
        var hash = location.hash;

        var routeLength = hash.indexOf("/");

        if (routeLength < 0) {
            throw new Classes.InvalidQueryStringException("Invalid hash");
        }

        var route = hash.substring(1, routeLength);

        if (route === "study") {
            // Support ?route=... format
            route = findParameter(location, "route") || route;
        }

        if (!_.contains([
            "study",
            "print",
            "script",
            "meeting",
            "report",
            "keyimages",
            "mobile",
            "mpr"
        ], route)) {
            throw new Classes.InvalidQueryStringException("Unknown route");
        }

        var stop = hash.indexOf("?");

        if (stop < 0) {
            stop = hash.length;
        }

        var tailIndex = window.location.hash.indexOf("/");

        if (tailIndex < 0 || hash.charAt(tailIndex) !== '/') {
            throw new Classes.InvalidQueryStringException("Invalid hash: missing study identifier");
        }

        var study = decodeURIComponent(hash.substring(tailIndex + 1, stop));
        var parts = study.split("/", 3);

        if (parts.length != 3) {
            throw new Classes.InvalidQueryStringException("Invalid study ID");
        }

        var storageNamespace = new Classes.StorageNamespace(parts[0]);
        var studyUid = new Classes.StudyUid(parts[1]);
        var phiNamespace = new Classes.PhiNamespace(parts[2]);

        return {
            route: route,
            queryObject: new Classes.QueryObject(storageNamespace, phiNamespace, studyUid)
        };
    }
    Query.parseQueryString = parseQueryString;

    /**
    * Parse the path used by the personal accelerator.
    *
    * The path is of the form /session/{sid}/{storageNamespace}/{studyUID}/local.html?phinamespace=value&sid=value
    */
    function parsePersonalAcceleratorPath(location) {
        var path = location.pathname;
        var parts = path.split("/", 5);

        if (parts.length != 5) {
            throw new Classes.InvalidQueryStringException("Invalid study ID");
        }

        var storageNamespace = new Classes.StorageNamespace(parts[3]);
        var studyUid = new Classes.StudyUid(parts[4]);
        var phiNamespace = new Classes.PhiNamespace(Query.findParameter(location, "phinamespace"));

        return {
            route: "study",
            queryObject: new Classes.QueryObject(storageNamespace, phiNamespace, studyUid)
        };
    }
    Query.parsePersonalAcceleratorPath = parsePersonalAcceleratorPath;
})(Query || (Query = {}));
var Classes;
(function (Classes) {
    

    

    

    /**
    * Meeting data attached to a study
    */
    var StudyMeeting = (function () {
        function StudyMeeting() {
        }
        return StudyMeeting;
    })();
    Classes.StudyMeeting = StudyMeeting;

    var MeetingLink = (function () {
        function MeetingLink() {
        }
        return MeetingLink;
    })();
    Classes.MeetingLink = MeetingLink;

    /**
    * Meeting ID, identifies a meeting in services
    */
    var MeetingId = (function () {
        function MeetingId(value) {
            this.value = value;
        }
        return MeetingId;
    })();
    Classes.MeetingId = MeetingId;

    /**
    * Link ID
    */
    var MeetingLinkId = (function () {
        function MeetingLinkId(value) {
            this.value = value;
        }
        return MeetingLinkId;
    })();
    Classes.MeetingLinkId = MeetingLinkId;

    var RecordingLink = (function (_super) {
        __extends(RecordingLink, _super);
        function RecordingLink() {
            _super.apply(this, arguments);
        }
        return RecordingLink;
    })(MeetingLink);
    Classes.RecordingLink = RecordingLink;

    /**
    * Link ID
    */
    var RecordingLinkId = (function (_super) {
        __extends(RecordingLinkId, _super);
        function RecordingLinkId() {
            _super.apply(this, arguments);
        }
        return RecordingLinkId;
    })(MeetingLinkId);
    Classes.RecordingLinkId = RecordingLinkId;

    /**
    * User UUID
    */
    var UserUUID = (function () {
        function UserUUID(value) {
            this.value = value;
        }
        return UserUUID;
    })();
    Classes.UserUUID = UserUUID;

    

    
})(Classes || (Classes = {}));
///<reference path='../classes/Types.ts' />
var Models;
(function (Models) {
    /**
    * Study information from the /study/get call
    */
    var StudyStorageInfo = (function () {
        function StudyStorageInfo() {
            /**
            * A list of custom fields
            */
            this.customfields = [];
            /**
            * True if this study is cached in a local accelerator
            */
            this.localAccelerator = false;
        }
        return StudyStorageInfo;
    })();
    Models.StudyStorageInfo = StudyStorageInfo;

    

    

    /**
    * Attributes defined at the study level
    */
    var StudyAttributes = (function () {
        function StudyAttributes() {
            /**
            * A list of custom fields
            */
            this.customfields = [];
        }
        return StudyAttributes;
    })();
    Models.StudyAttributes = StudyAttributes;

    /**
    * A study and its series
    */
    var Study = (function () {
        function Study() {
            /**
            * The series in this study
            */
            this.series = [];
            /**
            * The original series in this study (before splitting multiframes)
            */
            this.originalSeries = [];
            /**
            * Study attachments
            */
            this.attachments = [];
            /**
            * Radiology reports
            */
            this.reports = [];
            /**
            * Structured report instances
            */
            this.structuredReports = [];
            /**
            * Study actions (routing rules)
            */
            this.actions = [];
            /**
            * A list of meetings in progress
            */
            this.meetings = [];
        }
        return Study;
    })();
    Models.Study = Study;

    /**
    * Attachment metadata
    */
    var Attachment = (function () {
        function Attachment() {
        }
        return Attachment;
    })();
    Models.Attachment = Attachment;

    /**
    * Customfield metadata
    */
    var CustomField = (function () {
        function CustomField() {
        }
        return CustomField;
    })();
    Models.CustomField = CustomField;

    /**
    * Report metadata
    */
    var Report = (function () {
        function Report() {
        }
        return Report;
    })();
    Models.Report = Report;

    /**
    * Study action
    */
    var StudyAction = (function () {
        function StudyAction() {
        }
        return StudyAction;
    })();
    Models.StudyAction = StudyAction;

    /**
    * Structured report metadata
    */
    var StructuredReport = (function () {
        function StructuredReport() {
        }
        return StructuredReport;
    })();
    Models.StructuredReport = StructuredReport;

    

    /**
    * Attributes defined at the series level
    */
    var SeriesAttributes = (function () {
        function SeriesAttributes() {
        }
        return SeriesAttributes;
    })();
    Models.SeriesAttributes = SeriesAttributes;

    

    /**
    * A series and its instances
    */
    var Series = (function () {
        function Series() {
            /**
            * The instances in this series
            */
            this.instances = [];
            this.loadedStatus = 0 /* None */;
            this.splitSelected = false;
        }
        return Series;
    })();
    Models.Series = Series;

    /**
    * Attributes defined at the instance level
    */
    var InstanceAttributes = (function () {
        function InstanceAttributes() {
            /**
            * A list of related presentation states
            */
            this.presentationStates = [];
            /**
            * A list of CADSR annotations
            */
            this.mesaurementsCADSR = [];
            /**
            * Indicates whether the most recent thumbnail load attempt failed for this image
            */
            this.mostRecentThumbnailLoadFailed = false;
            /**
            * Indicates whether the most recent hi-res load attempt failed for this image
            */
            this.mostRecentHiResLoadFailed = false;
            /**
            * True if this instance contains overlay data
            */
            this.containsOverlayData = false;
            /**
            * True if an overlay plane contains data
            */
            this.containsOverlayDataPlane = [];
        }
        return InstanceAttributes;
    })();
    Models.InstanceAttributes = InstanceAttributes;

    /**
    * An image along with its frames
    */
    var Instance = (function () {
        function Instance() {
        }
        return Instance;
    })();
    Models.Instance = Instance;

    var SeriesDocumentTypes = (function () {
        function SeriesDocumentTypes() {
        }
        return SeriesDocumentTypes;
    })();
    Models.SeriesDocumentTypes = SeriesDocumentTypes;
})(Models || (Models = {}));

/**
* Methods not provided on the String prototype
* @see String
*/
var Strings;
(function (Strings) {
    String.prototype.truncate = function (maxLength) {
        var str = this;
        if (str.length <= maxLength) {
            return str;
        } else {
            return str.substr(0, maxLength) + "...";
        }
    };
})(Strings || (Strings = {}));
///<reference path="../classes/Types.ts" />
///<reference path="../models/Study.ts" />
///<reference path="Strings.ts" />
/**
* Routes for service calls
*/
var Routes;
(function (Routes) {
    /**
    * Encode parameters as a query string
    */
    function encodeParameters(params) {
        if (params) {
            return "?" + _.map(params, function (p) {
                if (p.length == 1) {
                    return p[0];
                } else if (p.length == 2) {
                    return p[0] + "=" + encodeURIComponent(p[1]);
                } else {
                    throw new Error("encodeParameters: length");
                }
            }).join("&");
        }

        return "";
    }
    Routes.encodeParameters = encodeParameters;

    /**
    * Converts an object's properties to a list of parameter pairs.
    * @param params
    * @returns {string[][]}
    */
    function covertObjectToParams(params) {
        if (params) {
            var result = [];
            _.each(params, function (value, key) {
                result.push([key, value]);
            });

            return result;
        }

        return [];
    }
    Routes.covertObjectToParams = covertObjectToParams;

    /**
    * Session ID query parameter
    */
    function sid(sessionId) {
        return ["sid", sessionId.value];
    }
    Routes.sid = sid;

    /**
    * Study triplet query parameters
    */
    function studyTriplet(query) {
        return [
            ["study_uid", query.studyUid.value],
            ["storage_namespace", query.storageNamespace.value],
            ["phi_namespace", query.phiNamespace.value]
        ];
    }
    Routes.studyTriplet = studyTriplet;

    /**
    * Get user info
    */
    function GetUser(sessionId) {
        return "/api/v3/session/user" + encodeParameters([sid(sessionId)]);
    }
    Routes.GetUser = GetUser;

    /**
    * Get user permissions
    */
    function GetPermissions(sessionId, phiNamespace) {
        return "/api/v3/session/permissions" + encodeParameters([
            sid(sessionId),
            ["namespace_id", phiNamespace.value]
        ]);
    }
    Routes.GetPermissions = GetPermissions;

    function GetStoragePermissions(sessionId, namespace) {
        return "/api/v3/session/permissions" + encodeParameters([
            sid(sessionId),
            ["namespace_id", namespace.value]
        ]);
    }
    Routes.GetStoragePermissions = GetStoragePermissions;

    /**
    * Get user settings
    */
    function GetSettings(sessionId, key) {
        return "/api/v3/setting/get" + encodeParameters([
            sid(sessionId),
            ["key", key]
        ]);
    }
    Routes.GetSettings = GetSettings;

    /**
    * Save user settings
    */
    Routes.SaveSettings = "/api/v3/setting/set";

    /**
    * Load terminology
    */
    function Terminology(sessionId, tags, language, query) {
        return "/api/v3/terminology/tags" + encodeParameters([
            ["tags", tags.join(",")],
            ["language", language.code],
            sid(sessionId)
        ].concat(studyTriplet(query)));
    }
    Routes.Terminology = Terminology;

    /**
    * Load all terminology
    */
    function TerminologyAll(sessionId, language, query) {
        return "/api/v3/terminology/tags" + encodeParameters([["language", language.code], sid(sessionId)].concat(studyTriplet(query)));
    }
    Routes.TerminologyAll = TerminologyAll;

    /**
    * Get account settings
    */
    function GetAccountSettings(sessionId, query, fqdn) {
        var uri = "/api/v3/study/viewer/settings" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)));

        if (fqdn) {
            return "//" + fqdn + uri;
        } else {
            return uri;
        }
    }
    Routes.GetAccountSettings = GetAccountSettings;

    /**
    * Get a list of studies for a patient
    */
    function GetStudyList(sessionId, phiNamespace, patientId, accessionNumber, studyUuid, notStudyUuid, worklistFilter) {
        return "/api/v3/study/list" + encodeParameters([
            sid(sessionId),
            ["filter.phi_namespace.equals", phiNamespace.value],
            ["filter.patientid.equals", patientId.value]
        ].concat(accessionNumber ? [["filter.accession_number.equals", accessionNumber.value]] : []).concat(studyUuid ? [["filter.uuid.equals", studyUuid.value]] : []).concat(notStudyUuid ? [["filter.uuid.not_equals", notStudyUuid.value]] : []).concat(worklistFilter ? worklistFilter : []));
    }
    Routes.GetStudyList = GetStudyList;

    /**
    * Get a list of studies by uuid
    */
    function GetStudyListByUuid(sessionId, studyUuidList) {
        return "/api/v3/study/list" + encodeParameters([sid(sessionId)].concat([["filter.uuid.in", JSON.stringify(_.map(studyUuidList, function (uuid) {
                    return uuid.value;
                }))]]));
    }
    Routes.GetStudyListByUuid = GetStudyListByUuid;

    /**
    * study/get
    */
    function StudyStorageInfo(sessionId, query) {
        return "/api/v3/study/get" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)));
    }
    Routes.StudyStorageInfo = StudyStorageInfo;

    /**
    * study/get (with study UUID)
    * @param sessionId
    * @param uuid
    * @constructor
    */
    function StudyInfo(sessionId, uuid) {
        return "/api/v3/study/get" + encodeParameters([sid(sessionId), ["uuid", uuid.value]]);
    }
    Routes.StudyInfo = StudyInfo;

    /**
    * study/set
    */
    function StudySet(sessionId, query, tags) {
        return "/api/v3/study/set" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)).concat(_.map(tags, function (t) {
            return [
                "customfield-({g},{e})".replace("{g}", t.group.toString(16).padStart(4, "0")).replace("{e}", t.element.toString(16).padStart(4, "0")),
                t.value
            ];
        })));
    }
    Routes.StudySet = StudySet;

    /**
    * study/retrieve
    */
    function RetrieveStudy(sessionId, uuid) {
        return "/api/v3/study/retrieve" + encodeParameters([
            sid(sessionId),
            ["uuid", uuid.value]
        ]);
    }
    Routes.RetrieveStudy = RetrieveStudy;

    /**
    * study/audit
    */
    function StudyAudit(sessionId, uuid, action) {
        return "/api/v3/study/audit" + encodeParameters([
            sid(sessionId),
            ["uuid", uuid.value],
            ["action", action]
        ]);
    }
    Routes.StudyAudit = StudyAudit;

    /**
    * viewer/annotations/list
    */
    function ListAnnotations(sessionId, query) {
        var uri = "/api/v3/annotation/list" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)));

        return uri;
    }
    Routes.ListAnnotations = ListAnnotations;

    /**
    * viewer/annotations/get
    */
    function GetAnnotation(sessionId, query, id) {
        var uri = "/api/v3/annotation/get" + encodeParameters([
            sid(sessionId),
            ["uuid", id.value]
        ].concat(studyTriplet(query)));

        return uri;
    }
    Routes.GetAnnotation = GetAnnotation;

    /**
    * viewer/annotations/delete
    */
    function DeleteAnnotation(sessionId, query, id) {
        return "/api/v3/annotation/delete" + encodeParameters([
            sid(sessionId),
            ["uuid", id.value]
        ].concat(studyTriplet(query)));
    }
    Routes.DeleteAnnotation = DeleteAnnotation;

    /**
    * viewer/keyimage/list
    */
    function ListKeyImages(sessionId, query) {
        var uri = "/api/v3/keyimage/list" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)));

        return uri;
    }
    Routes.ListKeyImages = ListKeyImages;

    /**
    * viewer/keyimage/get
    */
    function GetKeyImage(sessionId, id) {
        return "/api/v3/keyimage/get" + encodeParameters([
            sid(sessionId),
            ["uuid", id.value]
        ]);
    }
    Routes.GetKeyImage = GetKeyImage;

    /**
    * viewer/keyimage/add
    */
    function AddKeyImage(sessionId, query, series, instance, frameNumber, version) {
        return "/api/v3/keyimage/add" + encodeParameters([
            sid(sessionId),
            ["series_uid", series.value],
            ["instance_uid", instance.value],
            ["frame_number", frameNumber.value.toString()],
            ["version", version.value]
        ].concat(studyTriplet(query)));
    }
    Routes.AddKeyImage = AddKeyImage;

    /**
    * viewer/keyimage/delete
    */
    function DeleteKeyImage(sessionId, id) {
        return "/api/v3/keyimage/delete" + encodeParameters([
            sid(sessionId),
            ["uuid", id.value]
        ]);
    }
    Routes.DeleteKeyImage = DeleteKeyImage;

    /**
    * study/manual/route
    */
    function RunRoutingRule(sessionId, studyId, routeId, email, message) {
        return "/api/v3/study/manual/route" + encodeParameters([
            sid(sessionId),
            ["uuid", studyId.value],
            ["route_id", routeId.value]
        ].concat(email ? [["email", email.value]] : []).concat(message ? [["message", message]] : []));
    }
    Routes.RunRoutingRule = RunRoutingRule;

    /**
    * log message
    */
    function AuditLog(error, bucket, userAgent, source, sessionId, studyId, seriesId) {
        return "/api/v3/audit/log" + encodeParameters([
            sid(sessionId),
            ["error", error],
            ["bucket", bucket],
            ["userAgent", userAgent],
            ["source", source]
        ].concat(studyId ? [["studyId", studyId.value]] : []).concat(seriesId ? [["seriesId", seriesId.value]] : []));
    }
    Routes.AuditLog = AuditLog;

    /**
    * log metric
    */
    function AuditMetric(name, metric, bucket, userAgent) {
        return "/api/v3/audit/log" + encodeParameters([
            ["name", name],
            ["bucket", bucket],
            ["userAgent", userAgent]
        ].concat(covertObjectToParams(metric)));
    }
    Routes.AuditMetric = AuditMetric;

    /**
    * meeting/add
    */
    Routes.StartMeeting = "/api/v3/meeting/add";

    /**
    * meeting/delete
    */
    Routes.EndMeeting = "/api/v3/meeting/delete";

    /**
    * meeting/set
    */
    Routes.UpdateMeeting = "/api/v3/meeting/set";

    /**
    * meeting/events/add
    */
    Routes.BroadcastEvent = "/api/v3/meeting/events/add";

    /**
    * meeting/ping
    */
    Routes.PingMeeting = "/api/v3/meeting/ping";

    /**
    * meeting/join
    */
    Routes.JoinMeeting = "/api/v3/meeting/join";

    /**
    * meeting/leave
    */
    Routes.LeaveMeeting = "/api/v3/meeting/leave";

    /**
    * meeting/presenter
    */
    Routes.ChangeMeetingPresenter = "/api/v3/meeting/presenter";

    /**
    * link/add
    */
    Routes.CreateMeetingLink = "/api/v3/link/add";

    /**
    * link/email
    * @type {string}
    */
    Routes.ShareLink = "/api/v3/link/mail";

    /**
    * accelerator/used
    */
    Routes.AcceleratorUsed = "/api/v3/accelerator/used";

    /**
    * annotation/add
    * @type {string}
    */
    Routes.AddAnnotation = "/api/v3/annotation/add";

    /**
    * annotation/set
    * @type {string}
    */
    Routes.SetAnnotation = "/api/v3/annotation/set";

    Routes.CreateScriptLink = "/api/v3/link/add";

    Routes.StudyPHIExtended = "/api/v3/study/phi/extended";

    /**
    * meeting/roster
    */
    function MeetingRoster(sessionId, meetingId) {
        return "/api/v3/meeting/roster" + encodeParameters([
            sid(sessionId),
            ["uuid", meetingId.value]
        ]);
    }
    Routes.MeetingRoster = MeetingRoster;

    /**
    * meeting/get
    */
    function GetMeeting(sessionId, meetingId) {
        return "/api/v3/meeting/get" + encodeParameters([
            sid(sessionId),
            ["uuid", meetingId.value]
        ]);
    }
    Routes.GetMeeting = GetMeeting;

    /**
    * meeting/list
    */
    function ListMeetings(sessionId, query) {
        return "/api/v3/meeting/list" + encodeParameters([
            sid(sessionId)
        ].concat(studyTriplet(query)));
    }
    Routes.ListMeetings = ListMeetings;

    /**
    * Storage address
    */
    function Storage(storageInfo, useServices, useMultipleHosts, canUseLocalAccelerator) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        if (typeof canUseLocalAccelerator === "undefined") { canUseLocalAccelerator = false; }
        if (LocalViewer.isLocalViewer()) {
            return "./";
        } else if (canUseLocalAccelerator && storageInfo.localAccelerator) {
            return LocalAccelerator.LOCAL_ACCELERATOR_PROTOCOL + LocalAccelerator.LOCAL_ACCELERATOR_HOSTNAME + ":" + LocalAccelerator.LOCAL_ACCELERATOR_PORT + "/api/v3/storage";
        } else if (useServices) {
            return "/api/v3/cac";
        } else if (!ImageHosts.acceleratorUsed() && 'withCredentials' in new XMLHttpRequest() && !Browser.isIE()) {
            // CORS is supported, connect directly to engine.
            return (ImageHosts.getImageHost(storageInfo.engine_fqdn) || "//" + storageInfo.engine_fqdn) + "/api/v3/storage";
        } else {
            // CORS is not supported, use /host proxy.
            if (useMultipleHosts) {
                if (ImageHosts.acceleratorUsed()) {
                    return ImageHosts.getImageHost(location.hostname) + "/host/" + storageInfo.engine_fqdn + "/api/v3/storage";
                } else {
                    return ImageHosts.getImageHost() + "/host/" + storageInfo.engine_fqdn + "/api/v3/storage";
                }
            } else {
                return "/host/" + storageInfo.engine_fqdn + "/api/v3/storage";
            }
        }
    }
    Routes.Storage = Storage;

    var LocalAccelerator = (function () {
        function LocalAccelerator() {
        }
        LocalAccelerator.LOCAL_ACCELERATOR_HOSTNAME = "local.ambrahealth.com";
        LocalAccelerator.LOCAL_ACCELERATOR_PROTOCOL = "https://";
        LocalAccelerator.LOCAL_ACCELERATOR_PORT = "8021";
        LocalAccelerator.LOCAL_ACCELERATOR_TRUE = 1;
        return LocalAccelerator;
    })();
    Routes.LocalAccelerator = LocalAccelerator;

    /**
    * Private counter which tracks the next host to use for an image request
    */
    var ImageHosts = (function () {
        function ImageHosts() {
        }
        ImageHosts.nextHost = function (imageMaxHosts, engine_fqdn) {
            ImageHosts.currentImageHostIndex = (ImageHosts.currentImageHostIndex + 1) % imageMaxHosts;

            // image_host_index is 1-based
            return "//image" + (ImageHosts.currentImageHostIndex + 1).toString() + "-" + engine_fqdn;
        };

        ImageHosts.acceleratorUsed = function () {
            if (ImageHosts.isAcceleratorUsed === undefined) {
                if (Main && Main.Main && Main.Main.acceleratorUsed !== undefined) {
                    ImageHosts.isAcceleratorUsed = Main.Main.acceleratorUsed;
                }
            }
            return ImageHosts.isAcceleratorUsed;
        };

        ImageHosts.corsSupported = function () {
            if (ImageHosts.isCorsSupported === undefined) {
                var image = new window.Image();
                var corsBrowser = image.crossOrigin !== undefined;
                var isIE = (window.navigator.userAgent.indexOf("MSIE") != -1) || (navigator.userAgent.match(/Trident\/7\./) != null);
                ImageHosts.isCorsSupported = (corsBrowser && !isIE);
            }
            return ImageHosts.isCorsSupported;
        };

        ImageHosts.getImageHost = function (engine_fqdn) {
            if (typeof engine_fqdn === "undefined") { engine_fqdn = ImageHosts.imageBaseHost; }
            if (ImageHosts.isHttp2 === undefined) {
                ImageHosts.isHttp2 = false;

                if (performance && (typeof performance.getEntries === "function")) {
                    var entries = performance.getEntries();
                    for (var i = 0; i < entries.length; i++) {
                        if (entries[i].nextHopProtocol === 'h2') {
                            ImageHosts.isHttp2 = true;
                            break;
                        }
                    }
                }
            }

            if (ImageHosts.isHttp2)
                return '//' + engine_fqdn;

            if (!isNaN(ImageHosts.imageMaxHostsString)) {
                var imageMaxHosts = parseInt(ImageHosts.imageMaxHostsString);

                if (ImageHosts.corsSupported() && imageMaxHosts > 0 && typeof (ImageHosts.imageBaseHost) === "string" && ImageHosts.imageBaseHost.length > 0 && ImageHosts.imageBaseHost !== "(none)") {
                    return ImageHosts.nextHost(imageMaxHosts, engine_fqdn);
                }
            }

            return "";
        };
        ImageHosts.currentImageHostIndex = 0;
        ImageHosts.imageMaxHostsString = $("meta[name=imagemaxhosts]").attr("content");
        ImageHosts.imageBaseHost = $("meta[name=imagebasehost]").attr("content");
        return ImageHosts;
    })();

    /**
    * Get study metadata
    */
    function Study(storageInfo, queryObject, useServices, useMultipleHosts, canUseLocalAccelerator) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        if (typeof canUseLocalAccelerator === "undefined") { canUseLocalAccelerator = false; }
        if (LocalViewer.isStandardLocalViewer()) {
            return Storage(storageInfo, useServices, useMultipleHosts, canUseLocalAccelerator);
        } else {
            return Storage(storageInfo, useServices, useMultipleHosts, canUseLocalAccelerator) + "/study/" + encodeURIComponent(queryObject.storageNamespace.value) + "/" + encodeURIComponent(queryObject.studyUid.value);
        }
    }
    Routes.Study = Study;

    /**
    * Get series metadata
    */
    function Series(storageInfo, queryObject, useServices, useMultipleHosts, seriesUid) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        if (typeof seriesUid === "undefined") { seriesUid = null; }
        return Study(storageInfo, queryObject, useServices, useMultipleHosts) + "/series/" + encodeURIComponent(seriesUid.value);
    }
    Routes.Series = Series;

    /**
    * Get study schema information
    */
    function StudySchema(sessionId, storageInfo, queryObject, useServices) {
        return Study(storageInfo, queryObject, useServices, false, false) + "/schema" + encodeParameters([
            ["phi_namespace", queryObject.phiNamespace.value],
            sid(sessionId)
        ]);
    }
    Routes.StudySchema = StudySchema;

    /**
    * Get study PHI information
    */
    function StudyPhi(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false, false, true) + "/phi" + encodeParameters([
            ["phi_namespace", queryObject.phiNamespace.value],
            sid(sessionId)
        ]);
    }
    Routes.StudyPhi = StudyPhi;

    /**
    * Get study tags
    */
    function StudyTag(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false, false, true) + "/tag" + encodeParameters([
            ["phi_namespace", queryObject.phiNamespace.value],
            sid(sessionId)
        ]);
    }
    Routes.StudyTag = StudyTag;

    /**
    * Root URI for an image
    */
    function Image(storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts, canUseLocalAccelerator) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        if (typeof canUseLocalAccelerator === "undefined") { canUseLocalAccelerator = false; }
        var uri;

        if (LocalViewer.isStandardLocalViewer()) {
            uri = Study(storageInfo, queryObject, useServices, useMultipleHosts);

            if (version) {
                uri += encodeURIComponent(version.value);
            }
        } else {
            uri = Study(storageInfo, queryObject, useServices, useMultipleHosts, canUseLocalAccelerator) + "/image/" + encodeURIComponent(instanceUid.value);

            if (version) {
                uri += "/version/" + encodeURIComponent(version.value);
            }
        }

        return uri;
    }
    Routes.Image = Image;

    /**
    * Get image data for a single frame
    */
    function ImageData(sessionId, storageInfo, queryObject, instanceUid, version, frameNumber, type, depth, useServices, useMultipleHosts, size) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        if (typeof size === "undefined") { size = 0; }
        return Image(storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts, true) + (LocalViewer.isStandardLocalViewer() ? "/f/" : "/frame/") + frameNumber.value + Classes.ImageTypes.toUriComponent(type) + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["depth", depth.toString()]
        ]) + ((size > 0) ? "&size=" + size : "");
    }
    Routes.ImageData = ImageData;

    /**
    * Get PDF data for encapsulated PDF
    */
    function PDFData(sessionId, storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        return Image(storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts) + '/pdf' + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.PDFData = PDFData;

    /**
    * Get Video data for encapsulated Video
    */
    function VideoData(sessionId, storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts) {
        if (typeof useMultipleHosts === "undefined") { useMultipleHosts = false; }
        return Image(storageInfo, queryObject, instanceUid, version, useServices, useMultipleHosts) + '/video' + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.VideoData = VideoData;

    /**
    * Get Video download endpoint
    */
    function VideoDownload(sessionId, storageInfo, queryObject, seriesUid, format) {
        return Series(storageInfo, queryObject, false, false, seriesUid) + "/video" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["format", format]
        ]);
    }
    Routes.VideoDownload = VideoDownload;

    /**
    * Download series
    */
    function SeriesDownload(sessionId, storageInfo, queryObject, seriesUid) {
        return Study(storageInfo, queryObject, false) + "/download" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["series_uid", seriesUid.value],
            ["bundle", "dicom"]
        ]);
    }
    Routes.SeriesDownload = SeriesDownload;

    /**
    * Download study
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param seriesUid
    * @constructor
    */
    function StudyDownload(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/download" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["bundle", "dicom"]
        ]);
    }
    Routes.StudyDownload = StudyDownload;

    /**
    * Download ISO
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param seriesUid
    * @constructor
    */
    function ISODownload(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/download" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["bundle", "iso"]
        ]);
    }
    Routes.ISODownload = ISODownload;

    /**
    * Download local viewer
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param seriesUid
    * @constructor
    */
    function LocalViewerDownload(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/download" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["bundle", Browser.viewerOS()]
        ]);
    }
    Routes.LocalViewerDownload = LocalViewerDownload;

    /**
    * Get attributes for a study (when the accelerator is present)
    */
    function StudyAttributes(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/attributes" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.StudyAttributes = StudyAttributes;

    /**
    * Get attributes for an instance
    */
    function ImageAttributes(sessionId, storageInfo, queryObject, instanceUid, version) {
        return Image(storageInfo, queryObject, instanceUid, version, false, false, true) + "/attribute" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.ImageAttributes = ImageAttributes;

    /**
    * Get all attributes for an instance
    */
    function ImageJSON(sessionId, storageInfo, queryObject, instanceUid, version) {
        return Image(storageInfo, queryObject, instanceUid, version, false, false, true) + "/json" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["all_dicom_values", "1"]
        ]);
    }
    Routes.ImageJSON = ImageJSON;

    /**
    * Get CAD SR annotations
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param instanceUid
    * @param version
    * @constructor
    */
    function CADSR(sessionId, storageInfo, queryObject, instanceUid, version) {
        return Image(storageInfo, queryObject, instanceUid, version, false, false, false) + "/cadsr" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.CADSR = CADSR;

    /**
    * Delete URI for an image
    */
    function DeleteImage(sessionId, storageInfo, queryObject, instanceUid, version) {
        var uri = Image(storageInfo, queryObject, instanceUid, null, false);

        return uri + encodeParameters([sid(sessionId)].concat(version ? [["version", version.value]] : []));
    }
    Routes.DeleteImage = DeleteImage;

    function DeleteAttachment(sessionId, storageInfo, queryObject, attachment) {
        return Attachment(sessionId, storageInfo, queryObject, attachment.id, attachment.phiNamespace, attachment.version);
    }
    Routes.DeleteAttachment = DeleteAttachment;

    /**
    * Get GSPS data
    */
    function GetGSPS(sessionId, storageInfo, queryObject, instanceUid, version) {
        return Image(storageInfo, queryObject, instanceUid, version, false, false, true) + "/gsps" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.GetGSPS = GetGSPS;

    /**
    * Video for a multiframe instance
    */
    function Multiframe(sessionId, storageInfo, queryObject, instanceUid, version) {
        return Image(storageInfo, queryObject, instanceUid, version, false) + "/multiframe" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["millisPerFrame=-1"]
        ]);
    }
    Routes.Multiframe = Multiframe;

    /**
    * Post a secondary capture image
    */
    function SecondaryCapture(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/capture" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.SecondaryCapture = SecondaryCapture;

    /**
    * Anonymize a series
    */
    function AnonymizeStudy(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/anonymize" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.AnonymizeStudy = AnonymizeStudy;

    /**
    * Split series out of study.
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param seriesUIDs
    * @constructor
    */
    function SplitStudy(sessionId, storageInfo, queryObject, seriesUIDs) {
        return Study(storageInfo, queryObject, false) + "/split" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["delete_series_from_original", "1"]
        ]) + "&series_uid=" + seriesUIDs.join(',');
    }
    Routes.SplitStudy = SplitStudy;

    function CropStudy(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/crop" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]]);
    }
    Routes.CropStudy = CropStudy;

    /**
    * Post GSPS data
    */
    function PutGSPS(sessionId, storageInfo, queryObject) {
        return Study(storageInfo, queryObject, false) + "/gsps" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.PutGSPS = PutGSPS;

    /**
    * Download an attachment
    */
    function Attachment(sessionId, storageInfo, queryObject, attachmentId, phiNamespace, version) {
        return Study(storageInfo, queryObject, false) + "/attachment/" + attachmentId.value + "/version/" + version.value + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.Attachment = Attachment;

    /**
    * Upload an attachment
    */
    function PostAttachment(sessionId, storageInfo, queryObject, contentType, uploadedBy, filename) {
        if (typeof filename === "undefined") { filename = "recorder.json"; }
        return Study(storageInfo, queryObject, false) + "/attachment/blob" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["content_type", contentType],
            ["uploaded_by", uploadedBy],
            ["filename", filename]
        ]);
    }
    Routes.PostAttachment = PostAttachment;

    /**
    * Upload an image attachment
    * @param {Classes.SessionId} sessionId
    * @param {Models.StudyStorageInfo} storageInfo
    * @param {Classes.QueryObject} queryObject
    * @param {string} uploadedBy
    * @returns {string}
    */
    function PostImageAttachment(sessionId, storageInfo, queryObject, uploadedBy, filename) {
        return Study(storageInfo, queryObject, false) + "/attachment" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value],
            ["content_type", "image/png"],
            ["uploaded_by", uploadedBy],
            ["filename", filename]
        ]);
    }
    Routes.PostImageAttachment = PostImageAttachment;

    /**
    * Download an attachment
    */
    function HL7Report(sessionId, studyId, reportId) {
        return "/report.html" + encodeParameters([
            sid(sessionId),
            ["study_uuid", studyId.value],
            ["uuid", reportId.value]
        ]);
    }
    Routes.HL7Report = HL7Report;

    /**
    * Download a structured report
    */
    function StructuredReport(sessionId, studyStorage, queryObject, id, version) {
        return Image(studyStorage, queryObject, id, version, false) + "/sr" + encodeParameters([
            sid(sessionId),
            ["phi_namespace", queryObject.phiNamespace.value]
        ]);
    }
    Routes.StructuredReport = StructuredReport;
})(Routes || (Routes = {}));
/**
* A library for working with composable updatable values, aka. Subjects
*/
var Subjects;
(function (Subjects) {
    

    

    /**
    * A trivial updatable value which never changes
    */
    function ret(t) {
        return {
            read: function () {
                return t;
            },
            updates: Observable.empty()
        };
    }
    Subjects.ret = ret;

    /**
    * Like ret, but for writable subjects
    */
    function retW(t) {
        var sub1 = Subjects.ret(t);

        return {
            read: sub1.read,
            updates: sub1.updates,
            write: function (t) {
                throw new Error("Tried to write to read-only subject.");
            }
        };
    }
    Subjects.retW = retW;

    /**
    * Apply a value when reading the latest value
    */
    function map(s, f) {
        return {
            read: function () {
                return f(s.read());
            },
            updates: Observable.map(s.updates, f)
        };
    }
    Subjects.map = map;

    /**
    * Composition of updatable values. The choice of second updatable value may depend on the value of the first.
    */
    function bind(sub, f, g) {
        return {
            read: function () {
                var s = sub.read();
                var t = f(s).read();
                return g(s, t);
            },
            updates: Observable.bind2(sub.updates, function (s) {
                var tSub = f(s);
                var initialValue = tSub.read();
                return Observable.cons(initialValue, tSub.updates);
            }, g)
        };
    }
    Subjects.bind = bind;

    /**
    * Like bind, but for writable subjects
    */
    function bindW(sub, f) {
        var sub1 = Subjects.bind(sub, f, function (s, t) {
            return t;
        });

        return {
            read: sub1.read,
            updates: sub1.updates,
            write: function (t) {
                var s = sub.read();
                var tSub = f(s);
                tSub.write(t);
            }
        };
    }
    Subjects.bindW = bindW;

    /**
    * Parallel composition of independent updatable values.
    */
    function zip(s1, s2, f) {
        var s0 = s1.read();
        var t0 = s2.read();

        return {
            read: function () {
                var s = s1.read();
                var t = s2.read();
                return f(s, t);
            },
            updates: Observable.zipLatest(s1.updates, s2.updates, function (s, t) {
                return f(s === undefined ? s0 : s, t === undefined ? t0 : t);
            })
        };
    }
    Subjects.zip = zip;

    /**
    * Apply a function when reading a WritableSubject's value, and another function when writing its value.
    */
    function dimap(sub, t, s) {
        return {
            read: function () {
                var s = sub.read();
                return t(s);
            },
            updates: Observable.map(sub.updates, t),
            write: function (t) {
                sub.write(s(t));
            }
        };
    }
    Subjects.dimap = dimap;

    /**
    * Perform an action when a value is written
    */
    function onWrite(sub, action) {
        return {
            read: sub.read,
            updates: sub.updates,
            write: function (t) {
                sub.write(t);
                action(t);
            }
        };
    }
    Subjects.onWrite = onWrite;

    /**
    * Apply a getter/setter pair to a Subject to get a WritableSubject
    */
    function lens(sub, _get, _set) {
        return {
            read: function () {
                var s = sub.read();
                return _get(s);
            },
            updates: Observable.map(sub.updates, _get),
            write: function (t) {
                Subjects.modify(sub, function (s) {
                    return _set(s, t);
                });
            }
        };
    }
    Subjects.lens = lens;

    /**
    * Modify a writable subject by applying a function
    */
    function modify(sub, f) {
        sub.write(f(sub.read()));
    }
    Subjects.modify = modify;

    /**
    * Modify a writable subject by applying a function
    */
    function listen(sub, onNext) {
        return sub.updates.subscribe({
            next: onNext,
            done: function () {
            },
            fail: function (_) {
            }
        });
    }
    Subjects.listen = listen;

    /**
    * A default implementation of a Subject
    */
    var SubjectBase = (function () {
        function SubjectBase() {
            var _this = this;
            this.updates = {
                subscribe: function (ob) {
                    _this.observers.push(ob);
                    return {
                        cancel: function () {
                            for (var i = 0; i < _this.observers.length; i++) {
                                if (_this.observers[i] === ob) {
                                    _this.observers.splice(i, 1);
                                    break;
                                }
                            }
                        }
                    };
                }
            };
            this.observers = [];
        }
        SubjectBase.prototype.raiseChangedEvent = function (value) {
            for (var i = 0; i < this.observers.length; i++) {
                this.observers[i].next(value);
            }
        };
        return SubjectBase;
    })();
    Subjects.SubjectBase = SubjectBase;

    /**
    * A default implementation of a WritableSubject
    */
    var ObservableValue = (function (_super) {
        __extends(ObservableValue, _super);
        function ObservableValue(value) {
            _super.call(this);
            this.value = value;
        }
        ObservableValue.prototype.read = function () {
            return this.value;
        };

        ObservableValue.prototype.write = function (value) {
            this.value = value;
            this.raiseChangedEvent(this.value);
        };
        return ObservableValue;
    })(SubjectBase);
    Subjects.ObservableValue = ObservableValue;
})(Subjects || (Subjects = {}));
/// <reference path="Subject.ts" />
/**
* Priority queues
*/
var Queue;
(function (_Queue) {
    

    

    

    

    

    /**
    * A default implementation of an observable priority queue, implemented using a sorted array of priority/item pairs.
    */
    var ObservablePriorityQueue = (function () {
        function ObservablePriorityQueue() {
            this.items = [];
            this.isEmpty = new Subjects.ObservableValue(true);
        }
        ObservablePriorityQueue.prototype.clear = function () {
            this.items = [];
            this.isEmpty.write(true);
        };

        ObservablePriorityQueue.prototype.dequeue = function () {
            var t = this.items.pop();

            if (this.items.length == 0) {
                this.isEmpty.write(true);
            }

            return {
                key: t.key,
                value: t.value
            };
        };

        ObservablePriorityQueue.prototype.enqueue = function (key, t, priority, isKey, isPriority) {
            var index = this.items.indexWhere(function (item) {
                return isKey.equals(item.key, key);
            });

            if (index >= 0 && this.items[index].priority > priority) {
                return;
            }

            var item;

            if (index >= 0) {
                item = { key: key, value: this.items[index].value, priority: priority };
                this.items.splice(index, 1);
            } else {
                item = { key: key, value: t, priority: priority };
            }

            var insertIndex = this.items.lastSortedIndex(item, function (item) {
                return item.priority;
            }, isPriority.compare);

            this.items.splice(insertIndex + 1, 0, item);

            if (this.items.length > 0) {
                this.isEmpty.write(false);
            }
        };
        return ObservablePriorityQueue;
    })();
    _Queue.ObservablePriorityQueue = ObservablePriorityQueue;
})(Queue || (Queue = {}));
/// <reference path="Queue.ts" />
/**
* A library for working with asychronous values (aka. futures, aka. observables)
*/
var Observable;
(function (_Observable) {
    

    

    /**
    * Monoidal unit for subscriptions
    */
    _Observable.memptySubscription = {
        cancel: function () {
        }
    };

    /**
    * Monoidal composition for subscriptions
    */
    function mappendSubscription(s1, s2) {
        return {
            cancel: function () {
                s1.cancel();
                s2.cancel();
            }
        };
    }
    _Observable.mappendSubscription = mappendSubscription;

    /**
    * A cancellation token
    */
    var CancellationToken = (function () {
        function CancellationToken() {
            this.cancelled = false;
        }
        /**
        * Cancel the subscription
        */
        CancellationToken.prototype.cancel = function () {
            this.cancelled = true;
        };
        return CancellationToken;
    })();
    _Observable.CancellationToken = CancellationToken;

    

    /**
    * A trivial computation which returns a value immediately and then terminates.
    */
    function ret(t) {
        return {
            subscribe: function (ob) {
                ob.next(t);
                ob.done();
                return _Observable.memptySubscription;
            }
        };
    }
    _Observable.ret = ret;

    /**
    * A trivial computation which fails immediately with an error message.
    */
    function fail(msg) {
        return {
            subscribe: function (ob) {
                ob.fail(msg);
                return _Observable.memptySubscription;
            }
        };
    }
    _Observable.fail = fail;

    /**
    * A trivial computation which terminates immediately.
    */
    function empty() {
        return {
            subscribe: function (ob) {
                ob.done();
                return _Observable.memptySubscription;
            }
        };
    }
    _Observable.empty = empty;

    /**
    * Apply a function when an asynchronous computation successfully completes.
    */
    function map(o, f) {
        return {
            subscribe: function (ob) {
                return o.subscribe({
                    next: function (s) {
                        return ob.next(f(s));
                    },
                    done: ob.done,
                    fail: ob.fail
                });
            }
        };
    }
    _Observable.map = map;

    /**
    * Flatten a stream-of-streams
    */
    function join(o) {
        return Observable.bind(o, function (ts) {
            return ts;
        });
    }
    _Observable.join = join;

    /**
    * Sequential composition of asynchronous computations.
    *
    * For example, the result of one AJAX call may determine the input to a second AJAX request.This function
    * allows the result of the first to be bound to a variable which can be used to compute a second Observable.
    * Subscription to the resulting Observable will then trigger the sequential computation.
    */
    function bind(o, f) {
        return {
            subscribe: function (ob) {
                var latestSubscription = null;

                var subscription = o.subscribe({
                    done: function () {
                        ob.done();
                    },
                    fail: ob.fail,
                    next: function (s) {
                        if (latestSubscription) {
                            latestSubscription.cancel();
                        }
                        latestSubscription = f(s).subscribe({
                            done: function () {
                            },
                            next: function (t) {
                                ob.next(t);
                            },
                            fail: function (msg) {
                                subscription.cancel();
                                ob.fail(msg);
                                latestSubscription = null;
                            }
                        });
                    }
                });

                return {
                    cancel: function () {
                        if (latestSubscription) {
                            latestSubscription.cancel();
                            latestSubscription = null;
                        }
                        subscription.cancel();
                    }
                };
            }
        };
    }
    _Observable.bind = bind;

    /**
    * Cons a result onto the front of a stream
    */
    function cons(t0, o) {
        return {
            subscribe: function (ob) {
                ob.next(t0);
                return o.subscribe(ob);
            }
        };
    }
    _Observable.cons = cons;

    /**
    * A variation on bind which applies a function after successful completion of both asynchronous computations.
    *
    * @see bind
    */
    function bind2(o, f, g) {
        return bind(o, function (s) {
            return map(f(s), function (t) {
                return g(s, t);
            });
        });
    }
    _Observable.bind2 = bind2;

    /**
    * An asynchronous computation which returns a value if a condition is met
    */
    function filter(o, p) {
        return {
            subscribe: function (ob) {
                return o.subscribe({
                    done: ob.done,
                    next: function (t) {
                        if (p(t)) {
                            ob.next(t);
                        }
                    },
                    fail: ob.fail
                });
            }
        };
    }
    _Observable.filter = filter;

    /**
    * An asynchronous computation which only starts if a condition is met
    *
    * @param {test} The condition which must be met before computation starts
    * @returns A computation which either starts o and returns its result, or succeeds immediately with an empty value.
    */
    function ifThenElse(test, tr, fa) {
        return {
            subscribe: function (ob) {
                if (test()) {
                    return tr.subscribe(ob);
                } else {
                    return fa.subscribe(ob);
                }
            }
        };
    }
    _Observable.ifThenElse = ifThenElse;

    /**
    * An asynchronous computation which runs a method on successful completion
    *
    * @param {f} The method to call on successful completion
    */
    function invoke(o, f) {
        return {
            subscribe: function (ob) {
                return o.subscribe({
                    done: ob.done,
                    next: function (t) {
                        f(t);
                        ob.next(t);
                    },
                    fail: ob.fail
                });
            }
        };
    }
    _Observable.invoke = invoke;

    /**
    * Run a method on each event
    */
    function on(o, onNext, onFail, onDone) {
        return {
            subscribe: function (ob) {
                return o.subscribe({
                    done: function () {
                        onDone();
                        ob.done();
                    },
                    next: function (t) {
                        onNext(t);
                        ob.next(t);
                    },
                    fail: function (err) {
                        onFail(err);
                        ob.fail(err);
                    }
                });
            }
        };
    }
    _Observable.on = on;

    /**
    * An asynchronous computation which runs a method on subscription
    *
    * @param {f} The method to call on subscription
    */
    function invokeFirst(o, f) {
        return {
            subscribe: function (ob) {
                f();
                return o.subscribe(ob);
            }
        };
    }
    _Observable.invokeFirst = invokeFirst;

    /**
    * An asynchronous computation which runs a method on successful completion
    *
    * @param {f} The method to call on completion or failure
    */
    function _finally(o, f) {
        return {
            subscribe: function (ob) {
                return o.subscribe({
                    done: function () {
                        f();
                        ob.done();
                    },
                    next: function (t) {
                        ob.next(t);
                    },
                    fail: function (err) {
                        f();
                        ob.fail(err);
                    }
                });
            }
        };
    }
    _Observable._finally = _finally;

    /**
    * Parallel composition of asynchronous computations. The resulting Observable succeeds if both inputs succeed, and
    * applies a function to the two results.
    */
    function zip(o1, o2, f) {
        return {
            subscribe: function (ob) {
                var ss = [];
                var ts = [];
                var s1 = o1.subscribe({
                    next: function (s) {
                        if (ts.length == 0) {
                            ss.push(s);
                        } else {
                            var t = ts[0];
                            ob.next(f(s, t));
                            ts = ts.slice(1);
                        }
                    },
                    done: function () {
                        ob.done();
                    },
                    fail: function (err) {
                        s2.cancel();
                        ob.fail(err);
                    }
                });
                var s2 = o2.subscribe({
                    next: function (t) {
                        if (ss.length == 0) {
                            ts.push(t);
                        } else {
                            var s = ss[0];
                            ob.next(f(s, t));
                            ss = ss.slice(1);
                        }
                    },
                    done: function () {
                        ob.done();
                    },
                    fail: function (err) {
                        s1.cancel();
                        ob.fail(err);
                    }
                });
                return mappendSubscription(s1, s2);
            }
        };
    }
    _Observable.zip = zip;

    /**
    * Parallel composition of asynchronous computations. The resulting Observable succeeds whenever either input succeeds, and
    * applies a function to the two latest results.
    */
    function zipLatest(o1, o2, f) {
        return {
            subscribe: function (ob) {
                var latest_s;
                var latest_t;

                var s1 = o1.subscribe({
                    next: function (s) {
                        ob.next(f(s, latest_t));
                        latest_s = s;
                    },
                    done: ob.done,
                    fail: function (err) {
                        s2.cancel();
                        ob.fail(err);
                    }
                });
                var s2 = o2.subscribe({
                    next: function (t) {
                        ob.next(f(latest_s, t));
                        latest_t = t;
                    },
                    done: ob.done,
                    fail: function (err) {
                        s1.cancel();
                        ob.fail(err);
                    }
                });
                return mappendSubscription(s1, s2);
            }
        };
    }
    _Observable.zipLatest = zipLatest;

    /**
    * Applicative composition
    */
    function ap(f, x) {
        return zip(f, x, function (a, b) {
            return a(b);
        });
    }
    _Observable.ap = ap;

    /**
    * Parallel composition of multiple asynchronous computations.
    * The resulting Observable succeeds if all inputs succeed.
    */
    function sequenceA(os) {
        if (os.length == 0) {
            return ret([]);
        }

        var head = os[0];
        var tail = os.slice(1);

        return ap(map(head, function (t) {
            return function (ts) {
                return [t].concat(ts);
            };
        }), sequenceA(tail));
    }
    _Observable.sequenceA = sequenceA;

    /**
    * Sequential composition of multiple asynchronous computations.
    * The resulting Observable succeeds if all inputs succeed.
    */
    function sequenceM(os) {
        return {
            subscribe: function (ob) {
                var results = [];
                var currentSubscription = null;

                var fire = function (i) {
                    if (i < os.length) {
                        currentSubscription = os[i].subscribe({
                            next: function (t) {
                                results[i] = t;
                            },
                            done: function () {
                                fire(i + 1);
                            },
                            fail: function (err) {
                                ob.fail(err);
                            }
                        });
                    } else {
                        ob.done();
                    }
                };

                fire(0);

                return {
                    cancel: function () {
                        if (currentSubscription) {
                            currentSubscription.cancel();
                        }
                    }
                };
            }
        };
    }
    _Observable.sequenceM = sequenceM;

    /**
    * Forget the return type of a computation.
    */
    function forget(o) {
        return map(o, function (_) {
            return {};
        });
    }
    _Observable.forget = forget;

    /**
    * Handle the failure case by returning a specified value.
    *
    * @param {onError} The function to apply on failure
    */
    function catchError(o, onError) {
        return catchErrorWith(o, function (err) {
            return ret(onError(err));
        });
    }
    _Observable.catchError = catchError;

    /**
    * Create an Observable which only yields a finite number of results.
    */
    function take(n, o) {
        return bind(withIndex(o), function (s) {
            if (s.index >= n) {
                return empty();
            } else {
                return ret(s.value);
            }
        });
    }
    _Observable.take = take;

    /**
    * Create an Observable which also yields the indices of its results.
    */
    function withIndex(o) {
        return scan(o, null, function (s, t) {
            return {
                value: t,
                index: s !== null ? s.index + 1 : 0
            };
        });
    }
    _Observable.withIndex = withIndex;

    /**
    * Create an Observable which accumulates state based on its inputs.
    */
    function scan(o, s0, f) {
        return {
            subscribe: function (ob) {
                var s = s0;
                return o.subscribe({
                    done: ob.done,
                    fail: ob.fail,
                    next: function (t) {
                        s = f(s, t);
                        ob.next(s);
                    }
                });
            }
        };
    }
    _Observable.scan = scan;

    /**
    * Handle the failure case by returning the value from a second Observable.
    *
    * @param {onError} The observable to subscribe to on failure
    */
    function catchErrorWith(o, onError) {
        return {
            subscribe: function (ob) {
                var innerSubscription = null;

                var subscription = o.subscribe({
                    done: ob.done,
                    next: ob.next,
                    fail: function (err) {
                        innerSubscription = onError(err).subscribe(ob);
                    }
                });

                return {
                    cancel: function () {
                        if (innerSubscription) {
                            innerSubscription.cancel();
                        }
                        subscription.cancel();
                    }
                };
            }
        };
    }
    _Observable.catchErrorWith = catchErrorWith;

    /**
    * An observable which fires once every number of milliseconds
    */
    function interval(millis) {
        return {
            subscribe: function (ob) {
                var interval = window.setInterval(function () {
                    ob.next({});
                }, millis);

                return {
                    cancel: function () {
                        window.clearInterval(interval);
                    }
                };
            }
        };
    }
    _Observable.interval = interval;

    /**
    * Tries the observable, and fails if no answer is received after
    * the specified number of milliseconds.
    */
    function timeout(o, millis) {
        return {
            subscribe: function (ob) {
                var completed = false;
                var timeout = window.setTimeout(function () {
                    if (!completed) {
                        ob.fail("Timed out after " + millis + "ms");
                        completed = true;
                    }
                }, millis);
                var subscription = o.subscribe({
                    next: function (t) {
                        ob.next(t);
                    },
                    done: function () {
                        if (!completed) {
                            window.clearTimeout(timeout);
                            ob.done();
                            completed = true;
                        }
                    },
                    fail: function (err) {
                        if (!completed) {
                            ob.fail(err);
                            completed = true;
                        }
                    }
                });
                return {
                    cancel: function () {
                        if (!completed) {
                            window.clearTimeout(timeout);
                        }
                        subscription.cancel();
                    }
                };
            }
        };
    }
    _Observable.timeout = timeout;

    var QueueObservable = (function () {
        function QueueObservable(queue, maxConcurrentTasks, isKey) {
            this.concurrentTasks = 0;
            this.cancelled = false;
            this.queue = queue;
            this.maxConcurrentTasks = maxConcurrentTasks;
            this.isKey = isKey;
        }
        QueueObservable.prototype.tryFire = function () {
            var _this = this;
            while (!this.cancelled && this.concurrentTasks < this.maxConcurrentTasks && !this.queue.isEmpty.read()) {
                this.concurrentTasks++;

                var item = this.queue.dequeue();

                item.value.subscribe({
                    next: function (_) {
                    },
                    done: function () {
                        _this.concurrentTasks--;
                        setTimeout(function () {
                            return _this.tryFire();
                        }, 0);
                    },
                    fail: function (err) {
                        console.error("Queued job " + _this.isKey.show(item.key) + " failed: " + err);
                        _this.concurrentTasks--;
                        setTimeout(function () {
                            return _this.tryFire();
                        }, 0);
                    }
                });
            }
        };

        QueueObservable.prototype.subscribe = function (ob) {
            var _this = this;
            this.queue.isEmpty.updates.subscribe({
                done: function () {
                },
                next: function (_) {
                    _this.tryFire();
                },
                fail: function (err) {
                    console.error("Error waiting for updates from the queue: " + err);
                }
            });

            for (var i = 0; i < this.maxConcurrentTasks; i++) {
                this.tryFire();
            }

            return {
                cancel: function () {
                    _this.cancelled = true;
                }
            };
        };
        return QueueObservable;
    })();

    /**
    * Queued composition of many Observables.
    *
    * The queue is repeatedly polled as Observables succeed or fail. The Observer never receives updates directly, so
    * some other method of observation, such as invoke, should be used to respond to copmpletion of individual computations.
    *
    * @param {queue} The queue which provides new work
    * @param {maxConcurrentTasks} The maximum number of parallel tasks to spawn
    */
    function queue(queue, maxConcurrentTasks, isKey) {
        return new QueueObservable(queue, maxConcurrentTasks, isKey);
    }
    _Observable.queue = queue;

    /**
    * An observable which passes after a delay
    */
    function delay(millis) {
        return {
            subscribe: function (ob) {
                var timeout = window.setTimeout(function () {
                    ob.next({});
                    ob.done();
                }, millis);
                return {
                    cancel: function () {
                        window.clearTimeout(timeout);
                    }
                };
            }
        };
    }
    _Observable.delay = delay;

    /**
    * Try an observable a number of times with a delay
    */
    function retryWithDelay(o, millis, maxRetries) {
        if (maxRetries <= 1) {
            return o;
        }

        return catchErrorWith(o, function () {
            return bind(delay(millis), function (_) {
                return retryWithDelay(o, millis, maxRetries - 1);
            });
        });
    }
    _Observable.retryWithDelay = retryWithDelay;
})(Observable || (Observable = {}));
/// <reference path="Observable.ts" />
/**
* Helper functions for using jQuery's AJAX methods with Observables
*/
var AJAX;
(function (AJAX) {
    /**
    * An AJAX GET request as an Observable
    */
    function ajaxGet(uri, dataType) {
        if (typeof dataType === "undefined") { dataType = "json"; }
        return ajax(uri, { dataType: dataType });
    }
    AJAX.ajaxGet = ajaxGet;

    /**
    * An AJAX POST request as an Observable
    */
    function ajax(uri, options) {
        return {
            subscribe: function (ob) {
                var token = new Observable.CancellationToken();

                var ajax = $.ajax(uri, options);

                ajax.done(function (res) {
                    if (!token.cancelled) {
                        ob.next(res);
                        ob.done();
                    }
                }).fail(function (_1, _2, err) {
                    if (!token.cancelled) {
                        // Redirect on unauthorized
                        if (_1.status == 401) {
                            window.location.assign("/");
                        }

                        var errorMsg = err;
                        if (_1 && _1.responseText) {
                            try  {
                                var json = JSON.parse(_1.responseText);
                                if (json["error_type"]) {
                                    errorMsg += (" (" + json["error_type"] + ")");
                                } else {
                                    errorMsg += (" (" + _1.responseText + ")");
                                }
                            } catch (ex) {
                                errorMsg += (" (" + _1.status + ")");
                            }
                        }

                        ob.fail(errorMsg);
                    }
                });

                return token;
            }
        };
    }
    AJAX.ajax = ajax;
})(AJAX || (AJAX = {}));
var Models;
(function (Models) {
    

    

    

    

    

    

    

    
})(Models || (Models = {}));
var Models;
(function (Models) {
    /**
    * List of terminology returned by the terminology tags service call
    */
    var TerminologyTags = (function () {
        function TerminologyTags() {
        }
        return TerminologyTags;
    })();
    Models.TerminologyTags = TerminologyTags;
})(Models || (Models = {}));
var Models;
(function (Models) {
    

    

    

    

    

    

    

    

    

    

    

    

    

    /**
    * Post attachment result
    */
    var AttachmentResult = (function () {
        function AttachmentResult() {
        }
        return AttachmentResult;
    })();
    Models.AttachmentResult = AttachmentResult;
})(Models || (Models = {}));
/// <reference path="../classes/Types.ts" />
/// <reference path="../classes/Meeting.ts" />
/// <reference path="../typings/jquery/jquery.d.ts" />
/// <reference path="Routes.ts" />
/// <reference path="Observable.ts" />
/// <reference path="AJAX.ts" />
/// <reference path="../models/Annotations.ts" />
/// <reference path="../models/User.ts" />
/// <reference path="../models/Terminology.ts" />
/// <reference path="../models/StudySchema.ts" />
/// <reference path="../models/Permissions.ts" />
/// <reference path="../models/KeyImages.ts" />
/**
* Observables for service methods
*
* @see Observable
* @see Routes
*/
var Services;
(function (Services) {
    /**
    * A wrapper for the GetUser AJAX call
    */
    function getUser(sessionId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetUser(sessionId));
        } else {
            if (LocalViewer.user) {
                return Observable.ret(LocalViewer.user);
            } else {
                return Observable.ret({
                    uuid: "",
                    name: "Local User",
                    first: "",
                    last: "",
                    email: "",
                    namespace_id: "",
                    sid_md5: ""
                });
            }
        }
    }
    Services.getUser = getUser;

    /**
    * A wrapper for the GetSettings AJAX call
    */
    function getPermissions(sessionId, phiNamespace) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetPermissions(sessionId, phiNamespace));
        } else {
            if (LocalViewer.permissions) {
                return Observable.ret(LocalViewer.permissions);
            } else {
                return Observable.ret({});
            }
        }
    }
    Services.getPermissions = getPermissions;

    function getStoragePermissions(sessionId, phiNamespace) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetStoragePermissions(sessionId, phiNamespace));
        } else {
            if (LocalViewer.permissions) {
                return Observable.ret(LocalViewer.permissions);
            } else {
                return Observable.ret({});
            }
        }
    }
    Services.getStoragePermissions = getStoragePermissions;

    /**
    * A wrapper for the GetSettings AJAX call
    */
    function getViewerSettings(sessionId) {
        if (!LocalViewer.isLocalViewer()) {
            return Observable.map(AJAX.ajaxGet(Routes.GetSettings(sessionId, "viewer")), function (res) {
                try  {
                    var settings = JSON.parse(res.value);

                    var version = settings.version || Classes.MIN_SETTINGS_VERSION;

                    if (version < Classes.CURRENT_SETTINGS_VERSION) {
                        return {
                            version: Classes.CURRENT_SETTINGS_VERSION
                        };
                    }

                    return settings;
                } catch (ex) {
                    return {
                        version: Classes.CURRENT_SETTINGS_VERSION
                    };
                }
            });
        } else if (LocalViewer.isPersonalAccelerator()) {
            return Observable.ret(LocalViewer.settings);
        } else {
            if (LocalViewer.settings) {
                return Observable.ret({
                    modalities: LocalViewer.settings.modalities,
                    version: Classes.CURRENT_SETTINGS_VERSION
                });
            } else {
                return Observable.ret({
                    version: Classes.CURRENT_SETTINGS_VERSION
                });
            }
        }
    }
    Services.getViewerSettings = getViewerSettings;

    /**
    * A wrapper for the GetSettings call.
    * @param sessionId
    */
    function getViewerKeyImageSettings(sessionId) {
        return Observable.map(AJAX.ajaxGet(Routes.GetSettings(sessionId, "viewerKeyImageLayout")), function (res) {
            if (res) {
                return JSON.parse(res.value);
            }

            return null;
        });
    }
    Services.getViewerKeyImageSettings = getViewerKeyImageSettings;

    /**
    * A wrapper for the GetSettings AJAX call
    */
    function getDateSettings(sessionId) {
        if (!LocalViewer.isLocalViewer()) {
            return Observable.map(AJAX.ajaxGet(Routes.GetSettings(sessionId, "user_default_date_format")), function (res) {
                return res.value || 'MM-DD-YYYY';
            });
        } else {
            if (LocalViewer.date && LocalViewer.date.value) {
                return Observable.ret(LocalViewer.date.value);
            } else {
                return Observable.ret('MM-DD-YYYY');
            }
        }
    }
    Services.getDateSettings = getDateSettings;

    /**
    * A wrapper for the GetSettings AJAX call
    */
    function getSettings(sessionId) {
        return Observable.zip(Services.getViewerSettings(sessionId), Services.getDateSettings(sessionId), function (settings, date) {
            settings.dateFormat = date;
            return settings;
        });
    }
    Services.getSettings = getSettings;

    /**
    * A wrapper for the SaveSettings AJAX call
    */
    function putSettings(sessionId, settings) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.SaveSettings, {
                type: "POST",
                data: {
                    key: "viewer",
                    sid: sessionId.value,
                    value: JSON.stringify(settings)
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.putSettings = putSettings;

    /**
    * Returns the KeyImageHang setting.
    * @param sessionId
    */
    function getKeyImageSettings(sessionId) {
        return Services.getViewerKeyImageSettings(sessionId);
    }
    Services.getKeyImageSettings = getKeyImageSettings;

    /**
    * Stores KeyImageHang setting
    * @param sessionId
    * @param settings
    */
    function putKeyImageSettings(sessionId, settings) {
        return AJAX.ajax(Routes.SaveSettings, {
            type: "POST",
            data: {
                key: "viewerKeyImageLayout",
                sid: sessionId.value,
                value: JSON.stringify(settings)
            }
        });
    }
    Services.putKeyImageSettings = putKeyImageSettings;

    /**
    * A wrapper for the Terminology/Tags AJAX call
    */
    function loadTerminology(sessionId, tags, language, query) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.Terminology(sessionId, tags, language, query));
        } else {
            return Observable.ret({ values: {} });
        }
    }
    Services.loadTerminology = loadTerminology;

    /**
    * A wrapper for the Terminology/Tags AJAX call to get all tags
    */
    function loadAllTerminology(sessionId, language, query) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.TerminologyAll(sessionId, language, query));
        } else {
            if (LocalViewer.terminology) {
                return Observable.ret(LocalViewer.terminology);
            } else {
                return Observable.ret({ values: {} });
            }
        }
    }
    Services.loadAllTerminology = loadAllTerminology;

    /**
    * A wrapper for the GetAccountSettings AJAX call
    */
    function getAccountSettings(sessionId, query, fqdn) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetAccountSettings(sessionId, query, fqdn));
        } else {
            if (LocalViewer.account) {
                return Observable.ret(LocalViewer.account);
            } else {
                return Observable.ret({});
            }
        }
    }
    Services.getAccountSettings = getAccountSettings;

    /**
    * A wrapper for the study/get AJAX call
    */
    function getStudyStorageInfo(sessionId, query) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.StudyStorageInfo(sessionId, query));
        } else {
            if (LocalViewer.studyinfo) {
                return Observable.ret(LocalViewer.studyinfo);
            } else {
                var customfields = (LocalViewer && LocalViewer.study && LocalViewer.study.customfields) ? LocalViewer.study.customfields : [];
                return Observable.ret({
                    uuid: "",
                    engine_fqdn: "",
                    hl7: [],
                    customfields: customfields
                });
            }
        }
    }
    Services.getStudyStorageInfo = getStudyStorageInfo;

    /**
    * Study info, using study UUID
    * @param sessionId
    * @param uuid
    */
    function getStudyInfo(sessionId, uuid) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.StudyInfo(sessionId, uuid));
        } else {
            return Observable.ret({});
        }
    }
    Services.getStudyInfo = getStudyInfo;

    /**
    * A wrapper for the study/set AJAX call
    */
    function setStudy(sessionId, query, tags) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.StudySet(sessionId, query, tags));
        } else {
            return Observable.ret({});
        }
    }
    Services.setStudy = setStudy;

    /**
    * A wrapper for the study/retrieve AJAX call
    */
    function retrieveStudy(sessionId, uuid) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.RetrieveStudy(sessionId, uuid));
        } else {
            return Observable.ret({});
        }
    }
    Services.retrieveStudy = retrieveStudy;

    /**
    * A wrapper for the study/audit AJAX call
    */
    function setStudyAudit(sessionId, uuid, action) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.StudyAudit(sessionId, uuid, action));
        } else {
            return Observable.ret({});
        }
    }
    Services.setStudyAudit = setStudyAudit;

    /**
    * A wrapper for the GetStudyList AJAX call
    */
    function getStudyList(sessionId, phiNamespace, patientId, accessionNumber, studyUuid, notStudyUuid) {
        if (!LocalViewer.isLocalViewer()) {
            return Observable.map(AJAX.ajaxGet(Routes.GetStudyList(sessionId, phiNamespace, patientId, accessionNumber, studyUuid, notStudyUuid)), function (response) {
                return response.studies;
            });
        } else {
            throw new Error("Not supported in the local viewer");
        }
    }
    Services.getStudyList = getStudyList;

    /**
    * A wrapper for the GetStudyList AJAX call
    */
    function getStudyListFiltered(sessionId, phiNamespace, patientId, accessionNumber, studyUuid, notStudyUuid, worklistFilter) {
        if (!LocalViewer.isLocalViewer()) {
            return Observable.map(AJAX.ajaxGet(Routes.GetStudyList(sessionId, phiNamespace, patientId, accessionNumber, studyUuid, notStudyUuid, worklistFilter)), function (response) {
                return response.studies;
            });
        } else {
            throw new Error("Not supported in the local viewer");
        }
    }
    Services.getStudyListFiltered = getStudyListFiltered;

    /**
    * A wrapper for the GetStudyList AJAX call
    */
    function getStudyListByUuid(sessionId, studyUuidList) {
        if (!LocalViewer.isLocalViewer()) {
            return Observable.map(AJAX.ajaxGet(Routes.GetStudyListByUuid(sessionId, studyUuidList)), function (response) {
                return response.studies;
            });
        } else {
            throw new Error("Not supported in the local viewer");
        }
    }
    Services.getStudyListByUuid = getStudyListByUuid;

    /**
    * A wrapper for the Image Annotations AJAX call
    */
    function listImageAnnotations(sessionId, query) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.ListAnnotations(sessionId, query));
        } else {
            if (LocalViewer.annotations) {
                return Observable.ret(LocalViewer.annotations);
            } else {
                return Observable.ret({
                    annotations: []
                });
            }
        }
    }
    Services.listImageAnnotations = listImageAnnotations;

    /**
    * A wrapper for the Image Annotation AJAX call
    */
    function getImageAnnotation(sessionId, query, id) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetAnnotation(sessionId, query, id));
        } else {
            throw "Not supported in local viewer";
        }
    }
    Services.getImageAnnotation = getImageAnnotation;

    /**
    * A wrapper for the Add Image Annotation AJAX call
    */
    function addImageAnnotation(sessionId, query, series, instance, frameNumber, annotation) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajax(Routes.AddAnnotation, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    study_uid: query.studyUid.value,
                    storage_namespace: query.storageNamespace.value,
                    phi_namespace: query.phiNamespace.value,
                    series_uid: series.value,
                    instance_uid: instance.value,
                    frame_number: frameNumber.value.toString(),
                    stamp: annotation.type == Classes.MouseTool[31 /* Stamp */] ? 1 : 0,
                    json: JSON.stringify(annotation)
                } });
        } else {
            return Observable.ret({
                uuid: ""
            });
        }
    }
    Services.addImageAnnotation = addImageAnnotation;

    /**
    * A wrapper for the Delete Image Annotation AJAX call
    */
    function deleteImageAnnotation(sessionId, query, id) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.DeleteAnnotation(sessionId, query, id));
        } else {
            return Observable.ret({});
        }
    }
    Services.deleteImageAnnotation = deleteImageAnnotation;

    /**
    * A wrapper for the Edit Image Annotation AJAX call
    */
    function editImageAnnotation(sessionId, query, id, annotation) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajax(Routes.SetAnnotation, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    study_uid: query.studyUid.value,
                    storage_namespace: query.storageNamespace.value,
                    phi_namespace: query.phiNamespace.value,
                    uuid: id.value,
                    json: JSON.stringify(annotation)
                } });
        } else {
            return Observable.ret({
                uuid: ""
            });
        }
    }
    Services.editImageAnnotation = editImageAnnotation;

    /**
    * List all key images in a study
    */
    function listKeyImages(sessionId, query) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.ListKeyImages(sessionId, query));
        } else {
            if (LocalViewer.keyimages) {
                return Observable.ret(LocalViewer.keyimages);
            } else {
                return Observable.ret({
                    images: []
                });
            }
        }
    }
    Services.listKeyImages = listKeyImages;

    /**
    * Add a key image
    */
    function getKeyImage(sessionId, id) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetKeyImage(sessionId, id));
        } else {
            return Observable.ret({});
        }
    }
    Services.getKeyImage = getKeyImage;

    /**
    * Add a key image
    */
    function addKeyImage(sessionId, query, series, instance, frameNumber, version) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.AddKeyImage(sessionId, query, series, instance, frameNumber, version));
        } else {
            return Observable.ret({});
        }
    }
    Services.addKeyImage = addKeyImage;

    /**
    * Delete a key image
    */
    function deleteKeyImage(sessionId, id) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.DeleteKeyImage(sessionId, id));
        } else {
            return Observable.ret({});
        }
    }
    Services.deleteKeyImage = deleteKeyImage;

    /**
    * Run a routing rule
    */
    function runRoutingRule(sessionId, studyId, routeId, email, message) {
        if (!LocalViewer.isStandardLocalViewer()) {
            return AJAX.ajaxGet(Routes.RunRoutingRule(sessionId, studyId, routeId, email, message));
        } else {
            return Observable.ret({});
        }
    }
    Services.runRoutingRule = runRoutingRule;

    /**
    * meeting/add
    */
    function StartMeeting(sessionId, queryObject, meetingName, state) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.StartMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    study_uid: queryObject.studyUid.value,
                    storage_namespace: queryObject.storageNamespace.value,
                    phi_namespace: queryObject.phiNamespace.value,
                    name: meetingName,
                    state: JSON.stringify(state)
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.StartMeeting = StartMeeting;

    /**
    * link/add
    */
    function CreateMeetingLink(sessionId, meetingId, queryObject, uuid, emailAddress) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.CreateMeetingLink, {
                type: "POST",
                data: {
                    action: "STUDY_VIEW",
                    sid: sessionId.value,
                    study_id: uuid.value,
                    namespace_id: queryObject.storageNamespace.value,
                    meeting_id: meetingId.value,
                    email: emailAddress.value,
                    parameters: JSON.stringify([
                        "route", "meeting",
                        "meetingId", meetingId.value,
                        "phiNamespace", queryObject.phiNamespace.value
                    ])
                }
            });
        } else {
            throw "Not supported in local viewer";
        }
    }
    Services.CreateMeetingLink = CreateMeetingLink;

    function ShareMeetingLink(sessionId, meetingLinkId, emailAddress) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.ShareLink, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingLinkId.value,
                    email: emailAddress.value
                }
            });
        } else {
            throw "Not supported in local viewer";
        }
    }
    Services.ShareMeetingLink = ShareMeetingLink;

    function ShareRecordingLink(sessionId, linkId, emailAddress) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.ShareLink, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: linkId.value,
                    email: emailAddress.value
                }
            });
        } else {
            throw "Not supported in local viewer";
        }
    }
    Services.ShareRecordingLink = ShareRecordingLink;

    /**
    * meeting/set
    */
    function UpdateMeeting(sessionId, meetingId, state) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.UpdateMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value,
                    state: JSON.stringify(state)
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.UpdateMeeting = UpdateMeeting;

    /**
    * meeting/delete
    */
    function EndMeeting(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.EndMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.EndMeeting = EndMeeting;

    /**
    * meeting/events/add
    */
    function BroadcastEvent(sessionId, meetingId, event) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.BroadcastEvent, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value,
                    event: JSON.stringify(event)
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.BroadcastEvent = BroadcastEvent;

    /**
    * meeting/ping
    */
    function PingMeeting(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.PingMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.PingMeeting = PingMeeting;

    /**
    * meeting/join
    */
    function JoinMeeting(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.JoinMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.JoinMeeting = JoinMeeting;

    /**
    * meeting/leave
    */
    function LeaveMeeting(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.LeaveMeeting, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.LeaveMeeting = LeaveMeeting;

    /**
    * meeting/presenter
    */
    function ChangeMeetingPresenter(sessionId, meetingId, userId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.ChangeMeetingPresenter, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: meetingId.value,
                    user_id: userId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.ChangeMeetingPresenter = ChangeMeetingPresenter;

    /**
    * meeting/roster
    */
    function MeetingRoster(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.MeetingRoster(sessionId, meetingId));
        } else {
            return Observable.ret({});
        }
    }
    Services.MeetingRoster = MeetingRoster;

    /**
    * meeting/get
    */
    function GetMeeting(sessionId, meetingId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajaxGet(Routes.GetMeeting(sessionId, meetingId));
        } else {
            return Observable.ret({});
        }
    }
    Services.GetMeeting = GetMeeting;

    function StudyPHIExtended(sessionId, uuid, extended) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.StudyPHIExtended, {
                type: "POST",
                data: {
                    sid: sessionId.value,
                    uuid: uuid.value,
                    extended: extended
                }
            });
        } else {
            throw new Error("Not supported in the local viewer");
        }
    }
    Services.StudyPHIExtended = StudyPHIExtended;

    function CreateScriptLink(sessionId, queryObject, uuid, attachmentId, attachmentVersion, attachmentPhiNamespace, emailAddress) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.CreateScriptLink, {
                type: "POST",
                data: {
                    action: "STUDY_VIEW",
                    sid: sessionId.value,
                    study_id: uuid.value,
                    namespace_id: queryObject.storageNamespace.value,
                    email: emailAddress.value,
                    parameters: JSON.stringify([
                        "route", "script",
                        "attachmentId", attachmentId.value,
                        "attachmentVersion", attachmentVersion.value,
                        "attachmentPhiNs", attachmentPhiNamespace.value
                    ])
                }
            });
        } else {
            throw "Not supported in local viewer";
        }
    }
    Services.CreateScriptLink = CreateScriptLink;

    /**
    * A wrapper for the Accelerator Used AJAX call
    */
    function AcceleratorUsed(sessionId) {
        if (!LocalViewer.isLocalViewer()) {
            return AJAX.ajax(Routes.AcceleratorUsed, {
                type: "GET",
                data: {
                    sid: sessionId.value
                }
            });
        } else {
            return Observable.ret({});
        }
    }
    Services.AcceleratorUsed = AcceleratorUsed;

    /**
    * Log error message to /audit/log
    * @param {string} message
    * @param {Classes.SessionId} sessionId
    * @param {Classes.StudyUUID} studyId
    * @param {Classes.SeriesUid} seriesId
    * @returns {Observable.Observable<{}>}
    * @constructor
    */
    function AuditLog(message, source, sessionId, studyId, seriesId) {
        AJAX.ajaxGet(Routes.AuditLog(message, "viewererror", window.navigator.userAgent, source, sessionId, studyId, seriesId)).subscribe({
            next: function () {
                console.error(message);
            },
            done: function () {
            },
            fail: function (err) {
                console.log("Unable to send to /audit/log: " + err);
            }
        });
    }
    Services.AuditLog = AuditLog;

    /**
    * Log viewer metric to /audit/log
    * @param data
    * @param {string} source
    * @param {Classes.SessionId} sessionId
    * @constructor
    */
    function AuditMetric(name, data) {
        AJAX.ajaxGet(Routes.AuditMetric(name, data, "viewermetric", window.navigator.userAgent)).subscribe({
            next: function () {
            },
            done: function () {
            },
            fail: function (err) {
                console.log("Unable to send to /audit/log: " + err);
            }
        });
    }
    Services.AuditMetric = AuditMetric;
})(Services || (Services = {}));
///<reference path="../typings/jquery/jquery.d.ts" />
///<reference path="../classes/Types.ts" />
///<reference path="Services.ts" />
///<reference path="Observable.ts" />

/**
* Methods for working with the terminology and translation service
*/
var Terminology;
(function (Terminology) {
    /**
    * Enumerates customizable strings displayed in the viewer
    */
    Terminology.Terms = {
        /* Error Messages */
        ErrorLoadingSettings: new Classes.Term("webviewer:error-loading-settings", "Error loading settings"),
        ErrorLoadingStudy: new Classes.Term("webviewer:error-loading-study", "Error loading study"),
        NotForDiagnosticUse: new Classes.Term("webviewer:not-for-diagnostic-use", "Not intended for diagnostic use on mobile devices"),
        ErrorStudyNotFound: new Classes.Term("webviewer:error-study-not-found", "Study not found"),
        /* Thin Studies */
        CannotRetrieveThinStudy: new Classes.Term("webviewer:cannot-retrieve-this-study", "Unable to initiate retrieval of the study."),
        StudyBeingRetrieved: new Classes.Term("webviewer:study-being-retrieved", "The study is being retrieved from the PACS. It will be available in three to seven minutes."),
        /* General Terms */
        Study: new Classes.Term("webviewer:study", "Study"),
        StudyPage: new Classes.Term("webviewer:study-page", "Go to Study Page"),
        Series: new Classes.Term("webviewer:series", "Series"),
        NoImageSeries: new Classes.Term("webviewer:no-image-series", "No image series"),
        SeriesPlural: new Classes.Term("webviewer:series-plural", "Series"),
        Instance: new Classes.Term("webviewer:instance", "Instance"),
        Instances: new Classes.Term("webviewer:instances", "Instances"),
        Frame: new Classes.Term("webviewer:frame", "Frame"),
        Frames: new Classes.Term("webviewer:frames", "Frames"),
        /* Thumbnails */
        RelatedStudies: new Classes.Term("webviewer:related-studies", "Related Studies"),
        LoadImages: new Classes.Term("webviewer:load-images", "Load Images"),
        AccessionNumberShort: new Classes.Term("webviewer:accession-short", "Acc #"),
        Date: new Classes.Term("webviewer:date", "Date"),
        Attachments: new Classes.Term("webviewer:attachments", "Attachments"),
        Filename: new Classes.Term("webviewer:filename", "Filename"),
        Download: new Classes.Term("webviewer:download", "Download"),
        Reports: new Classes.Term("webviewer:reports", "Reports"),
        View: new Classes.Term("webviewer:view", "View"),
        HL7Report: new Classes.Term("webviewer:hl7-report", "HL7 Report"),
        StructuredReport: new Classes.Term("webviewer:structured-report", "Structured Report"),
        RecordedSession: new Classes.Term("webviewer:recorded-session", "Recorded Session"),
        MeetingInProgress: new Classes.Term("webviewer:meeting-in-progress", "Meeting in Progress"),
        StackSeries: new Classes.Term("webviewer:stack-series", "Stack Series"),
        KeyImageSeries: new Classes.Term("webviewer:keyimage-series", "Key Images"),
        /* Toolbar */
        Scroll: new Classes.Term("webviewer:scroll", "Scroll"),
        ScrollTooltip: new Classes.Term("webviewer:scroll-tooltip", "Scroll"),
        Transform: new Classes.Term("webviewer:transform", "View"),
        TransformTooltip: new Classes.Term("webviewer:transform-tooltip", "Image Transformation Tools"),
        Move: new Classes.Term("webviewer:move", "Move"),
        MoveTooltip: new Classes.Term("webviewer:move-tooltip", "Move"),
        Zoom: new Classes.Term("webviewer:zoom", "Zoom"),
        ZoomTooltip: new Classes.Term("webviewer:zoom-tooltip", "Zoom"),
        FreeRotate: new Classes.Term("webviewer:zoom-rotate", "Rotate"),
        FreeRotateTooltip: new Classes.Term("webviewer:zoom-rotate-tooltip", "Free Rotate"),
        Flip: new Classes.Term("webviewer:flip", "Flip H"),
        FlipTooltip: new Classes.Term("webviewer:flip-tooltip", "Flip Horizontally"),
        FlipV: new Classes.Term("webviewer:flip-v", "Flip V"),
        FlipVTooltip: new Classes.Term("webviewer:flip-v-tooltip", "Flip Vertically"),
        Rotate: new Classes.Term("webviewer:rotate", "Rotate"),
        RotateTooltip: new Classes.Term("webviewer:rotate-tooltip", "Rotate"),
        WindowLevel: new Classes.Term("webviewer:window-level", "W/L"),
        WindowLevelTooltip: new Classes.Term("webviewer:window-level-tooltip", "Window/Level"),
        SelectWindowLevel: new Classes.Term("webviewer:window-level", "Select"),
        SelectWindowLevelTooltip: new Classes.Term("webviewer:window-level-tooltip", "Select Window/Level"),
        SavePreset: new Classes.Term("webviewer:save-preset", "Save"),
        SavePresetTooltip: new Classes.Term("webviewer:save-preset-tooltip", "Save Window/Level Preset"),
        DetectWindowLevel: new Classes.Term("webviewer:detect-window-level", "Detect"),
        DetectWindowLevelTooltip: new Classes.Term("webviewer:detect-window-level-tooltip", "Detect Window/Level"),
        PleaseSelectAnAnnotation: new Classes.Term("webviewer:please-select-an-annotation", "Please select a rectangle or ellipse annotation to auto-detect the window level settings."),
        CannotDetectWindowLevel: new Classes.Term("webviewer:cannot-detect-window-level", "Unable to auto-detect the window level settings."),
        InvalidUltrasoundMeasurement: new Classes.Term("webviewer:invalid-us-measurement", "This measurement crosses regions with different or no pixel spacing."),
        Annotations: new Classes.Term("webviewer:annotations", "Annot."),
        AnnotationsTooltip: new Classes.Term("webviewer:annotations-tooltip", "Annotations"),
        SelectAnnotation: new Classes.Term("webviewer:select-annotation", "Select"),
        SelectAnnotationTooltip: new Classes.Term("webviewer:select-annotation-tooltip", "Select Annotation"),
        Filled: new Classes.Term("webviewer:filled", "Fill"),
        FilledTooltip: new Classes.Term("webviewer:filled-tooltip", "Fill Annotation"),
        DeleteAnnotation: new Classes.Term("webviewer:delete-annotation", "Delete"),
        DeleteAnnotationTooltip: new Classes.Term("webviewer:delete-annotation-tooltip", "Delete Selected Annotation"),
        ExportGSPS: new Classes.Term("webviewer:export-gsps", "GSPS"),
        ExportGSPSTooltip: new Classes.Term("webviewer:export-gsps-tooltip", "Export Annotations as Grayscale Presentation State"),
        GSPS: new Classes.Term("webviewer:gsps", "GSPS"),
        GSPSTooltip: new Classes.Term("webviewer:gsps-tooltip", "Show All GSPS"),
        Line: new Classes.Term("webviewer:line", "Line"),
        LineTooltip: new Classes.Term("webviewer:line-tooltip", "Measure Line"),
        Arrow: new Classes.Term("webviewer:arrow", "Arrow"),
        ArrowTooltip: new Classes.Term("webviewer:arrow-tooltip", "Draw Arrow"),
        Angle: new Classes.Term("webviewer:angle", "Angle"),
        AngleTooltip: new Classes.Term("webviewer:angle-tooltip", "Measure Angle"),
        Cobb: new Classes.Term("webviewer:cobb", "Cobb"),
        CobbTooltip: new Classes.Term("webviewer:cobb-tooltip", "Measure Cobb Angle"),
        Rectangle: new Classes.Term("webviewer:rectangle", "Rect"),
        RectangleTooltip: new Classes.Term("webviewer:rectangle-tooltip", "Measure Rectangle"),
        Ellipse: new Classes.Term("webviewer:ellipse", "Ellipse"),
        EllipseTooltip: new Classes.Term("webviewer:ellipse-tooltip", "Measure Ellipse"),
        Radius: new Classes.Term("webviewer:radius", "Radius"),
        RadiusTooltip: new Classes.Term("webviewer:radius-tooltip", "Measure Radius"),
        Circle: new Classes.Term("webviewer:circle", "Circle"),
        CircleTooltip: new Classes.Term("webviewer:circle-tooltip", "Measure Circle"),
        Square: new Classes.Term("webviewer:square", "Square"),
        SquareTooltip: new Classes.Term("webviewer:square-tooltip", "Measure Square"),
        CoLocalization: new Classes.Term("webviewer:colocate", "Co-Locate"),
        CoLocalizationTooltip: new Classes.Term("webviewer:colocate-tooltip", "Co-Locate Measurements"),
        OrthoAxes: new Classes.Term("webviewer:orthoaxes", "Orthogonal Axes"),
        OrthoAxesTooltip: new Classes.Term("webviewer:orthoaxes-tooltip", "Measure Orthogonal Axes"),
        FemoralHead: new Classes.Term("webviewer:femoralhead", "Femoral Head"),
        FemoralHeadTooltip: new Classes.Term("webviewer:femoralhead-tooltip", "Measure Femoral Head"),
        Polygon: new Classes.Term("webviewer:polygon", "Polygon"),
        PolygonTooltip: new Classes.Term("webviewer:polygon-tooltip", "Measure Polygon"),
        Trace: new Classes.Term("webviewer:trace", "Trace"),
        TraceTooltip: new Classes.Term("webviewer:trace-tooltip", "Measure Trace"),
        Paint: new Classes.Term("webviewer:paint", "Paint"),
        PaintTooltip: new Classes.Term("webviewer:paint-tooltip", "Measure Paint"),
        Label: new Classes.Term("webviewer:label", "Label"),
        LabelTooltip: new Classes.Term("webviewer:label-tooltip", "Label Measurement"),
        AssignPixelSpacing: new Classes.Term("webviewer:pixel-spacing", "Pixel Size"),
        AssignPixelSpacingTooltip: new Classes.Term("webviewer:assign-pixel-spacing-tooltip", "Assign Pixel Spacing"),
        AssignPixelSpacingDialog: new Classes.Term("webviewer:enter-pixel-spacing-value", "Enter Pixel Spacing Value (mm)"),
        AssignSliceSpacing: new Classes.Term("webviewer:slice-spacing", "Slice Size"),
        AssignSliceSpacingTooltip: new Classes.Term("webviewer:assign-slice-spacing-tooltip", "Assign Slice Spacing"),
        AssignSliceSpacingDialog: new Classes.Term("webviewer:enter-clie-spacing-value", "Enter Slice Spacing Value (mm)"),
        Text: new Classes.Term("webviewer:text", "Text"),
        TextTooltip: new Classes.Term("webviewer:text-tooltip", "Add Text Annotation"),
        ProstateTool: new Classes.Term("webviewer:prostate-tool", "Measure"),
        ProstateToolTooltip: new Classes.Term("webviewer:prostate-tool-tooltip", "Measure Prostate"),
        Stamp: new Classes.Term("webviewer:stamp", "Stamp"),
        StampTooltip: new Classes.Term("webviewer:stamp-tooltip", "Stamp Annotation"),
        ArrowAnnotate: new Classes.Term("webviewer:arrow", "Arrow"),
        ArrowAnnotateTooltip: new Classes.Term("webviewer:arrow-tooltip", "Draw Arrow"),
        CircleAnnotate: new Classes.Term("webviewer:circle", "Circle"),
        CircleAnnotateTooltip: new Classes.Term("webviewer:circle-tooltip", "Draw Circle"),
        EllipseAnnotate: new Classes.Term("webviewer:ellipse", "Ellipse"),
        EllipseAnnotateTooltip: new Classes.Term("webviewer:ellipse-tooltip", "Draw Ellipse"),
        LineAnnotate: new Classes.Term("webviewer:line", "Line"),
        LineAnnotateTooltip: new Classes.Term("webviewer:line-tooltip", "Draw Line"),
        LineCalibrate: new Classes.Term("webviewer:line-calibrate", "Calibrate"),
        LineCalibrateTooltip: new Classes.Term("webviewer:line-calibrate-tooltip", "Calibrate Line"),
        LineCalibratePrompt: new Classes.Term("webviewer:enter-length", "Enter length (mm)"),
        ThresholdPrompt: new Classes.Term("webviewer:enter-threshold", "Enter threshold"),
        ThresholdMinPrompt: new Classes.Term("webviewer:enter-threshold-min", "Enter minimum threshold"),
        ThresholdMaxPrompt: new Classes.Term("webviewer:enter-threshold-max", "Enter maximum threshold"),
        RectangleAnnotate: new Classes.Term("webviewer:rectangle", "Rect"),
        RectangleAnnotateTooltip: new Classes.Term("webviewer:rectangle-tooltip", "Draw Rectangle"),
        SquareAnnotate: new Classes.Term("webviewer:square", "Square"),
        SquareAnnotateTooltip: new Classes.Term("webviewer:square-tooltip", "Draw Square"),
        Propagate: new Classes.Term("webviewer:propagate", "Propagate"),
        PropagateTooltip: new Classes.Term("webviewer:propagate-tooltip", "Propagate Across Slices"),
        PropagateAllTooltip: new Classes.Term("webviewer:propagate-all-tooltip", "Propagate Across Series"),
        Threshold: new Classes.Term("webviewer:threshold", "Threshold"),
        ThresholdTooltip: new Classes.Term("webviewer:threshold-tooltip", "Threshold To Area"),
        Range: new Classes.Term("webviewer:range", "Range"),
        RangeTooltip: new Classes.Term("webviewer:range-tooltip", "Range To Area"),
        ShrinkWrap: new Classes.Term("webviewer:shrinkwrap", "Shrink"),
        ShrinkWrapTooltip: new Classes.Term("webviewer:shrinkwrap-tooltip", "Shrink Wrap To Area"),
        EnterText: new Classes.Term("webviewer:enter-text", "Enter Text"),
        Fit: new Classes.Term("webviewer:fit", "Fit"),
        FitTooltip: new Classes.Term("webviewer:fit-tooltip", "Fit To Viewport"),
        Reset: new Classes.Term("webviewer:reset", "Reset"),
        ResetTooltip: new Classes.Term("webviewer:reset-tooltip", "Reset"),
        ThreeD: new Classes.Term("webviewer:3d", "3D"),
        ThreeDTooltip: new Classes.Term("webviewer:3d-tooltip", "3D Tools"),
        Probe: new Classes.Term("webviewer:probe", "Probe"),
        ProbeTooltip: new Classes.Term("webviewer:probe-tooltip", "Probe Tool"),
        ReferenceLines: new Classes.Term("webviewer:ref-lines", "R/L"),
        ReferenceLinesTooltip: new Classes.Term("webviewer:ref-lines-tooltip", "Reference Lines"),
        LinkedSeries: new Classes.Term("webviewer:linked-series", "Link"),
        LinkedSeriesTooltip: new Classes.Term("webviewer:linked-series-tooltip", "Linked Series"),
        Layout: new Classes.Term("webviewer:layout", "Layout"),
        LayoutTooltip: new Classes.Term("webviewer:layout-tooltip", "Layout"),
        NewWindow: new Classes.Term("webviewer:new-window", "New"),
        NewWindowTooltip: new Classes.Term("webviewer:new-window-tooltip", "New Study Window"),
        Maximize: new Classes.Term("webviewer:maximize", "Max."),
        MaximizeTooltip: new Classes.Term("webviewer:maximize-tooltip", "Maximize/Minimize"),
        Print: new Classes.Term("webviewer:print", "Print"),
        PrintTooltip: new Classes.Term("webviewer:print-tooltip", "Print"),
        Thumbnails: new Classes.Term("webviewer:thumbnails", "Thumbs"),
        ThumbnailsTooltip: new Classes.Term("webviewer:thumbnails-tooltip", "Show/Hide Thumbnails"),
        Record: new Classes.Term("webviewer:record", "Record"),
        RecordTooltip: new Classes.Term("webviewer:record-tooltip", "Record"),
        Actions: new Classes.Term("webviewer:actions", "Actions"),
        ActionsTooltip: new Classes.Term("webviewer:actions-tooltip", "Study Actions"),
        Settings: new Classes.Term("webviewer:settings", "Settings"),
        SettingsTooltip: new Classes.Term("webviewer:settings-tooltip", "Edit User Settings"),
        PlaneLocalization: new Classes.Term("webviewer:plane-localization", "Local."),
        PlaneLocalizationTooltip: new Classes.Term("webviewer:plane-localization-tooltip", "Plane Localization"),
        MPR: new Classes.Term("webviewer:mpr", "MPR"),
        MPRTooltip: new Classes.Term("webviewer:mpr-tooltip", "Multi-planar Reconstruction"),
        MIP: new Classes.Term("webviewer:mip", "MIP"),
        MIPTooltip: new Classes.Term("webviewer:mip-tooltip", "Maximum Intensity Projection"),
        Magnify: new Classes.Term("webviewer:magnify", "Mag."),
        MagnifyTooltip: new Classes.Term("webviewer:magnify-tooltip", "Magnify Image"),
        Info: new Classes.Term("webviewer:info", "Info"),
        InfoTooltip: new Classes.Term("webviewer:info-tooltip", "Show/Hide Information"),
        ShowAnnotations: new Classes.Term("webviewer:show-annotations", "Annot."),
        ShowAnnotationsTooltip: new Classes.Term("webviewer:show-annotations-tooltip", "Show/Hide Annotations"),
        ShowAnnotationsDetail: new Classes.Term("webviewer:show-annotations-detail", "Detail"),
        ShowAnnotationsDetailTooltip: new Classes.Term("webviewer:show-annotations-detail-tooltip", "Show/Hide Annotations Detail"),
        ToggleCreatedByOthersAnnotationsTooltip: new Classes.Term("webviewer:toggle-created-by-others-annotations-tooltip", "Show/Hide Annotations created by others"),
        ShowRulerTooltip: new Classes.Term("webviewer:show-ruler-tooltip", "Show/Hide Ruler"),
        Invert: new Classes.Term("webviewer:invert", "Invert"),
        InvertTooltip: new Classes.Term("webviewer:invert-tooltip", "Invert Colors"),
        Subtract: new Classes.Term("webviewer:subtract", "Subtract"),
        SubtractTooltip: new Classes.Term("webviewer:subtract-tooltip", "Subtract"),
        Subtraction: new Classes.Term("webviewer:subtraction", "Subtraction"),
        Enhance: new Classes.Term("webviewer:enhance", "Enhance"),
        EnhanceTooltip: new Classes.Term("webviewer:enhance-tooltip", "Enhance Image"),
        Export: new Classes.Term("webviewer:export", "Export"),
        ExportTooltip: new Classes.Term("webviewer:export-tooltip", "Export"),
        ExportAVI: new Classes.Term("webviewer:export-png", "AVI"),
        ExportAVITooltip: new Classes.Term("webviewer:export-png-tooltip", "Export AVI"),
        ExportMP4: new Classes.Term("webviewer:export-png", "MP4"),
        ExportMP4Tooltip: new Classes.Term("webviewer:export-png-tooltip", "Export MP4"),
        ExportPNG: new Classes.Term("webviewer:export-png", "PNG"),
        ExportPNGTooltip: new Classes.Term("webviewer:export-png-tooltip", "Export PNG"),
        StorePNG: new Classes.Term("webviewer:store-png", "Store PNG"),
        StorePNGTooltip: new Classes.Term("webviewer:store-png-tooltip", "Store PNG as Study Attachment"),
        ExportAllPNG: new Classes.Term("webviewer:export-all-png", "All"),
        ExportAllPNGTooltip: new Classes.Term("webviewer:export-all-png-tooltip", "Export All as PNG"),
        ExportSeries: new Classes.Term("webviewer:export-series", "Series"),
        ExportSeriesTooltip: new Classes.Term("webviewer:export-series-tooltip", "Export Series"),
        ExportStudy: new Classes.Term("webviewer:export-study", "Study"),
        ExportStudyTooltip: new Classes.Term("webviewer:export-study-tooltip", "Export Study"),
        ExportISO: new Classes.Term("webviewer:export-iso", "ISO"),
        ExportISOTooltip: new Classes.Term("webviewer:export-iso-tooltip", "Export ISO"),
        ExportViewer: new Classes.Term("webviewer:export-viewer", "Viewer"),
        ExportViewerTooltip: new Classes.Term("webviewer:export-viewer-tooltip", "Export Viewer"),
        EditMetadata: new Classes.Term("webviewer:edit-metadata", "Meta"),
        EditMetadataTooltip: new Classes.Term("webviewer:edit-metadata-tooltip", "Edit Metadata"),
        SecondaryCapture: new Classes.Term("webviewer:secondary-capture", "DICOM"),
        SecondaryCaptureTooltip: new Classes.Term("webviewer:secondary-capture-tooltip", "Secondary Capture"),
        Anonymize: new Classes.Term("webviewer:anonymize", "Anon."),
        AnonymizeTooltip: new Classes.Term("webviewer:anonymize-tooltip", "Anonymize Series"),
        AnonymizeStudy: new Classes.Term("webviewer:anonymize-study", "Anon."),
        AnonymizeStudyTooltip: new Classes.Term("webviewer:anonymize-study-tooltip", "Anonymize Study"),
        AnonymizeImage: new Classes.Term("webviewer:anonymize-image", "Anon."),
        AnonymizeImageTooltip: new Classes.Term("webviewer:anonymize-image-tooltip", "Anonymize Image Only"),
        CropSeries: new Classes.Term("webviewer:crop-series", "Crop"),
        CropSeriesTooltip: new Classes.Term("webviewer:crop-series-tooltip", "Crop Series"),
        SplitStudy: new Classes.Term("webviewer:split-study", "Split"),
        SplitStudyTooltip: new Classes.Term("webviewer:split-study-tooltip", "Split Study"),
        ReverseSeries: new Classes.Term("webviewer:reverse-series", "Reverse"),
        ReverseSeriesTooltip: new Classes.Term("webviewer:reverse-series-tooltip", "Reverse Series"),
        UnweaveSeries: new Classes.Term("webviewer:unweave-series", "Unweave"),
        UnweaveSeriesTooltip: new Classes.Term("webviewer:unweave-series-tooltip", "Unweave Series"),
        RearrangeSeries: new Classes.Term("webviewer:rearrange-series", "Rearrange"),
        RearrangeSeriesTooltip: new Classes.Term("webviewer:rearrange-series-tooltip", "Rearrange Series"),
        PartSeries: new Classes.Term("webviewer:part-series", "Part"),
        PartSeriesTooltip: new Classes.Term("webviewer:part-series-tooltip", "Part Series"),
        MergeSeries: new Classes.Term("webviewer:merge-series", "Merge"),
        MergeSeriesTooltip: new Classes.Term("webviewer:merge-series-tooltip", "Merge Series"),
        ResetStudyArrangement: new Classes.Term("webviewer:reset-study-arrangement", "Reset"),
        ResetStudyArrangementTooltip: new Classes.Term("webviewer:reset-study-arrangement-tooltip", "Reset Arrangement"),
        StartMeeting: new Classes.Term("webviewer:start-meeting", "Meet"),
        StartMeetingTooltip: new Classes.Term("webviewer:start-meeting-tooltip", "Start New Meeting"),
        KeyImage: new Classes.Term("webviewer:key-image", "Key"),
        KeyImageTooltip: new Classes.Term("webviewer:key-image-tooltip", "Mark As Key Image"),
        ShowOnlyKeyImages: new Classes.Term("webviewer:show-only-key-image", "Key Only"),
        ShowOnlyKeyImagesTooltip: new Classes.Term("webviewer:show-only-key-image-tooltip", "Only Display Key Images"),
        SaveKeyImageLayout: new Classes.Term("webviewer:save-key-image-layout", "Save Layout"),
        SaveKeyImageLayoutTooltip: new Classes.Term("webviewer:save-key-image-layout-tooltip", "Save Key Image Layout"),
        Delete: new Classes.Term("webviewer:delete", "Delete"),
        DeleteTooltip: new Classes.Term("webviewer:delete-tooltip", "Delete Images and Series"),
        DeleteImage: new Classes.Term("webviewer:delete-image", "Image"),
        DeleteImageTooltip: new Classes.Term("webviewer:delete-image-tooltip", "Delete Selected Image"),
        RemoveImages: new Classes.Term("webviewer:remove-images", "Remove"),
        RemoveImagesTooltip: new Classes.Term("webviewer:remove-images-tooltip", "Remove Images"),
        DeleteSeries: new Classes.Term("webviewer:delete-series", "Series"),
        DeleteSeriesTooltip: new Classes.Term("webviewer:delete-series-tooltip", "Delete Entire Series"),
        PreviousSeriesSet: new Classes.Term("webviewer:prev-series", "Prev."),
        PreviousSeriesSetTooltip: new Classes.Term("webviewer:prev-series-tooltip", "Previous Series"),
        NextSeriesSet: new Classes.Term("webviewer:next-series", "Next"),
        NextSeriesSetTooltip: new Classes.Term("webviewer:next-series-tooltip", "Next Series"),
        CineAll: new Classes.Term("webviewer:cine-all", "Cine All"),
        CineAllTooltip: new Classes.Term("webviewer:cine-all-tooltip", "Play/Pause All Multiframe Series"),
        DuplicateAnnotation: new Classes.Term("webviewer:duplicate-anotation", "Dupl."),
        DuplicateAnnotationTooltip: new Classes.Term("webviewer:duplicate-anotation-tooltip", "Duplicate Annotation"),
        CopyAnnotation: new Classes.Term("webviewer:copy-anotation", "Copy"),
        CopyAnnotationTooltip: new Classes.Term("webviewer:copy-anotation-tooltip", "Copy Annotation"),
        PasteAnnotation: new Classes.Term("webviewer:paste-anotation", "Paste"),
        PasteAnnotationTooltip: new Classes.Term("webviewer:paste-anotation-tooltip", "Paste Annotation"),
        Cine: new Classes.Term("webviewer:cine", "Cine"),
        CineTooltip: new Classes.Term("webviewer:cine-tooltip", "Cine"),
        Play: new Classes.Term("webviewer:play", "Play"),
        PlayTooltip: new Classes.Term("webviewer:play-tooltip", "Play"),
        Pause: new Classes.Term("webviewer:pause", "Pause"),
        PauseTooltip: new Classes.Term("webviewer:pause-tooltip", "Pause"),
        Faster: new Classes.Term("webviewer:faster", "Faster"),
        FasterTooltip: new Classes.Term("webviewer:faster-tooltip", "Faster"),
        Slower: new Classes.Term("webviewer:slower", "Slower"),
        SlowerTooltip: new Classes.Term("webviewer:slower-tooltip", "Slower"),
        Stop: new Classes.Term("webviewer:stop", "Stop"),
        StopTooltip: new Classes.Term("webviewer:stop-tooltip", "Stop"),
        Recordings: new Classes.Term("webviewer:recordings", "Recordings"),
        RecordingsTooltip: new Classes.Term("webviewer:recordings-tooltip", "Show Recordings"),
        AttachedReports: new Classes.Term("webviewer:attached-reports", "Attached Reports"),
        ReportsTooltip: new Classes.Term("webviewer:reports-tooltip", "Show Attached Reports"),
        Rewind: new Classes.Term("webviewer:rewind", "Rewind"),
        RewindTooltip: new Classes.Term("webviewer:rewind-tooltip", "Rewind 5 Seconds"),
        Forward: new Classes.Term("webviewer:forward", "Forward"),
        ForwardTooltip: new Classes.Term("webviewer:forward-tooltip", "Fast Forward 5 Seconds"),
        FullScreen: new Classes.Term("webviewer:fullscreen", "Full"),
        FullScreenTooltip: new Classes.Term("webviewer:fullscreen-tooltip", "Show FullScreen"),
        UseDiagnosticQuality: new Classes.Term("webviewer:use-diagnostic-quality", "Diag"),
        UseDiagnosticQualityTooltip: new Classes.Term("webviewer:use-diagnostic-quality-tooltip", "Use Diagnostic Quality Always"),
        RecordAudio: new Classes.Term("webviewer:record-audio", "Record"),
        RecordAudioTooltip: new Classes.Term("webviewer:record-audio-tooltip", "Record Audio"),
        Show: new Classes.Term("webviewer:show", "Show"),
        Hide: new Classes.Term("webviewer:hide", "Hide"),
        Mouse: new Classes.Term("webviewer:mouse", "Mouse"),
        MouseTooltip: new Classes.Term("webviewer:mouse-tool-settings", "Mouse Tool Settings"),
        ToolDependent: new Classes.Term("webviewer:tool-dependent", "Tool Dependent"),
        Close: new Classes.Term("webviewer:close", "Close"),
        Done: new Classes.Term("webviewer:done", "Done"),
        Cancel: new Classes.Term("webviewer:cancel", "Cancel"),
        Save: new Classes.Term("webviewer:save", "Save"),
        NextStudy: new Classes.Term("webviewer:next", "Next"),
        PreviousStudy: new Classes.Term("webviewer:previous", "Previous"),
        NextStudyTooltip: new Classes.Term("webviewer:next-study", "Next Study"),
        PreviousStudyTooltip: new Classes.Term("webviewer:previous-study", "Previous Study"),
        NextStudyByMRN: new Classes.Term("webviewer:next-mrn", "Next"),
        PreviousStudyByMRN: new Classes.Term("webviewer:previous-mrn", "Previous"),
        NextStudyByMRNTooltip: new Classes.Term("webviewer:next-study-mrn", "Next Patient"),
        PreviousStudyByMRNTooltip: new Classes.Term("webviewer:previous-study-mrn", "Previous Patient"),
        NextImage: new Classes.Term("webviewer:next-image", "Next"),
        PreviousImage: new Classes.Term("webviewer:previous-image", "Previous"),
        NextImageTooltip: new Classes.Term("webviewer:next-image", "Next Image"),
        PreviousImageTooltip: new Classes.Term("webviewer:previous-image", "Previous Image"),
        LayoutSingleSeries: new Classes.Term("webviewer:layout-series", "Layout Series"),
        LayoutSingleSeriesTooltip: new Classes.Term("webviewer:layout-single-series", "Layout Single Series"),
        ShowUltrasoundRegion: new Classes.Term("webviewer:ultrasound-region", "Show US"),
        HideUltrasoundRegion: new Classes.Term("webviewer:ultrasound-region", "Hide US"),
        UltrasoundRegionTooltip: new Classes.Term("webviewer:paste-anotation-tooltip", "Show/Hide Ultrasound Regions"),
        OpenReports: new Classes.Term("webviewer:reports", "Reports"),
        OpenReportsTooltip: new Classes.Term("webviewer:reports-tooltip", "Open Reports"),
        NewReport: new Classes.Term("webviewer:new-report", "Report"),
        NewReportTooltip: new Classes.Term("webviewer:new-report-tooltip", "Create New Report"),
        OpenReport: new Classes.Term("webviewer:open-report", "Open Report"),
        DeleteReport: new Classes.Term("webviewer:delete-report", "Delete Report"),
        /* Text Annotations */
        Patient: new Classes.Term("webviewer:patient", "Patient"),
        DOB: new Classes.Term("webviewer:dob", "DOB"),
        Primary: new Classes.Term("webviewer:primary", "Primary"),
        Related: new Classes.Term("webviewer:related", "Related"),
        Current: new Classes.Term("webviewer:current", "Latest Study"),
        Prior: new Classes.Term("webviewer:prior", "Prior"),
        FPS: new Classes.Term("webviewer:fps", "FPS"),
        ReferringPhysician: new Classes.Term("webviewer:referring-physician", "Ref. Phys."),
        Quality: new Classes.Term("webviewer:quality", "Quality"),
        HD: new Classes.Term("webviewer:hd", "HD"),
        SD: new Classes.Term("webviewer:sd", "SD"),
        Diagnostic: new Classes.Term("webviewer:diagnostic", "Diagnostic"),
        CenterWidth: new Classes.Term("webviewer:center-width", "Center/Width"),
        Auto: new Classes.Term("webviewer:auto", "Auto"),
        ImageType: new Classes.Term("webviewer:image-type", "Image Type"),
        Measurement: new Classes.Term("webviewer:measurement", "Measurement"),
        Area: new Classes.Term("webviewer:area", "Area"),
        Min: new Classes.Term("webviewer:min", "Min"),
        Max: new Classes.Term("webviewer:max", "Max"),
        Mean: new Classes.Term("webviewer:mean", "Mean"),
        StandardDev: new Classes.Term("webviewer:stdev", "SD"),
        SliceThickness: new Classes.Term("webviewer:slice-thickness", "Slice Thickness"),
        mm: new Classes.Term("webviewer:mm", "mm"),
        PixelSpacing: new Classes.Term("webviewer:pixel-spacing", "Distance"),
        PatientGeometry: new Classes.Term("webviewer:patient-geometry", "Patient Geometry"),
        AtImagingPlate: new Classes.Term("webviewer:at-imaging-plate", "Imaging Plate"),
        kVp: new Classes.Term("webviewer:kVp", "KVp"),
        mA: new Classes.Term("webviewer:mA", "mAs"),
        Exposure: new Classes.Term("webviewer:exposure", "Exposure"),
        MachineName: new Classes.Term("webviewer:machine-name", "Machine Name"),
        ImageLaterality: new Classes.Term("webviewer:image-laterality", "Image Laterality"),
        ViewPosition: new Classes.Term("webviewer:view-position", "View Position"),
        StationName: new Classes.Term("webviewer:station-name", "Station Name"),
        OperatorsName: new Classes.Term("webviewer:operators-name", "Operator's Name"),
        InstitutionName: new Classes.Term("webviewer:institution-name", "Institution Name"),
        InstitutionAddress: new Classes.Term("webviewer:institution-address", "Institution Address"),
        DetectorID: new Classes.Term("webviewer:detector-id", "Detector ID"),
        PatientMRN: new Classes.Term("webviewer:mrn", "MRN"),
        /* Print Viewer */
        PatientDOB: new Classes.Term("webviewer:patient-dob", "Patient DOB"),
        StudyDescription: new Classes.Term("webviewer:study-description", "Study Description"),
        StudyCreatedOn: new Classes.Term("webviewer:study-created-on", "Study Created On"),
        Untitled: new Classes.Term("webviewer:untitled", "Untitled"),
        TrueSize: new Classes.Term("webviewer:truesize", "True Size"),
        FullSize: new Classes.Term("webviewer:fullsize", "Full Size"),
        FitToPage: new Classes.Term("webviewer:fit-to-page", "Fit to Page"),
        ShowAllTrueSize: new Classes.Term("webviewer:show-all-truesize", "Show All True Size"),
        ShowAllFullSize: new Classes.Term("webviewer:show-all-fullsize", "Show All Full Size"),
        ShowAllFitToPage: new Classes.Term("webviewer:show-all-fit-to-page", "Show All Fit To Page"),
        Clear: new Classes.Term("webviewer:clear", "Clear"),
        Prev: new Classes.Term("webviewer:prev", "Prev"),
        Previous: new Classes.Term("webviewer:previous", "Previous"),
        Next: new Classes.Term("webviewer:next", "Next"),
        Remove: new Classes.Term("webviewer:remove", "Remove"),
        BackToTop: new Classes.Term("webviewer:back-to-top", "Back To Top"),
        PrintViewerRedirectMessageLine1: new Classes.Term("webviewer:print-redirect-line1", "Your web browser does not meet the minimum requirements for the Ambra Viewer application. You have been redirected to the Basic Viewer."),
        PrintViewerRedirectMessageLine2: new Classes.Term("webviewer:print-redirect-line2", "Click here for more information"),
        PrintViewerInstructions: new Classes.Term("webviewer:print-viewer-instructions", "Select images below to include them in the printed study."),
        /* Window Level Presets */
        SoftTissue: new Classes.Term("webviewer:soft-tissue", "Soft Tissue"),
        Bone: new Classes.Term("webviewer:bone", "Bone"),
        Head: new Classes.Term("webviewer:head", "Head"),
        Lung: new Classes.Term("webviewer:lung", "Lung"),
        Default: new Classes.Term("webviewer:default", "Default"),
        EnterPresetName: new Classes.Term("webviewer:enter-preset-name", "Enter a name for this window level preset"),
        SelectPresetToReplace: new Classes.Term("webviewer:select-preset-to-replace", "Which preset (1-9) would you like to replace?"),
        /* Color Table Presets */
        HotIron: new Classes.Term("webviewer:hot-iron", "Hot Iron"),
        PET: new Classes.Term("webviewer:pet", "PET"),
        PET20: new Classes.Term("webviewer:pet20", "PET 20"),
        HotBlue: new Classes.Term("webviewer:hot-blue", "Hot Blue"),
        Fire: new Classes.Term("webviewer:fire", "Fire"),
        /* Cine */
        VideoLoading: new Classes.Term("webviewer:video-loading", "Preparing video, please wait..."),
        VideoLoadingError: new Classes.Term("webviewer:video-error", "Error loading video"),
        VideoNotSupported: new Classes.Term("webviewer:video-not-supported", "Sorry, your browser does not support MP4 video. Please use a recent version of Chrome, IE or Safari to use the multiframe cine functionality."),
        /* Secondary Capture */
        SecondaryCaptureSuccess: new Classes.Term("webviewer:secondary-capture-success", "The secondary capture image was stored successfully."),
        SecondaryCaptureFailed: new Classes.Term("webviewer:secondary-capture-failed", "An error occurred during storage of the secondary capture image."),
        ImageCaptureSuccess: new Classes.Term("webviewer:image-capture-success", "The capture image was stored successfully."),
        ImageCaptureFailed: new Classes.Term("webviewer:image-capture-failed", "An error occurred during storage of the capture image."),
        /* Anonymization */
        CannotAnonymize: new Classes.Term("webviewer:cannot-anonymize", "Please create at least one rectangular measurement before anonymizing a study."),
        AnonymizeWarning: new Classes.Term("webviewer:anonymize-warning", "Are you sure you would like to anonymize this study? This operation will create a new study."),
        AnonymizeSuccess: new Classes.Term("webviewer:anonymize-success", "The study was enqueued for anonymization. The anonymized study will be stored in this namespace when the process is complete."),
        AnonymizeFailed: new Classes.Term("webviewer:anonymize-failed", "An error occurred during storage of the anonymized image(s)."),
        /* Crop */
        CannotCrop: new Classes.Term("webviewer:cannot-crop-1", "Please create at least one rectangular measurement before cropping."),
        CropWarning: new Classes.Term("webviewer:crop-warning", "Are you sure you would like to crop these series? This operation will create a new study."),
        CropSuccess: new Classes.Term("webviewer:crop-success", "The study was enqueued for cropping."),
        CropFailed: new Classes.Term("webviewer:crop-failed", "An error occurred during storage of the cropped series."),
        /* Study Structure Modification */
        SplitWarningNoSeries: new Classes.Term("webviewer:no-series-found-to-split", "No series found to split."),
        SplitWarningAllSeries: new Classes.Term("webviewer:all-series-found-to-split", "Cannot select all series to split."),
        SplitWarning: new Classes.Term("webviewer:no-series-found-to-split", "The selected series will be split into a new study.  Proceed?"),
        SplitSuccess: new Classes.Term("webviewer:split-success", "The split study was enqueued. It will appear in the study list when the process is complete."),
        SplitFailed: new Classes.Term("webviewer:split-failed", "An error occurred during storage of the split study."),
        SplitInstructions: new Classes.Term("webviewer:split-instructions", "Click thumbnails to select series.  Then click Split again to finalize."),
        RemoveImagesInstructions: new Classes.Term("webviewer:remove-images-instructions", "Remove images (format: \"1-16,18,20,25-36\") from series:"),
        RemoveImagesWarning: new Classes.Term("webviewer:remove-images-warning", "The range of values was invalid."),
        RemoveImagesError: new Classes.Term("webviewer:remove-images-error", "There was an error removing one or more images."),
        RemoveImagesSuccess: new Classes.Term("webviewer:remove-images-success", "The specified images have been removed."),
        StudyPHIExtendedError: new Classes.Term("webviewer:study-phi-extended-error", "Could not update study information."),
        StudyPHIExtendedSuccess: new Classes.Term("webviewer:study-phi-extended-success", "Study has been updated."),
        RearrangeImagesInstructions: new Classes.Term("webviewer:rearrange-images-instructions", "Move images (format: \"1-16,18,20,25-36\") to just before current image location for series:"),
        RearrangeImagesWarning: new Classes.Term("webviewer:rearrange-images-warning", "The range of values was invalid."),
        PartImagesInstructions: new Classes.Term("webviewer:part-images-instructions", "Move images (format: \"1-16,18,20,25-36\") to new series from series:"),
        StudyPHIExtendedResetSuccess: new Classes.Term("webviewer:study-phi-reset-extended-success", "Please reload the study to viewer the original data."),
        MergeInstructions: new Classes.Term("webviewer:merge-instructions", "Click thumbnails to select series.  Then click Merge again to finalize."),
        /* GSPS */
        SaveGSPSSuccess: new Classes.Term("webviewer:save-gsps-success", "The presentation state was stored successfully."),
        SaveGSPSFailed: new Classes.Term("webviewer:save-gsps-failed", "An error occurred during storage of the presentation state data."),
        /* Recorder */
        RecordedScriptIntroText: new Classes.Term("webviewer:recorded-script-intro", "The following URL can be copied and shared to replay the recorded script in the browser."),
        RecordingInProgress: new Classes.Term("webviewer:recording-in-progress", "Recording in progress"),
        PlaybackInProgress: new Classes.Term("webviewer:playback-in-progress", "Playback in progress"),
        ScriptSaved: new Classes.Term("webviewer:script-saved", "The recorded script was saved as an attachment."),
        ScriptSaveError: new Classes.Term("webviewer:script-save-error", "An error occurred while trying to store the recorded script."),
        AudioRecordingNotSupported: new Classes.Term("webviewer:audio-recording-not-supported", "Audio recording is not supported."),
        RecordingName: new Classes.Term("webviewer:recording-name", "Recording name"),
        RecordingUntitled: new Classes.Term("webviewer:recording-untitled", "Untitled Recording"),
        SentRecordingLink: new Classes.Term("webviewer:recording-link-sent", "The recording link has been sent."),
        ErrorSharingRecording: new Classes.Term("webviewer:error-sharing-recording", "An error occurred while attempting to create a public link to this recording."),
        ShareRecordingLink: new Classes.Term("webviewer:share-recording-link", "Share Recording Link"),
        PlayRecording: new Classes.Term("webviewer:play-recording", "Play Recording"),
        DeleteRecording: new Classes.Term("webviewer:delete-recording", "Delete Recording"),
        ErrorDeletingRecording: new Classes.Term("webviewer:error-deleting-recording", "A problem occurred while trying to delete the recording."),
        DeleteRecordingWarning: new Classes.Term("webviewer:delete-recording-warning", "Are you sure you would like to delete this recording? This operation cannot be undone."),
        NoRecordingsFound: new Classes.Term("webviewer:no-recordings-found", "No recordings found"),
        DeleteReportWarning: new Classes.Term("webviewer:delete-report-warning", "Are you sure you would like to delete this report? This operation cannot be undone."),
        ErrorDeletingReport: new Classes.Term("webviewer:error-deleting-report", "A problem occurred while trying to delete the report."),
        NoReportsFound: new Classes.Term("webviewer:no-reports-found", "No reports found"),
        NoGSPSFound: new Classes.Term("webviewer:no-gsps-found", "No GSPS found"),
        /* Meeting */
        EnterMeetingName: new Classes.Term("webviewer:enter-meeting-name", "Enter a name for this meeting:"),
        DefaultMeetingName: new Classes.Term("webviewer:default-meeting-name", "Untitled Meeting"),
        CannotStartMeeting: new Classes.Term("webviewer:cannot-start-meeting", "An error occurred while trying to start the meeting."),
        CannotJoinMeeting: new Classes.Term("webviewer:cannot-join-meeting", "An error occurred while trying to join the meeting."),
        CannotEndMeeting: new Classes.Term("webviewer:cannot-end-meeting", "An error occurred while trying to end the meeting."),
        CannotLeaveMeeting: new Classes.Term("webviewer:cannot-leave-meeting", "An error occurred while trying to leave the meeting."),
        MeetingEnded: new Classes.Term("webviewer:meeting-ended", "The meeting has ended."),
        ShareMeetingLink: new Classes.Term("webviewer:share-meeting-link", "Share Meeting Link"),
        CreatePublicLink: new Classes.Term("webviewer:create-public-link", "Create Public Link"),
        MeetingShared: new Classes.Term("webviewer:meeting-shared", "You can invite users to this meeting by sharing the following link."),
        MeetingAttendees: new Classes.Term("webviewer:meeting-attendees", "Attendees"),
        ErrorSharingMeeting: new Classes.Term("webviewer:error-sharing-meeting", "An error occurred while attempting to create a public link to this meeting."),
        YouAreThePresenter: new Classes.Term("webviewer:you-are-the-presenter", "You have been made the meeting presenter."),
        LeaveMeeting: new Classes.Term("webviewer:leave-meeting", "Leave Meeting"),
        EndMeeting: new Classes.Term("webviewer:end-meeting", "End Meeting"),
        MakePresenter: new Classes.Term("webviewer:make-presenter", "Make Presenter"),
        ConfirmChangeOfPresenter: new Classes.Term("webviewer:confirm-change-of-presenter", "Are you sure you would like to change the meeting presenter?"),
        CannotChangePresenter: new Classes.Term("webviewer:cannot-change-presenter", "An error occurred while trying to change the meeting presenter."),
        SentMeetingLink: new Classes.Term("webviewer:meeting-link-sent", "The meeting link has been sent."),
        MeetingSharing: new Classes.Term("webviewer:sharing", "Sharing"),
        /* Delete Images */
        DeleteImageWarning: new Classes.Term("webviewer:delete-image-warning", "Are you sure you would like to delete this image? This operation cannot be undone."),
        DeleteSeriesWarning: new Classes.Term("webviewer:delete-series-warning", "Are you sure you would like to delete this series? This operation cannot be undone."),
        ErrorDeletingImage: new Classes.Term("webviewer:error-deleting-image", "A problem occurred while trying to delete the image."),
        ErrorDeletingSeries: new Classes.Term("webviewer:error-deleting-series", "A problem occurred while trying to delete the series."),
        /* Study Actions */
        EnterEmailAddress: new Classes.Term("webviewer:enter-email-address", "Please enter an email address to share the study:"),
        EnterShareMessage: new Classes.Term("webviewer:enter-share-message", "You may enter an optional message for the email recipient:"),
        /* Image loading error */
        ImageLoadingErrorLine1: new Classes.Term("webviewer:image-error-line1", "This image is currently unavailable."),
        ImageLoadingErrorLine2: new Classes.Term("webviewer:image-error-line2", "Please try again later."),
        /* End User Agreement */
        EndUserTitle: new Classes.Term("webviewer:enduser-title", "Not Intended For Diagnostic Use"),
        EndUserBody: new Classes.Term("webviewer:enduser-body", 'By clicking "I Agree" you acknowledge this viewer is not intended for diagnostic use.'),
        EndUserDecline: new Classes.Term("webviewer:enduser-decline", "Cancel"),
        EndUserAccept: new Classes.Term("webviewer:enduser-accept", "I Agree"),
        /* Mouse Tool Settings */
        MouseSettingsPanelInstructions: new Classes.Term("webviewer:mousepanel-instructions", "Click with left or right mouse button to assign a tool to a mouse button."),
        RightButton: new Classes.Term("webviewer:mouse-right-button", "Right Button"),
        LeftButton: new Classes.Term("webviewer:mouse-left-button", "Left Button"),
        WheelScroll: new Classes.Term("webviewer:mouse-wheel-scroll", "Wheel"),
        /* Local Viewer Instructions */
        LocalViewerInstructionsTitle: new Classes.Term("webviewer:localviewer-instructions-title", "Viewer Instructions"),
        LocalViewerInstructionsBody: new Classes.Term("webviewer:localviewer-instructions-body", "To run the viewer on your computer, please use the following instructions."),
        LocalViewerInstructionsWinHeader: new Classes.Term("webviewer:localviewer-instructions-win-head", "Windows"),
        LocalViewerInstructionsWinBody: new Classes.Term("webviewer:localviewer-instructions-win-body", "Navigate to your CD and find the file named viewer.exe. Double click this file to start the viewer."),
        LocalViewerInstructionsMacHeader: new Classes.Term("webviewer:localviewer-instructions-mac-head", "Mac OS X"),
        LocalViewerInstructionsMacBody: new Classes.Term("webviewer:localviewer-instructions-mac-body", "Navigate to your CD and find the file named viewer. Double click this file to start the viewer."),
        /* Key Images */
        ClearedKeyImageLayout: new Classes.Term("webviewer:cleared-key-image-layout", "The key image layout has been cleared."),
        SaveKeyImageLayoutSuccess: new Classes.Term("webviewer:save-key-image-layout-success", "The current key image layout has been saved."),
        SaveKeyImageLayoutError: new Classes.Term("webviewer:save-key-image-layout-error", "There was a problem saving the current key image layout."),
        /* Warning Banners */
        PropagateInstructions: new Classes.Term("webviewer:propagate-instructions", "Scroll to propagate the selected annotation to slice."),
        PropagateAllComplete: new Classes.Term("webviewer:propagate-all-complete", "The annotation has been propagated to all instances in this series."),
        WarningCalibrationUsed: new Classes.Term("webviewer:calibration-used", "Measurements on this image are calculated using manually entered data."),
        WarningInstanceStamped: new Classes.Term("webviewer:instance-stamped", "This instance has been stamped by another user.  A new annotation cannot be created at this time."),
        OperationNotSupported: new Classes.Term("webviewer:operation-not-supported", "This operation is not supported."),
        OperationExceededLimits: new Classes.Term("webviewer:operation-not-supported", "This operation could not be completed."),
        OperationNoResult: new Classes.Term("webviewer:operation-no-result", "No area was found based on that threshold."),
        FrozenStudyWarning: new Classes.Term("webviewer:frozen-study", "This study is locked and cannot be edited at this time.")
    };

    function allViewerTerms() {
        var terms = [];

        for (var name in Terminology.Terms) {
            if (Terminology.Terms.hasOwnProperty(name)) {
                terms.push(Terminology.Terms[name]);
            }
        }

        return terms;
    }

    /**
    * A map of default values
    */
    function defaultValues() {
        return { lookup: function (term) {
                return term.defaultValue;
            } };
    }
    Terminology.defaultValues = defaultValues;

    /**
    * Load terminology asynchronously
    */
    function loadTerminology(sessionId, query) {
        var tags = _.map(allViewerTerms(), function (t) {
            return t.code;
        });
        var language = currentLanguage();

        return Observable.catchError(Observable.map(Services.loadTerminology(sessionId, tags, language, query), function (tags) {
            return {
                lookup: function (term) {
                    if (tags.values[term.code]) {
                        return tags.values[term.code];
                    } else {
                        return term.defaultValue;
                    }
                }
            };
        }), function () {
            return defaultValues();
        });
    }
    Terminology.loadTerminology = loadTerminology;

    /**
    * Load terminology asynchronously
    */
    function loadAllTerminology(sessionId, query) {
        var language = currentLanguage();

        return Observable.catchError(Observable.map(Services.loadAllTerminology(sessionId, language, query), function (tags) {
            return {
                lookup: function (term) {
                    if (tags.values[term.code]) {
                        return tags.values[term.code];
                    } else {
                        return term.defaultValue;
                    }
                }
            };
        }), function () {
            return defaultValues();
        });
    }
    Terminology.loadAllTerminology = loadAllTerminology;

    /**
    * Get the current language from the query string or browser
    */
    function currentLanguage() {
        var language = Query.findParameter(window.location, "language");

        if (!language) {
            var iso6391Code;

            if (navigator.userLanguage) {
                iso6391Code = navigator.userLanguage;
            } else if (navigator.language) {
                iso6391Code = navigator.language;
            } else {
                iso6391Code = "en";
            }

            language = iso6391Code.split("-")[0];
        }

        return new Classes.Language(language);
    }
    Terminology.currentLanguage = currentLanguage;

    /**
    * Convert an image type to a term
    */
    function imageTypeToTerm(type) {
        switch (type) {
            case 0 /* Thumbnail */:
                return Terminology.Terms.SD;
            case 1 /* FullResolution */:
                return Terminology.Terms.HD;
            case 3 /* FullResolutionHD */:
                return Terminology.Terms.HD;
            case 2 /* Diagnostic */:
                return Terminology.Terms.Diagnostic;
        }

        return null;
    }
    Terminology.imageTypeToTerm = imageTypeToTerm;
})(Terminology || (Terminology = {}));
var Browser;
(function (Browser) {
    Browser.MAX_MOBILE_DIM = 4096;

    function isChrome() {
        return navigator.userAgent.indexOf('Chrome') >= 0;
    }
    Browser.isChrome = isChrome;

    function isFirefox() {
        return !!window.InstallTrigger;
    }
    Browser.isFirefox = isFirefox;

    function isSafari() {
        return navigator.userAgent.indexOf('Safari') >= 0;
    }
    Browser.isSafari = isSafari;

    function isIE() {
        return navigator.userAgent.indexOf('MSIE') >= 0 || navigator.userAgent.indexOf('Trident') >= 0;
    }
    Browser.isIE = isIE;

    function isIE11() {
        return !!window.MSInputMethodContext && !!document.documentMode;
    }
    Browser.isIE11 = isIE11;

    function isIE9() {
        return $('body').is('.ie9');
    }
    Browser.isIE9 = isIE9;

    function isEdge() {
        return navigator.userAgent.indexOf('Edge') >= 0;
    }
    Browser.isEdge = isEdge;

    function isiOS() {
        return navigator.userAgent.indexOf('iPad') >= 0 || navigator.userAgent.indexOf('iPhone') >= 0;
    }
    Browser.isiOS = isiOS;

    function isiPad() {
        return navigator.userAgent.indexOf('iPad') >= 0;
    }
    Browser.isiPad = isiPad;

    function isiPadRetina() {
        return window.devicePixelRatio > 1;
    }
    Browser.isiPadRetina = isiPadRetina;

    function isMobile() {
        var ua = navigator.userAgent;
        return ua.indexOf("Android") >= 0 || ua.indexOf("iPhone") >= 0 || ua.indexOf("iPad") >= 0 || ua.indexOf("Windows Phone") >= 0;
    }
    Browser.isMobile = isMobile;

    function isPhone() {
        return (window.screen.width <= 640) || (window.matchMedia && window.matchMedia("only screen and (max-width: 640px)").matches);
    }
    Browser.isPhone = isPhone;

    function OS() {
        var matches = ["Win", "Mac", "X11", "Linux"];
        return _.find(matches, function (os) {
            return navigator.appVersion.indexOf(os) != -1;
        });
    }

    function viewerOS() {
        var osMap = { Mac: "osx", Win: "win" };
        return osMap[OS()] || "Browser";
    }
    Browser.viewerOS = viewerOS;

    function uiFound() {
        if (window.self !== window.top) {
            return !!window.parent.DG;
        }

        return !!window.DG;
    }
    Browser.uiFound = uiFound;

    /**
    * Check if HD image needs to be resized, since maximum canvas size can be exceeded on mobile platforms.
    * @param instance
    */
    function needsResize(instance) {
        return instance && ((instance.instanceAttributes.columns > Browser.MAX_MOBILE_DIM) || (instance.instanceAttributes.rows > Browser.MAX_MOBILE_DIM)) && Browser.isMobile();
    }
    Browser.needsResize = needsResize;

    function resize(instance) {
        var longest = Math.max(instance.instanceAttributes.rows, instance.instanceAttributes.columns);
        var shortest = Math.min(instance.instanceAttributes.rows, instance.instanceAttributes.columns);
        return Math.floor(Browser.MAX_MOBILE_DIM * (shortest / longest));
    }
    Browser.resize = resize;
})(Browser || (Browser = {}));
/// <reference path="../classes/Types.ts" />
/// <reference path="../models/StudySchema.ts" />
/// <reference path='../models/KO.ts' />
/// <reference path="../typings/jquery/jquery.d.ts" />
/// <reference path="Routes.ts" />
/// <reference path="Observable.ts" />
/**
* Observable wrappers for AJAX calls to storage
*
* @see Observable
*/
var V3Storage;
(function (V3Storage) {
    /**
    * A wrapper for the Study Schema method
    */
    function getStudySchema(sessionId, storageInfo, queryObject, useServices) {
        if (LocalViewer.isLocalViewer()) {
            return Observable.ret(LocalViewer.schema);
        } else {
            return AJAX.ajaxGet(Routes.StudySchema(sessionId, storageInfo, queryObject, useServices));
        }
    }
    V3Storage.getStudySchema = getStudySchema;

    /**
    * A wrapper for the Study PHI method
    */
    function getStudyPhi(sessionId, storageInfo, queryObject) {
        if (LocalViewer.isLocalViewer()) {
            return Observable.ret(LocalViewer.phi);
        } else {
            return AJAX.ajaxGet(Routes.StudyPhi(sessionId, storageInfo, queryObject));
        }
    }
    V3Storage.getStudyPhi = getStudyPhi;

    /**
    * A wrapper for the Study Tags method
    */
    function getStudyTag(sessionId, storageInfo, queryObject) {
        if (LocalViewer.isLocalViewer()) {
            return Observable.ret(LocalViewer.tag);
        } else {
            return AJAX.ajaxGet(Routes.StudyTag(sessionId, storageInfo, queryObject));
        }
    }
    V3Storage.getStudyTag = getStudyTag;

    /**
    * A wrapper for the Study Attributes method
    */
    function getStudyAttributes(sessionId, storageInfo, queryObject) {
        return AJAX.ajaxGet(Routes.StudyAttributes(sessionId, storageInfo, queryObject));
    }
    V3Storage.getStudyAttributes = getStudyAttributes;

    /**
    * A wrapper for the Image Attributes method
    */
    function getImageAttributes(sessionId, storageInfo, queryObject, instanceUid, imageVersion) {
        if (LocalViewer.isPersonalAccelerator()) {
            if (LocalViewer.images[instanceUid.value]) {
                return Observable.ret(LocalViewer.images[instanceUid.value]);
            } else {
                return AJAX.ajaxGet(Routes.ImageAttributes(sessionId, storageInfo, queryObject, instanceUid, imageVersion));
            }
        } else if (LocalViewer.isLocalViewer()) {
            return Observable.ret(LocalViewer.images[instanceUid.value]);
        } else {
            return AJAX.ajaxGet(Routes.ImageAttributes(sessionId, storageInfo, queryObject, instanceUid, imageVersion));
        }
    }
    V3Storage.getImageAttributes = getImageAttributes;

    /**
    * A wrapper for the Image Delete method
    */
    function deleteImage(sessionId, storageInfo, queryObject, instanceUid, imageVersion) {
        if (LocalViewer.isFileSystemViewer()) {
            return Observable.ret({});
        } else {
            return AJAX.ajax(Routes.DeleteImage(sessionId, storageInfo, queryObject, instanceUid), { type: "DELETE" });
        }
    }
    V3Storage.deleteImage = deleteImage;

    /**
    * Delete an attachment
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param attachment
    */
    function deleteAttachment(sessionId, storageInfo, queryObject, attachment) {
        if (LocalViewer.isFileSystemViewer()) {
            return Observable.ret({});
        } else {
            return AJAX.ajax(Routes.DeleteAttachment(sessionId, storageInfo, queryObject, attachment), { type: "DELETE" });
        }
    }
    V3Storage.deleteAttachment = deleteAttachment;

    /**
    * A wrapper for the GSPS method
    */
    function getGSPS(sessionId, storageInfo, queryObject, instanceUid, imageVersion) {
        if (LocalViewer.isLocalViewer()) {
            return Observable.ret({
                identificationModule: {},
                relationshipModule: {
                    referencedSeriesSequence: []
                },
                graphicAnnotationModule: {
                    graphicAnnotationSequence: []
                }
            });
        } else {
            return AJAX.ajaxGet(Routes.GetGSPS(sessionId, storageInfo, queryObject, instanceUid, imageVersion));
        }
    }
    V3Storage.getGSPS = getGSPS;

    /**
    * A wrapper for the attributes JSON method
    */
    function getImageJSON(sessionId, storageInfo, queryObject, instanceUid, imageVersion) {
        if (LocalViewer.isLocalViewer()) {
            return Observable.ret({});
        } else {
            return AJAX.ajaxGet(Routes.ImageJSON(sessionId, storageInfo, queryObject, instanceUid, imageVersion));
        }
    }
    V3Storage.getImageJSON = getImageJSON;

    /**
    * A wrapper for the CADSR method.
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param instanceUid
    * @param imageVersion
    */
    function getCADSR(sessionId, storageInfo, queryObject, instanceUid, imageVersion) {
        return AJAX.ajaxGet(Routes.CADSR(sessionId, storageInfo, queryObject, instanceUid, imageVersion));
    }
    V3Storage.getCADSR = getCADSR;

    /**
    * A wrapper for the Secondary Capture method
    */
    function postSecondaryCaptureImage(sessionId, storageInfo, queryObject, base64ImageData) {
        return AJAX.ajax(Routes.SecondaryCapture(sessionId, storageInfo, queryObject), {
            type: "POST",
            data: JSON.stringify({ base64ImageData: base64ImageData }),
            dataType: "json",
            contentType: "application/json"
        });
    }
    V3Storage.postSecondaryCaptureImage = postSecondaryCaptureImage;

    /**
    * A wrapper for the Secondary Capture method
    */
    function anonymizeStudy(sessionId, storageInfo, queryObject, regions) {
        return AJAX.ajax(Routes.AnonymizeStudy(sessionId, storageInfo, queryObject), {
            type: "POST",
            data: JSON.stringify(regions),
            contentType: "application/json"
        });
    }
    V3Storage.anonymizeStudy = anonymizeStudy;

    /**
    * A wrapper for Study Split method.
    * @param sessionId
    * @param storageInfo
    * @param queryObject
    * @param seriesUIDs
    */
    function splitStudy(sessionId, storageInfo, queryObject, seriesUIDs) {
        return AJAX.ajax(Routes.SplitStudy(sessionId, storageInfo, queryObject, seriesUIDs), {
            type: "POST",
            contentType: "application/json"
        });
    }
    V3Storage.splitStudy = splitStudy;

    function cropStudy(sessionId, storageInfo, queryObject, regions) {
        return AJAX.ajax(Routes.CropStudy(sessionId, storageInfo, queryObject), {
            type: "POST",
            data: JSON.stringify(regions),
            contentType: "application/json"
        });
    }
    V3Storage.cropStudy = cropStudy;

    /**
    * A wrapper for the GSPS method
    */
    function postGSPS(sessionId, storageInfo, queryObject, gsps) {
        return AJAX.ajax(Routes.PutGSPS(sessionId, storageInfo, queryObject), {
            type: "POST",
            data: JSON.stringify(gsps),
            dataType: "json",
            contentType: "application/json"
        });
    }
    V3Storage.postGSPS = postGSPS;

    /**
    * Get an attachment via AJAX
    */
    function GetAttachment(sessionId, storageInfo, queryObject, attachmentId, phiNamespace, version) {
        return AJAX.ajaxGet(Routes.Attachment(sessionId, storageInfo, queryObject, attachmentId, phiNamespace, version));
    }
    V3Storage.GetAttachment = GetAttachment;

    /**
    * Get a structured report
    */
    function GetStructuredReport(sessionId, storageInfo, queryObject, imageUid, version) {
        return AJAX.ajaxGet(Routes.StructuredReport(sessionId, storageInfo, queryObject, imageUid, version));
    }
    V3Storage.GetStructuredReport = GetStructuredReport;

    /**
    * Post an attachment to storage
    */
    function PostAttachment(sessionId, storageInfo, queryObject, contentType, uploadedBy, filename, data) {
        return AJAX.ajax(Routes.PostAttachment(sessionId, storageInfo, queryObject, contentType, uploadedBy, filename), {
            type: "POST",
            data: JSON.stringify(data),
            dataType: "json",
            contentType: "application/json"
        });
    }
    V3Storage.PostAttachment = PostAttachment;

    /**
    * Post an image attachment to storage
    * @param {Classes.SessionId} sessionId
    * @param {Models.StudyStorageInfo} storageInfo
    * @param {Classes.QueryObject} queryObject
    * @param {string} uploadedBy
    * @param blob
    * @returns {Observable.Observable<Models.AttachmentResult>}
    * @constructor
    */
    function PostImageAttachment(sessionId, storageInfo, queryObject, uploadedBy, filename, blob) {
        var fd = new FormData();
        fd.append("data", blob, filename);

        return AJAX.ajax(Routes.PostImageAttachment(sessionId, storageInfo, queryObject, uploadedBy, filename), {
            type: "POST",
            data: fd,
            processData: false,
            contentType: false
        });
    }
    V3Storage.PostImageAttachment = PostImageAttachment;
})(V3Storage || (V3Storage = {}));
/// <reference path="Map.d.ts" />

/**
* Additional methods for dealing with arrays
* @see Array
*/
var Arrays;
(function (Arrays) {
    function toMap(ts, keySelector, valueSelector) {
        var obj = {};

        for (var i = 0; i < ts.length; i++) {
            var key = keySelector(ts[i]);
            var value = valueSelector(ts[i]);
            obj[key] = value;
        }

        return Maps.fromObject(obj);
    }

    function indexWhere(ts, filter) {
        for (var i = 0; i < ts.length; i++) {
            if (filter(ts[i])) {
                return i;
            }
        }
        return -1;
    }

    function only(ts) {
        if (ts.length === 1) {
            return ts[0];
        }
        return null;
    }

    function lastSortedIndex(ts, t, ord, compare) {
        var k = ord(t);
        for (var i = ts.length - 1; i >= 0; i--) {
            if (compare(ord(ts[i]), k) <= 0) {
                return i;
            }
        }
        return -1;
    }

    function sum(ts, f) {
        return _.foldl(ts, function (sum, t) {
            return sum + f(t);
        }, 0);
    }

    function foldl1(ts, f) {
        return _.foldl(_.tail(ts), function (x, a, index, list) {
            return f(x, a);
        }, _.head(ts));
    }

    Array.prototype.toMap = function (keySelector, valueSelector) {
        return toMap(this, keySelector, valueSelector);
    };

    Array.prototype.sum = function (f) {
        return sum(this, f);
    };

    Array.prototype.indexWhere = function (f) {
        return indexWhere(this, f);
    };

    Array.prototype.only = function () {
        return only(this);
    };

    Array.prototype.lastSortedIndex = function (t, f, c) {
        return lastSortedIndex(this, t, f, c);
    };

    Array.prototype.foldl1 = function (f) {
        return foldl1(this, f);
    };
})(Arrays || (Arrays = {}));

/**
* Methods for dealing with maps
* @see Map
*/
var Maps;
(function (Maps) {
    /**
    * Apply the toString function before looking up a key
    */
    function preMap(m) {
        return {
            lookup: function (k) {
                return m.lookup(k.toString());
            }
        };
    }
    Maps.preMap = preMap;

    /**
    * Convert an object to a map, where keys are proprties and values are property values
    */
    function fromObject(obj) {
        return {
            lookup: function (key) {
                return obj[key];
            }
        };
    }
    Maps.fromObject = fromObject;

    /**
    * Lookup a key, returning a default value if none is present
    */
    function lookupDef(m, k, def) {
        var val = m.lookup(k);

        if (val === undefined) {
            return def;
        }

        return val;
    }
    Maps.lookupDef = lookupDef;
})(Maps || (Maps = {}));
var Dicom;
(function (Dicom) {
    /**
    * Represents a DICOM tag - a group and an element
    */
    var Tag = (function () {
        function Tag(group, element, name) {
            this.group = group;
            this.element = element;
            this.name = name;
        }
        Tag.prototype.toString = function () {
            return "(" + this.group + "," + this.element + ")";
        };
        return Tag;
    })();
    Dicom.Tag = Tag;

    var Overlay = (function () {
        function Overlay() {
        }
        Overlay.MAX = 16;
        return Overlay;
    })();
    Dicom.Overlay = Overlay;

    var UltrasoundRegion = (function () {
        function UltrasoundRegion(index, tags) {
            this.index = index;
            this.typeCode = Dicom.findDecimalTagSafely(tags, ["(0018,6014)"]);
            this.unitsCode = {
                x: Dicom.findDecimalTagSafely(tags, ["(0018,6024)"]),
                y: Dicom.findDecimalTagSafely(tags, ["(0018,6026)"]) };
            this.spacing = {
                x: Dicom.findDecimalTagSafely(tags, ["(0018,602C)"]),
                y: Dicom.findDecimalTagSafely(tags, ["(0018,602E)"]) };
            this.bounds = {
                min: {
                    x: Dicom.findDecimalTagSafely(tags, ["(0018,6018)"]),
                    y: Dicom.findDecimalTagSafely(tags, ["(0018,601A)"])
                },
                max: {
                    x: Dicom.findDecimalTagSafely(tags, ["(0018,601C)"]),
                    y: Dicom.findDecimalTagSafely(tags, ["(0018,601E)"])
                }
            };
        }
        UltrasoundRegion.prototype.isDrawable = function () {
            return !isNaN(this.bounds.min.x) && !isNaN(this.bounds.min.y) && !isNaN(this.bounds.max.x) && !isNaN(this.bounds.max.y);
        };

        UltrasoundRegion.prototype.isAnnotatable = function () {
            return (this.unitsCode.x == UltrasoundRegion.UNIT_CM) && (this.unitsCode.y == UltrasoundRegion.UNIT_CM) && (this.spacing.x > 0) && (this.spacing.y > 0);
        };

        UltrasoundRegion.prototype.contains = function (pt) {
            var p1 = this.bounds.min;
            var p2 = this.bounds.max;

            return pt.x >= Math.min(p1.x, p2.x) && pt.x <= Math.max(p1.x, p2.x) && pt.y >= Math.min(p1.y, p2.y) && pt.y <= Math.max(p1.y, p2.y);
        };
        UltrasoundRegion.UNIT_CM = 0x0003;
        return UltrasoundRegion;
    })();
    Dicom.UltrasoundRegion = UltrasoundRegion;

    var Ultrasound = (function () {
        function Ultrasound(json) {
            this.tags = Dicom.findTag(json.tags, ["(0018,6011)"], -1);
        }
        Ultrasound.prototype.getNumRegions = function () {
            if (this.tags && this.tags.items) {
                return this.tags.items.length;
            }

            return 0;
        };

        Ultrasound.prototype.getRegion = function (index) {
            return new UltrasoundRegion(index, this.tags.items[index].tags);
        };

        Ultrasound.prototype.getAllRegions = function () {
            var regions = [];
            var num = this.getNumRegions();

            for (var ctr = 0; ctr < num; ctr += 1) {
                regions.push(this.getRegion(ctr));
            }

            return regions;
        };
        Ultrasound.isUltrasound = function (instance) {
            return instance.seriesAttributes.modality == "US";
        };

        Ultrasound.isUltrasoundSeries = function (seriesAttributes) {
            return seriesAttributes.modality == "US";
        };

        Ultrasound.findSpacing = function (measurementInProgress, instance, json) {
            var us = new Dicom.Ultrasound(json || instance.instanceAttributes.json);
            var regions = us.getAllRegions();
            var pts = measurementInProgress.endpoints();
            var spacingX, spacingY;
            var regionFound = false;
            var validMeasurement = true;

            loop:
            for (var ctrP = 0; ctrP < pts.length; ctrP += 1) {
                var loc = pts[ctrP].location;
                var ptFound = false;

                for (var ctrR = 0; ctrR < regions.length; ctrR += 1) {
                    var region = regions[ctrR];

                    if (region.isAnnotatable()) {
                        if (region.contains(loc)) {
                            ptFound = true;

                            if (regionFound) {
                                if (spacingX != region.spacing.x) {
                                    validMeasurement = false;
                                    break loop;
                                } else if (spacingY != region.spacing.y) {
                                    validMeasurement = false;
                                    break loop;
                                }
                            } else {
                                spacingX = region.spacing.x;
                                spacingY = region.spacing.y;
                                regionFound = true;
                            }
                        }
                    }
                }

                if (!ptFound) {
                    validMeasurement = false;
                    break;
                }
            }

            return { valid: validMeasurement, spacingX: spacingX * 10, spacingY: spacingY * 10 };
        };
        return Ultrasound;
    })();
    Dicom.Ultrasound = Ultrasound;

    var Subtraction = (function () {
        function Subtraction(json) {
            this.tags = Dicom.findTag(json.tags, ["(0028,6100)"], -1);
        }
        Subtraction.prototype.getMaskIndices = function () {
            if (this.tags && this.tags.length) {
                var originalIndices = Dicom.findDecimalsTagSafely(this.tags.items[0].tags, ["(0028,6110)"]);

                if (!originalIndices) {
                    originalIndices = [1];
                }

                return _.map(originalIndices, function (index) {
                    return index - 1;
                });
            }

            return [0];
        };

        Subtraction.prototype.isMask = function (instance, series) {
            var index = _.indexOf(series.instances, instance);
            return _.contains(this.getMaskIndices(), index);
        };

        Subtraction.prototype.allMasksLoaded = function (series) {
            var maskIndices = this.getMaskIndices();

            if (!maskIndices) {
                maskIndices = [0];
            }

            var allLoaded = true;

            _.each(maskIndices, function (index) {
                var maskInstance = series.series.instances[index];
                var imageType = series.getImageType(maskInstance);
                var instanceKey = series.getInstanceKey(index);
                var image = series.renderer.imageElements.get(instanceKey, imageType);

                allLoaded = (allLoaded && (!!image && image.imageElement.complete && typeof image.imageElement.naturalWidth !== "undefined" && image.imageElement.naturalWidth !== 0));
            });

            return allLoaded;
        };

        Subtraction.supportsSubtraction = function (seriesAttributes) {
            return seriesAttributes.modality == "XA";
        };

        Subtraction.buildXA = function (instance) {
            if (instance.instanceAttributes.json) {
                return new Subtraction(instance.instanceAttributes.json);
            } else {
                return null;
            }
        };
        return Subtraction;
    })();
    Dicom.Subtraction = Subtraction;

    /**
    * Standard DICOM tags used by the application
    */
    var Dictionary = (function () {
        function Dictionary() {
        }
        Dictionary.ImageType = new Tag("0008", "0008", "Image Type");
        Dictionary.SOPInstanceUID = new Tag("0008", "0018", "SOP Instance UID");
        Dictionary.StudyCreateDate = new Tag("0008", "0020", "Study Create Date");
        Dictionary.SeriesCreateDate = new Tag("0008", "0021", "Series Create Date");
        Dictionary.AcquisitionDateTime = new Tag("0008", "002A", "Acquisition Date/Time");
        Dictionary.StudyCreateTime = new Tag("0008", "0030", "Study Create Time");
        Dictionary.SeriesCreateTime = new Tag("0008", "0031", "Series Create Time");
        Dictionary.AccessionNumber = new Tag("0008", "0050", "Accession Number");
        Dictionary.Modality = new Tag("0008", "0060", "Modality");
        Dictionary.ReferringPhysicianName = new Tag("0008", "0090", "Referring Physician Name");
        Dictionary.StationName = new Tag("0008", "1010", "Station Name");
        Dictionary.StudyDescription = new Tag("0008", "1030", "Study Description");
        Dictionary.SeriesDescription = new Tag("0008", "103E", "Series Description");
        Dictionary.OperatorsName = new Tag("0008", "1070", "Operator's Name");
        Dictionary.InstitutionName = new Tag("0008", "0080", "Institution Name");
        Dictionary.InstitutionAddress = new Tag("0008", "0081", "Institution Address");
        Dictionary.ReferencedImageSequence = new Tag("0008", "1140", "Referenced Image Sequence");
        Dictionary.ReferencedInstanceUID = new Tag("0008", "1155", "Referenced Instance UID");
        Dictionary.RecommendedFrameRate = new Tag("0008", "2144", "Recommended Frame Rate");
        Dictionary.PatientName = new Tag("0010", "0010", "Patient Name");
        Dictionary.PatientID = new Tag("0010", "0020", "Patient ID");
        Dictionary.PatientBirthDate = new Tag("0010", "0030", "Patient Birth Date");
        Dictionary.PatientSex = new Tag("0010", "0040", "Patient Sex");
        Dictionary.OtherPatientIDs = new Tag("0010", "1000", "Other Patient IDs");
        Dictionary.PatientAge = new Tag("0010", "1010", "Patient Age");
        Dictionary.PatientSize = new Tag("0010", "1020", "Patient Size");
        Dictionary.PatientWeight = new Tag("0010", "1030", "Patient Weight");
        Dictionary.PatientAddress = new Tag("0010", "1040", "Patient Address");
        Dictionary.AdditionalPatientHistory = new Tag("0010", "21B0", "Additional Patient History");
        Dictionary.PatientComments = new Tag("0010", "4000", "Patient Comments");
        Dictionary.BodyPart = new Tag("0018", "0015", "Body Part");
        Dictionary.CineRate = new Tag("0018", "0040", "Cine Rate");
        Dictionary.HeartRate = new Tag("0018", "1088", "Heart Rate");
        Dictionary.RadiographicMagnificationFactor = new Tag("0018", "1114", "Radiographic Magnification Factor");
        Dictionary.ImagerPixelSpacing = new Tag("0018", "1164", "Imager Pixel Spacing");
        Dictionary.PatientPosition = new Tag("0018", "5100", "Patient Position");
        Dictionary.ViewPosition = new Tag("0018", "5101", "View Position");
        Dictionary.DetectorID = new Tag("0018", "700A", "Detector ID");
        Dictionary.StudyUID = new Tag("0020", "000D", "Study UID");
        Dictionary.SeriesUID = new Tag("0020", "000E", "Series UID");
        Dictionary.InstanceNumber = new Tag("0020", "0013", "Instance Number");
        Dictionary.PatientOrientation = new Tag("0020", "0020", "Patient Orientation");
        Dictionary.ImagePositionPatient = new Tag("0020", "0032", "Image Position Patient");
        Dictionary.ImageOrientationPatient = new Tag("0020", "0037", "Image Orientation Patient");
        Dictionary.ImageLaterality = new Tag("0020", "0062", "Image Laterality");
        Dictionary.PhotometricInterpretation = new Tag("0028", "0004", "Photometric Interpretation");
        Dictionary.Rows = new Tag("0028", "0010", "Rows");
        Dictionary.Columns = new Tag("0028", "0011", "Columns");
        Dictionary.PixelSpacing = new Tag("0028", "0030", "Pixel Spacing");
        Dictionary.PixelAspectRatio = new Tag("0028", "0034", "Pixel Aspect Ratio");
        Dictionary.BitsStored = new Tag("0028", "0101", "Bits Stored");
        Dictionary.PixelRepresentation = new Tag("0028", "0103", "Pixel Representation");
        Dictionary.PixelSpacingCalibrationDescription = new Tag("0028", "0A04", "Pixel Spacing Calibration Description");
        Dictionary.WindowCenter = new Tag("0028", "1050", "Window Center");
        Dictionary.WindowWidth = new Tag("0028", "1051", "Window Width");
        Dictionary.RescaleIntercept = new Tag("0028", "1052", "RescaleIntercept");
        Dictionary.RescaleSlope = new Tag("0028", "1053", "Rescale Slope");
        Dictionary.RescaleType = new Tag("0028", "1054", "Rescale Type");
        Dictionary.VOILUTFunction = new Tag("0028", "1056", "VOI LUT Function");
        Dictionary.FrameTime = new Tag("0018", "1063", "Frame Time");
        Dictionary.BreastImplantPresent = new Tag("0028", "1300", "Breast Implant Present");
        Dictionary.RequestedProcedureDescription = new Tag("0032", "1060", "Requested Procedure Description");
        Dictionary.SliceThickness = new Tag("0018", "0050", "Slice Thickness");
        Dictionary.KVP = new Tag("0018", "0060", "KVP");
        Dictionary.Exposure = new Tag("0018", "1152", "Exposure");
        Dictionary.PresentationLUTShape = new Tag("2050", "0020", "Presentation LUT Shape");
        Dictionary.RadiationMachineName = new Tag("3002", "0020", "Radiation Machine Name");
        Dictionary.OverlayDataTags = _.map(_.range(Overlay.MAX), function (num) {
            return new Dicom.Tag((0x6000 + (num * 2)).toString(16), "3000", "Overlay Data");
        });
        return Dictionary;
    })();
    Dicom.Dictionary = Dictionary;

    /**
    * Enhanced (multi-frame) DICOM sequences
    */
    var Multiframe = (function () {
        function Multiframe() {
        }
        Multiframe.SharedFunctionalGroupSeq = new Tag("5200", "9229", "Shared Functional Group Sequence");
        Multiframe.PerFrameFunctionalGroupSeq = new Tag("5200", "9230", "Per-frame Functional Group Sequence");
        Multiframe.PixelValueTransSeq = new Tag("0028", "9145", "Pixel Value Transformation Sequence");
        Multiframe.FrameVOILUTSeq = new Tag("0028", "9132", "Frame VOI LUT Sequence");
        Multiframe.PlaneOrientationSeq = new Tag("0020", "9116", "Plane Orientation Sequence");
        Multiframe.PlanePositionSeq = new Tag("0020", "9113", "Plane Position Sequence");
        Multiframe.PixelMeasuresSeq = new Tag("0028", "9110", "Pixel Measures Sequence");
        return Multiframe;
    })();
    Dicom.Multiframe = Multiframe;

    /**
    * A map of tags by group and element number
    */
    Dicom.tagsByGroupAndElement = Maps.preMap(Maps.fromObject((function () {
        var o = {};

        for (var prop in Dictionary) {
            if (Dictionary.hasOwnProperty(prop)) {
                var tag = Dictionary[prop];

                o[tag.toString()] = tag.name;
            }
        }

        return o;
    })()));

    function hexToDec(tag) {
        var tags = tag.replace(/\(|\)/g, '').split(/,|-/);
        return _.map(tags, function (item) {
            return parseInt('0x' + item);
        });
    }

    /**
    *
    * @param currentTags - structured metadata via /json
    * @param {string[]} tags - list of nested tags, e.g., (1234,1234) or a sequence with item index (1234,1234)[0]
    * @param indexDefault
    * @returns {any}
    */
    function findTag(currentTags, tags, indexDefault) {
        if (typeof indexDefault === "undefined") { indexDefault = 0; }
        var foundTag;

        _.each(tags, function (tag) {
            var itemIndex = indexDefault;
            if (tag.indexOf("[") != -1) {
                itemIndex = parseInt(tag.split("[")[1].replace(/\D/g, ''));
            }

            var tagDecimal = hexToDec(tag);
            foundTag = _.find(currentTags, function (item) {
                return ((item.group == tagDecimal[0]) && (item.element == tagDecimal[1]));
            });

            if (foundTag && foundTag.items && (foundTag.items.length > itemIndex) && (itemIndex != -1)) {
                currentTags = foundTag.items[itemIndex].tags;
            }
        });

        return foundTag;
    }
    Dicom.findTag = findTag;

    function findDecimalTagSafely(currentTags, tags, indexDefault) {
        if (typeof indexDefault === "undefined") { indexDefault = 0; }
        var tag = Dicom.findTag(currentTags, tags, indexDefault);
        if (tag && tag.value) {
            return parseFloat(tag.value);
        }

        return Number.NaN;
    }
    Dicom.findDecimalTagSafely = findDecimalTagSafely;

    function findDecimalsTagSafely(currentTags, tags, indexDefault) {
        if (typeof indexDefault === "undefined") { indexDefault = 0; }
        var tag = Dicom.findTag(currentTags, tags, indexDefault);
        if (tag && tag.value) {
            return Parsers.parseDS(tag.value);
        }

        return null;
    }
    Dicom.findDecimalsTagSafely = findDecimalsTagSafely;
})(Dicom || (Dicom = {}));
///<reference path="../typings/underscore/underscore.d.ts" />
/**
* Helper methods for working with DICOM-formatted strings
*/
var Parsers;
(function (Parsers) {
    /**
    * Parse a DS (double string)
    */
    function parseDS(ds) {
        if (ds) {
            return _.map(ds.split(/\\|\//), parseFloat);
        }
        return null;
    }
    Parsers.parseDS = parseDS;

    /**
    * Test whether a DS value is corrupt
    */
    function isCorruptDS(ds) {
        if (ds) {
            return _.any(ds.split(/\\|\//), function (s, index, list) {
                return isNaN(s);
            });
        }
        return false;
    }
    Parsers.isCorruptDS = isCorruptDS;

    /**
    * Parse a DICOM date string
    */
    function parseDicomDate(ds) {
        if (!ds) {
            return null;
        }

        ds = $.trim(ds);

        if (ds.length === 8 && !isNaN(ds)) {
            var year = ds.substr(0, 4);
            var month = ds.substr(4, 2);
            var day = ds.substr(6, 2);

            if (!isNaN(year) && !isNaN(month) && !isNaN(day)) {
                return new Date(parseInt(year, 10), parseInt(month, 10) - 1, parseInt(day, 10), 0, 0, 0, 0);
            }
        }

        return null;
    }
    Parsers.parseDicomDate = parseDicomDate;

    /**
    * Parse a DICOM time string
    */
    function parseDicomTime(ts) {
        if (!ts) {
            return null;
        }

        ts = $.trim(ts);

        var date = null;

        if (ts.length >= 2 && !isNaN(ts)) {
            var hourS = ts.substr(0, 2);
            var hour = 0;
            var minute = 0;
            var second = 0;

            if (!isNaN(hourS)) {
                hour = parseInt(hourS, 10);

                if (ts.length >= 4) {
                    var minuteS = ts.substr(2, 2);

                    if (!isNaN(minuteS)) {
                        minute = parseInt(minuteS, 10);

                        if (ts.length >= 6) {
                            var secondS = ts.substring(4, ts.length);

                            if (!isNaN(secondS)) {
                                second = parseFloat(secondS);
                            }
                        }
                    }
                }
            }

            var wholeSeconds = Math.floor(second);
            var millis = Math.round((second - wholeSeconds) * 1000);

            return new Date(0, 0, 0, hour, minute, wholeSeconds, millis);
        }

        return null;
    }
    Parsers.parseDicomTime = parseDicomTime;

    /**
    * Split a set of strings into an array
    */
    function parseCS(ss) {
        if (ss) {
            return ss.split(/\\|\//);
        }
        return [];
    }
    Parsers.parseCS = parseCS;

    /**
    * Fixes bit stored when it's stored as a bit mask.
    * @param {number} val
    * @returns {number}
    */
    function fixBitCountField(val) {
        if (val > 16) {
            switch (val) {
                case 0xffff:
                    val = 16;
                    break;
                case 0x0fff:
                    val = 12;
                    break;
                case 0x08ff:
                    val = 10;
                    break;
                case 0x00ff:
                    val = 8;
                    break;
            }
        }

        return val;
    }
    Parsers.fixBitCountField = fixBitCountField;
})(Parsers || (Parsers = {}));
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Routes.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Observable.ts' />
///<reference path='Map.d.ts' />
///<reference path='Array.ts' />
///<reference path='DicomDictionary.ts' />
///<reference path='Parsers.ts' />
///<reference path='../Main.ts' />
/**
* Observables for working with study metadata
*/
var Study;
(function (Study) {
    /**
    * Make all necessary AJAX calls to assemble the parts of a study object required for initial viewing.
    *
    * Loads top-level metadata and instance attributes for the first image in every series.
    */
    function loadStudy(sessionId, studyStorage, queryObject, keyImagesOnly, permissions, viewerSettings, settings) {
        var accountSettings = settings ? Observable.ret(settings) : Observable.map(Observable.catchError(Services.getAccountSettings(sessionId, queryObject), function () {
            return {};
        }), function (settings) {
            return Main.Main.applySettingsOverrides(settings);
        });

        return Observable.bind(accountSettings, function (accountSettings) {
            var useServices = accountSettings.cache == 1;
            var accelerated = accountSettings.is_accelerated == 1;

            var schema = V3Storage.getStudySchema(sessionId, studyStorage, queryObject, useServices);
            var phi = V3Storage.getStudyPhi(sessionId, studyStorage, queryObject);
            var tags = V3Storage.getStudyTag(sessionId, studyStorage, queryObject);
            var keyImages = permissions.keyimage_view !== 0 ? Services.listKeyImages(sessionId, queryObject) : Observable.ret({ images: [] });

            var loadInParallel = Observable.ap(Observable.ap(Observable.ap(Observable.ap(Observable.ret(function (s) {
                return function (p) {
                    return function (t) {
                        return function (k) {
                            return {
                                schema: s,
                                phi: p,
                                tags: t,
                                keyImages: k
                            };
                        };
                    };
                };
            }), schema), phi), tags), keyImages);

            return Observable.bind(loadInParallel, function (o) {
                return Study.convertStudySchema(sessionId, studyStorage, queryObject, o.schema, o.phi, o.tags, o.keyImages, keyImagesOnly, useServices, accelerated, viewerSettings, accountSettings);
            });
        });
    }
    Study.loadStudy = loadStudy;

    /**
    * Convert a list of tag value pairs into a typed dictionary
    */
    function tagsToMap(tags) {
        var dict = tags.toMap(function (t) {
            return t.tag;
        }, function (t) {
            return t.value;
        });

        return Maps.preMap(dict);
    }

    // order schema series by id and images by rank
    function preprocessSchema(schema) {
        schema.series = _.sortBy(schema.series, function (series) {
            return series.id;
        });

        _.each(schema.series, function (series) {
            series.images = _.sortBy(series.images, function (image) {
                return image.rank;
            });
        });

        return schema;
    }

    /**
    * Convert the results of AJAX calls into a study object.
    *
    * @returns An Observable which completes the computation by loading instance attributes
    */
    function convertStudySchema(sessionId, studyStorage, query, schema, phi, tags, keyImages, keyImagesOnly, useServices, accelerated, viewerSettings, accountSettings) {
        var _this = this;
        var showPDFs = (viewerSettings != null && viewerSettings.showPDFs != null && viewerSettings.showPDFs == true) ? true : false;
        var singleInstanceSeries = (accountSettings != null && accountSettings.viewer_single_instance_series > 0) ? true : false;

        schema = preprocessSchema(schema);

        var newStudy = new Models.Study();

        newStudy.studyAttributes = new Models.StudyAttributes();
        newStudy.studyAttributes.queryObject = query;
        newStudy.studyAttributes.studyStorage = studyStorage;
        newStudy.studyAttributes.accelerated = accelerated;

        var phiDict = tagsToMap(phi.attr);
        var typedDict = tagsToMap(tags.tags);

        newStudy.studyAttributes.patientId = new Classes.PatientID(phiDict.lookup(Dicom.Dictionary.PatientID));
        newStudy.studyAttributes.patientName = phiDict.lookup(Dicom.Dictionary.PatientName);
        newStudy.studyAttributes.patientBirthDate = Parsers.parseDicomDate(phiDict.lookup(Dicom.Dictionary.PatientBirthDate));
        newStudy.studyAttributes.patientSex = phiDict.lookup(Dicom.Dictionary.PatientSex);
        newStudy.studyAttributes.referringPhysicianName = phiDict.lookup(Dicom.Dictionary.ReferringPhysicianName);
        newStudy.studyAttributes.accessionNumber = new Classes.AccessionNumber(phiDict.lookup(Dicom.Dictionary.AccessionNumber));
        newStudy.studyAttributes.studyDescription = phiDict.lookup(Dicom.Dictionary.StudyDescription);
        newStudy.studyAttributes.studyCreateDate = Parsers.parseDicomDate(phiDict.lookup(Dicom.Dictionary.StudyCreateDate) || typedDict.lookup(Dicom.Dictionary.StudyCreateDate));
        newStudy.studyAttributes.studyCreateTime = Parsers.parseDicomTime(typedDict.lookup(Dicom.Dictionary.StudyCreateTime));
        newStudy.studyAttributes.uuid = new Classes.StudyUUID(studyStorage.uuid);

        newStudy.studyAttributes.customfields = studyStorage.customfields;

        newStudy.series = [];

        var futures = [];

        // if modality is missing, set as OT
        _.each(schema.series, function (series) {
            if (series.modality == null && series.images.length) {
                if (isImageSOPClass(series.images[0].sop_class)) {
                    series.modality = "OT";
                }
            }
        });

        var imageSeries = _.filter(schema.series, function (series) {
            return isImageModality(series.modality) && _.any(series.images, function (image) {
                return isImageSOPClass(image.sop_class) || (isPDFSOPClass(image.sop_class) && showPDFs) || isVideoSOPClass(image.sop_class);
            });
        });

        imageSeries = filterSeriesSchema(imageSeries, viewerSettings);

        var standAloneSeries = [];

        // Pull each video or PDF out and put it in a stand-alone series
        _.each(imageSeries, function (series) {
            var filteredImages = _.filter(series.images, function (image) {
                if ((isPDFSOPClass(image.sop_class) && showPDFs) || isVideoSOPClass(image.sop_class) || singleInstanceSeries) {
                    var newSeries = {
                        id: series.id,
                        images: [image],
                        modality: series.modality,
                        series_uid: series.series_uid
                    };

                    standAloneSeries.push(newSeries);

                    return false;
                } else {
                    return true;
                }
            });

            // Original series should only include images, not videos or PDFs
            series.images = filteredImages;
        });

        // Any series no longer containing images should be removed
        imageSeries = _.filter(_.flatten([imageSeries, standAloneSeries]), function (series) {
            return series.images.length > 0;
        });

        _.each(imageSeries, function (series) {
            var newSeries = new Models.Series();

            // Each Series or SeriesLike needs a unique id for the viewer to keep track of what is displayed in each
            // viewing pane and what data a thumbnail refers to. This is needed because individual series are now being
            // split into multiple thumbnails.
            newSeries.uuid = _this.generateUUID();

            newSeries.id = series.id;
            newSeries.studyAttributes = newStudy.studyAttributes;

            newSeries.seriesAttributes = new Models.SeriesAttributes();
            newSeries.seriesAttributes.seriesUid = new Classes.SeriesUid(series.series_uid);
            newSeries.seriesAttributes.modality = series.modality ? series.modality : "";
            newSeries.seriesAttributes.instanceCount = series.images.length;

            newSeries.seriesAttributes.imageAttributesLoaded = new Subjects.ObservableValue(0);
            newSeries.seriesAttributes.imageDataLoaded = new Subjects.ObservableValue(0);
            newSeries.seriesAttributes.description = series.description;

            if (_.any(series.images, function (image) {
                return isPDFSOPClass(image.sop_class);
            })) {
                newSeries.seriesAttributes.documentType = { type: 0 /* PDF */, thumbnailText: 'PDF', thumbnailIcon: null };
            }

            if (_.any(series.images, function (image) {
                return isVideoSOPClass(image.sop_class);
            })) {
                newSeries.seriesAttributes.documentType = { type: 1 /* Video */, thumbnailText: null, thumbnailIcon: 'fa-film' };
            }

            newSeries.instances = [];

            var images = _.filter(series.images, function (image) {
                return isImageSOPClass(image.sop_class) || (isPDFSOPClass(image.sop_class) && showPDFs) || isVideoSOPClass(image.sop_class);
            });

            _.each(images, function (image, index, list) {
                var newInstance = new Models.Instance();

                newInstance.frameNumber = new Classes.FrameNumber(0);

                newInstance.seriesAttributes = newSeries.seriesAttributes;
                newInstance.studyAttributes = newStudy.studyAttributes;

                newInstance.instanceAttributes = new Models.InstanceAttributes();

                newInstance.instanceAttributes.instanceIndex = index;

                newInstance.instanceAttributes.attributesLoaded = false;
                newInstance.instanceAttributes.attributesDownloadState = 0 /* None */;

                newInstance.id = new Classes.InstanceUid(image.id);
                newInstance.instanceAttributes.rank = image.rank;
                newInstance.instanceAttributes.frameCount = image.frame_count;
                newInstance.instanceAttributes.sopClass = image.sop_class;
                newInstance.instanceAttributes.version = new Classes.ImageVersion(image.version);
                newInstance.instanceAttributes.instanceNumber = index;
                newInstance.instanceAttributes.isKeyImage = new Subjects.ObservableValue(false);

                newSeries.instances.push(newInstance);
            });

            newStudy.series.push(newSeries);
        });

        if (keyImagesOnly && _.any(keyImages.images)) {
            newStudy.series = _.filter(newStudy.series, function (series) {
                return _.any(keyImages.images, function (k) {
                    return k.series_uid === series.seriesAttributes.seriesUid.value;
                });
            });

            _.each(newStudy.series, function (series) {
                series.instances = _.filter(series.instances, function (instance) {
                    return _.any(keyImages.images, function (k) {
                        return k.instance_uid === instance.id.value && k.version === instance.instanceAttributes.version.value;
                    });
                });
            });
        }

        if (accelerated) {
            futures.push(Study.loadStudyAttributes(sessionId, newStudy));
        } else {
            _.each(newStudy.series, function (series) {
                var isMultiframe = _.any(series.instances, function (instance) {
                    return instance.instanceAttributes.frameCount > 1;
                });

                _.each(series.instances, function (instance, index, list) {
                    if (accountSettings.viewer_multiframe_split_method == 2) {
                        futures.push(Study.loadImageAttributes(sessionId, instance));
                    } else if (isMultiframe || index == 0 || Multiframe.shouldSplitInstances(series.seriesAttributes.modality)) {
                        if (!Multiframe.shouldSplitInstances(series.seriesAttributes.modality)) {
                            futures.push(Study.loadImageAttributes(sessionId, instance));
                        } else {
                            futures.push(Observable.ret({}));
                        }
                    }
                });
            });
        }

        // load /json metadata, if necessary
        var seriesFilterNeedsJson = false;
        var hangingProtocolNeedsJson = false;

        if (viewerSettings && newStudy.series.length) {
            seriesFilterNeedsJson = filterSeriesNeedsJson(newStudy.series, viewerSettings);
            hangingProtocolNeedsJson = HangingProtocols.needsJSON(HangingProtocols.select(newStudy, viewerSettings));
        }

        _.each(newStudy.series, function (series) {
            var needsSeriesJSON = false;
            var needsImageJSON = false;

            if (viewerSettings) {
                needsSeriesJSON = TextAnnotations.usesCustomSeriesTag(series.seriesAttributes.modality, viewerSettings) || seriesFilterNeedsJson || hangingProtocolNeedsJson;
                needsImageJSON = TextAnnotations.usesCustomImageTag(series.seriesAttributes.modality, viewerSettings);
            }

            var isMultiframe = _.any(series.instances, function (instance) {
                return instance.instanceAttributes.frameCount > 1;
            });

            _.each(series.instances, function (instance, index, list) {
                if (isMultiframe) {
                    futures.push(Study.loadImageJSON(sessionId, instance, series, false)); // multiframe split into series downstream
                } else if (needsImageJSON) {
                    futures.push(Study.loadImageJSON(sessionId, instance, series, needsSeriesJSON));
                } else if ((needsSeriesJSON && index == 0)) {
                    futures.push(Study.loadImageJSON(sessionId, instance, series, true));
                }
            });
        });

        _.each(schema.attachments || [], function (attachment) {
            var newAttachment = new Models.Attachment();

            newAttachment.id = new Classes.AttachmentID(attachment.id);
            newAttachment.stored = attachment.stored;
            newAttachment.mime = attachment.mime;
            newAttachment.version = new Classes.ImageVersion(attachment.version);
            newAttachment.filename = attachment.filename;
            newAttachment.phiNamespace = new Classes.PhiNamespace(attachment.phi_namespace);

            newStudy.attachments.push(newAttachment);
        });

        _.each(studyStorage.hl7 || [], function (hl7) {
            var newReport = new Models.Report();

            newReport.id = new Classes.ReportID(hl7.uuid);

            newStudy.reports.push(newReport);
        });

        _.each(studyStorage.routes || [], function (route) {
            var newAction = new Models.StudyAction();

            newAction.name = route.name;
            newAction.id = new Classes.StudyActionID(route.uuid);
            newAction.requiresEmailAddress = route.capture_email == 1;

            newStudy.actions.push(newAction);
        });

        _.each(studyStorage.meetings || [], function (meeting) {
            var newMeeting = new Classes.StudyMeeting();

            newMeeting.id = new Classes.MeetingId(meeting.uuid);
            newMeeting.name = meeting.name;
            newMeeting.host = meeting.user_name;

            newStudy.meetings.push(newMeeting);
        });

        _.each(keyImages.images, function (keyImage) {
            var series = _.find(newStudy.series, function (s) {
                return s.seriesAttributes.seriesUid.value === keyImage.series_uid;
            });

            if (series) {
                var instance = _.find(series.instances, function (i) {
                    return i.id.value === keyImage.instance_uid;
                });

                if (instance) {
                    instance.instanceAttributes.isKeyImage.write(true);
                    instance.instanceAttributes.keyImageId = new Classes.KeyImageId(keyImage.uuid);
                }
            }
        });

        var presentationStateSeries = _.filter(schema.series, function (series) {
            if (series.images.length) {
                return isPresentationState(series.modality, series.images[0].sop_class);
            } else {
                return isPresentationState(series.modality);
            }
        });
        var allPresentationStates = _.flatten(_.map(presentationStateSeries, function (series) {
            return series.images;
        }));

        newStudy.studyAttributes.presentationStates = allPresentationStates;

        var structuredReportSeries = _.filter(schema.series, function (series) {
            return isStructuredReport(series.modality);
        });
        var allStructuredReports = _.flatten(_.map(structuredReportSeries, function (series) {
            return _.map(series.images, function (instance) {
                var report = new Models.StructuredReport();
                report.id = new Classes.InstanceUid(instance.id);
                report.version = new Classes.ImageVersion(instance.version);
                return report;
            });
        }));

        newStudy.structuredReports = allStructuredReports;

        return Observable.map(Observable.sequenceA(futures), function (_) {
            return newStudy;
        });
    }
    Study.convertStudySchema = convertStudySchema;

    /**
    * Update study from /get/study call
    */
    function updateStudyFromStorage(study, studyStorage) {
        study.studyAttributes.customfields = studyStorage.customfields;
        return study;
    }
    Study.updateStudyFromStorage = updateStudyFromStorage;

    /**
    * Utility function to create UUID
    */
    function generateUUID() {
        var d = new Date().getTime();
        var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = (d + Math.random() * 16) % 16 | 0;
            d = Math.floor(d / 16);
            return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
        });
        return uuid;
    }
    Study.generateUUID = generateUUID;

    /**
    * Create a "stack series" for a study
    */
    function createStackSeries(study) {
        var instances = _.chain(study.series).map(function (s) {
            return s.instances;
        }).flatten().value();

        var seriesAttributes = {
            seriesUid: new Classes.SeriesUid(generateUUID()),
            instanceCount: instances.length,
            modality: _.chain(study.series).map(function (s) {
                return s.seriesAttributes.modality;
            }).uniq().value().join(", "),
            imageAttributesLoaded: new Subjects.ObservableValue(0),
            imageDataLoaded: new Subjects.ObservableValue(0),
            documentType: null,
            description: null,
            json: null,
            multiframe: false
        };

        return {
            uuid: this.generateUUID(),
            seriesAttributes: seriesAttributes,
            studyAttributes: study.studyAttributes,
            instances: instances,
            loadedStatus: 0 /* None */
        };
    }
    Study.createStackSeries = createStackSeries;

    function orderSeries(allSeries, desc) {
        var sorted = _.sortBy(allSeries, function (series) {
            if (series.studyAttributes.studyCreateDate) {
                return series.studyAttributes.studyCreateDate.getTime();
            } else {
                return Number.NEGATIVE_INFINITY;
            }
        });

        if (desc) {
            sorted = sorted.reverse();
        }

        return sorted;
    }
    Study.orderSeries = orderSeries;

    function createKeyImageSeries(study, stackKeyImageSeries) {
        var _this = this;
        var keyImageSeries = [];

        if (stackKeyImageSeries) {
            var series = this.createStackSeries(study);
            series.instances = _.filter(series.instances, function (i) {
                return i.instanceAttributes.isKeyImage.read();
            });
            series.seriesAttributes.instanceCount = series.instances.length;
            return (series.instances.length > 0) ? [series] : [];
        } else {
            keyImageSeries = _.map(study.series, function (series) {
                var seriesAttributes = {
                    seriesUid: new Classes.SeriesUid(generateUUID()),
                    instanceCount: 0,
                    modality: _.chain(study.series).map(function (s) {
                        return s.seriesAttributes.modality;
                    }).uniq().value().join(", "),
                    imageAttributesLoaded: new Subjects.ObservableValue(0),
                    imageDataLoaded: new Subjects.ObservableValue(0),
                    documentType: null,
                    description: null,
                    json: null,
                    multiframe: false
                };

                var instances = _.filter(series.instances, function (i) {
                    return i.instanceAttributes.isKeyImage.read();
                });
                seriesAttributes.instanceCount = instances.length;

                var series = {
                    uuid: _this.generateUUID(),
                    seriesAttributes: seriesAttributes,
                    studyAttributes: study.studyAttributes,
                    instances: instances,
                    loadedStatus: 0 /* None */
                };

                return (series.instances.length > 0) ? series : null;
            });
        }

        return _.compact(keyImageSeries);
    }
    Study.createKeyImageSeries = createKeyImageSeries;

    function splitSeries(series) {
        var _this = this;
        var splitSeries = [];

        _.each(series.instances, function (i) {
            var seriesAttributes = {
                seriesUid: new Classes.SeriesUid(generateUUID()),
                instanceCount: 0,
                modality: series.seriesAttributes.modality,
                imageAttributesLoaded: new Subjects.ObservableValue(0),
                imageDataLoaded: new Subjects.ObservableValue(0),
                documentType: null,
                description: null,
                json: null,
                multiframe: false
            };

            var split = {
                uuid: _this.generateUUID(),
                seriesAttributes: seriesAttributes,
                studyAttributes: series.studyAttributes,
                instances: [i],
                parent: series,
                loadedStatus: 0 /* None */
            };

            seriesAttributes.instanceCount = 1;
            splitSeries.push(split);
        });

        return splitSeries;
    }
    Study.splitSeries = splitSeries;

    /**
    * Load GSPS data and apply it to a study
    */
    function loadAndApplyAllGSPSData(sessionId, study, allowSR) {
        var loadAllPresentationStates = Observable.sequenceA(_.map(study.studyAttributes.presentationStates, function (presentationState) {
            return V3Storage.getGSPS(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, new Classes.InstanceUid(presentationState.id), new Classes.ImageVersion(presentationState.version));
        }));

        if (allowSR) {
            var loadAllCADSR = Observable.sequenceA(_.map(study.structuredReports, function (sr) {
                return V3Storage.getCADSR(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, new Classes.InstanceUid(sr.id.value), new Classes.ImageVersion(sr.version.value));
            }));

            return Observable.zip(loadAllPresentationStates, loadAllCADSR, function (presentationStates, cadsrs) {
                GSPS.apply(presentationStates, study);
                CADSR.apply(cadsrs, study);
                return presentationStates;
            });
        } else {
            return Observable.invoke(loadAllPresentationStates, function (presentationStates) {
                GSPS.apply(presentationStates, study);
            });
        }
    }
    Study.loadAndApplyAllGSPSData = loadAndApplyAllGSPSData;

    // Changes to this should also be made to Storage study download service in DicomService::canTranscodeImage()
    function isImageSOPClass(sopClass) {
        switch (sopClass) {
            case "1.2.840.10008.5.1.4.1.1.104.1":
            case "1.2.840.10008.5.1.4.1.1.66":
            case "1.3.46.670589.11.0.0.12.2":
            case "1.3.46.670589.11.0.0.12.4":
            case "1.2.840.10008.5.1.4.1.1.66.1":
            case "1.2.840.10008.5.1.4.1.1.11.1":
            case "1.2.840.10008.5.1.4.1.1.11.2":
            case "1.2.840.10008.5.1.4.1.1.11.3":
            case "1.2.840.10008.5.1.4.1.1.11.4":
            case "1.2.826.0.1.3680043.2.93.1.0.1":
                return false;
            default:
                return true;
        }
    }
    Study.isImageSOPClass = isImageSOPClass;

    function isPDFSOPClass(sopClass) {
        switch (sopClass) {
            case "1.2.840.10008.5.1.4.1.1.104.1":
                return true;
            default:
                return false;
        }
    }
    Study.isPDFSOPClass = isPDFSOPClass;

    function isVideoSOPClass(sopClass) {
        switch (sopClass) {
            case "1.2.840.10008.5.1.4.1.1.77.1.4.1":
            case "1.2.840.10008.5.1.4.1.1.77.1.1.1":
                return true;
            default:
                return false;
        }
    }
    Study.isVideoSOPClass = isVideoSOPClass;

    // Changes to this should also be made to Storage study download service in DicomService::canTranscodeImage()
    function isImageModality(modality) {
        if (!modality) {
            return false;
        }

        switch (modality.toUpperCase()) {
            case "CC":
            case "KO":
            case "PR":
            case "RIS":
            case "RTSTRUCT":
            case "RTPLAN":
            case "RTRECORD":
            case "SR":
            case "REG":
                return false;
        }

        return true;
    }
    Study.isImageModality = isImageModality;

    function isPresentationState(modality, sopClass) {
        if (typeof sopClass === "undefined") { sopClass = ""; }
        return modality === "PR" || sopClass.indexOf("1.2.840.10008.5.1.4.1.1.11.") !== -1;
    }
    Study.isPresentationState = isPresentationState;

    function isStructuredReport(modality) {
        return modality === "SR";
    }
    Study.isStructuredReport = isStructuredReport;

    function isKeyObjectSelection(modality) {
        return modality === "KO";
    }
    Study.isKeyObjectSelection = isKeyObjectSelection;

    function isPDFSeries(series) {
        return (series && series.instances && series.instances.length > 0 && series.instances[0].instanceAttributes && series.instances[0].instanceAttributes.sopClass && series.instances[0].instanceAttributes.sopClass == '1.2.840.10008.5.1.4.1.1.104.1');
    }
    Study.isPDFSeries = isPDFSeries;

    function isImagedORUSeries(series) {
        return (series && series.instances && series.instances.length > 0 && series.instances[0].instanceAttributes && series.instances[0].instanceAttributes.sopClass && series.instances[0].instanceAttributes.sopClass == '1.2.840.10008.5.1.4.1.1.7' && series.instances[0].instanceAttributes.seriesDescription != null && (series.instances[0].instanceAttributes.seriesDescription == 'report' || series.instances[0].instanceAttributes.seriesDescription.indexOf('pdf') > -1 || series.instances[0].instanceAttributes.seriesDescription.indexOf('doc') > -1) && series.seriesAttributes.seriesUid.value.indexOf('1.3.6.1.4.1.34692') == 0);
    }
    Study.isImagedORUSeries = isImagedORUSeries;

    function loadStudyAttributes(sessionId, study) {
        var getStudyAttributes = V3Storage.getStudyAttributes(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject);

        return Observable.forget(Observable.invoke(getStudyAttributes, function (attr) {
            var allImages = _.flatten(_.map(study.series, function (s) {
                return s.instances;
            }));

            _.each(attr.images, function (attribs) {
                var instance = _.find(allImages, function (image) {
                    return image.id.value === attribs.value;
                });

                if (instance) {
                    updateInstanceAttributes(attribs, instance);
                }
            });
        }));
    }
    Study.loadStudyAttributes = loadStudyAttributes;

    function loadImageAttributes(sessionId, instance, series) {
        // If this attribute is already loaded return immediately
        if (instance.instanceAttributes.attributesDownloadState == 3 /* Success */) {
            return Observable.ret({});
        }

        // Observable function - returns the image attributes from storage (AJAX call)
        var getImageAttributes = V3Storage.getImageAttributes(sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version);

        // Set the image attributes to the started state
        var setStartedState = function () {
            instance.instanceAttributes.attributesDownloadState = 2 /* Started */;
        };

        // Observable function - first set the state, then get the attributes from storage
        var processRequest = Observable.invokeFirst(getImageAttributes, setStartedState);

        // Only download the attributes if the download is not already started (or done)
        var shouldDownload = function (i) {
            var needToDownload = (i.instanceAttributes.attributesDownloadState == null || i.instanceAttributes.attributesDownloadState == 0 /* None */ || i.instanceAttributes.attributesDownloadState == 4 /* Failed */);

            // If this attribute file needs to download then mark it as being queued
            if (needToDownload) {
                i.instanceAttributes.attributesDownloadState = 1 /* Queued */;
            }
            return needToDownload;
        };

        return Observable.ifThenElse(function () {
            return shouldDownload(instance);
        }, Observable.invoke(processRequest, function (attribs) {
            updateInstanceAttributes(attribs, instance);
            if (series) {
                series.attributesLoaded(instance);
            }
        }), Observable.ret({}));
    }
    Study.loadImageAttributes = loadImageAttributes;

    function loadImageJSON(sessionId, instance, series, useSeries) {
        // If this attribute is already loaded return immediately
        if (instance.instanceAttributes.jsonDownloadState == 3 /* Success */) {
            return Observable.ret({});
        }

        // Observable function - returns the image attributes from storage (AJAX call)
        var getImageJSON = V3Storage.getImageJSON(sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version);

        // Set the image attributes to the started state
        var setStartedState = function () {
            instance.instanceAttributes.jsonDownloadState = 2 /* Started */;
        };

        // Observable function - first set the state, then get the attributes from storage
        var processRequest = Observable.invokeFirst(getImageJSON, setStartedState);

        // Only download the attributes if the download is not already started (or done)
        var shouldDownload = function (i) {
            var needToDownload = (i.instanceAttributes.jsonDownloadState == null || i.instanceAttributes.jsonDownloadState == 0 /* None */ || i.instanceAttributes.jsonDownloadState == 4 /* Failed */);

            // If this attribute file needs to download then mark it as being queued
            if (needToDownload) {
                i.instanceAttributes.jsonDownloadState = 1 /* Queued */;
            }
            return needToDownload;
        };

        return Observable.ifThenElse(function () {
            return shouldDownload(instance);
        }, Observable.invoke(processRequest, function (json) {
            instance.instanceAttributes.json = json;

            if (useSeries) {
                instance.seriesAttributes.json = json;
            }
        }), Observable.ret({}));
    }
    Study.loadImageJSON = loadImageJSON;

    function updateInstanceAttributes(attribs, instance) {
        var instanceDict = attribs.attr.toMap(function (t) {
            return t.tag;
        }, function (t) {
            return t.value;
        });
        var typedInstanceDict = Maps.preMap(instanceDict);

        instance.instanceAttributes.attributesLoaded = true;
        instance.instanceAttributes.attributesDownloadState = 3 /* Success */;

        instance.instanceAttributes.seriesDescription = typedInstanceDict.lookup(Dicom.Dictionary.SeriesDescription);

        if (typedInstanceDict.lookup(Dicom.Dictionary.PixelSpacing)) {
            instance.instanceAttributes.pixelSpacing = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.PixelSpacing));
            instance.instanceAttributes.pixelSpacingMeaning = 0 /* PatientGeometry */;
        } else if (typedInstanceDict.lookup(Dicom.Dictionary.ImagerPixelSpacing)) {
            instance.instanceAttributes.pixelSpacing = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.ImagerPixelSpacing));
            instance.instanceAttributes.pixelSpacingMeaning = 1 /* AtImagingPlate */;
        }

        instance.instanceAttributes.imagePositionPatient = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.ImagePositionPatient));
        instance.instanceAttributes.imageOrientationPatient = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.ImageOrientationPatient));
        instance.instanceAttributes.rows = parseInt(typedInstanceDict.lookup(Dicom.Dictionary.Rows));
        instance.instanceAttributes.columns = parseInt(typedInstanceDict.lookup(Dicom.Dictionary.Columns));
        instance.instanceAttributes.bitsStored = Parsers.fixBitCountField(parseInt(typedInstanceDict.lookup(Dicom.Dictionary.BitsStored)));
        instance.instanceAttributes.windowWidth = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.WindowWidth));
        instance.instanceAttributes.windowCenter = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.WindowCenter));
        instance.instanceAttributes.windowLevelIsCorrupt = Parsers.isCorruptDS(typedInstanceDict.lookup(Dicom.Dictionary.WindowWidth)) || Parsers.isCorruptDS(typedInstanceDict.lookup(Dicom.Dictionary.WindowCenter));

        instance.instanceAttributes.rescaleSlope = parseFloat(Maps.lookupDef(typedInstanceDict, Dicom.Dictionary.RescaleSlope, "1"));
        instance.instanceAttributes.rescaleIntercept = parseFloat(Maps.lookupDef(typedInstanceDict, Dicom.Dictionary.RescaleIntercept, "0"));

        instance.instanceAttributes.signed = typedInstanceDict.lookup(Dicom.Dictionary.PixelRepresentation) === "1";
        instance.instanceAttributes.presentationLUTShape = typedInstanceDict.lookup(Dicom.Dictionary.PresentationLUTShape);
        instance.instanceAttributes.photometricInterpretation = typedInstanceDict.lookup(Dicom.Dictionary.PhotometricInterpretation);

        instance.instanceAttributes.sliceThickness = parseFloat(typedInstanceDict.lookup(Dicom.Dictionary.SliceThickness));
        instance.instanceAttributes.kvp = parseFloat(typedInstanceDict.lookup(Dicom.Dictionary.KVP));
        instance.instanceAttributes.exposure = parseFloat(typedInstanceDict.lookup(Dicom.Dictionary.Exposure));
        instance.instanceAttributes.radiationMachineName = typedInstanceDict.lookup(Dicom.Dictionary.RadiationMachineName);

        instance.instanceAttributes.imageType = Parsers.parseCS(typedInstanceDict.lookup(Dicom.Dictionary.ImageType));
        instance.instanceAttributes.imageLaterality = typedInstanceDict.lookup(Dicom.Dictionary.ImageLaterality);
        instance.instanceAttributes.voiLutFunction = typedInstanceDict.lookup(Dicom.Dictionary.VOILUTFunction);
        instance.instanceAttributes.frameTime = Parsers.parseDS(typedInstanceDict.lookup(Dicom.Dictionary.FrameTime));

        _.each(Dicom.Dictionary.OverlayDataTags, function (tag, index) {
            instance.instanceAttributes.containsOverlayDataPlane[index] = typedInstanceDict.lookup(tag) === "1";
        });
        instance.instanceAttributes.containsOverlayData = _.find(instance.instanceAttributes.containsOverlayDataPlane, function (item) {
            return item;
        });

        instance.instanceAttributes.imageLaterality = typedInstanceDict.lookup(Dicom.Dictionary.ImageLaterality) || '';
        instance.instanceAttributes.viewPosition = typedInstanceDict.lookup(Dicom.Dictionary.ViewPosition) || '';
        instance.instanceAttributes.stationName = typedInstanceDict.lookup(Dicom.Dictionary.StationName) || '';
        instance.instanceAttributes.operatorsName = typedInstanceDict.lookup(Dicom.Dictionary.OperatorsName) || '';
        instance.instanceAttributes.institutionName = typedInstanceDict.lookup(Dicom.Dictionary.InstitutionName) || '';
        instance.instanceAttributes.institutionAddress = typedInstanceDict.lookup(Dicom.Dictionary.InstitutionAddress) || '';
        instance.instanceAttributes.detectorID = typedInstanceDict.lookup(Dicom.Dictionary.DetectorID) || '';

        if (typedInstanceDict.lookup(Dicom.Dictionary.SeriesCreateDate)) {
            instance.instanceAttributes.seriesCreateDate = Parsers.parseDicomDate(typedInstanceDict.lookup(Dicom.Dictionary.SeriesCreateDate));
        }

        if (typedInstanceDict.lookup(Dicom.Dictionary.SeriesCreateTime)) {
            instance.instanceAttributes.seriesCreateTime = Parsers.parseDicomTime(typedInstanceDict.lookup(Dicom.Dictionary.SeriesCreateTime));
        }

        if (instance.seriesAttributes.multiframe && instance.seriesAttributes.json) {
            Multiframe.loadSharedMultiframeTags(instance.seriesAttributes, [instance]);
            Multiframe.loadPerFrameMultiframeTags(instance.seriesAttributes, [instance]);
        }

        Subjects.modify(instance.seriesAttributes.imageAttributesLoaded, function (n) {
            return n + 1;
        });
    }
    Study.updateInstanceAttributes = updateInstanceAttributes;

    // tests whether this study will require a series-level /json call, based on series modality and modality settings
    function filterSeriesNeedsJson(allSeries, viewerSettings) {
        var modalitySettings = (viewerSettings != null && viewerSettings.modalities);
        var needsJson = false;

        if (modalitySettings) {
            _.each(allSeries, function (series) {
                if (series.instances.length) {
                    var modalitySetting = _.find(modalitySettings, function (m) {
                        return m.modality === series.seriesAttributes.modality;
                    });

                    if (modalitySetting && modalitySetting.seriesFilters) {
                        _.each(modalitySetting.seriesFilters, function (filter) {
                            if (filter.type == "tag") {
                                needsJson = true;
                            }
                        });
                    }
                }
            });
        }

        return needsJson;
    }
    Study.filterSeriesNeedsJson = filterSeriesNeedsJson;

    // filter series by series description or DICOM tag based on /attributes and /json calls
    function filterSeries(series, viewerSettings) {
        var modalitySettings = (viewerSettings != null && viewerSettings.modalities);
        var imageSeries = series;

        if (modalitySettings) {
            imageSeries = _.filter(imageSeries, function (series) {
                if (series.instances.length) {
                    var match = false;
                    var modalitySetting = _.find(modalitySettings, function (m) {
                        return m.modality === series.seriesAttributes.modality;
                    });

                    if (modalitySetting && modalitySetting.seriesFilters) {
                        _.each(modalitySetting.seriesFilters, function (filter) {
                            var filterVal = filter.value.toLowerCase();
                            var filterFilter = filter.filter;
                            var compareWith;

                            if (filter.type == "tag") {
                                if (series.instances[0].seriesAttributes.json) {
                                    var currentTags = series.instances[0].seriesAttributes.json.tags;
                                    var tags = filter.option.split('->');
                                    var foundTag = Dicom.findTag(currentTags, tags);

                                    if (foundTag && foundTag.value) {
                                        compareWith = foundTag.value.toLowerCase();
                                    }
                                }
                            } else if (series.instances[0].instanceAttributes.seriesDescription) {
                                compareWith = series.instances[0].instanceAttributes.seriesDescription.toLowerCase();
                            }

                            if (compareWith) {
                                if (filterFilter == "equals") {
                                    if (filterVal == compareWith) {
                                        match = true;
                                    }
                                } else {
                                    var contains = (compareWith.indexOf(filterVal) != -1);
                                    var containsFilter = (filterFilter != "not");
                                    if (contains == containsFilter) {
                                        match = true;
                                    }
                                }
                            }
                        });
                    }
                }

                return !match;
            });
        }

        return imageSeries;
    }
    Study.filterSeries = filterSeries;

    // filter series by description based on /schema call
    function filterSeriesSchema(series, viewerSettings) {
        var imageSeries = series;
        var modalitySettings = (viewerSettings != null && viewerSettings.modalities);

        if (modalitySettings) {
            imageSeries = _.filter(imageSeries, function (series) {
                if (series.description) {
                    var match = false;
                    var modalitySetting = _.find(modalitySettings, function (m) {
                        return m.modality === series.modality;
                    });

                    if (modalitySetting && modalitySetting.seriesFilters) {
                        _.each(modalitySetting.seriesFilters, function (filter) {
                            if (filter.type == null) {
                                var seriesDes = series.description.toLowerCase();
                                var filterVal = filter.value.toLowerCase();
                                var filterFilter = filter.filter;

                                if (filter.filter == "equals") {
                                    if (filterVal == seriesDes) {
                                        match = true;
                                    }
                                } else {
                                    var contains = (seriesDes.indexOf(filterVal) != -1);
                                    var containsFilter = (filterFilter != "not");
                                    if (contains == containsFilter) {
                                        match = true;
                                    }
                                }
                            }
                        });
                    }
                }

                return !match;
            });
        }

        return imageSeries;
    }
})(Study || (Study = {}));


/**
* Methods not provided on the Date prototype
* @see Date
*/
var Dates;
(function (Dates) {
    Date.prototype.defaultDateFormat = 'MM-DD-YYYY';
    Date.prototype.defaultTimeFormat = 'hh:mm A';

    Date.prototype.toShortDateString = function (format) {
        if (typeof format === "undefined") { format = Date.prototype.defaultDateFormat; }
        var date = this;

        /* Hack around node-webkit issue */
        if (typeof moment === 'undefined') {
            moment = global.moment;
        }

        return moment(date).format(format);
    };

    Date.prototype.toShortTimeString = function (format) {
        if (typeof format === "undefined") { format = Date.prototype.defaultTimeFormat; }
        var date = this;

        /* Hack around node-webkit issue */
        if (typeof moment === 'undefined') {
            moment = global.moment;
        }

        return moment(date).format(format);
    };

    Date.prototype.toShortDateTimeString = function (format, timeZone) {
        if (typeof format === "undefined") { format = Date.prototype.defaultDateFormat; }
        var date = this;

        /* Hack around node-webkit issue */
        if (typeof moment === 'undefined') {
            moment = global.moment;
        }

        format = format + ' ' + Date.prototype.defaultTimeFormat;

        var result = moment(date);

        if (timeZone) {
            // get a moment representing the current time
            var original = moment(date);

            // create a new moment based on the original one
            var shifted = original.clone();

            // change the time zone of the new moment
            shifted.tz(timeZone);

            // shift the moment by the difference in offsets
            shifted.add(original.utcOffset() - shifted.utcOffset(), 'minutes');

            // shift to user's current timezone
            shifted.tz(moment.tz.guess());
            result = shifted;
        }

        return result.format(format);
    };

    Date.prototype.toDicomDate = function () {
        var date = this;
        return date.getFullYear().padStart(4, "0") + (date.getMonth() + 1).padStart(2, "0") + date.getDate().padStart(2, "0");
    };

    Date.prototype.toDicomTime = function () {
        var date = this;
        return date.getHours().padStart(2, "0") + date.getHours().padStart(2, "0") + date.getSeconds().padStart(2, "0");
    };
})(Dates || (Dates = {}));


/**
* Methods not provided on the Number prototype
* @see Number
*/
var Numbers;
(function (Numbers) {
    Number.prototype.padStart = function (length, char) {
        return this.toString().padStart(length, char);
    };

    String.prototype.padStart = function (length, char) {
        var s = this;
        while (s.length < length) {
            s = char + s;
        }
        return s;
    };
})(Numbers || (Numbers = {}));
/**
* Helper methods for handling multiframe data
*/
var Multiframe;
(function (Multiframe) {
    /**
    * Test whether or not a series contains multiframe instances
    */
    function isMultiframe(series) {
        if (series) {
            return _.any(series.instances, function (instance) {
                return instance.instanceAttributes.frameCount > 1;
            });
        }

        return false;
    }
    Multiframe.isMultiframe = isMultiframe;

    /**
    * Determine whether or not a series should be split into instances, based on its
    * modality.
    */
    function shouldSplitInstances(modality) {
        return modality === "MG";
    }
    Multiframe.shouldSplitInstances = shouldSplitInstances;

    /**
    * Split a multiframe series into multiple collections of frames.
    * If the series is not a multiframe series, only the series itself is returned.
    *
    * @see Models.SeriesLike
    */
    function split(series, settings) {
        if (!settings.viewer_multiframe_split_method || settings.viewer_multiframe_split_method == 0) {
            // This method will put each multiframe instance in its own series
            if (isMultiframe(series) || shouldSplitInstances(series.seriesAttributes.modality)) {
                var multiframeInstances = [];

                _.each(series.instances, function (instance) {
                    if (instance.instanceAttributes.frameCount > 1) {
                        multiframeInstances.push(instance);
                    }
                });

                if (multiframeInstances.length) {
                    series.instances = _.difference(series.instances, multiframeInstances);

                    var originalSeries = [];
                    if (series.instances.length) {
                        // clean up instance indices and counts
                        _.each(series.instances, function (instance, index, list) {
                            instance.instanceAttributes.instanceIndex = index;
                            instance.instanceAttributes.instanceNumber = index;
                        });

                        series.seriesAttributes.instanceCount = series.instances.length;
                        originalSeries.push(series);
                    }

                    var splitMultiframe = _.map(multiframeInstances, function (instance) {
                        var seriesAttributes = copySeriesAttributes(instance.seriesAttributes);
                        seriesAttributes.json = instance.instanceAttributes.json;

                        return {
                            uuid: Study.generateUUID(),
                            instances: _.map(_.range(0, instance.instanceAttributes.frameCount), function (frame) {
                                var copy = new Models.Instance();
                                copy.frameNumber = new Classes.FrameNumber(frame);
                                copy.id = instance.id;
                                copy.instanceAttributes = copyInstanceAttributes(instance.instanceAttributes);
                                copy.instanceAttributes.measurements = [];
                                copy.instanceAttributes.instanceIndex = frame;
                                copy.instanceAttributes.instanceNumber = frame;
                                copy.seriesAttributes = seriesAttributes;
                                copy.seriesAttributes.instanceCount = instance.instanceAttributes.frameCount;

                                copy.studyAttributes = instance.studyAttributes;
                                return copy;
                            }),
                            seriesAttributes: seriesAttributes,
                            studyAttributes: series.studyAttributes
                        };
                    });

                    _.each(splitMultiframe, function (series) {
                        if (series.seriesAttributes.json) {
                            loadSharedMultiframeTags(series.seriesAttributes, series.instances);
                            loadPerFrameMultiframeTags(series.seriesAttributes, series.instances);
                        }
                    });

                    return _.flatten([originalSeries, splitMultiframe]);
                } else {
                    return [series];
                }
            } else {
                return [series];
            }
        } else if (settings.viewer_multiframe_split_method == 1) {
            // This method will put videos into their own series leaving the remaining instances in a single series
            // If the series is not a multiframe series, only the series itself is returned.
            if (!isMultiframe(series) && !shouldSplitInstances(series.seriesAttributes.modality)) {
                return [series];
            }

            var standAloneSeries = [];

            // Pull out each multiframe instance and create its own series
            series.instances = _.filter(series.instances, function (instance) {
                if (instance.instanceAttributes.frameCount > 1) {
                    var newSeries = {
                        uuid: Study.generateUUID(),
                        instances: _.map(_.range(0, instance.instanceAttributes.frameCount), function (frame) {
                            var copy = new Models.Instance();
                            copy.frameNumber = new Classes.FrameNumber(frame);
                            copy.id = instance.id;
                            copy.instanceAttributes = copyInstanceAttributes(instance.instanceAttributes);
                            copy.instanceAttributes.measurements = [];
                            copy.instanceAttributes.instanceIndex = frame;
                            copy.instanceAttributes.instanceNumber = frame;

                            copy.seriesAttributes = copySeriesAttributes(instance.seriesAttributes);
                            copy.seriesAttributes.instanceCount = instance.instanceAttributes.frameCount;

                            copy.studyAttributes = instance.studyAttributes;
                            return copy;
                        }),
                        seriesAttributes: series.seriesAttributes,
                        studyAttributes: series.studyAttributes
                    };

                    newSeries.seriesAttributes.instanceCount = instance.instanceAttributes.frameCount;

                    standAloneSeries.push(newSeries);

                    return false;
                } else {
                    return true;
                }
            });

            // Update number of instances to match new count in original series
            series.seriesAttributes.instanceCount = series.instances.length;

            var frameCount = 0;
            _.each(series.instances, function (instance) {
                instance.instanceAttributes.instanceIndex = frameCount;
                instance.instanceAttributes.instanceNumber = frameCount++;
            });

            // Return both the original series and the new stand-alone series
            return [series].concat(standAloneSeries);
        } else if (settings.viewer_multiframe_split_method == 2) {
            return _.map(series.instances, function (instance) {
                return {
                    uuid: Study.generateUUID(),
                    instances: _.map(_.range(0, instance.instanceAttributes.frameCount), function (frame) {
                        var copy = new Models.Instance();
                        copy.frameNumber = new Classes.FrameNumber(frame);
                        copy.id = instance.id;
                        copy.instanceAttributes = copyInstanceAttributes(instance.instanceAttributes);
                        copy.instanceAttributes.measurements = [];
                        copy.instanceAttributes.instanceIndex = frame;
                        copy.instanceAttributes.instanceNumber = frame;

                        copy.seriesAttributes = copySeriesAttributes(instance.seriesAttributes);
                        copy.seriesAttributes.instanceCount = instance.instanceAttributes.frameCount;

                        copy.studyAttributes = instance.studyAttributes;
                        return copy;
                    }),
                    seriesAttributes: series.seriesAttributes,
                    studyAttributes: series.studyAttributes,
                    loadedStatus: 0 /* None */
                };
            });
        }
    }
    Multiframe.split = split;

    function copyInstanceAttributes(attribs) {
        var copy = new Models.InstanceAttributes();
        copy.attributesLoaded = attribs.attributesLoaded;
        copy.attributesDownloadState = attribs.attributesDownloadState;
        copy.instanceIndex = attribs.instanceIndex;
        copy.rank = attribs.rank;
        copy.instanceNumber = attribs.instanceNumber;
        copy.frameCount = attribs.frameCount;
        copy.sopClass = attribs.sopClass;
        copy.version = attribs.version;
        copy.seriesDescription = attribs.seriesDescription;
        copy.pixelSpacing = attribs.pixelSpacing;
        copy.pixelSpacingMeaning = attribs.pixelSpacingMeaning;
        copy.imagePositionPatient = attribs.imagePositionPatient;
        copy.imageOrientationPatient = attribs.imageOrientationPatient;
        copy.photometricInterpretation = attribs.photometricInterpretation;
        copy.rows = attribs.rows;
        copy.columns = attribs.columns;
        copy.windowWidth = attribs.windowWidth;
        copy.windowCenter = attribs.windowCenter;
        copy.windowLevelIsCorrupt = attribs.windowLevelIsCorrupt;
        copy.bitsStored = attribs.bitsStored;
        copy.voiLutFunction = attribs.voiLutFunction;
        copy.frameTime = attribs.frameTime;
        copy.measurements = attribs.measurements;
        copy.presentationStates = attribs.presentationStates;
        copy.isKeyImage = new Subjects.ObservableValue(attribs.isKeyImage.read());
        copy.presentationLUTShape = attribs.presentationLUTShape;
        copy.keyImageId = attribs.keyImageId;
        copy.signed = attribs.signed;
        copy.containsOverlayData = attribs.containsOverlayData;
        copy.imageLaterality = attribs.imageLaterality;
        copy.rescaleSlope = attribs.rescaleSlope;
        copy.rescaleIntercept = attribs.rescaleIntercept;
        copy.json = attribs.json;
        return copy;
    }
    Multiframe.copyInstanceAttributes = copyInstanceAttributes;

    function copySeriesAttributes(attribs) {
        var copy = new Models.SeriesAttributes();
        copy.seriesUid = new Classes.SeriesUid(attribs.seriesUid.value);
        copy.instanceCount = attribs.instanceCount;
        copy.modality = attribs.modality;
        copy.imageAttributesLoaded = new Subjects.ObservableValue(0);
        copy.imageDataLoaded = new Subjects.ObservableValue(0);
        copy.documentType = attribs.documentType;
        copy.json = attribs.json;
        copy.description = attribs.description;
        return copy;
    }
    Multiframe.copySeriesAttributes = copySeriesAttributes;

    function loadSharedMultiframeTags(seriesAttributes, instances) {
        var currentTags = seriesAttributes.json.tags;
        var sharedFunctionalSeq = Dicom.findTag(currentTags, [Dicom.Multiframe.SharedFunctionalGroupSeq.toString()]);

        if (sharedFunctionalSeq && (sharedFunctionalSeq.items.length == 1)) {
            seriesAttributes.multiframe = true;

            var sharedTags = sharedFunctionalSeq.items[0].tags;

            var rescaleSlope = Dicom.findTag(sharedTags, [Dicom.Multiframe.PixelValueTransSeq.toString(), Dicom.Dictionary.RescaleSlope.toString()]);

            var rescaleIntercept = Dicom.findTag(sharedTags, [Dicom.Multiframe.PixelValueTransSeq.toString(), Dicom.Dictionary.RescaleIntercept.toString()]);

            var windowCenter = Dicom.findTag(sharedTags, [Dicom.Multiframe.FrameVOILUTSeq.toString(), Dicom.Dictionary.WindowCenter.toString()]);

            var windowWidth = Dicom.findTag(sharedTags, [Dicom.Multiframe.FrameVOILUTSeq.toString(), Dicom.Dictionary.WindowWidth.toString()]);

            var imageOrientation = Dicom.findTag(sharedTags, [Dicom.Multiframe.PlaneOrientationSeq.toString(), Dicom.Dictionary.ImageOrientationPatient.toString()]);

            var pixelSpacing = Dicom.findTag(sharedTags, [Dicom.Multiframe.PixelMeasuresSeq.toString(), Dicom.Dictionary.PixelSpacing.toString()]);

            _.each(instances, function (instance) {
                if (rescaleSlope) {
                    instance.instanceAttributes.rescaleSlope = parseFloat(rescaleSlope.value) || 1;
                }

                if (rescaleIntercept) {
                    instance.instanceAttributes.rescaleIntercept = parseFloat(rescaleIntercept.value) || 0;
                }

                if (windowCenter) {
                    instance.instanceAttributes.windowCenter = [parseFloat(windowCenter.value)];
                }

                if (windowWidth) {
                    instance.instanceAttributes.windowWidth = [parseFloat(windowWidth.value)];
                }

                if (windowCenter && windowWidth) {
                    instance.instanceAttributes.windowLevelIsCorrupt = Parsers.isCorruptDS(windowCenter.value) || Parsers.isCorruptDS(windowWidth.value);
                }

                if (imageOrientation) {
                    instance.instanceAttributes.imageOrientationPatient = Parsers.parseDS(imageOrientation.value);
                }

                if (pixelSpacing) {
                    instance.instanceAttributes.pixelSpacing = Parsers.parseDS(pixelSpacing.value);
                    instance.instanceAttributes.pixelSpacingMeaning = 0 /* PatientGeometry */;
                }
            });
        }
    }
    Multiframe.loadSharedMultiframeTags = loadSharedMultiframeTags;

    function loadPerFrameMultiframeTags(seriesAttributes, instances) {
        var currentTags = seriesAttributes.json.tags;
        var perFrameSeq = Dicom.findTag(currentTags, [Dicom.Multiframe.PerFrameFunctionalGroupSeq.toString()]);

        if (perFrameSeq && (perFrameSeq.items.length == instances.length)) {
            seriesAttributes.multiframe = true;

            _.each(perFrameSeq.items, function (item, index) {
                var imagePosition = Dicom.findTag(item.tags, [Dicom.Multiframe.PlanePositionSeq.toString(), Dicom.Dictionary.ImagePositionPatient.toString()]);
                if (imagePosition) {
                    instances[index].instanceAttributes.imagePositionPatient = Parsers.parseDS(imagePosition.value);
                }

                var imageOrientation = Dicom.findTag(item.tags, [Dicom.Multiframe.PlaneOrientationSeq.toString(), Dicom.Dictionary.ImageOrientationPatient.toString()]);
                if (imageOrientation) {
                    instances[index].instanceAttributes.imageOrientationPatient = Parsers.parseDS(imageOrientation.value);
                }

                var pixelSpacing = Dicom.findTag(item.tags, [Dicom.Multiframe.PixelMeasuresSeq.toString(), Dicom.Dictionary.PixelSpacing.toString()]);
                if (pixelSpacing) {
                    instances[index].instanceAttributes.pixelSpacing = Parsers.parseDS(pixelSpacing.value);
                    instances[index].instanceAttributes.pixelSpacingMeaning = 0 /* PatientGeometry */;
                }

                var rescaleSlope = Dicom.findTag(item.tags, [Dicom.Multiframe.PixelValueTransSeq.toString(), Dicom.Dictionary.RescaleSlope.toString()]);
                if (rescaleSlope) {
                    instances[index].instanceAttributes.rescaleSlope = parseFloat(rescaleSlope.value) || 1;
                }

                var rescaleIntercept = Dicom.findTag(item.tags, [Dicom.Multiframe.PixelValueTransSeq.toString(), Dicom.Dictionary.RescaleIntercept.toString()]);
                if (rescaleIntercept) {
                    instances[index].instanceAttributes.rescaleIntercept = parseFloat(rescaleIntercept.value) || 0;
                }

                var windowCenter = Dicom.findTag(item.tags, [Dicom.Multiframe.FrameVOILUTSeq.toString(), Dicom.Dictionary.WindowCenter.toString()]);
                if (windowCenter) {
                    instances[index].instanceAttributes.windowCenter = [parseFloat(windowCenter.value)];
                }

                var windowWidth = Dicom.findTag(item.tags, [Dicom.Multiframe.FrameVOILUTSeq.toString(), Dicom.Dictionary.WindowWidth.toString()]);
                if (windowWidth) {
                    instances[index].instanceAttributes.windowWidth = [parseFloat(windowWidth.value)];
                }

                if (windowCenter && windowWidth) {
                    instances[index].instanceAttributes.windowLevelIsCorrupt = Parsers.isCorruptDS(windowCenter.value) || Parsers.isCorruptDS(windowWidth.value);
                }
            });
        }
    }
    Multiframe.loadPerFrameMultiframeTags = loadPerFrameMultiframeTags;
})(Multiframe || (Multiframe = {}));
///<reference path="../typings/jquery/jquery.d.ts" />
///<reference path="Subject.ts" />

/**
* Implementation of the grid layout JQuery plugin.
*/
var GridLayout;
(function (GridLayout) {
    function gridLayout($this, layout, startAt, children) {
        var index = startAt;

        var $container = $('<div>').addClass('grid-layout-container');

        var elWidth = 100 / layout.columns;
        var elHeight = 100 / layout.rows;

        for (var row = 0; row < layout.rows; row++) {
            for (var column = 0; column < layout.columns; column++) {
                if (index < children.length) {
                    var $el = children[index];
                    var $cellInner = $('<div>').addClass('grid-layout-cell-inner').append($el);
                    var $cell = $('<div>').addClass('grid-layout-cell').append($cellInner);

                    $cell.css({
                        width: elWidth + '%',
                        height: elHeight + '%'
                    });

                    $container.append($cell);
                }

                index++;
            }
        }

        // Use detach() instead of remove() to keep event handlers around
        $this.children('.grid-layout-container').each(function () {
            $(this).detach();
        });
        $this.append($container);
    }

    $.prototype.grid = function (layout, startAt, children, onLayoutChanged) {
        var $this = $(this);

        var layoutHandler = function () {
            gridLayout($this, layout.read(), startAt.read(), children.read());

            if (onLayoutChanged) {
                onLayoutChanged();
            }
        };

        var layoutObserver = {
            next: function (_) {
                return layoutHandler();
            },
            done: function () {
            },
            fail: function (_) {
            }
        };

        var subscriptions = [];

        subscriptions.push(layout.updates.subscribe(layoutObserver));
        subscriptions.push(children.updates.subscribe(layoutObserver));
        subscriptions.push(startAt.updates.subscribe(layoutObserver));

        $this.unload(function (e) {
            for (var i = 0; i < subscriptions.length; i++) {
                subscriptions[i].cancel();
            }
        });

        layoutHandler();

        return $this;
    };
})(GridLayout || (GridLayout = {}));
/**
* Helper methods for working with functions
*/
var Functions;
(function (Functions) {
    /**
    * Discards method invocations occurring within 'timeout' milliseconds of the last successful invocation.
    *
    * Chooses an implementation based on the browser
    */
    function throttle(f, timeout) {
        if (Browser.isIE()) {
            return throttleImpl(f, timeout);
        } else {
            return _.throttle(f, timeout);
        }
    }
    Functions.throttle = throttle;

    /**
    * Discards method invocations occurring within 'timeout' milliseconds of the last successful invocation.
    * Similar to Underscore's throttle method, but does not use setTimeout, which causes issues in some versions of IE.
    */
    function throttleImpl(f, timeout) {
        var lastTimeCalled;

        return function (t) {
            var time = new Date().getTime();
            if (!lastTimeCalled || time - lastTimeCalled > timeout) {
                f.apply(null, arguments);
                lastTimeCalled = time;
            }
        };
    }
    Functions.throttleImpl = throttleImpl;
})(Functions || (Functions = {}));
///<reference path="../typings/jquery/jquery.d.ts" />

/**
* Helper methods for working with touch devices
*
* @see JQuery.fallback
*/
var Touch;
(function (Touch) {
    $.prototype.fallback = function (touchEvents, events, handler) {
        var lastTouchEventTime = null;

        return $(this).on(events.join(" ") + " " + touchEvents.join(" "), function (e) {
            if (lastTouchEventTime === null || (lastTouchEventTime < (new Date().getTime()) - 500)) {
                lastTouchEventTime = new Date().getTime();

                if (_.contains(touchEvents, e.type)) {
                    handler(fixEvent(e));
                } else {
                    handler(e);
                }
            }

            e.preventDefault();
        });
    };

    function fixEvent(event) {
        if (event.pageX !== undefined && event.pageY !== undefined && event.offsetX !== undefined && event.offsetY !== undefined) {
            return event;
        }

        var originalEvent = event.originalEvent;

        if (originalEvent && originalEvent.touches) {
            var touch = originalEvent.touches[0] || originalEvent.changedTouches[0];

            if (touch) {
                var offset = $(touch.target).offset();

                event.pageX = touch.pageX;
                event.pageY = touch.pageY;
                event.offsetX = touch.pageX - offset.left;
                event.offsetY = touch.pageY - offset.top;

                return event;
            }
        }

        if (originalEvent && originalEvent.pageX !== undefined && originalEvent.pageY !== undefined && originalEvent.offsetX !== undefined && originalEvent.offsetY !== undefined) {
            event.pageX = originalEvent.pageX;
            event.pageY = originalEvent.pageY;
            event.offsetX = originalEvent.offsetX;
            event.offsetY = originalEvent.offsetY;

            return event;
        }

        return null;
    }
})(Touch || (Touch = {}));
/**
* A library for working with 3D vectors and matrices.
*
* Homogeneous n-D matrices are assumed to be in standard form, i.e. consisting of a
* (n-1)-D transformation and a translation. That is, the (n, i) components are assumed
* to be zero.
*/
var Vectors;
(function (Vectors) {
    

    

    

    

    

    /**
    * Create a vector from an array of components
    */
    function fromArray(arr) {
        return {
            x: arr[0],
            y: arr[1],
            z: arr[2]
        };
    }
    Vectors.fromArray = fromArray;

    /**
    * Turn a vector into an array of components
    */
    function toArray(v) {
        return [v.x, v.y, v.z];
    }
    Vectors.toArray = toArray;

    /**
    * Compute the squared length of a vector
    */
    function len2(v) {
        return dot(v, v);
    }
    Vectors.len2 = len2;

    /**
    * Compute the length of a vector
    */
    function len(v) {
        return Math.sqrt(len2(v));
    }
    Vectors.len = len;

    /**
    * Add vectors
    */
    function add(v1, v2) {
        return {
            x: v1.x + v2.x,
            y: v1.y + v2.y,
            z: v1.z + v2.z
        };
    }
    Vectors.add = add;

    /**
    * Negate a vector
    */
    function neg(v) {
        return {
            x: -v.x,
            y: -v.y,
            z: -v.z
        };
    }
    Vectors.neg = neg;

    /**
    * Subtract vectors
    */
    function sub(v1, v2) {
        return {
            x: v1.x - v2.x,
            y: v1.y - v2.y,
            z: v1.z - v2.z
        };
    }
    Vectors.sub = sub;

    /**
    * Dot (scalar) product
    */
    function dot(v1, v2) {
        return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
    }
    Vectors.dot = dot;

    /**
    * Multiply a vector by a scalar
    */
    function scale(v, s) {
        return {
            x: v.x * s,
            y: v.y * s,
            z: v.z * s
        };
    }
    Vectors.scale = scale;

    /**
    * Normalize a non-zero vector. That is, rescale it to unit length.
    */
    function normalize(v) {
        return scale(v, 1.0 / len(v));
    }
    Vectors.normalize = normalize;

    /**
    * Cross (vector) product of two vectors
    */
    function cross(v1, v2) {
        return {
            x: v1.y * v2.z - v2.y * v1.z,
            y: v1.z * v2.x - v2.z * v1.x,
            z: v1.x * v2.y - v2.x * v1.y
        };
    }
    Vectors.cross = cross;

    /**
    * Compute the normal to a plane spanned by two vectors
    */
    function normal(v1, v2) {
        return normalize(cross(v1, v2));
    }
    Vectors.normal = normal;

    /**
    * Projection of one vector onto another
    */
    function project(v1, v2) {
        return dot(v1, v2) / len(v2);
    }
    Vectors.project = project;

    /**
    * Project a point onto an OrientedPlane, and return the result in the coordinate system
    * determined by the plane's preferred basis.
    */
    function coords(pl, p) {
        var v = sub(p, pl.o);

        var d1 = project(v, pl.v1);
        var d2 = project(v, pl.v2);

        return [d1, d2];
    }
    Vectors.coords = coords;

    /**
    * Project a point onto a plane
    */
    function project2(pl, p) {
        var coords = Vectors.coords(pl, p);

        var v1n = normalize(pl.v1);
        var v2n = normalize(pl.v2);

        return add(pl.o, add(scale(v1n, coords[0]), scale(v2n, coords[1])));
    }
    Vectors.project2 = project2;

    /**
    * Find the time travelled along an oriented line before intersection with a plane
    *
    * Time here is interpreted as the variable t in the equation r = L.o + t * L.v for the line
    */
    function intersectionDistance(p, l) {
        var n = normalize(cross(p.v1, p.v2));
        return (dot(n, sub(p.o, l.o))) / dot(l.v, n);
    }
    Vectors.intersectionDistance = intersectionDistance;

    /**
    * Find the intersection of a line and a plane
    */
    function intersect(p, l) {
        var d = intersectionDistance(p, l);
        return add(l.o, scale(l.v, d));
    }
    Vectors.intersect = intersect;

    /**
    * Creates a line pointing from one vector to another. The line's direction is not normalized.
    */
    function between(v1, v2) {
        return { o: v1, v: sub(v2, v1) };
    }
    Vectors.between = between;

    

    

    /**
    * Generate a translation matrix
    */
    function translate(dx, dy) {
        return {
            entries: [
                1, 0, dx,
                0, 1, dy
            ]
        };
    }
    Vectors.translate = translate;

    /**
    * Generate a 3D translation matrix
    */
    function translate3(dx, dy, dz) {
        return {
            entries: [
                1, 0, 0, dx,
                0, 1, 0, dy,
                0, 0, 1, dz,
                0, 0, 0, 1
            ]
        };
    }
    Vectors.translate3 = translate3;

    /**
    * Generate a flip matrix (horizontal)
    */
    function flipH() {
        return {
            entries: [
                -1, 0, 0,
                0, 1, 0
            ]
        };
    }
    Vectors.flipH = flipH;

    /**
    * Generate a flip matrix (vertical)
    */
    function flipV() {
        return {
            entries: [
                1, 0, 0,
                0, -1, 0
            ]
        };
    }
    Vectors.flipV = flipV;

    /**
    * Generate a rotation matrix
    */
    function rotate(rads) {
        var c = Math.cos(rads);
        var s = Math.sin(rads);

        return {
            entries: [
                c, -s, 0,
                s, c, 0
            ]
        };
    }
    Vectors.rotate = rotate;

    /**
    * Generate a rotation matrix
    */
    function rotateX(rads) {
        var c = Math.cos(rads);
        var s = Math.sin(rads);

        return {
            entries: [
                1, 0, 0, 0,
                0, c, -s, 0,
                0, s, c, 0,
                0, 0, 0, 1
            ]
        };
    }
    Vectors.rotateX = rotateX;

    /**
    * Generate a rotation matrix
    */
    function rotateY(rads) {
        var c = Math.cos(rads);
        var s = Math.sin(rads);

        return {
            entries: [
                c, 0, -s, 0,
                0, 1, 0, 0,
                s, 0, c, 0,
                0, 0, 0, 1
            ]
        };
    }
    Vectors.rotateY = rotateY;

    /**
    * Generate a scale matrix
    */
    function scaleM(s) {
        return scaleNonUniformM(s, s);
    }
    Vectors.scaleM = scaleM;

    /**
    * Generate a 3D scale matrix
    */
    function scale3(sx, sy, sz) {
        return {
            entries: [
                sx, 0, 0, 0,
                0, sy, 0, 0,
                0, 0, sz, 0,
                0, 0, 0, 1
            ]
        };
    }
    Vectors.scale3 = scale3;

    /**
    * Generate a scale matrix
    */
    function scaleNonUniformM(sx, sy) {
        return {
            entries: [
                sx, 0, 0,
                0, sy, 0
            ]
        };
    }
    Vectors.scaleNonUniformM = scaleNonUniformM;

    /**
    * Identity matrix
    */
    function identity() {
        return {
            entries: [
                1, 0, 0,
                0, 1, 0
            ]
        };
    }
    Vectors.identity = identity;

    /**
    * Identity matrix
    */
    function identity3() {
        return {
            entries: [
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1
            ]
        };
    }
    Vectors.identity3 = identity3;

    /**
    * Compose two 2D (homogeneous 3D) matrices (in standard form).
    */
    function composeM(m1, m2) {
        var m1e = m1.entries;
        var m2e = m2.entries;

        return {
            entries: [
                m1e[0] * m2e[0] + m1e[1] * m2e[3],
                m1e[0] * m2e[1] + m1e[1] * m2e[4],
                m1e[0] * m2e[2] + m1e[1] * m2e[5] + m1e[2],
                m1e[3] * m2e[0] + m1e[4] * m2e[3],
                m1e[3] * m2e[1] + m1e[4] * m2e[4],
                m1e[3] * m2e[2] + m1e[4] * m2e[5] + m1e[5]
            ]
        };
    }
    Vectors.composeM = composeM;

    /**
    * Compose two 3D (homogeneous 4D) matrices (in standard form)
    */
    function compose3(m1, m2) {
        var m1e = m1.entries;
        var m2e = m2.entries;

        return {
            entries: [
                m1e[0] * m2e[0] + m1e[1] * m2e[4] + m1e[2] * m2e[8],
                m1e[0] * m2e[1] + m1e[1] * m2e[5] + m1e[2] * m2e[9],
                m1e[0] * m2e[2] + m1e[1] * m2e[6] + m1e[2] * m2e[10],
                m1e[0] * m2e[3] + m1e[1] * m2e[7] + m1e[2] * m2e[11] + m1e[3],
                m1e[4] * m2e[0] + m1e[5] * m2e[4] + m1e[6] * m2e[8],
                m1e[4] * m2e[1] + m1e[5] * m2e[5] + m1e[6] * m2e[9],
                m1e[4] * m2e[2] + m1e[5] * m2e[6] + m1e[6] * m2e[10],
                m1e[4] * m2e[3] + m1e[5] * m2e[7] + m1e[6] * m2e[11] + m1e[7],
                m1e[8] * m2e[0] + m1e[9] * m2e[4] + m1e[10] * m2e[8],
                m1e[8] * m2e[1] + m1e[9] * m2e[5] + m1e[10] * m2e[9],
                m1e[8] * m2e[2] + m1e[9] * m2e[6] + m1e[10] * m2e[10],
                m1e[8] * m2e[3] + m1e[9] * m2e[7] + m1e[10] * m2e[11] + m1e[11]
            ]
        };
    }
    Vectors.compose3 = compose3;

    /**
    * Transpose a 3D (homogeneous 4D) matrix (in standard form).
    */
    function transpose3(m) {
        var me = m.entries;

        return [
            me[0], me[4], me[8], 0,
            me[1], me[5], me[9], 0,
            me[2], me[6], me[10], 0,
            me[3], me[7], me[11], 1
        ];
    }
    Vectors.transpose3 = transpose3;

    /**
    * Determinant of a 2D matrix (in standard form).
    */
    function determinant(m) {
        var e = m.entries;
        return e[0] * e[4] - e[1] * e[3];
    }
    Vectors.determinant = determinant;

    /**
    * Multiply a 2D (homogeneous 3D) matrix (in standard form) by a point.
    */
    function multiplyM(m, p) {
        return {
            x: m.entries[0] * p.x + m.entries[1] * p.y + m.entries[2],
            y: m.entries[3] * p.x + m.entries[4] * p.y + m.entries[5]
        };
    }
    Vectors.multiplyM = multiplyM;

    /**
    * Multiply a 3D (homogeneous 4D) matrix (in standard form) by a point.
    */
    function multiply3(m, p) {
        return {
            x: m.entries[0] * p.x + m.entries[1] * p.y + m.entries[2] * p.z + m.entries[3],
            y: m.entries[4] * p.x + m.entries[5] * p.y + m.entries[6] * p.z + m.entries[7],
            z: m.entries[8] * p.x + m.entries[9] * p.y + m.entries[10] * p.z + m.entries[11]
        };
    }
    Vectors.multiply3 = multiply3;
})(Vectors || (Vectors = {}));
///<reference path="../typings/underscore/underscore.d.ts" />
///<reference path="Vector.ts" />
/**
* A library for linking geometrically related series
*/
var SeriesGeometry;
(function (SeriesGeometry) {
    

    

    /**
    * Test if instances are orthogonal. The condition used is that planes should intersect with an acute angle > 30 degrees.
    */
    function areOrthogonal(source, target) {
        var sourcePlane = normalPlaneForInstance(source.instanceAttributes);

        var n1 = sourcePlane.n;
        var n2 = target.normal;
        var cosine = n1.x * n2[0] + n1.y * n2[1] + n1.z * n2[2];

        return Math.abs(cosine) < 0.5;
    }
    SeriesGeometry.areOrthogonal = areOrthogonal;

    /**
    * Test if an instance has the DICOM tags necessary for geometric operations
    */
    function hasGeometricMetadata(instance) {
        if (instance.instanceAttributes.imagePositionPatient && instance.instanceAttributes.imageOrientationPatient && instance.instanceAttributes.pixelSpacing && instance.instanceAttributes.pixelSpacingMeaning === 0 /* PatientGeometry */) {
            return true;
        }
        return false;
    }
    SeriesGeometry.hasGeometricMetadata = hasGeometricMetadata;

    /**
    * Find the index of the instance in the collection which is geometrically closest to the specified instance
    */
    function findClosestInstance(instances, target) {
        var imagePositionPatient = Vectors.fromArray(target.imagePositionPatient);

        return findClosestInstanceToPoint(_.filter(instances, function (instance) {
            return hasGeometricMetadata(instance) && !areOrthogonal(instance, target);
        }), imagePositionPatient);
    }
    SeriesGeometry.findClosestInstance = findClosestInstance;

    /**
    * Find the index of the instance in the collection which is geometrically closest to the specified point
    */
    function findClosestInstanceToPoint(instances, patientCoords) {
        var closestIndex = null;
        var minDistance = null;

        for (var index = 0; index < instances.length; index++) {
            var instanceAtIndex = instances[index];

            if (hasGeometricMetadata(instanceAtIndex)) {
                var plane = normalPlaneForInstance(instanceAtIndex.instanceAttributes);
                var offset = Vectors.sub(patientCoords, plane.o);
                var distance = Math.abs(plane.n.x * offset.x + plane.n.y * offset.y + plane.n.z * offset.z);

                if (minDistance === null || distance < minDistance) {
                    minDistance = distance;
                    closestIndex = index;
                }
            }
        }

        return closestIndex;
    }
    SeriesGeometry.findClosestInstanceToPoint = findClosestInstanceToPoint;

    /**
    * Get the plane defined by an instance
    */
    function planeForInstanceGeometry(instance) {
        return {
            o: Vectors.fromArray(instance.imagePositionPatient),
            v1: Vectors.fromArray(instance.imageOrientationPatient.slice(0, 3)),
            v2: Vectors.fromArray(instance.imageOrientationPatient.slice(3, 6))
        };
    }
    SeriesGeometry.planeForInstanceGeometry = planeForInstanceGeometry;

    /**
    * Get the plane defined by an instance
    */
    function normalPlaneForInstance(instance) {
        if (instance.plane) {
            return instance.plane;
        }

        var origin = Vectors.fromArray(instance.imagePositionPatient);
        var v1 = Vectors.fromArray(instance.imageOrientationPatient.slice(0, 3));
        var v2 = Vectors.fromArray(instance.imageOrientationPatient.slice(3, 6));

        return instance.plane = {
            o: origin,
            n: Vectors.normalize(Vectors.cross(v1, v2))
        };
    }
    SeriesGeometry.normalPlaneForInstance = normalPlaneForInstance;

    /**
    * Get the plane defined by an instance
    */
    function planeForInstance(instance) {
        return {
            o: Vectors.fromArray(instance.instanceAttributes.imagePositionPatient),
            v1: Vectors.fromArray(instance.instanceAttributes.imageOrientationPatient.slice(0, 3)),
            v2: Vectors.fromArray(instance.instanceAttributes.imageOrientationPatient.slice(3, 6))
        };
    }
    SeriesGeometry.planeForInstance = planeForInstance;

    /**
    * Map image coords to patient coords
    */
    function mapToPatient(imageCoords, instance) {
        var plane = planeForInstance(instance);
        return Vectors.add(plane.o, Vectors.add(Vectors.scale(plane.v1, imageCoords.x * instance.instanceAttributes.pixelSpacing[0]), Vectors.scale(plane.v2, imageCoords.y * instance.instanceAttributes.pixelSpacing[0])));
    }
    SeriesGeometry.mapToPatient = mapToPatient;

    /**
    * Map patient coords to image coords
    */
    function mapFromPatient(patientCoords, instance) {
        var plane = planeForInstance(instance);
        var coords = Vectors.coords(plane, patientCoords);
        return {
            x: coords[0] / instance.instanceAttributes.pixelSpacing[0],
            y: coords[1] / instance.instanceAttributes.pixelSpacing[0]
        };
    }
    SeriesGeometry.mapFromPatient = mapFromPatient;

    /**
    * Compute the bounding vertices which bound the image geometry in 3-dimensional space
    */
    function boundingVerticesForInstance(instance) {
        var imagePositionPatient = Vectors.fromArray(instance.imagePositionPatient);

        var row = Vectors.fromArray(instance.imageOrientationPatient.slice(0, 3));
        var column = Vectors.fromArray(instance.imageOrientationPatient.slice(3, 6));

        var topEdge = Vectors.scale(Vectors.normalize(row), instance.pixelSpacing[0] * instance.columns);
        var leftEdge = Vectors.scale(Vectors.normalize(column), instance.pixelSpacing[0] * instance.rows);

        var tl = imagePositionPatient;
        var tr = Vectors.add(tl, topEdge);
        var bl = Vectors.add(tl, leftEdge);
        var br = Vectors.add(tr, leftEdge);

        return {
            tl: tl,
            tr: tr,
            bl: bl,
            br: br
        };
    }
    SeriesGeometry.boundingVerticesForInstance = boundingVerticesForInstance;

    /**
    * Compute the lines which bound the image geometry in 3-dimensional space
    */
    function boundsForInstance(instance) {
        var vs = boundingVerticesForInstance(instance);

        return [
            Vectors.between(vs.tl, vs.tr),
            Vectors.between(vs.tr, vs.br),
            Vectors.between(vs.br, vs.bl),
            Vectors.between(vs.bl, vs.tl)
        ];
    }
    SeriesGeometry.boundsForInstance = boundsForInstance;
})(SeriesGeometry || (SeriesGeometry = {}));
///<reference path='../classes/Types.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Terminology.ts' />
/**
* Preset window level defaults
*/
var WindowLevelPresets;
(function (WindowLevelPresets) {
    /**
    * Default values
    */
    WindowLevelPresets.defaultCenter = 128.0;
    WindowLevelPresets.defaultWidth = 256.0;

    /**
    * Lookup default window level presets for a modality
    */
    function defaults(modality, terminology) {
        switch (modality.toUpperCase()) {
            case "CT":
                return [
                    {
                        name: terminology ? terminology.lookup(Terminology.Terms.SoftTissue) : Terminology.Terms.SoftTissue.defaultValue,
                        windowLevel: { center: 40, width: 400 },
                        opacity: 0.25
                    }, {
                        name: terminology ? terminology.lookup(Terminology.Terms.Bone) : Terminology.Terms.Bone.defaultValue,
                        windowLevel: { center: 500, width: 1500 },
                        opacity: 0.10
                    }, {
                        name: terminology ? terminology.lookup(Terminology.Terms.Head) : Terminology.Terms.Head.defaultValue,
                        windowLevel: { center: 35, width: 110 },
                        opacity: 0.25
                    }, {
                        name: terminology ? terminology.lookup(Terminology.Terms.Lung) : Terminology.Terms.Lung.defaultValue,
                        windowLevel: { center: -500, width: 1500 },
                        opacity: 1.0
                    }];
            default:
                return [];
        }
    }
    WindowLevelPresets.defaults = defaults;

    /**
    * Returns true if a valid window level is found
    * @param instance
    */
    function isValidWindowLevel(instance) {
        return instance && !instance.instanceAttributes.windowLevelIsCorrupt && instance.instanceAttributes.windowCenter && instance.instanceAttributes.windowCenter.length > 0 && instance.instanceAttributes.windowWidth && instance.instanceAttributes.windowWidth.length > 0;
    }
    WindowLevelPresets.isValidWindowLevel = isValidWindowLevel;

    /**
    * Check if an instance should be rendered using 16-bit window level
    */
    function shouldUse16BitWindowLevel(instance) {
        return instance && (instance.instanceAttributes.bitsStored > 8) && WindowLevelPresets.supports16Bit(instance) && !Browser.needsResize(instance);
    }
    WindowLevelPresets.shouldUse16BitWindowLevel = shouldUse16BitWindowLevel;

    function supports16Bit(instance) {
        return instance.instanceAttributes.photometricInterpretation.indexOf("MONO") !== -1;
    }
    WindowLevelPresets.supports16Bit = supports16Bit;

    function isFullResolutionHD(instance) {
        return (instance.instanceAttributes.bitsStored > 8 && !WindowLevelPresets.supports16Bit(instance));
    }
    WindowLevelPresets.isFullResolutionHD = isFullResolutionHD;

    /**
    * Scan image data for min/max and calculate window levels
    * @param instance
    * @param imageData
    */
    function findWindowLevels(instance, imageData) {
        var data = imageData.data;
        var fullImageSize = imageData.width * imageData.height / 2 * 4;
        var l = data.length / 2;
        var signed = instance.instanceAttributes.signed || false;
        var typeRange = (1 << instance.instanceAttributes.bitsStored);
        var typeSignedMax = (1 << instance.instanceAttributes.bitsStored - 1) - 1;
        var slope = instance.instanceAttributes.rescaleSlope || 1;
        var intercept = instance.instanceAttributes.rescaleIntercept || 0;

        var min = Number.MAX_VALUE;
        var max = -Number.MAX_VALUE;

        for (var i = 0; i < l;) {
            var raw = (data[i + fullImageSize] << 8) | data[i];

            if (signed) {
                raw = (raw & 0xffff) - 0x8000;

                if (raw > typeSignedMax) {
                    raw -= typeRange;
                }
            }

            min = Math.min(raw, min);
            max = Math.max(raw, max);

            i = i + 4;
        }

        min = (min * slope) + intercept;
        max = (max * slope) + intercept;

        var windowLevel = {
            center: (max + min) / 2.0,
            width: max - min
        };

        if (instance.instanceAttributes.presentationLUTShape === "INVERSE" || instance.instanceAttributes.photometricInterpretation === "MONOCHROME1") {
            windowLevel = {
                center: (signed ? 0 : (1 << instance.instanceAttributes.bitsStored)) * slope - windowLevel.center,
                width: windowLevel.width
            };
        }

        return windowLevel;
    }
    WindowLevelPresets.findWindowLevels = findWindowLevels;

    /**
    * True if instance needs to scan for window levels
    * @param instance
    */
    function instanceNeedsWindowLevel(instance) {
        return !WindowLevelPresets.isValidWindowLevel(instance) && !instance.instanceAttributes.windowLevelDetection;
    }
    WindowLevelPresets.instanceNeedsWindowLevel = instanceNeedsWindowLevel;
})(WindowLevelPresets || (WindowLevelPresets = {}));
///<reference path="../classes/Types.ts" />
/**
* Cine related helper methods
*/
var Cine;
(function (Cine) {
    /**
    * The default cine speed when one is not specified by the user settings
    */
    Cine.defaultCineSpeed = 12.0;

    /**
    * Rate at which storage renders frames
    */
    Cine.cineRenderRate = 30.0;

    /**
    * Check for compatibility
    */
    function isFormatSupported() {
        if (LocalViewer.isLocalViewer()) {
            return false;
        }

        if (Browser.isIE9()) {
            return false;
        }

        var testVideo = document.createElement('video');

        return testVideo.canPlayType && testVideo.canPlayType('video/mp4') !== '';
    }
    Cine.isFormatSupported = isFormatSupported;

    function isBrowserSupported() {
        return !(Browser.isIE() || Browser.isIE11());
    }
    Cine.isBrowserSupported = isBrowserSupported;

    /**
    * Get the default cine speed based on modality
    */
    function getDefaultCineSpeed(settings, modality) {
        if (settings.modalities) {
            var modalitySettings = _.find(settings.modalities, function (m) {
                return m.modality === modality;
            });

            if (modalitySettings && modalitySettings.cineSpeed) {
                return modalitySettings.cineSpeed;
            }
        }

        return Cine.defaultCineSpeed;
    }
    Cine.getDefaultCineSpeed = getDefaultCineSpeed;

    /**
    * Save the new cine speed back to the settings object
    */
    function saveCineSpeedToSettings(settings, modality, speed) {
        var copy = $.extend(true, settings, {
            modalities: []
        });

        var modalitySettings = _.find(copy.modalities, function (m) {
            return m.modality === modality;
        });

        if (!modalitySettings) {
            copy.modalities.push(modalitySettings = { modality: modality });
        }

        modalitySettings.cineSpeed = speed;

        return copy;
    }
    Cine.saveCineSpeedToSettings = saveCineSpeedToSettings;
})(Cine || (Cine = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path="../typings/jquery/jquery.d.ts" />
///<reference path="../typings/jquery.mousewheel/jquery.mousewheel.d.ts" />
///<reference path="../typings/underscore/underscore.d.ts" />
///<reference path="../classes/Types.ts" />
///<reference path="../libs/Observable.ts" />
///<reference path="../libs/Query.ts" />
///<reference path="../libs/Services.ts" />
///<reference path="../libs/Functions.ts" />
///<reference path="../libs/V3Storage.ts" />
///<reference path="../libs/Study.ts" />
///<reference path="../libs/Subject.ts" />
///<reference path="../libs/GridLayout.ts" />
///<reference path="../libs/Touch.ts" />
///<reference path="../libs/SeriesGeometry.ts" />
///<reference path="../libs/WindowLevelPresets.ts" />
///<reference path="../libs/Cine.ts" />
///<reference path='../libs/Browser.ts' />
///<reference path="../models/StudySchema.ts" />
///<reference path="../models/Study.ts" />
///<reference path="../layer/Layer.ts" />
///<reference path="../libs/LocalViewer.ts" />

var Views;
(function (Views) {
    (function (PreloadQueueKeyType) {
        PreloadQueueKeyType[PreloadQueueKeyType["ImageAttributes"] = 0] = "ImageAttributes";
        PreloadQueueKeyType[PreloadQueueKeyType["ImageData"] = 1] = "ImageData";
        PreloadQueueKeyType[PreloadQueueKeyType["ImageMetadata"] = 2] = "ImageMetadata";
    })(Views.PreloadQueueKeyType || (Views.PreloadQueueKeyType = {}));
    var PreloadQueueKeyType = Views.PreloadQueueKeyType;

    /**
    * Priority levels
    */
    (function (Priority) {
        Priority[Priority["ImageDataBackgroundPreloading"] = -2] = "ImageDataBackgroundPreloading";
        Priority[Priority["ImageAttributeBackgroundPreloading"] = -1] = "ImageAttributeBackgroundPreloading";
        Priority[Priority["FullResolutionImage"] = 99] = "FullResolutionImage";
    })(Views.Priority || (Views.Priority = {}));
    var Priority = Views.Priority;

    var PriorityIsPriority = (function () {
        function PriorityIsPriority() {
        }
        PriorityIsPriority.prototype.compare = function (p1, p2) {
            return p1 - p2;
        };
        return PriorityIsPriority;
    })();
    Views.PriorityIsPriority = PriorityIsPriority;

    

    var PreloadQueueKeyIsKey = (function () {
        function PreloadQueueKeyIsKey() {
        }
        PreloadQueueKeyIsKey.prototype.equals = function (k1, k2) {
            if (k1.type === 0 /* ImageAttributes */ && k2.type === 0 /* ImageAttributes */) {
                return k1.seriesUid.value === k2.seriesUid.value && k1.frameNumber.value === k2.frameNumber.value && k1.instanceUid.value === k2.instanceUid.value;
            } else if (k1.type === 1 /* ImageData */ && k2.type === 1 /* ImageData */) {
                return k1.seriesUid.value === k2.seriesUid.value && k1.instanceUid.value === k2.instanceUid.value && k1.frameNumber.value === k2.frameNumber.value && k1.imageType === k2.imageType;
            } else {
                return false;
            }
        };

        PreloadQueueKeyIsKey.prototype.show = function (k) {
            switch (k.type) {
                case 0 /* ImageAttributes */:
                    return "Load Image Attributes {" + k.seriesUid.value + ", " + k.instanceUid.value + "}";
                case 1 /* ImageData */:
                    return "Load Image Data {" + k.seriesUid.value + ", " + k.instanceUid.value + ", " + k.frameNumber.value + ", " + Classes.ImageType[k.imageType] + "}";
            }
        };
        return PreloadQueueKeyIsKey;
    })();
    Views.PreloadQueueKeyIsKey = PreloadQueueKeyIsKey;

    var ByInstanceNumber = (function () {
        function ByInstanceNumber() {
            this.values = {};
        }
        /**
        * Lookup a value
        */
        ByInstanceNumber.prototype.get = function (index, def) {
            return this.values["instance" + index] || def;
        };

        /**
        * Set a value
        */
        ByInstanceNumber.prototype.put = function (index, value) {
            this.values["instance" + index] = value;
        };

        /**
        * Remove a key
        */
        ByInstanceNumber.prototype.remove = function (index) {
            this.values["instance" + index] = null;
            delete this.values["instance" + index];
        };

        /**
        * Remove all keys
        */
        ByInstanceNumber.prototype.clear = function () {
            this.values = {};
        };

        /**
        * Iterate over all values
        */
        ByInstanceNumber.prototype.each = function (f) {
            for (var p in this.values) {
                if (this.values.hasOwnProperty(p)) {
                    f(this.values[p]);
                }
            }
        };
        return ByInstanceNumber;
    })();
    Views.ByInstanceNumber = ByInstanceNumber;

    /**
    * A dictionary with keys of type ImageType
    */
    var ByImageTypeAndInstanceNumber = (function () {
        function ByImageTypeAndInstanceNumber() {
            this.values = new ByInstanceNumber();
        }
        /**
        * Lookup a value
        */
        ByImageTypeAndInstanceNumber.prototype.get = function (index, type) {
            var instance = this.values.get(index, {});
            return instance[Classes.ImageType[type]];
        };

        /**
        * Set a value
        */
        ByImageTypeAndInstanceNumber.prototype.put = function (index, type, value) {
            var instance = this.values.get(index, {});
            instance[Classes.ImageType[type]] = value;
            this.values.put(index, instance);
        };

        /**
        * Remove a key
        */
        ByImageTypeAndInstanceNumber.prototype.remove = function (index, type) {
            var instance = this.values.get(index, {});
            instance[Classes.ImageType[type]] = null;
            delete instance[Classes.ImageType[type]];
        };
        return ByImageTypeAndInstanceNumber;
    })();
    Views.ByImageTypeAndInstanceNumber = ByImageTypeAndInstanceNumber;

    

    (function (DownloadState) {
        DownloadState[DownloadState["None"] = 0] = "None";
        DownloadState[DownloadState["Queued"] = 1] = "Queued";
        DownloadState[DownloadState["Started"] = 2] = "Started";
        DownloadState[DownloadState["Success"] = 3] = "Success";
        DownloadState[DownloadState["Failed"] = 4] = "Failed";
    })(Views.DownloadState || (Views.DownloadState = {}));
    var DownloadState = Views.DownloadState;

    

    /**
    * A renderer which uses image elements
    */
    var SimpleRenderer = (function () {
        function SimpleRenderer() {
            this.imageElements = new ByInstanceNumber();
        }
        SimpleRenderer.prototype.act = function (visitor) {
            if (visitor.visitSimpleRenderer) {
                visitor.visitSimpleRenderer(this);
            }
        };

        SimpleRenderer.prototype.visit = function (visitor, def) {
            if (visitor.visitSimpleRenderer) {
                return visitor.visitSimpleRenderer(this);
            } else {
                return def;
            }
        };
        return SimpleRenderer;
    })();
    Views.SimpleRenderer = SimpleRenderer;

    /**
    * A renderer which uses a HTML5 canvas
    */
    var CanvasRenderer = (function () {
        function CanvasRenderer() {
            /**
            * Image filters in use
            */
            this.filters = [];
        }
        CanvasRenderer.prototype.act = function (visitor) {
            if (visitor.visitCanvasRenderer) {
                visitor.visitCanvasRenderer(this);
            }
        };

        CanvasRenderer.prototype.visit = function (visitor, def) {
            if (visitor.visitCanvasRenderer) {
                return visitor.visitCanvasRenderer(this);
            } else {
                return def;
            }
        };
        return CanvasRenderer;
    })();
    Views.CanvasRenderer = CanvasRenderer;

    /**
    * A renderer which uses WebGL
    */
    var WebGLRenderer = (function () {
        function WebGLRenderer() {
        }
        WebGLRenderer.prototype.act = function (visitor) {
            if (visitor.visitWebGLRenderer) {
                visitor.visitWebGLRenderer(this);
            }
        };

        WebGLRenderer.prototype.visit = function (visitor, def) {
            if (visitor.visitWebGLRenderer) {
                return visitor.visitWebGLRenderer(this);
            } else {
                return def;
            }
        };
        return WebGLRenderer;
    })();
    Views.WebGLRenderer = WebGLRenderer;

    /**
    * A rendered handle in the upper-right corner for dragging series into other viewport
    */
    var Handle = (function () {
        function Handle(view) {
            var _this = this;
            this.view = view;
            this.supportsDragImage = (typeof DataTransfer.prototype.setDragImage === "function");
            var $handle = $('<div>').addClass("handle fa fa-2x fa-bars").appendTo(view.el);
            this.$el = $handle;
            $handle.prop("draggable", true);

            this.$el.on("mousedown", function (e) {
                var dataURL = _this.view.prepareCanvasForReport(_this.supportsDragImage, 150).toDataURL("image/png");
                var img = new Image();
                img.src = dataURL;
                _this.thumbnail = img;

                if (!_this.supportsDragImage) {
                    _this.$el.css("background-image", 'url("' + dataURL + '")');
                }
            }).on('selectstart', function (e) {
                if (Browser.isIE9()) {
                    _this.$el[0].dragDrop();
                    return false;
                }
            }).on("dragstart", function (e) {
                var dataTransfer = e.originalEvent.dataTransfer;

                if (dataTransfer) {
                    dataTransfer.dropEffect = "move";
                    dataTransfer.setData("text", "swap:" + _this.view.viewKey.value + ":" + _this.view.series.uuid);
                }

                if (dataTransfer && _this.supportsDragImage) {
                    dataTransfer.setDragImage(_this.thumbnail, 0, 0);
                } else {
                    _this.$el.removeClass("fa fa-2x fa-bars");
                    _this.$el.addClass("grabbed");
                }

                _this.$el.siblings(".instance").hide();
            }).on("drag", function (e) {
                _this.$el.hide();
            }).on("mouseup", function (e) {
                _this.reset();
                _this.view.renderAll();
            }).on("dragend", function (e) {
                _this.reset();
                _this.view.renderAll();
            });

            if (this.needsHandle()) {
                this.$el.show();
            } else {
                this.$el.hide();
            }
        }
        Handle.prototype.needsHandle = function () {
            var layout = this.view.application.layout.value;
            return (layout.rows * layout.columns) > 1;
        };

        Handle.prototype.show = function () {
            if (this.needsHandle()) {
                this.$el.show();
            }
        };

        Handle.prototype.hide = function () {
            this.$el.hide();
        };

        Handle.prototype.reset = function () {
            this.$el.addClass("fa fa-2x fa-bars");
            this.$el.removeClass("grabbed");
            this.$el.css("background-image", 'none');
            this.$el.show();
            this.$el.siblings(".instance").show();
        };

        Handle.prototype.clear = function () {
            this.view = null;
            this.$el = null;
        };
        return Handle;
    })();
    Views.Handle = Handle;

    /**
    * Unique key for a Series view
    */
    var SeriesViewKey = (function () {
        function SeriesViewKey(value) {
            this.value = value;
        }
        SeriesViewKey.Null = new SeriesViewKey(null);
        return SeriesViewKey;
    })();
    Views.SeriesViewKey = SeriesViewKey;

    /**
    * A view which displays a series, consisting of a grid of instance views
    */
    var Series = (function () {
        function Series(application, sessionId, el, series, settings, accountSettings, selectedSeriesKey, selectedTool, selectedTool2, selectedToolWheel, selectedInstanceGeometry, referenceLinesActive, planeLocalizationCursor, linkSeries, imagePreloadQueue, terminology, cineSpeed, ignoreFrameTimeTag, startCinePlaybackAutomatically, noKeyboardShortcuts, recorder, playbackMode) {
            var _this = this;
            this.viewKey = new SeriesViewKey(Math.round(Math.random() * 1000000).toString());
            /**
            * The current image transformation parameters
            */
            this.transform = new Subjects.ObservableValue({ offsetX: 0, offsetY: 0, scale: 1, flipped: false, rotation: 0 });
            this.mousePressed = false;
            /**
            * True if panning is active
            */
            this.panActive = false;
            /**
            * True if press is active
            */
            this.pressActive = false;
            /**
            * True if mouse cursor moved
            */
            this.mouseMoved = false;
            /**
            * True if a mouseUp has not happened following a mouseDown
            */
            this.needsMouseUp = false;
            /**
            * True if the annotation cursor needs to be hidden (e.g., while the mouse is dragging)
            */
            this.needsHideMeasurementCursor = false;
            /**
            * Shared subject which contains the location of the probe tool
            */
            this.probeTool = new Subjects.ObservableValue(null);
            /**
            * Shared subject which contains the location of the magnifier tool
            */
            this.magnifier = new Subjects.ObservableValue(null);
            /**
            * The center of the zoom/rotate operation
            */
            this.zoomCenter = new Subjects.ObservableValue(null);
            /**
            * True if cine is active
            */
            this.cineActive = new Subjects.ObservableValue(false);
            /**
            * True if text annotations are visible
            */
            this.infoVisible = new Subjects.ObservableValue(true);
            /**
            * True if measurements are visible
            */
            this.measurementsVisible = new Subjects.ObservableValue(true);
            /**
            * True if measurement details are visible
            */
            this.measurementsDetailsVisible = new Subjects.ObservableValue(false);
            /**
            * True if annotations created by other users are visible
            */
            this.annotationsCreatedByOtherUsersVisible = new Subjects.ObservableValue(true);
            /**
            * Collection of hidden GSPS layers
            */
            this.hiddenGSPSLayers = new Subjects.ObservableValue([]);
            /**
            * True if the image is inverted
            */
            this.invertActive = new Subjects.ObservableValue(false);
            /**
            * True if XA subtraction is being used
            */
            this.subtractionActive = new Subjects.ObservableValue(false);
            /**
            *
            * @type {Subjects.ObservableValue<Classes.ColorTable>}
            */
            this.colorTable = new Subjects.ObservableValue(null);
            /**
            * True if histogram equalization is turned on
            */
            this.enhance = new Subjects.ObservableValue(false);
            /**
            * The current window level settings
            */
            this.windowLevel = new Subjects.ObservableValue({ center: 128.0, width: 255.0 });
            this.useOriginalWindowLevel = new Subjects.ObservableValue(true);
            /**
            * Taken if low resolution images should be displayed, regardless of whether the high resolution image is available.
            * This is used when mouse operations are in progress to make sure rendering is fast.
            */
            this.forceLowResolution = new Subjects.ObservableValue(Classes.Resource.unused());
            /**
            * A list of subscriptions to dispose of when unloading this series
            */
            this.subscriptions = [];
            /**
            * The layers to render when the image is loaded
            */
            this.layers = [];
            this.progressBarLayer = new Layers.ProgressBarLayer();
            /**
            * The measurement currently being drawn
            */
            this.measurementInProgress = new Subjects.ObservableValue(null);
            /**
            * The currently selected measurement
            */
            this.selectedMeasurement = new Subjects.ObservableValue(null);
            /**
            * The measurement instance currently being edited.
            * @type {Subjects.ObservableValue<Measurements.Measurement>}
            */
            this.editingMeasurement = new Subjects.ObservableValue(null);
            /**
            * Overlay data
            */
            this.overlayData = new ByInstanceNumber();
            // Avoid preloading everything twice
            this.fullPreloadStarted = false;
            /**
            * Did the init function run yet?
            */
            this.didInit = false;
            /**
            * Accelerator cache miss count
            * Increment when X-Acc-Cached response header set to MISS
            *
            * Show message only once
            */
            this.acceleratorCacheMissCount = 0;
            this.acceleratorCacheMissMessage = "This study has images missing from the accelerator cache.";
            this.acceleratorCacheMessageDidShow = false;
            this.glTextureIsLoaded = false;
            this.glCreatedPaletteTexture = false;
            this.glTexturePaletteIsLoaded = false;
            /**
            * Enable to store extra data in an annotation (e.g., stats).
            * @type {number}
            */
            this.storeExtraAnnotationData = 0;
            /**
            * Use diagnostic quality always
            * @type {boolean}
            */
            this.useDiagnosticQualityAlways = false;
            /**
            * If true, measurement stats always shown in mm
            * @type {boolean}
            */
            this.alwaysUseMillimeters = false;
            /**
            * Ultrasound regions enabled
            */
            this.ultrasoundMeasurements = false;
            /**
            * True if this series is showing a warning banner.
            */
            this.showingBanner = false;
            this.lastRenderTime = 0;
            this.updateMousePosition = function (e) {
                if (_this.cursorLayer && _this.cursorLayer.visible && _this.cursorLayer.visible.read()) {
                    _this.cursorLayer.mousePosition = { x: e.offsetX, y: e.offsetY };
                    var context = _this.cursorCanvas.getContext("2d");

                    var instance = _this.currentInstance().read();
                    var instanceIndex = _this.selectedInstanceIndex.read();
                    var instanceKey = _this.getInstanceKey(instanceIndex);

                    _this.cursorLayer.render(context, instanceKey, instance);
                }
            };
            this.hideCursor = function () {
                _this.el.css({ cursor: '' });
                _this.el.addClass('hideCursor');
            };
            this.application = application;

            this.sessionId = sessionId;

            this.el = el;
            this.series = series;
            this.settings = settings;
            this.accountSettings = accountSettings;

            this.selectedSeriesKey = selectedSeriesKey;
            this.selectedTool = selectedTool;
            this.selectedTool2 = selectedTool2;
            this.selectedToolWheel = selectedToolWheel;

            this.selectedInstanceIndex = new Subjects.ObservableValue(0);

            this.ignoreFrameTimeTag = ignoreFrameTimeTag;

            if (!this.ignoreFrameTimeTag && series && !!series.instances[0].instanceAttributes.frameTime) {
                var fps = 1000 / series.instances[0].instanceAttributes.frameTime[0];
                this.cineSpeed = new Subjects.ObservableValue(fps);
            } else {
                this.cineSpeed = cineSpeed;
            }

            this.startCinePlaybackAutomatically = startCinePlaybackAutomatically;

            this.selectedInstanceGeometry = selectedInstanceGeometry;
            this.referenceLinesActive = referenceLinesActive;
            this.linkSeries = linkSeries;
            this.planeLocalizationCursor = planeLocalizationCursor;

            this.imagePreloadQueue = imagePreloadQueue;

            this.terminology = terminology;

            this.noKeyboardShortcuts = noKeyboardShortcuts;

            this.recorder = recorder;
            this.playbackMode = playbackMode;

            this.showStandardDev = new Subjects.ObservableValue(this.accountSettings.viewer_show_std_dev == 1);
            this.rulerVisible = new Subjects.ObservableValue(this.accountSettings.viewer_hide_ruler != 1);
            this.ultrasoundRegionsVisible = new Subjects.ObservableValue(false);
            this.hideActiveMeasuremntInfo = (this.accountSettings.viewer_hide_active_measurement_info == 1);
            this.storeExtraAnnotationData = this.accountSettings.viewer_store_extra_annotation_data;

            this.measurementsDetailsVisible.write(this.settings.read().showAnnotationDetails);
            this.annotationsCreatedByOtherUsersVisible.write(this.settings.read().showAnnotationsCreatedByOthers === undefined ? true : this.settings.read().showAnnotationsCreatedByOthers);
            this.measureVolumeDefault = this.settings.read().allowVolumeMeasurements;
            this.measureVolume = new Subjects.ObservableValue(this.measureVolumeDefault);

            this.cursorLayer = new Layers.CursorLayer(new Subjects.ObservableValue(false), this, this.application.currentColor);

            // showTextOnDirected is the opposite of disableArrowMeasurementText
            var disableArrowMeasurementText = (this.settings.read().disableArrowMeasurementText == null) ? false : this.settings.read().disableArrowMeasurementText;
            this.showTextOnDirected = new Subjects.ObservableValue(!disableArrowMeasurementText);

            this.alwaysUseMillimeters = this.settings.read().alwaysUseMillimeters;
            this.ultrasoundMeasurements = this.settings.read().ultrasoundMeasurements;
            this.useDiagnosticQualityAlways = (this.accountSettings.viewer_diagnostic_quality_always == 1) || this.settings.read().diagnosticQualityAlways;

            if (this.series && !this.useDiagnosticQualityAlways) {
                var modality = this.series.seriesAttributes.modality;
                var userSettings = this.settings.read();
                if (userSettings.modalities) {
                    var modalitySettings = _.find(userSettings.modalities, function (m) {
                        return m.modality === modality;
                    });
                    if (modalitySettings && modalitySettings.diagnosticQualityAlways == true) {
                        this.useDiagnosticQualityAlways = true;
                    }
                }
            }

            if (this.series && Dicom.Subtraction.supportsSubtraction(this.series.seriesAttributes)) {
                this.subtraction = Dicom.Subtraction.buildXA(this.series.instances[0]);
            }
        }
        /**
        * Create the necessary elements, add them to the DOM, and hook up event handlers
        * @param force true to initialize even if series is not in dom
        */
        Series.prototype.init = function (force) {
            var _this = this;
            if (typeof force === "undefined") { force = false; }
            if (!this.didInit && (force || $.contains(document.documentElement, this.el[0]))) {
                this.el.empty();

                // For PDFs insert an iframe
                if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 0 /* PDF */) {
                    var $instance = $('<div>').addClass('instance');
                    var $iframe = $('<iframe class="instance-pdf" frameborder="0"></iframe>');

                    $iframe[0].onload = function (e) {
                        _this.renderPDF();
                    };

                    $instance.append($iframe);
                    this.el.append($instance);

                    this.didInit = true;
                    return;
                }

                // For Video use an html video player
                if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 1 /* Video */) {
                    var $instance = $('<div>').addClass('instance');
                    $instance.append('<div class="instance-video"></div>');
                    this.el.append($instance);

                    this.renderVideo();

                    // Prevent browser default right click
                    this.el.on('contextmenu', function (e) {
                        return false;
                    });

                    this.didInit = true;
                    return;
                }

                var $instance = $('<div>').addClass('instance');

                this.renderer = this.createRenderer($instance);

                // Initiate and attach the global image cache
                this.renderer.act({
                    visitCanvasRenderer: function (renderer) {
                        if (_this.series) {
                            if (_this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value] == null) {
                                _this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value] = new ByImageTypeAndInstanceNumber();
                            }

                            renderer.imageElements = _this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value];
                        }
                    },
                    visitWebGLRenderer: function (renderer) {
                        if (_this.series) {
                            if (_this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value] == null) {
                                _this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value] = new ByImageTypeAndInstanceNumber();
                            }

                            renderer.imageElements = _this.application.imagePreloadStore[_this.series.seriesAttributes.seriesUid.value];
                        }
                    }
                });

                this.createLayers();

                this.canvas = document.createElement("canvas");

                var $canvas = $(this.canvas);
                $canvas.addClass('instance-canvas');
                $canvas.appendTo($instance);

                this.progressBarCanvas = document.createElement("canvas");

                var $progressBarCanvas = $(this.progressBarCanvas);
                $progressBarCanvas.addClass('progressBar-canvas');
                $progressBarCanvas.appendTo($instance);

                this.cursorCanvas = document.createElement("canvas");
                this.cursorCanvas.width = this.el.width();
                this.cursorCanvas.height = this.el.height();

                var $cursorCanvas = $(this.cursorCanvas);
                $cursorCanvas.addClass('cursor-canvas');
                $cursorCanvas.appendTo($instance);

                if (Browser.isMobile() && this.accountSettings.viewer_enable_tap_navigation == 1) {
                    // Set up arrow overlays
                    _.each([
                        'next-series',
                        'prev-series',
                        'next-image',
                        'prev-image'
                    ], function (clazz) {
                        $('<div>').addClass('arrow-overlay').addClass(clazz).hide().appendTo($(_this.el));
                    });
                }

                $(window).resize(Functions.throttle(function (_) {
                    _this.renderAll();
                }, 10));

                this.el.append($instance);

                var $glassPane = $('<div>').addClass("glasspane").appendTo(this.el);

                $glassPane.mousewheel(Functions.throttle(function (d) {
                    return _this.handleMouseWheelEvent(d, arguments[1]);
                }, 10));

                var hammer = new Hammer.Manager($glassPane.get(0));

                hammer.add(new Hammer.Pan({
                    threshold: 0
                }));

                hammer.add(new Hammer.Pinch({
                    enable: true,
                    threshold: 0.1
                }));

                hammer.add(new Hammer.Tap({
                    event: 'doubletap',
                    taps: 2
                }));
                hammer.add(new Hammer.Tap({
                    event: 'singletap',
                    taps: 1
                }));

                hammer.add(new Hammer.Press({
                    event: 'press',
                    taps: 1,
                    time: 0
                }));

                hammer.get('doubletap').recognizeWith('singletap');
                hammer.get('singletap').requireFailure('doubletap');

                hammer.on('singletap', function (e) {
                    _this.selectedSeriesKey.write(_this.viewKey);

                    if (_this.needsMouseUp) {
                        _this.handleMouseUpEvent(e);
                    }

                    if (Browser.isMobile() && _this.accountSettings.viewer_enable_tap_navigation == 1) {
                        var $frame = $(_this.el);
                        var offset = $frame.offset();

                        var w = $frame.width();
                        var h = $frame.height();

                        var x = e.center.x - offset.left;
                        var y = e.center.y - offset.top;

                        var b1 = x * h > y * w;
                        var b2 = (w - x) * h > y * w;

                        if (b1) {
                            if (b2) {
                                var newIndex = (_this.selectedInstanceIndex.read() - 1 < 0) ? _this.series.instances.length - 1 : _this.selectedInstanceIndex.read() - 1;
                                _this.selectedInstanceIndex.write(newIndex);
                                _this.arrow('prev-image');
                            } else {
                                _this.application.loadNextSeries();
                            }
                        } else {
                            if (b2) {
                                _this.application.loadPrevSeries();
                            } else {
                                var newIndex = (_this.selectedInstanceIndex.read() + 1 >= _this.series.instances.length) ? 0 : _this.selectedInstanceIndex.read() + 1;
                                _this.selectedInstanceIndex.write(newIndex);
                                _this.arrow('next-image');
                            }
                        }
                    }
                });

                hammer.on('press', function (e) {
                    if (!_this.pressActive && ((e.button === 0) || (e.button === undefined))) {
                        _this.pressActive = true;
                        _this.handleMouseDownEvent(e);
                    }
                });

                hammer.on('pressup', function (e) {
                    if (!_this.mouseMoved && ((e.button === 0) || (e.button === undefined))) {
                        _this.handleMouseUpEvent(e);
                    }
                });

                hammer.on('panstart', function (e) {
                    if (!_this.pressActive) {
                        _this.handleMouseDownEvent(e);
                    }

                    _this.pressActive = false;
                    _this.mouseMoved = true;
                });
                hammer.on('panmove', Functions.throttle(function (e) {
                    return _this.handleMouseMoveEvent(e);
                }, 10));
                hammer.on('panmove', Functions.throttle(function (e) {
                    return _this.recordMouseMoveEvent(e);
                }, 250));
                hammer.on('panend', function (e) {
                    return _this.handleMouseUpEvent(e);
                });

                hammer.on('pinchstart', function (e) {
                    return _this.pinchZoomStart(e);
                });
                hammer.on('pinchmove', function (e) {
                    return _this.pinchZoom(e);
                });
                hammer.on('pinchend', function (e) {
                    return _this.pinchZoomEnd(e);
                });

                hammer.on('doubletap', function (e) {
                    if (_this.series) {
                        _this.application.magnifyMinify(_this);
                    }

                    if (_this.needsMouseUp) {
                        _this.handleMouseUpEvent(e);
                    }

                    _this.selectedSeriesKey.write(_this.viewKey);
                });

                // Prevent browser default right click
                $glassPane.on('contextmenu', function (e) {
                    return false;
                });

                // Prevent mobile browser overscroll
                $glassPane.on('touchmove', function (e) {
                    return false;
                });

                // Allow right mouse click to open context menu
                $glassPane.on('mouseup pointerup', function (e) {
                    var mouseOnly = (e.type == "mouseup" && !window.PointerEvent);
                    var pointerOnly = (e.type == "pointerup" && window.PointerEvent);
                    if (!_this.panActive && e.button == 2 && (mouseOnly || pointerOnly)) {
                        $('.series').contextMenu({
                            x: e.pageX,
                            y: e.pageY
                        });
                    }
                    return true;
                });

                $glassPane.on('mouseenter', function (e) {
                    if (_this.cursorLayer.cursor != 0 /* Blank */) {
                        _this.cursorLayer.visible.write(true);
                    }
                });

                $glassPane.on('mouseleave', function (e) {
                    _this.cursorLayer.visible.write(false);
                });

                if (this.settings.read().recordMouseMovements) {
                    $glassPane.on('mousemove', Functions.throttle(function (e) {
                        return _this.recordMouseMoveEvent(e);
                    }, 10));
                }

                this.subscriptions.push(Subjects.listen(this.selectedTool, function (tool) {
                    if (!_this.settings.read().disableCustomAnnotationCursor && Classes.MouseTools.isMeasurementTool(_this.selectedTool.read())) {
                        _this.enableMeasurementCursor(tool);
                    } else {
                        _this.disableMeasurementCursor();
                    }

                    if (!Classes.MouseTools.isGroupMeasurementTool(tool) && Classes.MouseTools.isGroupMeasurementTool(_this.previousTool)) {
                        _this.application.hideWarningBanner();

                        if (_this.previousTool == 33 /* ProstateTool */) {
                            Measurements.ProstateTool.reset();
                        }
                    }

                    _this.previousTool = _this.selectedTool.read();
                }));

                // Initialize the selected tool
                this.application.selectedTool.write(this.selectedTool.read());

                if (this.series) {
                    this.handle = new Handle(this);
                }

                $glassPane.on('dragover', function (e) {
                    e.preventDefault();
                }).on('drop', function (e) {
                    e.preventDefault();
                    var dataTransfer = e.originalEvent.dataTransfer;

                    if (dataTransfer) {
                        var message = dataTransfer.getData("text");
                        var seriesLikeUUID;
                        var swapViewKey;

                        if (message.indexOf("swap") != -1) {
                            var parts = message.split(":");
                            swapViewKey = parts[1];
                            seriesLikeUUID = parts[2];
                        } else {
                            seriesLikeUUID = message;
                        }

                        if (swapViewKey) {
                            var views = _this.application.seriesViews.value;
                            var swapView = _.find(views, function (view) {
                                return (view.viewKey.value == swapViewKey);
                            });

                            if (swapView) {
                                var swapViewIndex = views.indexOf(swapView);
                                var currentIndex = views.indexOf(_this);
                                views[swapViewIndex] = views.splice(currentIndex, 1, views[swapViewIndex])[0]; // swap
                                _this.application.seriesViews.raiseChangedEvent([swapView, _this]);
                                swapView.handle.reset();
                            }
                        } else {
                            var series = _.chain(_this.application.studies).map(function (s) {
                                return s.series;
                            }).flatten().filter(function (s) {
                                return s.uuid === seriesLikeUUID;
                            }).value();

                            if (!(series && series[0])) {
                                series = _.chain(_this.application.studies).map(function (s) {
                                    return s.keyImageSeries;
                                }).flatten().filter(function (s) {
                                    return s.uuid === seriesLikeUUID;
                                }).value();
                            }

                            if (series && series[0]) {
                                _this.application.selectedSeriesKey.write(_this.viewKey);
                                _this.application.replaceSelectedSeries(series[0]);
                            }
                        }
                    }
                });

                this.subscriptions.push(Subjects.listen(this.selectedSeriesKey, function (key) {
                    if (key.value === _this.viewKey.value && !_this.el.is('.selected')) {
                        _this.el.addClass('selected');
                        _this.renderAll();
                    } else if (key.value !== _this.viewKey.value && _this.el.is('.selected')) {
                        _this.el.removeClass('selected');
                        _this.renderAll();
                    }
                }));

                var sendInstanceIndexEventToRecorder = Functions.throttle(function (index) {
                    _this.recordEvent({
                        type: 6 /* SelectedImageChanged */,
                        instanceIndex: index
                    });
                }, 500);

                var accelerated = this.series && this.series.studyAttributes.accelerated;

                this.subscriptions.push(Subjects.listen(this.selectedInstanceIndex, function (index) {
                    _this.glTextureIsLoaded = false;

                    if (Browser.isChrome() && !accelerated && !_this.fullPreloadStarted) {
                        _this.fullPreloadStarted = true;
                        _this.loadAllImageData(_this.imagePreloadQueue, -2 /* ImageDataBackgroundPreloading */);
                    }

                    if (_this.application.gspsInfoVisible.read()) {
                        _this.application.gspsInfo.render();
                    }

                    _this.renderer.act({
                        visitSimpleRenderer: function (renderer) {
                            renderer.imageElements.each(function (el) {
                                return $(el.imageElement).hide();
                            });

                            var instanceKey = _this.getInstanceKey(index);

                            var imageElement = renderer.imageElements.get(instanceKey, null);

                            if (imageElement) {
                                $(imageElement.imageElement).show();
                            }
                        }
                    });

                    sendInstanceIndexEventToRecorder(index);

                    _this.renderAll();
                }));

                if (LocalViewer.isLocalViewer() || this.application.studyStorage.localAccelerator) {
                    // This line appears to cause significant issues in the local viewer. More testing is needed to see if it is required.
                    this.loadAllImageData(this.imagePreloadQueue, -2 /* ImageDataBackgroundPreloading */);
                    this.preloadAllDiagnosticImages(this.imagePreloadQueue, this.application.supportsAcceleratedPreload() ? 99 /* FullResolutionImage */ : -2 /* ImageDataBackgroundPreloading */);
                }

                if (LocalViewer.isLocalViewer() || accelerated || this.application.studyStorage.localAccelerator || this.linkSeries.read()) {
                    this.loadImageAttributes(this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
                }

                var preloadRange = this.accountSettings.image_preload_window || this.settings.read().imagePreloadWindow || 10;

                var preloadVisibleHiResImages = _.debounce(function () {
                    return _this.preloadVisibleHiResImages(_this.imagePreloadQueue, 99 /* FullResolutionImage */);
                }, 1000);
                var preloadNearbyImages = _.debounce(function () {
                    return _this.preloadImageRange(_this.imagePreloadQueue, preloadRange, 0, preloadRange);
                }, 100);

                this.subscriptions.push(Subjects.listen(this.selectedInstanceIndex, function (index) {
                    preloadVisibleHiResImages();
                    preloadNearbyImages();
                }));

                this.subscriptions.push(Subjects.listen(this.selectedInstanceGeometry, function (data) {
                    if (data && _this.series && _this.series.studyAttributes.queryObject.studyUid.value === data.studyUid.value) {
                        if (_this.linkSeries.read() && _this.viewKey.value !== data.originator) {
                            _this.scrollToNearestInstance(data);
                        }

                        if (_this.referenceLinesActive.read() && _this.series.seriesAttributes.seriesUid.value !== data.seriesUid.value) {
                            _this.renderLayers();
                        }
                    }
                }));

                this.subscriptions.push(Subjects.listen(this.planeLocalizationCursor, function (cursor) {
                    if (cursor && _this.series && _this.series.studyAttributes.queryObject.studyUid.value === cursor.studyUid.value && _this.series.seriesAttributes.seriesUid.value !== cursor.seriesUid.value) {
                        _this.scrollToNearestInstanceToPoint(cursor.coords);
                    }

                    _this.renderAll();
                }));

                _.each([this.selectedTool, this.selectedTool2, this.selectedToolWheel], function (sub) {
                    Subjects.listen(sub, function (tool) {
                        _this.renderAll();
                    });
                });

                var renderLayers = Functions.throttle(function (_) {
                    _this.renderLayers();
                }, 25);

                var renderAll = Functions.throttle(function (_) {
                    _this.renderAll();
                }, 25);

                _.each([this.infoVisible, this.measurementsVisible, this.rulerVisible, this.ultrasoundRegionsVisible, this.measurementsDetailsVisible, this.annotationsCreatedByOtherUsersVisible, this.hiddenGSPSLayers, this.referenceLinesActive, this.probeTool, this.magnifier], function (sub) {
                    return _this.subscriptions.push(Subjects.listen(sub, function (e) {
                        renderLayers({});
                    }));
                });

                Subjects.listen(this.colorTable, function (e) {
                    _this.glTexturePaletteIsLoaded = false;
                    renderAll({});
                });

                _.each([this.invertActive, this.windowLevel, this.linkSeries, this.transform, this.enhance], function (sub) {
                    return _this.subscriptions.push(Subjects.listen(sub, function (e) {
                        renderAll({});
                    }));
                });

                this.subscriptions.push(Subjects.listen(this.subtractionActive, function (e) {
                    if (_this.subtractionActive.read()) {
                        if (!_this.subtractionWindowLevel) {
                            _this.subtractionWindowLevel = { center: 0, width: 256 };
                        }

                        var original = _this.windowLevel.read();
                        _this.windowLevel.write(_this.subtractionWindowLevel);
                        _this.preSubtractionWindowLevel = { center: original.center, width: original.width };
                    } else {
                        var original = _this.windowLevel.read();
                        _this.subtractionWindowLevel = { center: original.center, width: original.width };
                        _this.windowLevel.write(_this.preSubtractionWindowLevel);
                    }

                    renderAll({});
                }));

                this.setupMeetingEvents();

                if (!this.useDiagnosticQualityAlways) {
                    this.subscriptions.push(Subjects.listen(this.forceLowResolution, function (e) {
                        _this.renderAll();
                    }));
                }

                if (this.series) {
                    this.subscriptions.push(Subjects.listen(this.series.seriesAttributes.imageDataLoaded, function (e) {
                        _this.checkLoadedStatus();
                        _this.renderProgressBar();
                    }));
                }

                this.subscriptions.push(Subjects.listen(this.cineActive, function (active) {
                    _this.toggleCine(active);
                }));

                var saveCineSpeedToSettings = _.debounce(function () {
                    return _this.saveCineSpeedToSettings();
                }, 500);
                this.subscriptions.push(Subjects.listen(this.cineSpeed, function (speed) {
                    saveCineSpeedToSettings();
                }));

                if (this.series) {
                    if (!this.useDiagnosticQualityAlways || accelerated) {
                        var preloadRange = this.accountSettings.image_preload_window || this.settings.read().imagePreloadWindow || 10;
                        this.preloadImageRange(this.imagePreloadQueue, preloadRange, 0, preloadRange);
                    }

                    this.preloadVisibleHiResImages(this.imagePreloadQueue, 99 /* FullResolutionImage */);
                }

                this.didInit = true;

                // Initial render
                if (this.findRenderingMode() == 2 /* Simple */) {
                    this.renderAll();
                } else {
                    this.initialRenderTimer = setInterval(function () {
                        return _this.renderAll();
                    }, 200);
                }
            }
        };

        Series.prototype.arrow = function (clazz) {
            if (this.settings && !this.settings.read().disableMobileArrows) {
                var $arrow = $(this.el).find('.' + clazz);
                $arrow.show();
                $arrow.fadeOut(800);
            }
        };

        Series.prototype.enableMeasurementCursor = function (tool) {
            if (this.settings.read().disableCustomAnnotationCursor == true) {
                this.el.css({ cursor: 'crosshair' });
            } else {
                var $glassPane = this.el.find('.glasspane');

                // Show crosshair when annotation tools are selected
                this.cursorLayer.visible.write(true);

                if (Classes.MouseTools.isPaintTool(tool)) {
                    this.cursorLayer.cursor = 2 /* Paint */;
                } else {
                    this.cursorLayer.cursor = 1 /* Crosshair */;
                }

                // Enable mouse tracking for cursor layer
                $glassPane.on('mousemove', this.updateMousePosition);

                // Don't show normal operating system cursor
                $glassPane.one('mousemove', this.hideCursor);
            }
        };

        Series.prototype.disableMeasurementCursor = function () {
            if (this.settings.read().disableCustomAnnotationCursor == true) {
                this.el.css({ cursor: '' });
            } else {
                var $glassPane = this.el.find('.glasspane');

                // Don't show a custom cursor
                this.cursorLayer.visible.write(false);
                this.cursorLayer.cursor = 0 /* Blank */;

                // Stop tracking the mouse position for the cursor layer
                $glassPane.off('mousemove', this.updateMousePosition);
                $glassPane.off('mousemove', this.hideCursor);

                // Show the normal operating system cursor
                this.el.css({ cursor: '' });
                this.el.removeClass('hideCursor');
            }
        };

        /**
        * Add event listeners for meeting events
        */
        Series.prototype.setupMeetingEvents = function () {
            var _this = this;
            Subjects.listen(this.invertActive, function (inverted) {
                _this.recordEvent({
                    type: 9 /* InvertChanged */,
                    inverted: inverted
                });
            });

            Subjects.listen(this.enhance, function (enhance) {
                _this.recordEvent({
                    type: 15 /* EnhanceChanged */,
                    enhance: enhance
                });
            });

            Subjects.listen(this.infoVisible, function (visible) {
                _this.recordEvent({
                    type: 10 /* ToggleTextAnnotationsChanged */,
                    showTextAnnotations: visible
                });
            });

            Subjects.listen(this.measurementsVisible, function (visible) {
                _this.recordEvent({
                    type: 11 /* ToggleMeasurementsChanged */,
                    showMeasurements: visible
                });
            });

            // Transformation events
            var recordTransformation = _.throttle(function () {
                _this.recordTransformation();
            }, 250);

            this.subscriptions.push(Subjects.listen(this.transform, function (e) {
                recordTransformation();
                _this.application.thumbnails.render();
            }));

            // Magnifier events
            var recordMagnifier = _.throttle(function () {
                _this.recordMagnifier();
            }, 250);

            this.subscriptions.push(Subjects.listen(this.magnifier, function (e) {
                recordMagnifier();
            }));

            // Probe tool events
            var recordProbe = _.throttle(function () {
                _this.recordProbe();
            }, 250);

            this.subscriptions.push(Subjects.listen(this.probeTool, function (e) {
                recordProbe();
            }));

            // Plane localization events
            var recordPlaneLocalizationData = _.throttle(function () {
                _this.recordPlaneLocalizationData();
            }, 250);

            this.subscriptions.push(Subjects.listen(this.planeLocalizationCursor, function (e) {
                recordPlaneLocalizationData();
            }));

            if (this.startCinePlaybackAutomatically.read()) {
                this.cineActive.write(true);
            }
        };

        Series.prototype.attributesLoaded = function (instance) {
            if (this.series && instance) {
                if (this.selectedInstanceIndex.read() == instance.instanceAttributes.instanceIndex) {
                    this.preloadVisibleHiResImages(this.imagePreloadQueue, 99 /* FullResolutionImage */);
                }
            }
        };

        Series.prototype.findRenderingMode = function () {
            var mode;

            if (this.series) {
                var attribs = this.series.instances[0].instanceAttributes;
                var rows = attribs.rows;
                var cols = attribs.columns;

                mode = Rendering.getRenderingMode(Math.max(rows * 2, cols));
            } else {
                mode = Rendering.getRenderingMode();
            }

            return mode;
        };

        /**
        * Create an appropriate renderer based on capabilities
        */
        Series.prototype.createRenderer = function ($instance) {
            var mode = this.findRenderingMode();

            switch (mode) {
                case 2 /* Simple */: {
                    var simpleRenderer = new SimpleRenderer();

                    return simpleRenderer;
                }
                case 1 /* WebGL */: {
                    var webGLRenderer = new WebGLRenderer();

                    webGLRenderer.canvas3d = document.createElement("canvas");

                    var $canvas3d = $(webGLRenderer.canvas3d);
                    $canvas3d.addClass('instance-canvas-3d');
                    $canvas3d.appendTo($instance);

                    try  {
                        webGLRenderer.gl = WebGL.createContext(webGLRenderer.canvas3d);
                        webGLRenderer.program = WebGL.initShaders(this.application.shaders, webGLRenderer.gl);
                        WebGL.setupBuffersAndCreateTexture(webGLRenderer.gl, webGLRenderer.program);
                    } catch (ex) {
                        this.recordError("Unable to initialize WebGL: " + ex);
                        return new SimpleRenderer();
                    }

                    return webGLRenderer;
                }
                case 0 /* Canvas */: {
                    var canvasRenderer = new CanvasRenderer();

                    canvasRenderer.filters.push(new Filters.Subtraction(this, this.subtractionActive));
                    canvasRenderer.filters.push(new Filters.WindowLevel(this.combineWindowLevelFlags(), this.windowLevel, this.subtractionActive));
                    canvasRenderer.filters.push(new Filters.Invert(this.invertActive));
                    canvasRenderer.filters.push(new Filters.Colors(this.colorTable));

                    canvasRenderer.canvas2d = document.createElement("canvas");

                    var $canvas2d = $(canvasRenderer.canvas2d);
                    $canvas2d.addClass('instance-canvas-2d');
                    $canvas2d.appendTo($instance);

                    canvasRenderer.context = canvasRenderer.canvas2d.getContext("2d");

                    return canvasRenderer;
                }
            }

            throw "Unknown rendering mode";
        };

        /**
        * Setup the layers required for rendering
        */
        Series.prototype.createLayers = function () {
            var _this = this;
            var useMultiframeCine = Multiframe.isMultiframe(this.series) && Cine.isFormatSupported();
            var cineActive = Subjects.map(this.cineActive, function (active) {
                return active && useMultiframeCine;
            });
            var cineInactive = Subjects.map(cineActive, function (active) {
                return !active;
            });

            var settings = this.settings.read();

            if (this.series) {
                if (useMultiframeCine) {
                    this.layers.push(new Layers.ConditionalLayer(cineActive, this.cineLayer = new Layers.CineLayer(this.sessionId, cineActive, this.cineSpeed, this.transform, this.settings, this.terminology, function () {
                        return _this.renderAll();
                    }, function (frame) {
                        _this.selectedInstanceIndex.write(frame);
                        _this.renderAll();
                    })));
                }

                var textSize = Layers.DEFAULT_TEXT_SIZE;

                if (settings.annotationTextSize) {
                    var textSizeVal = parseInt(settings.annotationTextSize);
                    if (textSizeVal) {
                        textSize = textSizeVal;
                    }
                }

                this.layers.push(new Layers.OverlayLayer(this.measurementsVisible, this.overlayData, this, settings.supportROIOverlays));
                this.layers.push(new Layers.ConditionalLayer(cineInactive, this.measurementsLayer = new Layers.MeasurementLayer(this.measurementsVisible, this.measurementsDetailsVisible, this.annotationsCreatedByOtherUsersVisible, this.renderer, this.measurementInProgress, this.selectedTool, this.selectedMeasurement, this, this.playbackMode, this.showStandardDev, this.showTextOnDirected, this.editingMeasurement, this.hideActiveMeasuremntInfo, this.alwaysUseMillimeters, textSize, this.measureVolume, settings.cobbSmallAngle, this.application.user)));

                this.gspsLayer = new Layers.PresentationStateLayer(this.measurementsVisible, this.application.gspsInfoVisible, this.hiddenGSPSLayers, this.application.highlightedGraphic, this);
                this.layers.push(new Layers.ConditionalLayer(cineInactive, this.gspsLayer));

                var getImageType = function () {
                    var index = _this.selectedInstanceIndex.read();

                    return _this.getImageType(_this.series.instances[index]);
                };

                var attributesLayer = new Layers.Attributes(this.combineWindowLevelFlags(), getImageType, this.infoVisible, this.terminology, this.selectedMeasurement, this.settings, this, this);
                this.layers.push(attributesLayer);

                this.layers.push(new Layers.Orientation(this.infoVisible, this));
                this.layers.push(new Layers.ReferenceLines(this.selectedInstanceGeometry, this.referenceLinesActive, this));
                this.layers.push(new Layers.PlaneLocalizationLayer(this.planeLocalizationCursor, this.selectedTool, this));

                this.layers.push(new Layers.ProbeLayer(this.renderer, this.probeTool, this.selectedTool, this));

                this.layers.push(new Layers.MagnifierLayer(this.magnifier, this.selectedTool, this.transform, this));

                this.layers.push(new Layers.FreeRotateLayer(this.zoomCenter, this.selectedTool, this));

                this.layers.push(new Layers.RulerLayer(this, this.rulerVisible));

                if (this.ultrasoundMeasurements && Dicom.Ultrasound.isUltrasound(this.series.instances[0])) {
                    this.layers.push(new Layers.Ultrasound(this, this.ultrasoundRegionsVisible));
                }

                this.layers.push(new Layers.ErrorLayer(this.terminology));
            }

            var isSelected = Subjects.map(this.selectedSeriesKey, function (ser) {
                return ser.value === _this.viewKey.value;
            });

            var isLinked = Subjects.zip(this.linkSeries, this.selectedInstanceGeometry, function (linkSeries, selectedInstance) {
                if (_this.series && linkSeries && _this.series.instances && selectedInstance) {
                    var currentInstance = _this.currentInstance().read();
                    if (currentInstance && SeriesGeometry.hasGeometricMetadata(currentInstance)) {
                        return !SeriesGeometry.areOrthogonal(currentInstance, selectedInstance);
                    }
                }

                return false;
            });

            var borderLayer = new Layers.Border(isSelected, isLinked);
            this.layers.push(borderLayer);
        };

        /**
        * Render a single PDF
        */
        Series.prototype.renderPDF = function () {
            if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 0 /* PDF */) {
                var instance = this.series.instances[0];
                var uri = Routes.PDFData(this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, this.accountSettings.cache == 1, true);

                var $iframe = $(this.el).find(".instance-pdf");
                var $pdf = $('<object style="width:100%; height:100%;" data="' + uri + '" type="application/pdf"><embed style="width:100%; height:100%;" src="' + uri + '" type="application/pdf" /></object>');

                $iframe.contents().find('html body').append($pdf);
                $iframe.contents().find('html body').css('margin', '0px');
            }
        };

        /**
        * Render the video
        */
        Series.prototype.renderVideo = function () {
            if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 1 /* Video */) {
                if (Cine.isFormatSupported()) {
                    var instance = this.series.instances[0];

                    var $videobody = $(this.el).find(".instance-video");

                    // var instance = this.currentInstance().read();
                    var video = new Views.Video(this.sessionId, $videobody, this.settings, instance, this.terminology, false);
                    video.render();
                } else {
                    window.alert(this.terminology.lookup(Terminology.Terms.VideoNotSupported));
                }
            }
        };

        /**
        * Render the appropriate layers into the back buffer and switch buffers
        */
        Series.prototype.renderAllForceUpdate = function () {
            this.renderAll(null, true, null, false, true);
        };
        Series.prototype.renderAll = function (renderImage, clearOverlayLayer, layers, forceSquare, forceRender) {
            var _this = this;
            if (typeof clearOverlayLayer === "undefined") { clearOverlayLayer = true; }
            if (typeof forceSquare === "undefined") { forceSquare = false; }
            if (typeof forceRender === "undefined") { forceRender = false; }
            if (!this.didInit) {
                this.init();
                return;
            }
            if (this.didInit && $.contains(document.documentElement, this.el[0])) {
                if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 0 /* PDF */) {
                    // Do nothing, this is a PDF
                    return;
                }

                if (this.series && this.series.seriesAttributes && this.series.seriesAttributes.documentType && this.series.seriesAttributes.documentType.type === 1 /* Video */) {
                    // Do nothing, this is a Video
                    return;
                }

                // throttle render to at most once very 10ms
                var currentRenderTime = (new Date()).getTime();
                if (!forceRender && (currentRenderTime - this.lastRenderTime < 10)) {
                    return;
                }

                this.lastRenderTime = currentRenderTime;

                var targetWidth;
                var targetHeight;

                if (forceSquare) {
                    var size = Math.min(this.el.width(), this.el.height());
                    targetWidth = targetHeight = size;
                } else {
                    targetWidth = this.el.width();
                    targetHeight = this.el.height();
                }

                this.canvas.width = targetWidth;
                this.canvas.height = targetHeight;
                this.cursorCanvas.width = targetWidth;
                this.cursorCanvas.height = targetHeight;

                this.renderer.act({
                    visitCanvasRenderer: function (renderer) {
                        renderer.canvas2d.width = targetWidth;
                        renderer.canvas2d.height = targetHeight;
                    },
                    visitWebGLRenderer: function (renderer) {
                        renderer.canvas3d.width = targetWidth;
                        renderer.canvas3d.height = targetHeight;
                    }
                });

                var instance = this.currentInstance().read();
                var instanceIndex = this.selectedInstanceIndex.read();

                this.renderer.act({
                    visitCanvasRenderer: function (renderer) {
                        renderer.context.fillStyle = "#000000";
                        renderer.context.fillRect(0, 0, renderer.context.canvas.width, renderer.context.canvas.height);
                    },
                    visitWebGLRenderer: function (renderer) {
                        renderer.gl.clearColor(0, 0, 0, 0);
                        renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT);
                    }
                });

                var context = this.canvas.getContext("2d");

                if (this.series) {
                    if (this.currentImageIsLoaded()) {
                        this.renderer.act({
                            visitSimpleRenderer: function (renderer) {
                                renderer.imageElements.each(function (imageElement) {
                                    var matrix = _this.getImageTransformation(_this.canvas.width, _this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                                    var $el = $(imageElement.imageElement);

                                    $el.css("transform", "matrix({0},{1},{3},{4},{2},{5})".replace("{0}", matrix.entries[0].toString()).replace("{1}", matrix.entries[1].toString()).replace("{2}", matrix.entries[2].toString()).replace("{3}", matrix.entries[3].toString()).replace("{4}", matrix.entries[4].toString()).replace("{5}", matrix.entries[5].toString()));
                                });

                                var instanceKey = _this.getInstanceKey(instanceIndex);
                                var imageElement = renderer.imageElements.get(instanceKey, null);

                                if (imageElement) {
                                    $(imageElement.imageElement).show();
                                }
                            },
                            visitCanvasRenderer: function (renderer) {
                                var el = $(renderer.canvas2d);

                                var imageType = _this.getImageType(instance);
                                var instanceKey = _this.getInstanceKey(instanceIndex);
                                var image = renderer.imageElements.get(instanceKey, imageType);

                                if (image && image.imageElement.complete && typeof image.imageElement.naturalWidth !== "undefined" && image.imageElement.naturalWidth !== 0) {
                                    if (image.imageData == null) {
                                        renderer.imageElements.put(instanceKey, imageType, {
                                            imageElement: image.imageElement,
                                            imageData: Images.getImageData(image.imageElement),
                                            imageDownloadState: image.imageDownloadState
                                        });
                                        image = renderer.imageElements.get(instanceKey, imageType);
                                    }
                                    _this.renderUsingCanvas(el, renderer.context, image, instance);
                                }
                            },
                            visitWebGLRenderer: function (renderer) {
                                var transform = _this.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                                var imageType = _this.getImageType(instance);
                                var instanceKey = _this.getInstanceKey(instanceIndex);
                                var image = renderer.imageElements.get(instanceKey, imageType);

                                if (image && image.imageElement.complete && typeof image.imageElement.naturalWidth !== "undefined" && image.imageElement.naturalWidth !== 0) {
                                    if (image.imageData == null) {
                                        renderer.imageElements.put(instanceKey, imageType, {
                                            imageElement: image.imageElement,
                                            imageData: Images.getImageData(image.imageElement),
                                            imageDownloadState: image.imageDownloadState
                                        });
                                        image = renderer.imageElements.get(instanceKey, imageType);
                                    }
                                    _this.renderUsingWebGL(renderer.gl, renderer.program, image, instance, transform);
                                }
                            }
                        });

                        // Optional rendering step
                        if (renderImage) {
                            renderImage(context, instance);
                        }
                    } else {
                        var imageType = this.getImageType(instance);

                        this.preload(instanceIndex, imageType).subscribe({
                            done: function () {
                            },
                            next: function (_) {
                            },
                            fail: function (err) {
                                _this.recordError("Unable to preload image: " + err);
                            }
                        });

                        Rendering.writeLineCentered(context, "Image Loading", this.canvas.width / 2.0, this.canvas.height / 2.0, 12.0);
                    }
                }

                // Render layers on top
                this.renderLayers(clearOverlayLayer, layers, forceSquare);
            }
        };

        /**
        * Render just the overlay layers
        */
        Series.prototype.renderLayers = function (clearOverlayLayer, layers, hideBorder) {
            var _this = this;
            if (typeof clearOverlayLayer === "undefined") { clearOverlayLayer = true; }
            if (typeof hideBorder === "undefined") { hideBorder = false; }
            var context = this.canvas.getContext("2d");

            if (clearOverlayLayer) {
                context.clearRect(0, 0, context.canvas.width, context.canvas.height);
            }

            if (this.series && this.series.instances) {
                var instance = this.currentInstance().read();
                var instanceIndex = this.selectedInstanceIndex.read();
                var instanceKey = this.getInstanceKey(instanceIndex);

                if (layers) {
                    _.each(layers, function (layer) {
                        layer.render(context, instanceKey, instance, null, _this.series);
                    });
                } else {
                    _.each(this.layers, function (layer) {
                        if (!hideBorder || !(layer instanceof Layers.Border)) {
                            layer.render(context, instanceKey, instance, null, _this.series);
                        }
                    });
                }

                if ((instance.instanceAttributes.calibration && instance.instanceAttributes.calibration.calibrationValue) || instance.instanceAttributes.calibrationUser) {
                    if (!this.showingBanner) {
                        this.showingBanner = true;
                        this.application.showWarningBanner(this.terminology.lookup(Terminology.Terms.WarningCalibrationUsed));
                    }
                } else {
                    if (this.showingBanner) {
                        this.showingBanner = false;
                        this.application.hideWarningBanner();
                    }
                }
            }
        };

        /**
        * Render just the progress bar
        */
        Series.prototype.renderProgressBar = function (clearOverlayLayer) {
            if (typeof clearOverlayLayer === "undefined") { clearOverlayLayer = true; }
            if (this.progressBarCanvas != null) {
                var context = this.progressBarCanvas.getContext("2d");

                context.canvas.height = 15;
                context.canvas.width = this.el.width();

                var instance = this.currentInstance().read();
                var instanceIndex = this.selectedInstanceIndex.read();
                var instanceKey = this.getInstanceKey(instanceIndex);

                if (clearOverlayLayer) {
                    context.clearRect(0, 0, context.canvas.width, context.canvas.height);
                }

                this.progressBarLayer.render(context, instanceKey, instance);

                // Remove canvas and events when done loading
                var imageAttributesLoaded = instance.seriesAttributes.imageAttributesLoaded.read();
                var imageDataLoaded = instance.seriesAttributes.imageDataLoaded.read();
                if (imageAttributesLoaded == instance.seriesAttributes.instanceCount && imageDataLoaded == instance.seriesAttributes.instanceCount) {
                    // Discard the canvas
                    $(this.progressBarCanvas).remove();
                    this.progressBarCanvas = null;
                }
            }
        };

        /**
        * Get the image size, accounting for the double-height property of 16-bit images
        */
        Series.prototype.getActualImageSize = function (imageData, instance) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                return { width: imageData.width, height: imageData.height / 2 };
            }

            return { width: imageData.width, height: imageData.height };
        };

        /**
        * Render the image onto a Canvas
        */
        Series.prototype.renderUsingCanvas = function (el, context, image, instance) {
            var startRender = +new Date();

            var prepared = this.prepareImageData(context, image.imageData, instance);

            var transform = this.getImageTransformation(context.canvas.width, context.canvas.height, prepared.width, prepared.height);

            context.save();
            context.transform(transform.entries[0], transform.entries[3], transform.entries[1], transform.entries[4], transform.entries[2], transform.entries[5]);
            Images.drawImageData(context, prepared.imageData);
            context.restore();

            if (this.firstImageRenderTime == null) {
                clearInterval(this.initialRenderTimer);

                var accelerated = this.series && this.series.studyAttributes.accelerated;

                if (image && image.imageElement.complete && typeof image.imageElement.naturalWidth !== "undefined" && image.imageElement.naturalWidth !== 0) {
                    var elapsed = (+new Date()) - startRender;

                    var additionalMetrics = {
                        studyUID: this.series.studyAttributes.queryObject.studyUid,
                        accelerated: accelerated,
                        imageSize: image.imageElement.naturalWidth * image.imageElement.naturalHeight,
                        imageWidth: image.imageElement.naturalWidth,
                        imageHeight: image.imageElement.naturalHeight,
                        renderTime: elapsed
                    };

                    this.firstImageRenderTime = elapsed;

                    Main.Main.recordMetric('showFirstImage', additionalMetrics);
                }

                this.application.thumbnails.render();
            }
        };

        /**
        * Prepare an image for rendering
        */
        Series.prototype.prepareImageData = function (context, imageData, instance) {
            var imageType = this.getImageType(instance);

            var imageSize = this.getActualImageSize(imageData, instance);

            var filters = this.renderer.visit({
                visitCanvasRenderer: function (renderer) {
                    return renderer.filters;
                }
            }, null);

            if (filters === null) {
                filters = [
                    new Filters.Subtraction(this, this.subtractionActive),
                    new Filters.WindowLevel(this.combineWindowLevelFlags(), this.windowLevel, this.subtractionActive),
                    new Filters.Invert(this.invertActive),
                    new Filters.Colors(this.colorTable)
                ];
            }

            for (var i = 0; i < filters.length; i++) {
                imageData = filters[i].apply(context, instance, imageData);
            }

            return {
                imageData: imageData,
                width: imageSize.width,
                height: imageSize.height
            };
        };

        Series.prototype.getAndPrepareImageData = function (context, instance) {
            var imageType = this.getImageType(instance);
            var instanceIndex = this.selectedInstanceIndex.read();
            var instanceKey = this.getInstanceKey(instanceIndex);

            var imageData = this.renderer.visit({
                visitCanvasRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return Images.getImageData(imageElement.imageElement);
                    }

                    return null;
                },
                visitWebGLRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return Images.getImageData(imageElement.imageElement);
                    }

                    return null;
                }
            }, null);

            if (imageData) {
                return this.prepareImageData(context, imageData, instance);
            }

            return null;
        };

        /**
        * Combine the window level flags to find the correct window level
        */
        Series.prototype.combineWindowLevelFlags = function () {
            var _this = this;
            return Subjects.bind(this.useOriginalWindowLevel, function (useOriginalWindowLevel) {
                if (useOriginalWindowLevel) {
                    return Subjects.map(_this.selectedInstanceIndex, function (index) {
                        var instance = _this.series.instances[index];

                        return _this.getDefaultWindowLevel(instance);
                    });
                } else {
                    return _this.windowLevel;
                }
            }, function (_, windowLevel) {
                return windowLevel;
            });
        };

        /**
        * Render the image using WebGL
        */
        Series.prototype.renderUsingWebGL = function (gl, program, image, instance, transform) {
            var startRender = +new Date();

            var toImageCoords = Vectors.scaleNonUniformM(instance.instanceAttributes.columns, instance.instanceAttributes.rows);
            var fromScreenCoords = Vectors.scaleNonUniformM(1 / gl.canvas.width, 1 / gl.canvas.height);
            var toViewport = Vectors.composeM(Vectors.translate(-1.0, 1.0), Vectors.scaleNonUniformM(2.0, -2.0));

            var imageToScreen = Vectors.composeM(toViewport, Vectors.composeM(fromScreenCoords, Vectors.composeM(transform, toImageCoords)));

            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

            // Only send the texture to the video card if it is not already there
            if (!this.glTextureIsLoaded) {
                gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image.imageElement);
                this.glTextureIsLoaded = true;
            }

            if (WindowLevelPresets.instanceNeedsWindowLevel(instance)) {
                instance.instanceAttributes.windowLevelDetection = true;
                var wl = WindowLevelPresets.findWindowLevels(instance, image.imageData);
                instance.instanceAttributes.windowCenter = [wl.center];
                instance.instanceAttributes.windowWidth = [wl.width];
                this.windowLevel.write(wl);
            }

            var windowLevel = this.combineWindowLevelFlags().read();
            var inverted = this.invertActive.read();
            var enhance = this.enhance.read();
            var bitDepth = WindowLevelPresets.shouldUse16BitWindowLevel(instance) ? 16 : 8;
            var rescaleIntercept = (bitDepth > 8) ? ((instance.instanceAttributes.rescaleIntercept || 0) / 65536) : 0;
            var signed = instance.instanceAttributes.signed || false;
            var slope = instance.instanceAttributes.rescaleSlope || 1;

            if (instance.instanceAttributes.presentationLUTShape === "INVERSE" || instance.instanceAttributes.photometricInterpretation === "MONOCHROME1") {
                windowLevel = {
                    center: (signed ? 0 : (1 << instance.instanceAttributes.bitsStored)) * slope - windowLevel.center,
                    width: windowLevel.width
                };
            }

            var centerLocation = gl.getUniformLocation(program, "uCenter");
            gl.uniform1f(centerLocation, windowLevel.center / 65536);

            var widthLocation = gl.getUniformLocation(program, "uWidth");
            gl.uniform1f(widthLocation, windowLevel.width / 65536);

            var signedLocation = gl.getUniformLocation(program, "uSigned");
            gl.uniform1i(signedLocation, signed ? 1 : 0);

            var sigmoidLocation = gl.getUniformLocation(program, "uSigmoid");
            gl.uniform1i(sigmoidLocation, instance.instanceAttributes.voiLutFunction === "SIGMOID" ? 1 : 0);

            var invertedLocation = gl.getUniformLocation(program, "uInverted");
            gl.uniform1i(invertedLocation, inverted ? 1 : 0);

            var enhanceLocation = gl.getUniformLocation(program, "uEnhance");
            gl.uniform1i(enhanceLocation, enhance ? 1 : 0);

            var slopeLocation = gl.getUniformLocation(program, "uSlope");
            gl.uniform1f(slopeLocation, slope);

            var interceptLocation = gl.getUniformLocation(program, "uIntercept");
            gl.uniform1f(interceptLocation, rescaleIntercept);

            var depthLocation = gl.getUniformLocation(program, "uDepth");
            gl.uniform1i(depthLocation, bitDepth);

            var transformLocation = gl.getUniformLocation(program, "uTrans");
            gl.uniformMatrix3fv(transformLocation, false, [
                imageToScreen.entries[0], imageToScreen.entries[1], 0,
                imageToScreen.entries[3], imageToScreen.entries[4], 0,
                imageToScreen.entries[2], imageToScreen.entries[5], 1]);

            var colorTableLocation = gl.getUniformLocation(program, "uColorTable");

            if (this.colorTable.read()) {
                var paletteLoc = gl.getUniformLocation(program, "uPalette");
                gl.uniform1i(paletteLoc, 1);

                if (!this.glCreatedPaletteTexture) {
                    this.glCreatedPaletteTexture = true;
                    WebGL.createPaletteTexture(gl);
                }

                gl.uniform1i(colorTableLocation, 1);

                if (!this.glTexturePaletteIsLoaded) {
                    this.glTexturePaletteIsLoaded = true;
                    var palette = new Uint8Array(ColorTablePresets.createPalette(this.colorTable.read()));
                    gl.activeTexture(gl.TEXTURE1);
                    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);
                    gl.activeTexture(gl.TEXTURE0);
                }
            } else {
                gl.uniform1i(colorTableLocation, 0);
            }

            gl.drawArrays(gl.TRIANGLES, 0, 6);

            if (this.firstImageRenderTime == null) {
                clearInterval(this.initialRenderTimer);

                var accelerated = this.series && this.series.studyAttributes.accelerated;

                if (image && image.imageElement.complete && typeof image.imageElement.naturalWidth !== "undefined" && image.imageElement.naturalWidth !== 0) {
                    var elapsed = (+new Date()) - startRender;

                    var additionalMetrics = {
                        studyUID: this.series.studyAttributes.queryObject.studyUid,
                        accelerated: accelerated,
                        imageSize: image.imageElement.naturalWidth * image.imageElement.naturalHeight,
                        imageWidth: image.imageElement.naturalWidth,
                        imageHeight: image.imageElement.naturalHeight,
                        renderTime: elapsed
                    };

                    this.firstImageRenderTime = elapsed;

                    Main.Main.recordMetric('showFirstImage', additionalMetrics);
                }
            }
        };

        /**
        * Check if the current image is loaded
        */
        Series.prototype.currentImageIsLoaded = function () {
            var _this = this;
            var index = this.selectedInstanceIndex.read();
            var instance = this.series.instances[index];
            var instanceKey = this.getInstanceKey(index);

            return this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(instanceKey, null);

                    if (imageElement) {
                        return imageElement.imageElement.complete && instance.instanceAttributes.attributesLoaded;
                    }

                    return false;
                },
                visitCanvasRenderer: function (renderer) {
                    var imageType = _this.getImageType(instance);
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return imageElement.imageElement.complete && instance.instanceAttributes.attributesLoaded && typeof imageElement.imageElement.naturalWidth !== "undefined" && imageElement.imageElement.naturalWidth !== 0;
                    }
                },
                visitWebGLRenderer: function (renderer) {
                    var imageType = _this.getImageType(instance);
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return imageElement.imageElement.complete && instance.instanceAttributes.attributesLoaded && typeof imageElement.imageElement.naturalWidth !== "undefined" && imageElement.imageElement.naturalWidth !== 0;
                    }

                    return false;
                }
            }, false);
        };

        /**
        * Return the key used to find an instance's image data in the store
        */
        Series.prototype.getInstanceKey = function (index) {
            index = (index !== undefined) ? index : this.selectedInstanceIndex.read();
            var key = '';

            if (this.series && this.series.instances) {
                if (this.series.instances.length > index) {
                    var instance = this.series.instances[index];
                    key = instance.id.value + ':' + instance.frameNumber.value;
                }
            }

            return key;
        };

        /**
        * Check if the current image is loaded
        */
        Series.prototype.getImageState = function (index) {
            var _this = this;
            var index = index || this.selectedInstanceIndex.read();
            var instance = this.series.instances[index];
            var instanceKey = this.getInstanceKey(index);

            if (this.renderer == null) {
                return 0 /* None */;
            }

            return this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(instanceKey, null);

                    if (imageElement && imageElement.imageElement && imageElement.imageElement.complete) {
                        return 3 /* Success */;
                    }

                    return 0 /* None */;
                },
                visitCanvasRenderer: function (renderer) {
                    var imageType = _this.getImageType(instance);
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return imageElement.imageDownloadState;
                    }

                    return 0 /* None */;
                },
                visitWebGLRenderer: function (renderer) {
                    var imageType = _this.getImageType(instance);
                    var imageElement = renderer.imageElements.get(instanceKey, imageType);

                    if (imageElement) {
                        return imageElement.imageDownloadState;
                    }

                    return 0 /* None */;
                }
            }, 0 /* None */);
        };

        /**
        * Determine an appropriate image type to render
        */
        Series.prototype.getImageType = function (instance) {
            if (LocalViewer.isFileSystemViewer()) {
                return 1 /* FullResolution */;
            } else if (LocalViewer.isNodeWebkitViewer() || LocalViewer.isPersonalAccelerator()) {
                return 2 /* Diagnostic */;
            } else if (this.useDiagnosticQualityAlways) {
                return 2 /* Diagnostic */;
            } else if (this.forceLowResolution.read().used() && (!instance.instanceAttributes.mostRecentThumbnailLoadFailed || instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                return 0 /* Thumbnail */;
            } else {
                var instanceIndex = this.selectedInstanceIndex.read();
                var instanceKey = this.getInstanceKey(instanceIndex);
                var useDiagnostic = this.accountSettings.viewer_diagnostic_quality == 1 || WindowLevelPresets.shouldUse16BitWindowLevel(instance);

                var hiResImageType = useDiagnostic ? 2 /* Diagnostic */ : 1 /* FullResolution */;

                if (WindowLevelPresets.isFullResolutionHD(instance)) {
                    hiResImageType = 3 /* FullResolutionHD */;
                }

                return this.renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceKey, hiResImageType);

                        if (imageElement && imageElement.imageElement.complete && typeof imageElement.imageElement.naturalWidth !== "undefined" && imageElement.imageElement.naturalWidth !== 0) {
                            return hiResImageType;
                        } else {
                            return 0 /* Thumbnail */;
                        }
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceKey, hiResImageType);

                        if (imageElement && imageElement.imageElement.complete && typeof imageElement.imageElement.naturalWidth !== "undefined" && imageElement.imageElement.naturalWidth !== 0) {
                            return hiResImageType;
                        } else {
                            return 0 /* Thumbnail */;
                        }
                    }
                }, 1 /* FullResolution */);
            }
        };

        Series.prototype.hasSelectedMeasurement = function () {
            var instanceIndex = this.selectedInstanceIndex.read();
            var instance = this.series.instances[instanceIndex];
            var measurements = instance.instanceAttributes.measurements;
            var index = measurements.indexWhere(function (measurement) {
                return measurement.selected;
            });
            return (index >= 0);
        };

        /**
        * Delete the selected measurement
        */
        Series.prototype.deleteSelectedMeasurement = function () {
            var _this = this;
            var instanceIndex = this.selectedInstanceIndex.read();
            var instance = this.series.instances[instanceIndex];
            var measurements = instance.instanceAttributes.measurements;
            var index = measurements.indexWhere(function (measurement) {
                return measurement.selected;
            });

            if (index >= 0) {
                var measurement = measurements[index];
                if (measurement.editable) {
                    if (measurement == instance.instanceAttributes.calibration) {
                        instance.instanceAttributes.calibration = null;
                    }

                    if (measurement.volume) {
                        _.each(measurement.volume.slices, function (s) {
                            s.measurement.volume = null;
                        });
                    }

                    if (measurement.propagation) {
                        for (var ctr = 0; ctr < this.series.instances.length; ctr += 1) {
                            var currentMeasurements = this.series.instances[ctr].instanceAttributes.measurements;
                            var currentIndex = _.indexOf(currentMeasurements, measurement);
                            if (currentIndex >= 0) {
                                currentMeasurements.splice(currentIndex, 1);
                            }
                        }
                    } else {
                        measurements.splice(index, 1);
                    }

                    this.selectedMeasurement.write(null);

                    if (measurement.id) {
                        Services.deleteImageAnnotation(this.sessionId, this.series.studyAttributes.queryObject, measurement.id).subscribe({
                            done: function () {
                            },
                            next: function (_) {
                            },
                            fail: function (err) {
                                _this.recordError("Unable to delete annotation: " + err);
                            }
                        });
                    }

                    instance.instanceAttributes.calibrationUser = !!_.find(measurements, function (m) {
                        return !!m.pixelSpacingUser || !!m.sliceSpacingUser;
                    });

                    this.renderAll();
                }
            }
        };

        /**
        * Deselect all measurements
        */
        Series.prototype.clearSelection = function () {
            this.selectedEndpoint = null;
            var instanceIndex = this.selectedInstanceIndex.read();
            var measurements = this.series.instances[instanceIndex].instanceAttributes.measurements;
            _.each(measurements, function (measurement) {
                measurement.selected = false;
                if (measurement.volume) {
                    measurement.volume.selected = false;
                }
            });
            this.selectedMeasurement.write(null);
        };

        /**
        * Save the new cine speed back to the settings object
        */
        Series.prototype.saveCineSpeedToSettings = function () {
            var _this = this;
            if (this.series) {
                Subjects.modify(this.settings, function (settings) {
                    return Cine.saveCineSpeedToSettings(settings, _this.series.seriesAttributes.modality, _this.cineSpeed.read());
                });
            }
        };

        /**
        * Cancel all existing subscriptions
        */
        Series.prototype.unload = function () {
            _.each(this.subscriptions, function (s) {
                return s.cancel();
            });

            if (this.handle) {
                this.handle.clear();
                this.handle = null;
            }
        };

        /**
        * Get the default window level for this series
        */
        Series.prototype.getDefaultWindowLevel = function (instance) {
            if (!instance && this.series) {
                instance = this.series.instances[0];
            }

            if (instance) {
                if (this.subtractionActive.read()) {
                    return this.subtraction.windowLevel || this.subtractionWindowLevel;
                } else if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                    var windowWidth = instance.instanceAttributes.windowWidth;
                    var windowCenter = instance.instanceAttributes.windowCenter;

                    if (windowWidth && windowWidth.length && windowCenter && windowCenter.length) {
                        return { center: windowCenter[0], width: windowWidth[0] };
                    }
                }
            }

            return { center: WindowLevelPresets.defaultCenter, width: WindowLevelPresets.defaultWidth };
        };

        /**
        * Get the current display options
        */
        Series.prototype.getDisplayOptions = function () {
            var transform = this.transform.read();
            var windowLevel = this.windowLevel.read();

            return {
                flipped: transform.flipped,
                rotation: transform.rotation,
                invert: this.invertActive.read(),
                windowLevel: this.useOriginalWindowLevel.read() ? Maybe.Nothing() : Maybe.Just({
                    windowCenter: windowLevel.center,
                    windowWidth: windowLevel.width
                }),
                showMeasurements: this.measurementsVisible.read(),
                showTextAnnotations: this.infoVisible.read()
            };
        };

        Series.prototype.getExtendedDisplayOptions = function () {
            var transform = this.transform.read();
            var displayOptions = this.getDisplayOptions();
            displayOptions.scale = transform.scale;
            displayOptions.offsetX = transform.offsetX;
            displayOptions.offsetY = transform.offsetY;
            displayOptions.hideThumbnails = !this.application.thumbnailsVisible.read();
            return displayOptions;
        };

        /**
        * Apply display options from a hanging protocol
        */
        Series.prototype.applyDisplayOptions = function (displayOptions) {
            if (displayOptions) {
                var extendedOptions = displayOptions;

                var transform = this.transform.read();
                transform.flipped = displayOptions.flipped;
                transform.rotation = displayOptions.rotation;

                if (extendedOptions.scale) {
                    transform.scale = extendedOptions.scale;
                }

                if (extendedOptions.offsetX) {
                    transform.offsetX = extendedOptions.offsetX;
                }

                if (extendedOptions.offsetY) {
                    transform.offsetY = extendedOptions.offsetY;
                }

                this.transform.write(transform);

                this.transformHP = new Subjects.ObservableValue({ offsetX: 0, offsetY: 0, scale: 1, flipped: false, rotation: 0 });
                this.transformHP.write(transform);

                if (displayOptions.invert) {
                    this.invertActive.write(true);
                }

                if (!displayOptions.showTextAnnotations) {
                    this.infoVisible.write(false);
                }

                if (!displayOptions.showMeasurements) {
                    this.measurementsVisible.write(false);
                }

                if (extendedOptions.hideThumbnails) {
                    this.application.thumbnailsVisible.write(false);
                }

                if (displayOptions.windowLevel) {
                    var defaultWindowLevel = this.getDefaultWindowLevel();

                    var windowLevel = Maybe.maybe(displayOptions.windowLevel, defaultWindowLevel, function (wl) {
                        return {
                            center: wl.windowCenter,
                            width: wl.windowWidth
                        };
                    });

                    if (Maybe.hasValue(displayOptions.windowLevel)) {
                        this.useOriginalWindowLevel.write(false);
                    }

                    this.windowLevel.write(windowLevel);
                }
            } else {
                var defaultWindowLevel = this.getDefaultWindowLevel();

                this.windowLevel.write(defaultWindowLevel);
            }

            if (this.series) {
                var modality = this.series.seriesAttributes.modality;

                var settings = this.settings.read();

                if (settings.modalities) {
                    var modalitySettings = _.find(settings.modalities, function (m) {
                        return m.modality === modality;
                    });

                    if (modalitySettings && modalitySettings.displayMiddleSlice === true) {
                        this.selectedInstanceIndex.write(Math.floor(this.series.instances.length / 2));
                    }
                }
            }
        };

        /**
        * Reload all frames
        */
        Series.prototype.reloadAllFrames = function () {
            var preloadRange = this.accountSettings.image_preload_window || this.settings.read().imagePreloadWindow || 10;

            this.preloadImageRange(this.imagePreloadQueue, preloadRange, 0, preloadRange);
            this.preloadVisibleHiResImages(this.imagePreloadQueue, 99 /* FullResolutionImage */);

            this.renderAll();
        };

        /**
        * Load all image attributes with low priority
        * Necessary for series linking and text annotations to show up
        */
        Series.prototype.loadImageAttributes = function (queue, priority) {
            var _this = this;
            if (this.series) {
                var onLoad = Multiframe.shouldSplitInstances(this.series.seriesAttributes.modality) ? function () {
                    _this.application.thumbnails.render();
                } : function () {
                };

                _.each(this.series.instances, function (instance, index, list) {
                    var key = {
                        type: 0 /* ImageAttributes */,
                        seriesUid: _this.series.seriesAttributes.seriesUid,
                        instanceUid: instance.id,
                        frameNumber: instance.frameNumber
                    };

                    var loadAttr = Observable._finally(Study.loadImageAttributes(_this.sessionId, instance, _this), onLoad);
                    queue.enqueue(key, loadAttr, priority, new PreloadQueueKeyIsKey(), new PriorityIsPriority());

                    var isMultiframe = _.any(_this.series.instances, function (instance) {
                        return instance.instanceAttributes.frameCount > 1;
                    });
                    var settings = _this.settings.read();

                    if (!isMultiframe && (settings && settings.ultrasoundMeasurements && Dicom.Ultrasound.isUltrasoundSeries(_this.series.seriesAttributes))) {
                        var keyMetadata = {
                            type: 2 /* ImageMetadata */,
                            seriesUid: _this.series.seriesAttributes.seriesUid,
                            instanceUid: instance.id,
                            frameNumber: instance.frameNumber
                        };

                        queue.enqueue(keyMetadata, _this.loadImageJSON(instance), priority, new PreloadQueueKeyIsKey(), new PriorityIsPriority());
                    }
                });
            }
        };

        /**
        * Load all image thumbnail data with low priority
        */
        Series.prototype.loadAllImageData = function (queue, priority) {
            if (this.series) {
                this.preloadImageRange(queue, this.series.instances.length, priority, priority);
            }
        };

        /**
        * Preload image attributes and image data for instances or frames in the specified range.
        *
        * @param {queue} The queue to use for preloading
        * @param {window} The number of instances to load either side of the current instance
        * @param {minPriority} The priority to use for the most distant instance
        * @param {maxPriority} The priority to use for the current instance
        */
        Series.prototype.preloadImageRange = function (queue, window, minPriority, maxPriority) {
            var _this = this;
            var accelerated = this.series && this.series.studyAttributes.accelerated;

            var imageType = (accelerated || this.useDiagnosticQualityAlways) ? 2 /* Diagnostic */ : 0 /* Thumbnail */;

            if (this.series && this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    return false;
                }
            }, true)) {
                var selectedInstanceIndex = this.selectedInstanceIndex.read();

                // Make sure each image is only loaded once.
                // Sort indices by their distance from the loaded slice, and keep only unique indices.
                var indices = _.uniq(_.sortBy(_.map(_.range(-window, window), function (offset) {
                    var index = (offset + selectedInstanceIndex) % _this.series.instances.length;

                    if (index < 0) {
                        index += _this.series.instances.length;
                    }

                    return [offset, index];
                }), function (offset) {
                    return Math.abs(offset[0]);
                }), false, function (offset) {
                    return offset[1];
                });

                _.each(indices, function (offset) {
                    var instance = _this.series.instances[offset[1]];
                    var priority = Math.round(maxPriority - (maxPriority - minPriority) * Math.abs(offset[0]) / window);
                    var key = {
                        type: 1 /* ImageData */,
                        seriesUid: _this.series.seriesAttributes.seriesUid,
                        instanceUid: instance.id,
                        frameNumber: instance.frameNumber,
                        imageType: 0 /* Thumbnail */
                    };
                    queue.enqueue(key, _this.preload(offset[1], imageType), priority, new PreloadQueueKeyIsKey(), new PriorityIsPriority());
                });
            }
        };

        /**
        * For the local viewer
        */
        Series.prototype.preloadAllDiagnosticImages = function (queue, priority) {
            var _this = this;
            if (this.series && this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    return false;
                }
            }, true)) {
                _.each(this.series.instances, function (instance, index) {
                    var key = {
                        type: 1 /* ImageData */,
                        seriesUid: _this.series.seriesAttributes.seriesUid,
                        instanceUid: instance.id,
                        frameNumber: instance.frameNumber,
                        imageType: 2 /* Diagnostic */
                    };

                    // order series loading, first to last
                    var priorityMod = 0;
                    if (_this.application.supportsAcceleratedPreload()) {
                        var study = _.find(_this.application.studies, function (study) {
                            return study.studyAttributes.queryObject.toString() === _this.series.studyAttributes.queryObject.toString();
                        });
                        if (study) {
                            var seriesIndex = _.indexOf(study.series, _this.series);
                            if (seriesIndex > 0) {
                                priorityMod = seriesIndex;
                            }
                        }
                    }

                    queue.enqueue(key, _this.preload(index, 2 /* Diagnostic */), priority - priorityMod, new PreloadQueueKeyIsKey(), new PriorityIsPriority());
                });
            }
        };

        /**
        * Load the high resolution image for the visible frames of the visible instances
        */
        Series.prototype.preloadVisibleHiResImages = function (queue, priority) {
            if (this.series && !LocalViewer.isLocalViewer() && this.renderer && this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    return false;
                }
            }, true)) {
                var selectedInstanceIndex = this.selectedInstanceIndex.read();
                var instance = this.series.instances[selectedInstanceIndex];

                var useDiagnostic = this.useDiagnosticQualityAlways || WindowLevelPresets.shouldUse16BitWindowLevel(instance);

                var hiResImageType = useDiagnostic ? 2 /* Diagnostic */ : 1 /* FullResolution */;

                if (WindowLevelPresets.isFullResolutionHD(instance)) {
                    hiResImageType = 3 /* FullResolutionHD */;
                }

                var key = {
                    type: 1 /* ImageData */,
                    seriesUid: this.series.seriesAttributes.seriesUid,
                    instanceUid: instance.id,
                    frameNumber: instance.frameNumber,
                    imageType: hiResImageType
                };

                queue.enqueue(key, this.preload(selectedInstanceIndex, hiResImageType), priority, new PreloadQueueKeyIsKey(), new PriorityIsPriority());
            }
        };

        /**
        * Preload the image attributes and image data for the specified image type
        */
        Series.prototype.preload = function (instanceIndex, imageType) {
            var _this = this;
            if (this.series) {
                var instance = this.series.instances[instanceIndex];

                var loadMetadata = Study.loadImageAttributes(this.sessionId, instance);

                return Observable.bind2(loadMetadata, function (_) {
                    var loadImageData = Observable.on(Observable.ifThenElse(function () {
                        if (imageType == 0 /* Thumbnail */) {
                            return !instance.instanceAttributes.mostRecentThumbnailLoadFailed;
                        } else {
                            return !instance.instanceAttributes.mostRecentHiResLoadFailed;
                        }
                    }, _this.preloadImage(instanceIndex, imageType), Observable.ret(false)), function (success) {
                        if (success) {
                            if (_this.useDiagnosticQualityAlways) {
                                if (imageType !== 0 /* Thumbnail */) {
                                    Subjects.modify(instance.seriesAttributes.imageDataLoaded, function (n) {
                                        return n + 1;
                                    });
                                }
                            } else {
                                if (imageType === 0 /* Thumbnail */) {
                                    Subjects.modify(instance.seriesAttributes.imageDataLoaded, function (n) {
                                        return n + 1;
                                    });
                                }
                            }

                            if (imageType === 0 /* Thumbnail */) {
                                instance.instanceAttributes.mostRecentThumbnailLoadFailed = false;
                            } else {
                                instance.instanceAttributes.mostRecentHiResLoadFailed = false;
                            }

                            // Don't render if this is a preload
                            if (instanceIndex == _this.selectedInstanceIndex.read()) {
                                _this.renderAll();
                            }
                        }
                    }, function (err) {
                        if (imageType === 0 /* Thumbnail */) {
                            instance.instanceAttributes.mostRecentThumbnailLoadFailed = true;
                        } else {
                            instance.instanceAttributes.mostRecentHiResLoadFailed = true;
                        }

                        if (!err) {
                            err = "Image GET failed (no error): " + instance.id.value;
                        }

                        _this.recordError(err);
                        _this.renderAll();
                    }, function () {
                    });

                    var loadOverlay = Observable.forget(_this.loadOverlays(instanceIndex));

                    if (!Multiframe.isMultiframe(_this.series) && _this.settings.read().ultrasoundMeasurements && Dicom.Ultrasound.isUltrasoundSeries(_this.series.seriesAttributes) && !instance.instanceAttributes.json) {
                        _this.loadImageJSON(instance).subscribe({
                            done: function () {
                            },
                            next: function (_) {
                            },
                            fail: function (err) {
                                _this.recordError("Unable to load image metadata: " + err);
                            } });
                    }

                    return Observable.zip(loadImageData, loadOverlay, function (_1, _2) {
                        return _1;
                    });
                }, function (_1, _2) {
                    return _2;
                });
            }

            return Observable.ret({});
        };

        Series.prototype.loadImageJSON = function (instance) {
            return Observable.invoke(Study.loadImageJSON(this.sessionId, instance, this.series, false), function (json) {
                instance.instanceAttributes.json = json;
                _.each(instance.instanceAttributes.measurements, function (measurement) {
                    var result = Dicom.Ultrasound.findSpacing(measurement, instance);
                    if (result && result.valid) {
                        measurement.ultrasoundPixelSpacing = [result.spacingX, result.spacingY];
                    }
                });
            });
        };

        /**
        * Load overlay data
        */
        Series.prototype.loadOverlays = function (instanceIndex) {
            var futures = [];
            var instance = this.series.instances[instanceIndex];

            if (instance.instanceAttributes.attributesLoaded) {
                for (var ctr = 0; ctr < instance.instanceAttributes.containsOverlayDataPlane.length; ctr += 1) {
                    if (instance.instanceAttributes.containsOverlayDataPlane[ctr]) {
                        var loadOverlay = this.loadOverlayPlane(instanceIndex, ctr);

                        if (loadOverlay) {
                            futures.push(loadOverlay);
                        }
                    }
                }
            }

            return Observable.sequenceA(futures);
        };

        Series.prototype.loadOverlayPlane = function (instanceIndex, overlayPlane) {
            var _this = this;
            var overlayPlaneKey = (this.getInstanceKey(instanceIndex) + '-' + overlayPlane);
            var roiOverlay = this.settings.read().supportROIOverlays;

            return Observable.ifThenElse(function () {
                return _this.overlayData.get(overlayPlaneKey, null) === null;
            }, Observable.forget(Observable.invoke(Observable.catchError(this.preloadOverlayImage(instanceIndex, overlayPlane), function (err) {
                _this.recordError("Cannot load overlay: " + err);
                return null;
            }), function (data) {
                if (data) {
                    if (roiOverlay) {
                        var rgb = Annotations.fromHex(Annotations.OVERLY_COLORS[Math.min(1, overlayPlane)]);

                        for (var i = 0; i < data.data.length; i += 4) {
                            if (data.data[i]) {
                                data.data[i] = rgb.r;
                                data.data[i + 1] = rgb.g;
                                data.data[i + 2] = rgb.b;
                                data.data[i + 3] = overlayPlane ? 255 : 128;
                            } else {
                                data.data[i + 3] = data.data[i];
                            }
                        }
                    } else {
                        for (var i = 0; i < data.data.length; i += 4) {
                            data.data[i + 3] = data.data[i];
                        }
                    }
                }

                _this.overlayData.put(overlayPlaneKey, data);

                if (instanceIndex == _this.selectedInstanceIndex.read()) {
                    if (_this.firstImageRenderTime != null) {
                        _this.renderAll();
                    }
                }
            })), Observable.ret({}));
        };

        Series.prototype.preloadOverlayImage = function (instanceIndex, overlayPlane) {
            var _this = this;
            var instance = this.series.instances[instanceIndex];
            var overlayPlaneKey = (this.getInstanceKey(instanceIndex) + '-' + overlayPlane);

            return this.renderer.visit({
                visitSimpleRenderer: function (_) {
                    var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, _this.series.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(parseInt((0x6000 + (overlayPlane * 2)).toString(16) + "3000", 16)), 2 /* Diagnostic */, 8, _this.accountSettings.cache == 1, true);

                    return Images.loadImage(uri);
                },
                visitCanvasRenderer: function (renderer) {
                    var imageData = renderer.imageElements.get(overlayPlaneKey, 2 /* Diagnostic */);

                    if (!imageData) {
                        var image = new Image();
                        var state = 1 /* Queued */;

                        var cachedImage = {
                            imageElement: image,
                            imageData: null,
                            imageDownloadState: state
                        };

                        renderer.imageElements.put(overlayPlaneKey, 2 /* Diagnostic */, cachedImage);

                        var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, _this.series.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(parseInt((0x6000 + (overlayPlane * 2)).toString(16) + "3000", 16)), 2 /* Diagnostic */, 8, _this.accountSettings.cache == 1, true);

                        return Observable.map(Images.loadImageOnly(image, uri, cachedImage), function (_) {
                            cachedImage.imageData = Images.getImageData(image);
                            return cachedImage.imageData;
                        });
                    }

                    return Observable.ret(imageData.imageData);
                },
                visitWebGLRenderer: function (renderer) {
                    var imageData = renderer.imageElements.get(overlayPlaneKey, 2 /* Diagnostic */);

                    if (!imageData) {
                        var image = new Image();
                        var state = 1 /* Queued */;

                        var cachedImage = {
                            imageElement: image,
                            imageData: null,
                            imageDownloadState: state
                        };

                        renderer.imageElements.put(overlayPlaneKey, 2 /* Diagnostic */, cachedImage);

                        var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, _this.series.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(parseInt((0x6000 + (overlayPlane * 2)).toString(16) + "3000", 16)), 2 /* Diagnostic */, 8, _this.accountSettings.cache == 1, true);

                        return Observable.map(Images.loadImageOnly(image, uri, cachedImage), function (_) {
                            cachedImage.imageData = Images.getImageData(image);
                            return cachedImage.imageData;
                        });
                    }

                    return Observable.ret(imageData.imageData);
                }
            }, Observable.ret(null));
        };

        /**
        * Load image data with the specified image index
        */
        Series.prototype.preloadImage = function (instanceIndex, imageType) {
            var _this = this;
            var $instance = this.el.find('.instance');
            var instance = this.series.instances[instanceIndex];
            var instanceKey = this.getInstanceKey(instanceIndex);

            // Fail immediately if the image attributes are not available
            if (instance.instanceAttributes.attributesDownloadState != 3 /* Success */) {
                return Observable.ret(false);
            }

            return this.renderer.visit({
                visitSimpleRenderer: function (renderer) {
                    var imageData = renderer.imageElements.get(instanceKey, null);

                    if (!imageData) {
                        var image = new Image();

                        renderer.imageElements.put(instanceKey, {
                            imageElement: image
                        });

                        var $image = $(image);

                        $image.addClass('instance-image');
                        $image.appendTo($instance);

                        $image.hide();

                        var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, instance.frameNumber, 1 /* FullResolution */, 8, _this.accountSettings.cache == 1, true);

                        return Observable.map(Images.loadImageOnly(image, uri), function (res) {
                            _this.handleImageLoadResponse(res);
                            return true;
                        });
                    }

                    return Observable.ret(false);
                },
                visitCanvasRenderer: function (renderer) {
                    var instance = _this.series.instances[instanceIndex];

                    var imageData = renderer.imageElements.get(instanceKey, imageType);

                    if (!imageData) {
                        var image = new Image();
                        var state = 1 /* Queued */;

                        var cachedImage = {
                            imageElement: image,
                            imageData: null,
                            imageDownloadState: state
                        };

                        renderer.imageElements.put(instanceKey, imageType, cachedImage);

                        var use16Bit = WindowLevelPresets.shouldUse16BitWindowLevel(instance);

                        var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, instance.frameNumber, imageType, use16Bit ? 16 : 8, _this.accountSettings.cache == 1, true, (Browser.needsResize(instance) ? Browser.resize(instance) : 0));

                        var accelerated = (_this.series && _this.series.studyAttributes && _this.series.studyAttributes.accelerated) ? true : false;

                        return Observable.map(Images.loadImageOnly(image, uri, cachedImage, accelerated), function (res) {
                            _this.handleImageLoadResponse(res);
                            return true;
                        });
                    }

                    return Observable.ret(false);
                },
                visitWebGLRenderer: function (renderer) {
                    var imageData = renderer.imageElements.get(instanceKey, imageType);

                    if (!imageData) {
                        var image = new Image();
                        var state = 1 /* Queued */;

                        var cachedImage = {
                            imageElement: image,
                            imageData: null,
                            imageDownloadState: state
                        };

                        renderer.imageElements.put(instanceKey, imageType, cachedImage);

                        var use16Bit = WindowLevelPresets.shouldUse16BitWindowLevel(instance);

                        var uri = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, instance.frameNumber, imageType, use16Bit ? 16 : 8, _this.accountSettings.cache == 1, true);

                        var accelerated = (_this.series && _this.series.studyAttributes && _this.series.studyAttributes.accelerated) ? true : false;

                        return Observable.map(Images.loadImageOnly(image, uri, cachedImage, accelerated), function (res) {
                            _this.handleImageLoadResponse(res);
                            return true;
                        });
                    }

                    return Observable.ret(false);
                }
            }, Observable.ret(false));
        };

        Series.prototype.checkLoadedStatus = function () {
            var preloading = this.application.supportsAcceleratedPreload();
            var seriesLoaded = this.application.seriesLoaded(this.series);

            var loadedStatusStyle = preloading ? "" : "hide-icon";
            if (seriesLoaded == 3 /* Diagnostic */) {
                loadedStatusStyle = "loaded-hd-icon";
            } else if (seriesLoaded == 2 /* Thumbnail */) {
                loadedStatusStyle = "loaded-sd-icon";
            }

            if (preloading) {
                $("#series-" + this.series.uuid).find(".accelerated-icon").removeClass("hide-icon loaded-hd-icon loaded-sd-icon").addClass(loadedStatusStyle);
            } else {
                $("#series-" + this.series.uuid).find(".load-icon").removeClass("hide-icon loaded-hd-icon loaded-sd-icon").addClass(loadedStatusStyle);
            }
        };

        Series.prototype.handleImageLoadResponse = function (response) {
            if (response.acceleratorCached != null && response.acceleratorCached != '' && response.acceleratorCached != 'HIT') {
                this.acceleratorCacheMissCount = this.acceleratorCacheMissCount + 1;

                if (this.acceleratorCacheMissCount == 5 && !this.acceleratorCacheMessageDidShow) {
                    // Indicate problem on thumbnails area
                    $('.fa-flash').css('color', '#F33').attr('title', this.acceleratorCacheMissMessage);
                }
            }

            this.checkLoadedStatus();
        };

        Series.prototype.handleMouseDownEvent = function (e) {
            var tool = (e.button === 0 || typeof e.button == 'undefined' || e.button == null) ? this.selectedTool.read() : this.selectedTool2.read();
            var foundSelectedMeasurement = false;

            this.panActive = true;
            this.needsMouseUp = true;
            this.measurementsLayer.hideMeasurementInfo = this.hideActiveMeasuremntInfo;

            if (this.series) {
                this.mousePressed = true;
                this.measureVolume.write(false);

                var instance = this.currentInstance().read();

                var inverseTransform = this.getInverseImageTransformation(this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var mousePosition = Mouse.getOffset(e.pointers[0]);
                var mousePositionImage = Vectors.multiplyM(inverseTransform, mousePosition);

                this.recordEvent({
                    type: 8 /* MouseDown */,
                    mousePositionImage: mousePositionImage
                });

                // select annotation, even if measurement tool is not active
                var transform = this.getImageTransformation(this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                var instanceIndex = this.selectedInstanceIndex.read();
                var measurements = this.series.instances[instanceIndex].instanceAttributes.measurements;
                this.clearSelection();

                var selection = Measurements.select(tool, measurements, this.application.currentColor.read(), mousePositionImage, 20 / Math.sqrt(Math.abs(Vectors.determinant(transform))), Classes.MouseTools.isDropMeasurementTool(tool));

                if (selection.measurement) {
                    foundSelectedMeasurement = true;

                    if (selection.endpoint) {
                        this.selectedEndpoint = selection.endpoint;
                    }

                    selection.measurement.saveOriginalEndpoints(mousePositionImage);
                    this.selectedMeasurement.write(selection.measurement);
                    this.editingMeasurement.write(selection.measurement);
                }

                if (Classes.MouseTools.isDropMeasurementTool(tool) && !foundSelectedMeasurement) {
                    this.originalMousePosition = mousePositionImage;

                    var hideMeasurement = Classes.MouseTools.isAnnotationTool(tool, this.showTextOnDirected.read());
                    var newMeasurement = Measurements.createMeasurement(tool, this.originalMousePosition, this.terminology, hideMeasurement, false, instance, this.application.currentColor.read(), this.accountSettings.viewer_default_drop_shape_width);

                    this.addMeasurementToSelectedInstance(newMeasurement);
                    this.recordMeasurementAdded(newMeasurement, mousePositionImage);
                    this.clearSelection();
                } else if (Classes.MouseTools.isPaintTool(tool) && foundSelectedMeasurement) {
                    this.originalMousePosition = mousePositionImage;
                    this.measurementInProgress.write(selection.measurement);
                    selection.measurement.startDrawing(mousePositionImage);
                } else if (Classes.MouseTools.isMeasurementTool(tool) && !this.isPropagating()) {
                    this.originalMousePosition = mousePositionImage;
                    this.needsHideMeasurementCursor = true;

                    if (!foundSelectedMeasurement && (tool != 4 /* Select */)) {
                        if (tool != this.selectedTool.read()) {
                            this.enableMeasurementCursor(tool);
                        }

                        if (!this.measurementInProgress.read()) {
                            var hideMeasurement = Classes.MouseTools.isAnnotationTool(tool, this.showTextOnDirected.read());
                            var calibration = Classes.MouseTools.isCalibrationTool(tool);
                            var groupTool = Classes.MouseTools.isGroupMeasurementTool(tool);
                            var newMeasurement = Measurements.createMeasurement(tool, this.originalMousePosition, this.terminology, hideMeasurement, calibration, instance, this.application.currentColor.read());

                            if (newMeasurement) {
                                this.measurementInProgress.write(newMeasurement);

                                if (groupTool) {
                                    var groupMeasurement = newMeasurement;
                                    if (groupMeasurement.getCurrentStep() == 0) {
                                        this.application.showWarningBanner(this.terminology.lookup(groupMeasurement.getCurrentTermToken()));
                                    }
                                }
                            }
                        } else {
                            this.measurementInProgress.read().startDrawing(this.originalMousePosition);
                        }
                    }

                    this.renderLayers();
                } else {
                    this.forceLowResolution.write(this.forceLowResolution.read().take());
                    this.originalMousePosition = mousePosition;

                    this.zoomCenter.write(mousePosition);

                    this.originalTransform = this.transform.read();

                    var selectedInstanceIndex = this.selectedInstanceIndex.read();

                    this.originalIndex = selectedInstanceIndex;

                    // Recalculate window level if default value is stored
                    if (this.windowLevel.read() != null) {
                        if (this.windowLevel.read().center != WindowLevelPresets.defaultCenter && this.windowLevel.read().width != WindowLevelPresets.defaultWidth) {
                            this.originalWindowLevel = this.windowLevel.read();
                        } else {
                            this.originalWindowLevel = this.getDefaultWindowLevel();
                        }
                    }
                }
            }

            if (tool === 9 /* Localization */ || tool === 11 /* Probe */ || tool === 15 /* Magnify */) {
                this.el.css({ cursor: 'none' });
            } else {
                if (!this.cursorLayer.visible.read()) {
                    this.el.css({ cursor: 'crosshair' });
                }
            }

            this.selectedSeriesKey.write(this.viewKey);

            this.handleMouseMoveEvent(e);
        };

        Series.prototype.handleMouseUpEvent = function (e) {
            var _this = this;
            var tool = (e.button === 0 || typeof e.button == 'undefined' || e.button == null) ? this.selectedTool.read() : this.selectedTool2.read();

            this.panActive = false;
            this.needsMouseUp = false;
            this.editingMeasurement.write(null);

            if (this.mousePressed) {
                this.mousePressed = false;
                this.measureVolume.write(this.measureVolumeDefault);

                var instance = this.currentInstance().read();

                var newMousePosition = Mouse.getOffset(e.pointers.length ? e.pointers[0] : e.changedPointers[0]);
                var newMousePositionImage = this.mapToImage(newMousePosition, this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var measurementInProgress = this.measurementInProgress.read();
                var selectedMeasurement = this.selectedMeasurement.read();

                if (selectedMeasurement) {
                    selectedMeasurement.active = false;
                }

                if (Classes.MouseTools.isMeasurementTool(tool)) {
                    if (this.mouseMoved || Classes.MouseTools.isMultiClickMeasurementTool(tool) || Classes.MouseTools.isPaintTool(tool)) {
                        this.enableMeasurementCursor(tool);

                        if (this.selectedEndpoint) {
                            this.selectedEndpoint = null;
                        } else if (measurementInProgress) {
                            if (measurementInProgress.isNonEmpty() || Classes.MouseTools.isPaintTool(tool)) {
                                var transform = this.getImageTransformation(this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                                if (measurementInProgress.stopDrawing(newMousePositionImage, transform)) {
                                    var validMeasurement = true;
                                    if (tool == 10 /* Text */) {
                                        var text = window.prompt(this.terminology.lookup(Terminology.Terms.EnterText), "");
                                        if (text) {
                                            var measurement = measurementInProgress;
                                            measurement.text = text;
                                        } else {
                                            validMeasurement = false;
                                        }
                                    } else if (tool == 31 /* Stamp */) {
                                        var measurement = measurementInProgress;
                                        measurement.text = "\uD83D\uDD12 " + (this.application.user.name || this.application.user.email);
                                    }

                                    if (validMeasurement && this.ultrasoundMeasurements && Dicom.Ultrasound.isUltrasound(instance)) {
                                        var result = Dicom.Ultrasound.findSpacing(measurementInProgress, instance);

                                        if (result.valid) {
                                            measurementInProgress.ultrasoundPixelSpacing = [result.spacingX, result.spacingY];
                                        } else {
                                            measurementInProgress.ultrasoundPixelSpacing = null;
                                            this.ultrasoundRegionsVisible.write(true);
                                            this.renderAll();
                                            window.alert(this.terminology.lookup(Terminology.Terms.InvalidUltrasoundMeasurement));
                                        }
                                    }

                                    if (measurementInProgress.calibration) {
                                        validMeasurement = this.updateCalibration(measurementInProgress, instance);
                                    }

                                    if (validMeasurement && selectedMeasurement && Classes.MouseTools.isPaintTool(tool)) {
                                        if (measurementInProgress.isNonEmpty()) {
                                            this.editMeasurement(selectedMeasurement, instance);
                                            this.selectedMeasurement.write(measurementInProgress);
                                            this.measurementInProgress.write(measurementInProgress = null);
                                        } else {
                                            this.selectedMeasurement.write(measurementInProgress);
                                            this.deleteSelectedMeasurement();
                                            this.clearSelection();
                                            this.measurementInProgress.write(measurementInProgress = null);
                                        }
                                    } else if (validMeasurement) {
                                        var groupTool = Classes.MouseTools.isGroupMeasurementTool(tool);

                                        this.addMeasurementToSelectedInstance(measurementInProgress);
                                        this.recordMeasurementAdded(measurementInProgress, newMousePositionImage);
                                        this.clearSelection();
                                        this.selectedMeasurement.write(measurementInProgress);
                                        measurementInProgress.selected = true;

                                        if (groupTool) {
                                            var groupMeasurement = measurementInProgress;

                                            if (groupMeasurement.step()) {
                                                groupMeasurement.reset();
                                                this.application.hideWarningBanner();
                                            } else {
                                                this.application.updateWarningBanner(this.terminology.lookup(groupMeasurement.getCurrentTermToken()));
                                            }
                                        }
                                    } else {
                                        this.measurementInProgress.write(measurementInProgress = null);
                                    }
                                }
                            } else {
                                this.measurementInProgress.write(measurementInProgress = null);
                            }
                        }

                        if (!measurementInProgress && selectedMeasurement) {
                            if (selectedMeasurement.hasMoved() && selectedMeasurement.id && selectedMeasurement.editable) {
                                if (this.ultrasoundMeasurements && Dicom.Ultrasound.isUltrasound(instance)) {
                                    var result = Dicom.Ultrasound.findSpacing(selectedMeasurement, instance);

                                    if (result && result.valid) {
                                        selectedMeasurement.ultrasoundPixelSpacing = [result.spacingX, result.spacingY];
                                        this.editMeasurement(selectedMeasurement, instance);
                                    } else {
                                        selectedMeasurement.ultrasoundPixelSpacing = null;
                                        this.ultrasoundRegionsVisible.write(true);
                                        this.renderAll();
                                        window.alert(this.terminology.lookup(Terminology.Terms.InvalidUltrasoundMeasurement));
                                    }
                                } else {
                                    this.editMeasurement(selectedMeasurement, instance);
                                }
                            }
                        }

                        // End custom cursor for alternate mouse button
                        if (!Classes.MouseTools.isMeasurementTool(this.selectedTool.read()) && Classes.MouseTools.isMeasurementTool(tool)) {
                            var delayedRemoveCursor = function (e) {
                                var dist = Geometry.distanceBetween(newMousePosition, { x: e.offsetX, y: e.offsetY });

                                if (dist > 40) {
                                    $glassPane.off('mousemove', delayedRemoveCursor);
                                    _this.disableMeasurementCursor();
                                }
                            };

                            var $glassPane = this.el.find('.glasspane');
                            $glassPane.on('mousemove', delayedRemoveCursor);
                        }
                    } else if (tool == 10 /* Text */) {
                        if (measurementInProgress) {
                            var text = window.prompt(this.terminology.lookup(Terminology.Terms.EnterText), "");
                            if (text) {
                                var measurement = measurementInProgress;

                                if (measurementInProgress.isNonEmpty()) {
                                    measurement.text = text;
                                } else {
                                    var transform = this.getImageTransformation(this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                                    measurement.createAtPoint(text, transform);
                                }

                                this.addMeasurementToSelectedInstance(measurementInProgress);
                                this.recordMeasurementAdded(measurementInProgress, newMousePositionImage);
                                this.clearSelection();
                                this.selectedMeasurement.write(measurementInProgress);
                                measurementInProgress.selected = true;
                            } else {
                                this.measurementInProgress.write(measurementInProgress = null);
                            }
                        } else if (selectedMeasurement) {
                            var measurement = selectedMeasurement;
                            var text = window.prompt(this.terminology.lookup(Terminology.Terms.EnterText), measurement.text);
                            if (text) {
                                measurement.text = text;
                                this.editMeasurement(selectedMeasurement, instance);
                            } else {
                                this.measurementInProgress.write(measurementInProgress = null);
                            }
                        }
                    } else {
                        this.measurementInProgress.write(measurementInProgress = null);
                    }
                } else {
                    this.forceLowResolution.write(this.forceLowResolution.read().release());
                }

                if (this.propagatingInstance) {
                    this.finishPropagation();
                }

                this.recordMouseUpEvent(newMousePositionImage);

                this.zoomCenter.write(null);

                this.measurementsLayer.hideMeasurementInfo = false;
                this.renderAllForceUpdate();

                if (!this.cursorLayer.visible.read()) {
                    this.el.css({ cursor: '' });
                }
            }

            this.mouseMoved = false;
            this.pressActive = false;
            this.needsHideMeasurementCursor = false;
        };

        Series.prototype.isPropagating = function () {
            return this.propagatingInstance != null;
        };

        Series.prototype.startPropagation = function () {
            this.propagatingInstance = this.currentInstance().read();
            this.propagatingMeasurement = this.selectedMeasurement.read();
        };

        Series.prototype.finishPropagation = function () {
            var instance = this.currentInstance().read();
            var currentIndex = _.indexOf(this.series.instances, instance);
            var originalIndex = this.propagatingMeasurement.propagation ? this.propagatingMeasurement.propagation.originalIndex : _.indexOf(this.series.instances, this.propagatingInstance);

            var startIndex, endIndex;

            if (currentIndex < originalIndex) {
                startIndex = currentIndex;
                endIndex = this.propagatingMeasurement.propagation ? this.propagatingMeasurement.propagation.endIndex : currentIndex;
            } else {
                endIndex = currentIndex;
                startIndex = this.propagatingMeasurement.propagation ? this.propagatingMeasurement.propagation.startIndex : currentIndex;
            }

            Annotations.propagateAnnotation(this.propagatingMeasurement, this.series, originalIndex, startIndex, endIndex, true);
            this.editMeasurement(this.propagatingMeasurement, this.propagatingInstance);
            this.application.propagateMode.write(false);
            this.propagatingMeasurement = null;
            this.propagatingInstance = null;
            this.clearSelection();
        };

        Series.prototype.propapateToSeries = function () {
            var instance = this.currentInstance().read();
            var measurement = this.selectedMeasurement.read();
            var originalIndex = measurement.propagation ? measurement.propagation.originalIndex : _.indexOf(this.series.instances, instance);
            var startIndex = 0;
            var endIndex = this.series.instances.length - 1;

            Annotations.propagateAnnotation(measurement, this.series, originalIndex, startIndex, endIndex, true);
            this.editMeasurement(measurement, this.series.instances[originalIndex]);
            this.propagatingMeasurement = null;
            this.propagatingInstance = null;

            window.alert(this.terminology.lookup(Terminology.Terms.PropagateAllComplete));
        };

        Series.prototype.thresholdToArea = function (shrinkwrap, range) {
            var _this = this;
            if (typeof shrinkwrap === "undefined") { shrinkwrap = false; }
            if (typeof range === "undefined") { range = false; }
            var valMin;

            if (range) {
                valMin = window.prompt(this.terminology.lookup(Terminology.Terms.ThresholdMinPrompt));
            } else {
                valMin = window.prompt(this.terminology.lookup(Terminology.Terms.ThresholdPrompt));
            }

            var instance = this.currentInstance().read();
            var instanceIndex = this.selectedInstanceIndex.read();
            var instanceKey = this.getInstanceKey(instanceIndex);
            var selected;

            if (valMin) {
                var thresholdMin = parseFloat(valMin);
                var thresholdMax = Number.MAX_VALUE;

                if (range) {
                    var valMax = window.prompt(this.terminology.lookup(Terminology.Terms.ThresholdMaxPrompt));
                    thresholdMax = parseFloat(valMax);
                }

                var bounds;
                if (this.selectedMeasurement.read()) {
                    selected = this.selectedMeasurement.read();
                    if (selected instanceof Measurements.Rectangle) {
                        bounds = Geometry.findBoundingBox(selected.points);
                    } else {
                        selected = null;
                    }
                }

                if (!bounds) {
                    bounds = [{ x: 0, y: 0 }, { x: instance.instanceAttributes.columns - 1, y: instance.instanceAttributes.rows - 1 }];
                }

                if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                    var width = instance.instanceAttributes.columns;
                    var height = instance.instanceAttributes.rows;

                    var imageData = this.renderer.visit({
                        visitCanvasRenderer: function (renderer) {
                            var imageElement = renderer.imageElements.get(instanceKey, 2 /* Diagnostic */);

                            if (imageElement) {
                                return Images.getImageData(imageElement.imageElement);
                            }

                            return null;
                        },
                        visitWebGLRenderer: function (renderer) {
                            var imageElement = renderer.imageElements.get(instanceKey, 2 /* Diagnostic */);

                            if (imageElement) {
                                return Images.getImageData(imageElement.imageElement);
                            }

                            return null;
                        }
                    }, null);

                    if (imageData) {
                        var minX = Math.floor(Math.min(bounds[0].x, bounds[1].x));
                        var minY = Math.floor(Math.min(bounds[0].y, bounds[1].y));
                        var maxX = Math.ceil(Math.max(bounds[0].x, bounds[1].x));
                        var maxY = Math.ceil(Math.max(bounds[0].y, bounds[1].y));

                        var area;

                        var currentColor = this.application.currentColor.read();
                        var found = _.find(instance.instanceAttributes.measurements, function (m) {
                            return (m instanceof Measurements.Area) && m.color === currentColor;
                        });
                        if (found) {
                            area = found.area;
                        } else {
                            area = new Uint8Array(width * height);
                        }

                        var terminology = this.terminology;
                        var processResult = function (area, foundData) {
                            if (foundData) {
                                if (selected) {
                                    _this.deleteSelectedMeasurement();
                                    _this.clearSelection();
                                }

                                if (found) {
                                    var foundArea = found;
                                    foundArea.updateArea(area);
                                    _this.editMeasurement(foundArea, instance);
                                    foundArea.selected = true;
                                    _this.selectedMeasurement.write(foundArea);
                                } else {
                                    var length = Math.max(width, height);
                                    var cursorSize = Math.max(Measurements.Area.ELEMENT_RADIUS_MIN, Math.round(length * Measurements.Area.ELEMENT_RADIUS_RATIO));
                                    var areaMeasurement = new Measurements.Area([{ x: 0, y: 0 }, { x: width - 1, y: height - 1 }], width, height, cursorSize, currentColor, null, area);
                                    areaMeasurement.editable = true;
                                    _this.addMeasurementToSelectedInstance(areaMeasurement);
                                    areaMeasurement.selected = true;
                                    _this.selectedMeasurement.write(areaMeasurement);
                                }
                            } else {
                                window.alert(terminology.lookup(Terminology.Terms.OperationNoResult));
                            }

                            $('.overlay').removeClass('application-loading');
                            _this.renderLayers();
                        };

                        if (Worker && (!Browser.isIE() || Browser.isIE11())) {
                            $('.overlay').addClass('application-loading');

                            var worker = new Worker("./resources/workers/ThresholdWorker.js");
                            worker.onmessage = function (e) {
                                if (e.data.action === "success") {
                                    processResult(new Uint8Array(e.data.area), e.data.foundData);
                                } else {
                                    window.alert(terminology.lookup(Terminology.Terms.OperationExceededLimits));
                                    $('.overlay').removeClass('application-loading');
                                }
                            };

                            worker.postMessage({
                                action: shrinkwrap ? "shrinkwrap" : "threshold",
                                thresholdMin: thresholdMin,
                                thresholdMax: thresholdMax,
                                imageData: imageData.data.buffer,
                                width: width,
                                height: height,
                                slope: instance.instanceAttributes.rescaleSlope,
                                intercept: instance.instanceAttributes.rescaleIntercept,
                                signed: instance.instanceAttributes.signed,
                                minX: minX,
                                minY: minY,
                                maxX: maxX,
                                maxY: maxY,
                                area: area.buffer
                            }, [imageData.data.buffer, area.buffer]);
                        } else {
                            window.alert(terminology.lookup(Terminology.Terms.OperationNotSupported));
                        }
                    }
                }
            }
        };

        Series.prototype.updateCalibration = function (measurement, instance) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var validMeasurement = true;

            if (pixelSpacing && pixelSpacing[0]) {
                measurement.calibration = false;
            } else {
                var val = window.prompt(this.terminology.lookup(Terminology.Terms.LineCalibratePrompt));

                if (val && (measurement instanceof Measurements.LineMeasurement)) {
                    var length = parseFloat(val);
                    if (length > 0) {
                        measurement.calibrationValue = length;
                    } else {
                        validMeasurement = false;
                    }
                } else {
                    validMeasurement = false;
                }
            }

            return validMeasurement;
        };

        Series.prototype.pinchZoomStart = function (e) {
            this.originalTransform = this.transform.read();

            this.zoomCenter.write(e.center);
            this.originalMousePosition = e.center;
            this.forceLowResolution.write(this.forceLowResolution.read().take());

            this.selectedSeriesKey.write(this.viewKey);
        };

        Series.prototype.pinchZoomEnd = function (e) {
            this.zoomCenter.write(null);
            this.forceLowResolution.write(this.forceLowResolution.read().release());
        };

        Series.prototype.pinchZoom = function (e) {
            this.applyZoomRotate(this.originalTransform.scale * e.scale, this.originalTransform.rotation);
        };

        /**
        * Send a mouse up event to the recorder
        */
        Series.prototype.recordMouseUpEvent = function (newMousePositionImage) {
            switch (this.selectedTool.read()) {
                case 3 /* Window */:
                    this.recordWindowLevel(newMousePositionImage);
                    break;
            }
        };

        /**
        * Send an event to the recorder
        */
        Series.prototype.recordEvent = function (event) {
            if (this.series) {
                event.seriesInstanceUid = this.series.seriesAttributes.seriesUid;

                var index = this.selectedInstanceIndex.read();

                event.instanceUid = this.series.instances[index].id;

                this.recorder.read().append(event);
            }
        };

        /**
        * Send the current image transformation to the recorder
        */
        Series.prototype.recordTransformation = function () {
            this.recordEvent({
                type: 0 /* ImageTransformationChanged */,
                newTransformation: this.transform.read()
            });
        };

        /**
        * Send the current magnifier position to the recorder
        */
        Series.prototype.recordMagnifier = function () {
            this.recordEvent({
                type: 12 /* MagnifierPositionChanged */,
                magnifierPosition: this.magnifier.read()
            });
        };

        /**
        * Send the current probe tool position to the recorder
        */
        Series.prototype.recordProbe = function () {
            this.recordEvent({
                type: 13 /* ProbeToolChanged */,
                probeTool: this.probeTool.read()
            });
        };

        /**
        * Send the current plane localization data to the recorder
        */
        Series.prototype.recordPlaneLocalizationData = function () {
            this.recordEvent({
                type: 14 /* PlaneLocalizationChanged */,
                planeLocalizationData: this.planeLocalizationCursor.read()
            });
        };

        /**
        * Send the current window level to the recorder
        */
        Series.prototype.recordWindowLevel = function (newMousePositionImage) {
            this.recordEvent({
                type: 1 /* WindowLevelChanged */,
                mousePositionImage: newMousePositionImage,
                newWindowLevel: this.useOriginalWindowLevel.read() ? null : this.windowLevel.read()
            });
        };

        /**
        * Add a measurement
        */
        Series.prototype.addMeasurementToSelectedInstance = function (measurement) {
            var instance = this.currentInstance().read();
            this.addMeasurementToInstance(measurement, instance);
        };

        /**
        * Returns a measurement's annotation data.
        * @param {Measurements.Measurement} measurement
        * @param {Models.Instance} instance
        * @returns {Models.AnnotationData}
        */
        Series.prototype.getAnnotationData = function (measurement, instance) {
            var annotationData = measurement.toAnnotationData();

            if (measurement.label) {
                annotationData.label = measurement.label;
            }

            if (measurement.colocationId) {
                annotationData.colocationId = measurement.colocationId;
            }

            if (measurement.propagation) {
                annotationData.propagation = {
                    startIndex: measurement.propagation.startIndex,
                    originalIndex: measurement.propagation.originalIndex,
                    endIndex: measurement.propagation.endIndex
                };
            }

            if (measurement.pixelSpacingUser) {
                annotationData.pixelSpacing = measurement.pixelSpacingUser;
            }

            if (measurement.sliceSpacingUser) {
                annotationData.sliceSpacing = measurement.sliceSpacingUser;
            }

            if (this.storeExtraAnnotationData && instance) {
                var instanceIndex = this.selectedInstanceIndex.read();
                var instanceKey = this.getInstanceKey(instanceIndex);
                var statistics = measurement.statistics(instance, instanceKey, this.renderer, null);
                if (statistics) {
                    annotationData.stats = statistics;
                    annotationData.stats.pixelSpacing = instance.instanceAttributes.pixelSpacing ? instance.instanceAttributes.pixelSpacing[0] : 0;

                    if (this.storeExtraAnnotationData == 2) {
                        var pixelData = measurement.getAllPixelValues(instance, instanceKey, this.renderer);
                        if (pixelData) {
                            annotationData.stats.pixelData = pixelData;
                        }
                    }
                }

                annotationData.description = JSON.stringify([instance.instanceAttributes.seriesDescription, instance.instanceAttributes.imageType]);
                annotationData.instanceIndex = instance.instanceAttributes.instanceIndex;
            }

            return annotationData;
        };

        /**
        * Add a measurement
        */
        Series.prototype.addMeasurementToInstance = function (measurement, instance) {
            var _this = this;
            this.measurementInProgress.write(null);

            var stamped = false;
            var stamp = _.find(instance.instanceAttributes.measurements, function (m) {
                return m instanceof Measurements.Stamp;
            });
            if (stamp) {
                var stampCreatorId = stamp.creatorId;
                stamped = (stampCreatorId != this.application.user.uuid);
            }

            if (stamped) {
                var message = this.terminology.lookup(Terminology.Terms.WarningInstanceStamped);
                window.alert(message);
            } else {
                measurement.creatorId = this.application.user.uuid;
                var measurements = instance.instanceAttributes.measurements;
                measurements.push(measurement);

                if (!(this.application.user.is_anonymous == true && this.accountSettings.viewer_anon_annotations_clear == 1)) {
                    Services.addImageAnnotation(this.sessionId, this.series.studyAttributes.queryObject, instance.seriesAttributes.seriesUid, instance.id, instance.frameNumber, this.getAnnotationData(measurement, instance)).subscribe({
                        next: function (response) {
                            measurement.id = new Classes.AnnotationId(response.uuid);
                            measurement.creator = _this.application.user.name;

                            _this.renderLayers();
                        },
                        done: function () {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to add annotation: " + err);
                        }
                    });
                }
            }
        };

        /**
        * Send a measurement-added event to the recorder
        */
        Series.prototype.recordMeasurementAdded = function (measurement, mousePositionImage) {
            this.recordEvent({
                type: 2 /* AnnotationAdded */,
                mousePositionImage: mousePositionImage,
                annotation: measurement.toAnnotationData(),
                instanceIndex: this.selectedInstanceIndex.read()
            });
        };

        /**
        * Save a measurement to services
        */
        Series.prototype.editMeasurement = function (measurement, instance) {
            var _this = this;
            Services.editImageAnnotation(this.application.sessionId, this.series.studyAttributes.queryObject, measurement.id, this.getAnnotationData(measurement, instance)).subscribe({
                done: function () {
                },
                next: function (_) {
                },
                fail: function (err) {
                    _this.recordError("Unable to edit image annotation: " + err);
                }
            });
        };

        Series.prototype.recordMouseMoveEvent = function (e) {
            if (this.series) {
                var instance = this.currentInstance().read();

                var newMousePosition;

                if (e.pointers && e.pointers.length) {
                    newMousePosition = Mouse.getOffset(e.pointers[0]);
                } else {
                    newMousePosition = Mouse.getOffset(e);
                }

                var newMousePositionImage = this.mapToImage(newMousePosition, this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                this.recordEvent({
                    type: 7 /* MouseMove */,
                    mousePositionImage: newMousePositionImage
                });
            }
        };

        Series.prototype.handleMouseMoveEvent = function (e) {
            if (this.series) {
                var tool = (e.button === 0 || typeof e.button == 'undefined' || e.button == null) ? this.selectedTool.read() : this.selectedTool2.read();

                var instance = this.currentInstance().read();

                var newMousePosition = Mouse.getOffset(e.pointers[0]);
                var newMousePositionImage = this.mapToImage(newMousePosition, this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                if (Classes.MouseTools.isMeasurementTool(tool)) {
                    if (this.mousePressed) {
                        if (this.mouseMoved && this.needsHideMeasurementCursor) {
                            this.needsHideMeasurementCursor = false;

                            // hide special annotation cursor while dragging
                            if (!this.settings.read().disableCustomAnnotationCursor && !Classes.MouseTools.isPaintTool(tool)) {
                                this.disableMeasurementCursor();
                                this.el.css({ cursor: '' });
                                this.el.addClass('hideCursor');
                            }
                        }

                        var selectedMeasurement = this.selectedMeasurement.read();
                        var measurementInProgress = this.measurementInProgress.read();

                        var transform = this.getImageTransformation(this.canvas.width, this.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                        if (measurementInProgress && (measurementInProgress instanceof Measurements.Trace)) {
                            if (measurementInProgress.stopDrawing(newMousePositionImage, transform)) {
                                this.addMeasurementToSelectedInstance(measurementInProgress);
                                this.recordMeasurementAdded(measurementInProgress, newMousePositionImage);
                                this.clearSelection();
                                measurementInProgress.selected = true;
                            } else {
                                measurementInProgress.startDrawing(newMousePositionImage, transform);
                            }
                        } else if (this.selectedEndpoint || measurementInProgress || selectedMeasurement) {
                            var dx = newMousePositionImage.x - this.originalMousePosition.x;
                            var dy = newMousePositionImage.y - this.originalMousePosition.y;

                            if (selectedMeasurement) {
                                selectedMeasurement.active = true;
                            }

                            if (this.selectedEndpoint) {
                                if (Math.max(Math.abs(dx), Math.abs(dy)) > 5 / Math.sqrt(Math.abs(Vectors.determinant(transform)))) {
                                    this.selectedEndpoint.move(newMousePositionImage);
                                } else {
                                    this.selectedEndpoint.reset();
                                }
                            } else if (measurementInProgress !== null) {
                                measurementInProgress.continueDrawing(newMousePositionImage);
                            } else if (selectedMeasurement && selectedMeasurement.editable) {
                                if (Math.max(Math.abs(dx), Math.abs(dy)) > 5 / Math.sqrt(Math.abs(Vectors.determinant(transform)))) {
                                    selectedMeasurement.continueMoving(dx, dy);
                                } else {
                                    selectedMeasurement.continueMoving(0, 0);
                                }
                            }
                        }
                    }

                    this.renderLayers();
                } else {
                    if (this.mousePressed) {
                        switch (tool) {
                            case 0 /* Move */:
                                this.updateOffset(newMousePosition);
                                break;
                            case 2 /* Zoom */:
                                this.updateZoom(newMousePosition, false);
                                break;
                            case 14 /* FreeRotate */:
                                this.updateZoom(newMousePosition, true);
                                break;
                            case 1 /* Scroll */:
                                this.updateScroll(newMousePosition);
                                break;
                            case 3 /* Window */:
                                this.updateWindowLevel(newMousePosition);
                                break;
                            case 9 /* Localization */:
                                this.updateLocalizationCursor(newMousePosition);
                                break;
                            case 11 /* Probe */:
                                this.probeTool.write(newMousePositionImage);
                                break;
                            case 15 /* Magnify */:
                                this.magnifier.write(newMousePositionImage);
                                break;
                        }
                    }
                }
            }
        };

        /**
        * Apply a window level preset based on settings or defaults
        */
        Series.prototype.applyWindowLevelPreset = function (index) {
            var modalitySettings = this.settings.read().modalities;

            var presets = this.getWindowLevelPresets(modalitySettings);

            var windowLevel = presets[index];

            if (windowLevel) {
                this.useOriginalWindowLevel.write(false);
                this.windowLevel.write(windowLevel.windowLevel);
            }
        };

        /**
        * Apply color table preset
        * @param {number} index
        */
        Series.prototype.applyColorTablePreset = function (index) {
            var modalitySettings = this.settings.read().modalities;

            var presets = this.getColorTablePresets(modalitySettings);

            var colorTable = presets[index];

            if (colorTable) {
                this.colorTable.write(colorTable.colorTable);
            }
        };

        /**
        * Detect the window level based on the selected ROI
        */
        Series.prototype.detectWindowLevel = function () {
            var instanceIndex = this.selectedInstanceIndex.read();
            var instance = this.series.instances[instanceIndex];
            var instanceKey = this.getInstanceKey(instanceIndex);

            // Prefer the selected measurement, if there is one.
            var selectedMeasurement = this.selectedMeasurement.read();

            // If there is no selected measurement, try to return the _only_ measurement.
            if (!selectedMeasurement) {
                selectedMeasurement = instance.instanceAttributes.measurements.only();
            }

            if (selectedMeasurement) {
                var statistics = selectedMeasurement.statistics(instance, instanceKey, this.renderer, null);

                if (!statistics) {
                    window.alert(this.terminology.lookup(Terminology.Terms.CannotDetectWindowLevel));
                } else {
                    this.useOriginalWindowLevel.write(false);
                    this.windowLevel.write({
                        center: (statistics.max + statistics.min) / 2.0,
                        width: statistics.max - statistics.min
                    });
                }
            } else {
                window.alert(this.terminology.lookup(Terminology.Terms.PleaseSelectAnAnnotation));
            }
        };

        /**
        * Get window level presets based on settings or defaults
        */
        Series.prototype.getWindowLevelPresets = function (modalitySettings) {
            var modality = this.series.seriesAttributes.modality;

            if (modalitySettings) {
                var settings = _.find(modalitySettings, function (m) {
                    return m.modality === modality;
                });

                if (settings && settings.presets !== undefined && settings.presets.length > 0) {
                    _.each(settings.presets, function (preset) {
                        if (preset.windowLevel) {
                            preset.windowLevel.center = Number(preset.windowLevel.center);
                            preset.windowLevel.width = Number(preset.windowLevel.width);
                        }
                    });

                    return settings.presets;
                }
            }

            var index = this.selectedInstanceIndex.read();

            return WindowLevelPresets.defaults(modality, this.terminology);
        };

        /**
        * Get a color table preset
        * @param {Classes.ModalitySettings[]} modalitySettings
        * @returns {Classes.ColorTablePreset[]}
        */
        Series.prototype.getColorTablePresets = function (modalitySettings) {
            var modality = this.series.seriesAttributes.modality;

            if (modalitySettings) {
                var settings = _.find(modalitySettings, function (m) {
                    return m.modality === modality;
                });

                if (settings && settings.colorTablePresets !== undefined && settings.colorTablePresets.length > 0) {
                    return settings.colorTablePresets;
                }
            }

            return ColorTablePresets.defaults(modality, this.terminology);
        };

        /**
        * Update the offset portion of the current image transformation based on mouse movement
        */
        Series.prototype.updateOffset = function (newMousePosition) {
            var dx = newMousePosition.x - this.originalMousePosition.x;
            var dy = newMousePosition.y - this.originalMousePosition.y;

            this.transform.write({
                offsetX: this.originalTransform.offsetX + dx,
                offsetY: this.originalTransform.offsetY + dy,
                scale: this.originalTransform.scale,
                flipped: this.originalTransform.flipped,
                rotation: this.originalTransform.rotation
            });
        };

        /**
        * Update the zoom portion of the current image transformation based on mouse movement
        */
        Series.prototype.updateZoom = function (newMousePosition, rotate) {
            var dx = this.originalMousePosition.x - newMousePosition.x;
            var dy = this.originalMousePosition.y - newMousePosition.y;

            var newScale = this.originalTransform.scale;
            var newRotation = this.originalTransform.rotation;

            if (rotate) {
                var dx2 = dx;
                var dy2 = dy;

                if (Math.abs(dx2) < 5) {
                    dx2 = 0;
                }

                if (Math.abs(dy2) < 5) {
                    dy2 = 0;
                }

                if (dx2 * dx2 + dy2 * dy2 > 25) {
                    newRotation += Math.atan2(dy2, dx2) / Math.PI * 2;
                }
            } else {
                newScale *= Math.exp(dy / 75.0);
            }

            this.applyZoomRotate(newScale, newRotation);
        };

        Series.prototype.applyZoomRotate = function (newScale, newRotation) {
            var instance = this.currentInstance().read();

            var newTransform = Transform.zoomRotateAround(instance, this.originalMousePosition, this.originalTransform, newScale, newRotation, this.canvas.width, this.canvas.height);

            this.transform.write(newTransform);
        };

        /**
        * Update the currently selected instance or frame based on mouse movement
        */
        Series.prototype.updateScroll = function (newMousePosition) {
            var dy = this.originalMousePosition.y - newMousePosition.y;

            // Scroll one frame after a mouse drag is longer than pixelScrollingThreshold
            var pixelScrollingThreshold = 40;

            // On series larger than 20 images use a percentage of the screen to determine when to scroll,
            // Otherwise use the pixelScrollingThreshold
            var usePercentScrollingMethod = (this.series.instances.length > 20) ? true : false;

            var newIndex = 0;

            if (usePercentScrollingMethod) {
                newIndex = Math.round(this.originalIndex + dy * this.series.instances.length / this.el.height()) % this.series.instances.length;
            } else {
                newIndex = (this.originalIndex + Math.floor(dy / pixelScrollingThreshold)) % this.series.instances.length;
            }

            if (newIndex < 0) {
                newIndex += this.series.instances.length;
            }

            if (this.selectedInstanceIndex.read() !== newIndex) {
                this.selectedInstanceIndex.write(newIndex);
            }
        };

        /**
        * Update the window level settings based on mouse movement
        */
        Series.prototype.updateWindowLevel = function (newMousePosition) {
            var dx = this.originalMousePosition.x - newMousePosition.x;
            var dy = this.originalMousePosition.y - newMousePosition.y;

            var settings = this.settings.read();
            dx = (settings.reverseWLHorizontal === true) ? -dx : dx;
            dy = (settings.reverseWLVertical === true) ? -dy : dy;

            if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
                if (this.useOriginalWindowLevel.read()) {
                    this.originalWindowLevel = this.getDefaultWindowLevel(this.currentInstance().read());
                    this.useOriginalWindowLevel.write(false);
                }

                var sensitivity = Math.min(Math.max(1.0, this.originalWindowLevel.center / 200.0), 10.0);

                var newWindowLevel = {
                    center: Math.round(this.originalWindowLevel.center + sensitivity * dy),
                    width: Math.round(Math.max(1.0, this.originalWindowLevel.width / Math.exp(dx / 100.0)))
                };

                this.windowLevel.write(newWindowLevel);
            }
        };

        Series.prototype.handleMouseWheelEvent = function (e, delta) {
            if (this.selectedToolWheel.read() == 1 /* Scroll */) {
                this.wheelScroll(e, delta);
            } else if (this.selectedToolWheel.read() == 2 /* Zoom */) {
                this.wheelZoom(delta);
            } else {
                switch (this.selectedTool.read()) {
                    case 2 /* Zoom */:
                    case 14 /* FreeRotate */:
                    case 0 /* Move */:
                    case 15 /* Magnify */:
                        this.wheelZoom(delta);
                        break;
                    default:
                        this.wheelScroll(e, delta);
                        break;
                }
            }

            e.stopPropagation();
            e.preventDefault();
        };

        /**
        * Update the zoom of the current image transformation based on mouse wheel movement
        */
        Series.prototype.wheelZoom = function (delta) {
            var transform = this.transform.read();

            this.transform.write({
                offsetX: transform.offsetX,
                offsetY: transform.offsetY,
                scale: delta < 0 ? transform.scale / 1.2 : transform.scale * 1.2,
                flipped: transform.flipped,
                rotation: transform.rotation
            });
        };

        /**
        * Update the currently selected instance or frame based on mouse wheel movement
        */
        Series.prototype.wheelScroll = function (e, delta) {
            var d;

            if (delta > 0) {
                d = -1;
            } else {
                d = 1;
            }

            this.scrollBy(d);
        };

        /**
        * Move the instance number by the specified amount
        */
        Series.prototype.scrollBy = function (n) {
            var instance = this.selectedInstanceIndex.read();

            if (this.series) {
                var newInstance = (instance + n) % this.series.instances.length;

                if (newInstance < 0) {
                    newInstance += this.series.instances.length;
                }

                this.selectedInstanceIndex.write(newInstance);
            }
        };

        /**
        * Reset the image transformation settings
        */
        Series.prototype.fit = function () {
            this.transform.write({
                offsetX: 0,
                offsetY: 0,
                scale: 1,
                flipped: false,
                rotation: 0
            });
        };

        /**
        * Reset all settings for all frames
        */
        Series.prototype.reset = function () {
            this.deleteAllEditableMeasurements();

            if (this.subtractionActive.read()) {
                this.subtractionActive.write(false);
            }

            this.resetWindowLevel();
            this.invertActive.write(false);
            this.infoVisible.write(true);
            this.measurementsVisible.write(true);
            this.hiddenGSPSLayers.write([]);
            this.colorTable.write(null);

            this.fit();

            if (this.transformHP) {
                this.transform.write(this.transformHP.read());
            }

            this.reloadAllFrames();
        };

        /**
        * Delete all editable measurements
        */
        Series.prototype.deleteAllEditableMeasurements = function (skipArea) {
            var _this = this;
            if (typeof skipArea === "undefined") { skipArea = false; }
            var deleted = [];

            _.each(this.series.instances, function (instance) {
                var calibrationUser = false;

                _.each(instance.instanceAttributes.measurements, function (measurement) {
                    if (measurement.editable && !measurement.temporary && (!skipArea || !(measurement instanceof Measurements.Area))) {
                        if (measurement.id && !_.contains(deleted, measurement.id)) {
                            deleted.push(measurement.id);

                            Services.deleteImageAnnotation(_this.sessionId, _this.series.studyAttributes.queryObject, measurement.id).subscribe({
                                done: function () {
                                },
                                next: function (_) {
                                },
                                fail: function (err) {
                                    _this.recordError("Unable to delete annotation:" + err);
                                }
                            });

                            if (measurement == instance.instanceAttributes.calibration) {
                                instance.instanceAttributes.calibration = null;
                            }
                        }
                    } else {
                        if (measurement.pixelSpacingUser || measurement.sliceSpacingUser) {
                            calibrationUser = true;
                        }
                    }
                });
                instance.instanceAttributes.measurements = _.filter(instance.instanceAttributes.measurements, function (m) {
                    return !m.editable || (skipArea && (m instanceof Measurements.Area));
                });
                instance.instanceAttributes.calibrationUser = calibrationUser;
            });
        };

        Series.prototype.updateAreaColorSelection = function (color) {
            var _this = this;
            var instanceIndex = this.selectedInstanceIndex.read();
            var instance = this.series.instances[instanceIndex];

            this.clearSelection();

            _.each(instance.instanceAttributes.measurements, function (m) {
                if (m instanceof Measurements.Area) {
                    var area = m;
                    if (area.color === color) {
                        _this.selectedMeasurement.write(m);
                        m.selected = true;
                    }
                }
            });
        };

        /**
        * Reset only the window level
        */
        Series.prototype.resetWindowLevel = function () {
            this.useOriginalWindowLevel.write(true);
            this.windowLevel.write(this.getDefaultWindowLevel());
            this.subtractionWindowLevel = null;
            var original = this.windowLevel.read();
            this.preSubtractionWindowLevel = { center: original.center, width: original.width };
            this.recordWindowLevel();
        };

        /**
        * Export the current instance to PNG format
        */
        Series.prototype.exportCurrentImages = function () {
            var uri = this.renderImageToDataURI();
            this.renderAll();

            if (LocalViewer.isStandardLocalViewer()) {
                window.open(uri);
            } else {
                var html = $('<img>').attr('src', uri)[0].outerHTML;
                $(window.open().document.body).html(html);
            }
        };

        /**
        * Store current instance capture
        */
        Series.prototype.storeCurrentImage = function () {
            var dataURL = this.prepareCanvasForReport().toDataURL("image/png");
            this.renderAll(); // clear the prepared canvas
            SecondaryCapture.postCurrentImage(this.sessionId, this, dataURL);
        };

        /**
        * Export the current series to video
        */
        Series.prototype.exportVideo = function (format) {
            var uri = Routes.VideoDownload(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject, this.series.seriesAttributes.seriesUid, format);

            window.location.href = uri;
        };

        /**
        * Export the current series to zip
        */
        Series.prototype.exportSeries = function () {
            window.location.href = Routes.SeriesDownload(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject, this.series.seriesAttributes.seriesUid);
            ;
        };

        /**
        * Export this series' study to zip
        */
        Series.prototype.exportStudy = function () {
            window.location.href = Routes.StudyDownload(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject);
            ;
        };

        /**
        * Export this series' study as ISO
        */
        Series.prototype.exportISO = function () {
            var uri = window.location.href = Routes.ISODownload(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject);
            ;
        };

        /**
        * Export this series' study as a local viewer
        */
        Series.prototype.exportLocalViewer = function () {
            window.location.href = Routes.LocalViewerDownload(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject);
            ;
        };

        /**
        * Prepare the canvas for export to PNG
        */
        Series.prototype.prepareCanvasForExport = function (layers, forceSquare) {
            var _this = this;
            if (typeof forceSquare === "undefined") { forceSquare = false; }
            var canvas = this.canvas;

            this.renderAll(function (context, instance) {
                context.fillStyle = "#000000";
                context.fillRect(0, 0, canvas.width, canvas.height);

                _this.renderer.act({
                    visitSimpleRenderer: function (renderer) {
                        var transform = _this.getImageTransformation(canvas.width, canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                        context.save();
                        context.transform(transform.entries[0], transform.entries[3], transform.entries[1], transform.entries[4], transform.entries[2], transform.entries[5]);

                        var instanceIndex = _this.selectedInstanceIndex.read();
                        var instanceKey = _this.getInstanceKey(instanceIndex);

                        var $image = renderer.imageElements.get(instanceKey, null);

                        if ($image) {
                            context.drawImage($image.imageElement, 0, 0);
                        }

                        context.restore();
                    },
                    visitCanvasRenderer: function (renderer) {
                        context.drawImage(renderer.canvas2d, 0, 0);
                    },
                    visitWebGLRenderer: function (renderer) {
                        context.drawImage(renderer.canvas3d, 0, 0);
                    }
                });
            }, false, layers, forceSquare);

            return canvas;
        };

        /**
        * Draw canvas in square, thumbnail-sized dimensions, hiding all small textual info, for use in reports
        * @returns {HTMLCanvasElement}
        */
        Series.prototype.prepareCanvasForReport = function (selected, targetSize) {
            if (typeof selected === "undefined") { selected = false; }
            if (typeof targetSize === "undefined") { targetSize = 256; }
            var offScreenCanvas = document.createElement('canvas');
            offScreenCanvas.width = targetSize;
            offScreenCanvas.height = targetSize;
            var offScreenContext = offScreenCanvas.getContext("2d");

            // fit image to thumbnail frame
            var instanceIndex = this.selectedInstanceIndex.read();
            var instance = this.series.instances[instanceIndex];
            var imageCols = instance.instanceAttributes.columns;
            var imageRows = instance.instanceAttributes.rows;
            var transform = this.getImageTransformation(this.canvas.width, this.canvas.height, imageCols, imageRows);
            var longDim;

            if (imageCols > imageRows) {
                longDim = this.canvas.width - (transform.entries[2] * 2.0);
            } else {
                longDim = this.canvas.height - (transform.entries[5] * 2.0);
            }

            var layers = [];

            var measurementLayer = new Layers.MeasurementLayer(this.measurementsVisible, this.measurementsDetailsVisible, this.annotationsCreatedByOtherUsersVisible, this.renderer, this.measurementInProgress, this.selectedTool, this.selectedMeasurement, this, this.playbackMode, this.showStandardDev, this.showTextOnDirected, this.editingMeasurement, this.hideActiveMeasuremntInfo, this.alwaysUseMillimeters, Layers.DEFAULT_TEXT_SIZE, this.measureVolume, false, this.application.user);
            measurementLayer.alwaysHideInfo = true; // textual info is too small
            measurementLayer.lineWidth = (measurementLayer.lineWidth * (longDim / targetSize)); // maintain relative line thickness

            layers.push(new Layers.OverlayLayer(this.measurementsVisible, this.overlayData, this, false));
            layers.push(measurementLayer);
            var canvas = this.prepareCanvasForExport(layers);

            offScreenContext.fillStyle = "#000000";
            offScreenContext.fillRect(0, 0, offScreenCanvas.width, offScreenCanvas.height);
            offScreenContext.drawImage(canvas, (canvas.width - longDim) / 2.0, (canvas.height - longDim) / 2.0, longDim, longDim, 0, 0, offScreenCanvas.width, offScreenCanvas.height);

            if (selected) {
                Rendering.drawRectangleBorder(offScreenContext, 1.5, 1.5, offScreenContext.canvas.width - 3, offScreenContext.canvas.height - 3, '#00ffff');
            }

            return offScreenCanvas;
        };

        Series.prototype.renderImageToDataURI = function () {
            return this.prepareCanvasForExport(null, this.settings.read().exportSquare).toDataURL("image/png");
        };

        /**
        * Export the current instance as a secondary capture image
        */
        Series.prototype.secondaryCapture = function () {
            var _this = this;
            var uri = this.renderImageToDataURI();
            this.renderAll();

            var storeAndLoadImage = SecondaryCapture.secondaryCapture(this.sessionId, this.series, uri);

            storeAndLoadImage.subscribe({
                next: function (newSeries) {
                    _this.application.addSeries(newSeries);
                },
                done: function () {
                },
                fail: function (err) {
                    _this.recordError("Unable to save secondary capture image: " + err);
                    window.alert(_this.terminology.lookup(Terminology.Terms.SecondaryCaptureFailed));
                }
            });
        };

        /**
        * Export the current annotations as a GSPS object
        */
        Series.prototype.saveGSPS = function () {
            var _this = this;
            var editableAnnotations = _.flatten(_.map(this.series.instances, function (instance) {
                return _.filter(instance.instanceAttributes.measurements, function (m) {
                    return m.editable && !(m instanceof Measurements.Area);
                });
            }));

            if (_.any(editableAnnotations)) {
                $('.overlay').addClass('application-loading');

                var instanceIndex = this.selectedInstanceIndex.read();
                var instance = this.series.instances[instanceIndex];
                var instanceKey = this.getInstanceKey(instanceIndex);

                var gsps = GSPS.createGSPS(editableAnnotations, this.renderer, instanceKey, instance, this.showStandardDev.read());

                var storeAndLoadObject = Observable._finally(V3Storage.postGSPS(this.sessionId, this.series.studyAttributes.studyStorage, this.series.studyAttributes.queryObject, gsps), function () {
                    return $('.overlay').removeClass('application-loading');
                });

                storeAndLoadObject.subscribe({
                    next: function (imageResult) {
                        var study = _.find(_this.application.studies, function (study) {
                            return study.studyAttributes.queryObject.toString() === _this.series.studyAttributes.queryObject.toString();
                        });

                        _this.deleteAllEditableMeasurements(true);
                        GSPS.apply([gsps], study);

                        _this.renderAll();
                    },
                    done: function () {
                    },
                    fail: function (err) {
                        _this.recordError("Unable to save GSPS: " + err);
                        window.alert(_this.terminology.lookup(Terminology.Terms.SaveGSPSFailed));
                    }
                });
            }
        };

        /**
        * Toggle cine mode
        */
        Series.prototype.toggleCine = function (active) {
            var _this = this;
            var imagePositionSubject = this.selectedInstanceIndex;

            if (this.series && !Multiframe.isMultiframe(this.series) || !Cine.isFormatSupported()) {
                if (active) {
                    if (!LocalViewer.isLocalViewer()) {
                        this.loadAllImageData(this.imagePreloadQueue, -2 /* ImageDataBackgroundPreloading */);
                    }

                    this.resetCineStartPosition();
                    this.cineStartFrame = imagePositionSubject.read();
                    this.forceLowResolution.write(this.forceLowResolution.read().take());
                    this.cineTimer = window.setInterval(function () {
                        var time = new Date().getTime();
                        var elapsed = time - _this.cineStartTime;
                        var cineSpeed = _this.cineSpeed.read();
                        var frame = (_this.cineStartFrame + elapsed * cineSpeed / 1000) % _this.series.instances.length;
                        imagePositionSubject.write(Math.floor(frame));
                    }, 10);
                } else {
                    this.imagePreloadQueue.clear();
                    this.forceLowResolution.write(this.forceLowResolution.read().release());
                    window.clearInterval(this.cineTimer);
                }
            }

            this.renderAll();
        };

        /**
        * Update the cineStartFrame and cineStartTime in response to a change in frame rate, or start/stop event
        */
        Series.prototype.resetCineStartPosition = function () {
            this.cineStartFrame = this.selectedInstanceIndex.read();
            this.cineStartTime = new Date().getTime();
        };

        /**
        * Increase the cine playback frame rate
        */
        Series.prototype.increaseCineSpeed = function () {
            var cineSpeed = this.cineSpeed.read();
            if (++cineSpeed > 60) {
                cineSpeed = 60;
            }
            this.cineSpeed.write(cineSpeed);

            this.resetCineStartPosition();
        };

        /**
        * Decrease the cine playback frame rate
        */
        Series.prototype.decreaseCineSpeed = function () {
            var cineSpeed = this.cineSpeed.read();
            if (--cineSpeed < 1) {
                cineSpeed = 1;
            }
            this.cineSpeed.write(cineSpeed);

            this.resetCineStartPosition();
        };

        /**
        * Delete the selected image
        */
        Series.prototype.deleteSelectedImage = function () {
            var _this = this;
            if (this.series) {
                var selectedInstanceIndex = this.selectedInstanceIndex.read();
                var instance = this.series.instances[selectedInstanceIndex];

                if (this.application.singleSeriesEnabled.read()) {
                    selectedInstanceIndex = _.indexOf(this.application.seriesViews.read(), this) + this.application.singleSeriesIndex;
                    instance = this.application.singleSeries.instances[selectedInstanceIndex];
                }

                var message = this.terminology.lookup(Terminology.Terms.DeleteImageWarning) + "\n\n" + instance.instanceAttributes.seriesDescription + ", " + this.terminology.lookup(Terminology.Terms.Instance) + " #" + (instance.instanceAttributes.instanceNumber + 1);

                if (window.confirm(message)) {
                    var deleteImage = V3Storage.deleteImage(this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version);

                    $('.overlay').addClass('application-loading');

                    Observable._finally(deleteImage, function () {
                        $('.overlay').removeClass('application-loading');
                    }).subscribe({
                        done: function () {
                            if (_this.application.singleSeriesEnabled.read()) {
                                _this.application.singleSeries.instances.splice(selectedInstanceIndex, 1);

                                if (_this.application.singleSeries.instances.length) {
                                    _this.application.resetSeriesViews();
                                    _this.application.render(_this.application.singleSeries);
                                } else {
                                    _this.application.removeEmptySeries(_this.series);
                                }
                            } else {
                                _this.series.instances.splice(selectedInstanceIndex, 1);

                                if (_this.series.instances.length) {
                                    _this.selectedInstanceIndex.write(0);
                                } else {
                                    _this.application.removeEmptySeries(_this.series);
                                }
                            }
                        },
                        next: function (_) {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to delete image: " + err);
                            window.alert(_this.terminology.lookup(Terminology.Terms.ErrorDeletingImage));
                        }
                    });
                }
            }
        };

        /**
        * Find the instance which is geometrically closest to the specified instance and show it.
        */
        Series.prototype.scrollToNearestInstance = function (instance) {
            if (this.allAttributesAreLoaded()) {
                var index = SeriesGeometry.findClosestInstance(this.series.instances, instance);

                if (index !== null) {
                    this.selectedInstanceIndex.write(index);
                }
            }
        };

        /**
        * Find the instance which is geometrically closest to the specified point and show it.
        */
        Series.prototype.scrollToNearestInstanceToPoint = function (patientCoords) {
            if (this.allAttributesAreLoaded()) {
                var index = SeriesGeometry.findClosestInstanceToPoint(this.series.instances, patientCoords);

                if (index !== null) {
                    this.selectedInstanceIndex.write(index);
                }
            }
        };

        /**
        * Check whether all image attributes got loaded
        */
        Series.prototype.allAttributesAreLoaded = function () {
            return _.all(this.series.instances, function (instance) {
                return instance.instanceAttributes.attributesLoaded;
            });
        };

        /**
        * Gets the currently selected instance
        */
        Series.prototype.currentInstance = function () {
            var _this = this;
            return Subjects.map(this.selectedInstanceIndex, function (index) {
                return _this.series ? _this.series.instances[index] : null;
            });
        };

        /**
        * Update the global plane localization cursor
        */
        Series.prototype.updateLocalizationCursor = function (screenCoords) {
            var instance = this.currentInstance().read();

            if (SeriesGeometry.hasGeometricMetadata(instance)) {
                var el = this.canvas;

                var rows = instance.instanceAttributes.rows;
                var cols = instance.instanceAttributes.columns;

                var imageCoords = this.mapToImage(screenCoords, el.width, el.height, cols, rows);

                var patientCoords = SeriesGeometry.mapToPatient(imageCoords, instance);

                this.planeLocalizationCursor.write({
                    studyUid: this.series.studyAttributes.queryObject.studyUid,
                    seriesUid: this.series.seriesAttributes.seriesUid,
                    coords: patientCoords
                });
            }
        };

        /**
        * Cine opens in a new tab on iOS devices
        */
        Series.prototype.openMultiframeCineInNewTab = function () {
            if (Cine.isFormatSupported()) {
                var $popupBody = Popup.createPopup();

                var instance = this.currentInstance().read();

                var video = new Views.Video(this.sessionId, $popupBody, this.settings, instance, this.terminology);
                video.render();
            } else {
                window.alert(this.terminology.lookup(Terminology.Terms.VideoNotSupported));
            }
        };

        /**
        * Mark the selected image as a key image or not
        */
        Series.prototype.updateKeyImageStatusInServices = function () {
            var _this = this;
            var instance = this.currentInstance().read();

            if (instance) {
                if (instance.instanceAttributes.isKeyImage.read()) {
                    Services.addKeyImage(this.sessionId, instance.studyAttributes.queryObject, instance.seriesAttributes.seriesUid, instance.id, instance.frameNumber, instance.instanceAttributes.version).subscribe({
                        done: function () {
                        },
                        next: function (response) {
                            instance.instanceAttributes.keyImageId = new Classes.KeyImageId(response.uuid);
                        },
                        fail: function (err) {
                            _this.recordError("Unable to add key image: " + err);
                        }
                    });
                } else if (instance.instanceAttributes.keyImageId) {
                    Services.deleteKeyImage(this.sessionId, instance.instanceAttributes.keyImageId).subscribe({
                        done: function () {
                        },
                        next: function (_) {
                            instance.instanceAttributes.keyImageId = null;
                        },
                        fail: function (err) {
                            _this.recordError("Unable to delete key image: " + err);
                        }
                    });
                }
            }
        };

        /**
        * Get the image rendering parameters based on the current transformation settings
        */
        Series.prototype.getImageTransformation = function (dw, dh, sw, sh) {
            var transform = this.transform.read();

            return Transform.transformToMatrix(transform, dw, dh, sw, sh);
        };

        /**
        * Get the inverse image rendering parameters based on the current transformation settings
        */
        Series.prototype.getInverseImageTransformation = function (dw, dh, sw, sh) {
            var transform = this.transform.read();

            return Transform.transformToInverseMatrix(transform, dw, dh, sw, sh);
        };

        /**
        * Map canvas coordinates to image coordinates
        */
        Series.prototype.mapToImage = function (p, dw, dh, sw, sh) {
            var transform = this.getInverseImageTransformation(dw, dh, sw, sh);

            return Vectors.multiplyM(transform, p);
        };

        /**
        * Map image coordinates to canvas coordinates
        */
        Series.prototype.mapFromImage = function (p, dw, dh, sw, sh) {
            var transform = this.getImageTransformation(dw, dh, sw, sh);

            return Vectors.multiplyM(transform, p);
        };

        Series.prototype.recordError = function (message) {
            this.application.recordSeriesError(this.series.seriesAttributes.seriesUid, message, "Series");
        };

        /**
        * Returns true if this series is currently being displayed.
        * @returns {boolean}
        */
        Series.prototype.isActive = function () {
            return (this.canvas && ($(this.canvas).hasClass('instance-canvas')) && document.body.contains(this.canvas));
        };

        Series.prototype.supportsSubtraction = function () {
            return this.series && this.subtraction && Dicom.Subtraction.supportsSubtraction(this.series.seriesAttributes) && (this.findRenderingMode() === 0 /* Canvas */);
        };
        return Series;
    })();
    Views.Series = Series;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../typings/underscore/underscore.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Touch.ts' />
///<reference path='../libs/WindowLevelPresets.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Series.ts' />
var Views;
(function (Views) {
    

    

    /**
    * A collection of helper methods for creating toolbars
    */
    var MenuBar = (function () {
        function MenuBar($toolbar) {
            this.$toolbar = $toolbar;
            this.listeners = [];
        }
        /**
        * Add a text label to a toolbar
        */
        MenuBar.prototype.addLabel = function (options) {
            var label = $('<div>').addClass('toolbarLabel').text(options.text.read());

            var li = $('<div>').append(label);

            this.listeners.push(Subjects.listen(options.text, function (text) {
                label.text(text);
            }));

            if (options.visible) {
                this.listeners.push(Subjects.listen(options.visible, function (value) {
                    if (!value) {
                        li.hide();
                    } else {
                        li.show();
                    }
                }));

                if (!options.visible.read()) {
                    li.hide();
                }
            }

            this.$toolbar.append(li);

            return li;
        };

        /**
        * Add a button to a toolbar
        *
        * @param {$toolbar} The toolbar
        * @param {options.title} The text to display on the button
        * @param {options.tooltip} An optional tooltip
        * @param {options.icon} The name of the icon to display
        * @param {options.click} An optional action to perform on clicking
        * @param {options.enabled} A subject which determines whether the button is active or not
        * @param {options.closePopupsOnClick} True if clicking the button should close all open dropdown menus
        *
        * @returns The button jQuery DOM wrapper object
        */
        MenuBar.prototype.addToolbarButton = function (options) {
            if (!options.tooltip && options.title) {
                options.tooltip = options.title;
            }

            var icon = $('<div>').addClass("fa").addClass('imageControlIcon');

            var label = $('<div>').addClass('imageControlLabel');

            var li = $('<div>').addClass('imageControl').append(icon).append(label).addClass('glow');

            if (options.dataDynamic) {
                var data = options.dataDynamic.read();

                label.text(data.title);
                li.attr({ title: data.tooltip });
                icon.removeClass(icon.data("current-icon")).addClass(ToolbarButtons.iconFor(data.icon)).data("current-icon", ToolbarButtons.iconFor(data.icon));

                this.listeners.push(Subjects.listen(options.dataDynamic, function (data) {
                    label.text(data.title);
                    li.attr({ title: data.tooltip });

                    icon.removeClass(icon.data("current-icon")).addClass(ToolbarButtons.iconFor(data.icon)).data("current-icon", ToolbarButtons.iconFor(data.icon));
                }));
            } else {
                if (options.title) {
                    label.text(options.title);
                } else {
                    label.html('&nbsp;');
                    li.addClass('blank');
                }
                li.attr({ title: options.tooltip });

                icon.addClass(ToolbarButtons.iconFor(options.icon)).data("current-icon", ToolbarButtons.iconFor(options.icon));
            }

            if (options.click) {
                li.fallback(["touchstart", "pointerdown"], ["click"], function (e) {
                    if (!options.enabled || options.enabled.read()) {
                        options.click(e);
                    }

                    if (options.closePopupsOnClick === true) {
                        $('.dropdown-icon').removeClass('dropdown-visible');
                    }

                    e.stopPropagation();
                });
            }

            li.on("contextmenu", function (e) {
                e.preventDefault();
            });

            li.dblclick(function (e) {
                e.stopPropagation();
                e.preventDefault();
            });

            if (options.enabled) {
                this.listeners.push(Subjects.listen(options.enabled, function (value) {
                    if (!value) {
                        li.addClass('disabled');
                    } else {
                        li.removeClass('disabled');
                    }
                }));

                if (!options.enabled.read()) {
                    li.addClass('disabled');
                }
            }

            this.$toolbar.append(li);

            return li;
        };

        /**
        * Add a toggle button to a toolbar
        *
        * @param {$toolbar} The toolbar
        * @param {options.title} The text to display on the button
        * @param {options.tooltip} An optional tooltip
        * @param {options.icon} The name of the icon to display
        * @param {options.tool} The mouse tool to activate on clicking
        * @param {options.selected} A writable subject which determines whether the button is selected
        * @param {options.closePopupsOnClick} True if clicking the button should close all open dropdown menus
        *
        * @returns The button jQuery DOM wrapper object
        */
        MenuBar.prototype.addToggleButton = function (options) {
            var li = this.addToolbarButton({
                title: options.title,
                tooltip: options.tooltip,
                enabled: options.enabled,
                visible: options.visible,
                icon: options.icon,
                dataDynamic: options.toggledData ? Subjects.map(options.selected, function (s) {
                    return s ? options.toggledData : {
                        title: options.title,
                        tooltip: options.tooltip,
                        icon: options.icon
                    };
                }) : null,
                click: function (e) {
                    var newValue = !options.selected.read();
                    options.selected.write(newValue);

                    if (newValue) {
                        if (options.toggledData && options.toggledData.baseClass) {
                            li.addClass(options.toggledData.baseClass + '-selected');
                            li.removeClass(options.toggledData.baseClass + '-unselected');
                        } else {
                            li.addClass('selected');

                            if (options.highlightParent) {
                                li.parents('.dropdown-icon').addClass('selected');
                            }
                        }
                    } else {
                        if (options.toggledData && options.toggledData.baseClass) {
                            li.removeClass(options.toggledData.baseClass + '-selected');
                            li.addClass(options.toggledData.baseClass + '-unselected');
                        } else {
                            li.removeClass('selected');

                            // Only remove selected if no other children are selected
                            if (options.highlightParent && li.parents('.dropdown-icon').children('.dropdown-content').children('.selected').length < 1) {
                                li.parents('.dropdown-icon').removeClass('selected');
                            }
                        }
                    }
                    if (options.click) {
                        options.click(e);
                    }
                },
                closePopupsOnClick: options.closePopupsOnClick
            });

            if (options.highlightParent) {
                li.addClass("highlight-parent");
            }

            this.listeners.push(Subjects.listen(options.selected, function (value) {
                if (value) {
                    if (options.toggledData && options.toggledData.baseClass) {
                        li.addClass(options.toggledData.baseClass + '-selected');
                        li.removeClass(options.toggledData.baseClass + '-unselected');
                    } else {
                        li.addClass('selected');

                        if (options.highlightParent) {
                            li.parents('.dropdown-icon').addClass('selected');
                        }
                    }
                } else {
                    if (options.toggledData && options.toggledData.baseClass) {
                        li.removeClass(options.toggledData.baseClass + '-selected');
                        li.addClass(options.toggledData.baseClass + '-unselected');
                    } else {
                        li.removeClass('selected');

                        // Only remove selected if no other children are selected
                        if (options.highlightParent && li.parents('.dropdown-icon').children('.dropdown-content').children('.selected').length < 1) {
                            li.parents('.dropdown-icon').removeClass('selected');
                        }
                    }
                }
            }));

            if (options.selected.read()) {
                if (options.toggledData && options.toggledData.baseClass) {
                    li.addClass(options.toggledData.baseClass + '-selected');
                } else {
                    li.addClass('selected');
                }

                li.find('.selectedTool').addClass('disp');
            }

            if (options.selected2) {
                this.listeners.push(Subjects.listen(options.selected2, function (value) {
                    if (value) {
                        li.find('.selectedTool2').addClass('disp');
                    } else {
                        li.find('.selectedTool2').removeClass('disp');
                    }
                }));

                if (options.selected2.read()) {
                    li.find('.selectedTool2').addClass('disp');
                }
            }

            return li;
        };

        /**
        * Add a drop down button to a toolbar
        *
        * @param {$toolbar} The toolbar
        * @param {options.title} The text to display on the button
        * @param {options.tooltip} An optional tooltip
        * @param {options.icon} The name of the icon to display
        *
        * @returns The dropdown menu jQuery DOM wrapper object.
        */
        MenuBar.prototype.addDropDownButton = function (options, buttons) {
            var li = this.addToolbarButton({
                title: options.title,
                tooltip: options.tooltip,
                visible: options.visible,
                icon: options.icon,
                click: function () {
                    if (li.is('.dropdown-visible')) {
                        li.removeClass('dropdown-visible');
                    } else {
                        $('.dropdown-icon').removeClass('dropdown-visible');
                        li.addClass('dropdown-visible');
                    }
                }
            });

            li.addClass('dropdown-icon');

            if (!options.title) {
                li.addClass('no-title');
            }

            var $submenu = $('<div>').addClass('dropdown-content');
            var submenu = new MenuBar($submenu);

            this.listeners.concat(submenu.listeners);

            _.each(buttons, function (button) {
                return button.add(submenu);
            });

            $submenu.appendTo(li);

            if ($submenu.children('.highlight-parent.selected').length > 0) {
                li.addClass('selected');
            }

            return li;
        };

        /**
        * Add layout buttons to a toolbar for the standard layout modes (1x1, 1x2, 2x2 and 2x3)
        *
        * @param {$toolbar} The toolbar
        * @param {layout} A subject which determines the current layout setting
        */
        MenuBar.prototype.addLayoutButtons = function (layout) {
            var $layoutControl = $('<div>').addClass('layoutControl');

            $layoutControl.fallback(["touchstart", "pointerdown"], ["mouseup"], function (e) {
                var d = $(e.target).data();

                if (d != null && d.rows != null && d.cols != null) {
                    layout.write({ rows: d.rows, columns: d.cols });
                }
            });

            _.each(_.range(1, 5), function (rows) {
                var $row = $('<div>').addClass('layoutRow');

                _.each(_.range(1, 5), function (cols) {
                    var $cell = $('<span>').addClass('layoutCell').data({
                        rows: rows,
                        cols: cols
                    });

                    $cell.hover(function () {
                        var $cells = $layoutControl.find('.layoutCell');
                        $cells.each(function () {
                            var r = $(this).data('rows');
                            var c = $(this).data('cols');
                            $(this).toggleClass('layoutGlow', r <= rows && c <= cols);
                        });
                    }, function () {
                        var $cells = $layoutControl.find('.layoutCell');
                        $cells.removeClass('layoutGlow');
                    });

                    var $square = $('<span>').addClass('fa fa-square').css('display', 'inline-block').attr('title', cols + "x" + rows).data({
                        rows: rows,
                        cols: cols
                    }).appendTo($cell);

                    $row.append($cell);
                });

                $layoutControl.append($row);
            });

            this.$toolbar.append($layoutControl);

            return [$layoutControl];
        };

        MenuBar.prototype.addColorButtons = function (currentColor) {
            var $colorControl = $('<div>').addClass('colorControl');
            $colorControl.fallback(["touchstart", "pointerdown"], ["mouseup"], function (e) {
                var d = $(e.target).data();

                if (d != null) {
                    currentColor.write(d.color);
                    $colorControl.parents('.dropdown-icon').css("color", Annotations.COLORS[d.color]);
                }
            });

            var $row = $('<div>').addClass('layoutRow');

            _.each(Annotations.COLORS, function (color, index) {
                var $cell = $('<span>').addClass('layoutCell').data({ color: index });

                $('<span>').addClass('fa fa-square').css('display', 'inline-block').css("color", color).attr('title', "Color " + (index + 1)).data({ color: index }).appendTo($cell);

                $row.append($cell);
            });

            $colorControl.append($row);
            this.$toolbar.append($colorControl);

            return [$colorControl];
        };

        MenuBar.prototype.addMouseToolSettings = function (application) {
            var settings = application.settings.read();
            var selectedToolOriginalValue = settings.toolSelection;

            var selectedToolDidChange = false;

            var $overlay = $('.overlay');

            $overlay.html('');
            $overlay.addClass('overlay-standard');
            $overlay.show();

            var mousePanel = $('<div id="mouseSettingsPanel">');
            mousePanel.on("contextmenu", function (e) {
                e.preventDefault();
            });

            var header = $('<div id="mouseSettingsHeader">');

            var txtHeader = application.terminology.lookup(Terminology.Terms.MouseTooltip);
            var h1 = $("<h1>").text(txtHeader);

            var txtCancel = application.terminology.lookup(Terminology.Terms.Cancel);
            var btnCancel = $('<button type="button" class="btn">' + txtCancel + '</button>');

            var txtSave = application.terminology.lookup(Terminology.Terms.Save);
            var btnSave = $('<button type="button" class="btn btn-primary">' + txtSave + '</button>');

            var hidePanel = function () {
                $overlay.hide();
                mousePanel.remove();
            };

            btnCancel.on("click", function (e) {
                hidePanel();
            });

            btnSave.on("click", function (e) {
                // Only update the left mouse button tool if the user changes the dropdown
                if (selectedToolDidChange) {
                    application.selectedTool.write(parseInt(toolSelection.val()));

                    settings.toolSelection = parseInt(toolSelection.val());
                }

                // Always update the right mouse button and wheel
                application.selectedTool2.write(parseInt(toolSelection2.val()));
                application.selectedToolWheel.write(parseInt(toolSelectionWheel.val()));

                settings.toolSelection2 = parseInt(toolSelection2.val());
                settings.toolSelectionWheel = parseInt(toolSelectionWheel.val());

                // Writing to settings automatically fires the AJAX request to save the settings (See Application.ts)
                application.settings.write(settings);

                hidePanel();
            });

            var list = [
                { type: 0 /* Move */, name: application.terminology.lookup(Terminology.Terms.Move) },
                { type: 1 /* Scroll */, name: application.terminology.lookup(Terminology.Terms.Scroll) },
                { type: 2 /* Zoom */, name: application.terminology.lookup(Terminology.Terms.Zoom) },
                { type: 14 /* FreeRotate */, name: application.terminology.lookup(Terminology.Terms.FreeRotate) },
                { type: 3 /* Window */, name: application.terminology.lookup(Terminology.Terms.WindowLevelTooltip) },
                { type: 4 /* Select */, name: application.terminology.lookup(Terminology.Terms.SelectAnnotationTooltip) },
                { type: 5 /* Measure */, name: application.terminology.lookup(Terminology.Terms.Line) },
                { type: 12 /* Arrow */, name: application.terminology.lookup(Terminology.Terms.Arrow) },
                { type: 13 /* Angle */, name: application.terminology.lookup(Terminology.Terms.Angle) },
                { type: 7 /* CobbAngle */, name: application.terminology.lookup(Terminology.Terms.Cobb) },
                { type: 6 /* Rectangle */, name: application.terminology.lookup(Terminology.Terms.Rectangle) },
                { type: 8 /* Ellipse */, name: application.terminology.lookup(Terminology.Terms.Ellipse) },
                { type: 10 /* Text */, name: application.terminology.lookup(Terminology.Terms.Text) },
                { type: 11 /* Probe */, name: application.terminology.lookup(Terminology.Terms.Probe) },
                { type: 9 /* Localization */, name: application.terminology.lookup(Terminology.Terms.PlaneLocalization) }
            ];

            var listWheel = [
                { type: 34 /* None */, name: application.terminology.lookup(Terminology.Terms.ToolDependent) },
                { type: 2 /* Zoom */, name: application.terminology.lookup(Terminology.Terms.Zoom) },
                { type: 1 /* Scroll */, name: application.terminology.lookup(Terminology.Terms.Scroll) }
            ];

            // Left Mouse
            var toolSelection = $('<select id="toolSelection" name="toolSelection" />');
            for (var i = 0; i < list.length; i++) {
                $("<option />", { value: list[i].type, text: list[i].name }).appendTo(toolSelection);
            }

            toolSelection.val(settings.toolSelection.toString());

            toolSelection.on("change", function () {
                selectedToolDidChange = parseInt(toolSelection.val()) != selectedToolOriginalValue;
            });

            // Right Mouse
            var toolSelection2 = $('<select id="toolSelection2" name="toolSelection2" />');
            for (var i = 0; i < list.length; i++) {
                $("<option />", { value: list[i].type, text: list[i].name }).appendTo(toolSelection2);
            }

            toolSelection2.val(settings.toolSelection2.toString());

            // Wheel
            var toolSelectionWheel = $('<select id="toolSelectionWheel" name="toolSelectionWheel" />');
            for (var i = 0; i < listWheel.length; i++) {
                $("<option />", { value: listWheel[i].type, text: listWheel[i].name }).appendTo(toolSelectionWheel);
            }

            toolSelectionWheel.val(settings.toolSelectionWheel.toString());

            // Build Panel
            header.append(btnSave);
            header.append(btnCancel);
            header.append($("<h2/>").text(application.terminology.lookup(Terminology.Terms.Mouse)));

            mousePanel.append(header);

            mousePanel.append(toolSelection2);
            mousePanel.append($("<h4/>").text(application.terminology.lookup(Terminology.Terms.RightButton)));

            mousePanel.append(toolSelection);
            mousePanel.append($("<h4/>").text(application.terminology.lookup(Terminology.Terms.LeftButton)));

            mousePanel.append(toolSelectionWheel);
            mousePanel.append($("<h4/>").text(application.terminology.lookup(Terminology.Terms.WheelScroll)));

            $overlay.after(mousePanel);

            return [];
        };
        return MenuBar;
    })();
    Views.MenuBar = MenuBar;

    /**
    * A collection of helper methods for creating toolbars
    */
    var ContextMenu = (function () {
        function ContextMenu() {
        }
        ContextMenu.prototype.addLabel = function (options) {
            var item = {
                name: options.text.read(),
                type: "html"
            };

            if (options.visible !== undefined && !options.visible.read()) {
                item.disabled = true;
            }

            return item;
        };

        ContextMenu.prototype.addToolbarButton = function (options) {
            var item = {
                name: options.tooltip
            };

            if (options.click !== undefined) {
                item.callback = function (key, opts) {
                    options.click();
                    return true;
                };
            }

            if (options.enabled !== undefined && !options.enabled.read()) {
                item.disabled = true;
            }

            return item;
        };

        ContextMenu.prototype.addToggleButton = function (options) {
            return this.addToolbarButton({
                title: (options.selected.read() ? "\u2713 " : "") + options.title,
                tooltip: (options.selected.read() ? "\u2713 " : "") + options.tooltip,
                click: function () {
                    return Subjects.modify(options.selected, function (b) {
                        return !b;
                    });
                },
                enabled: options.enabled
            });
        };

        /**
        * Add a drop down button to a toolbar
        *
        * @param {$toolbar} The toolbar
        * @param {options.title} The text to display on the button
        * @param {options.tooltip} An optional tooltip
        * @param {options.icon} The name of the icon to display
        *
        * @returns The dropdown menu jQuery DOM wrapper object.
        */
        ContextMenu.prototype.addDropDownButton = function (options, buttons) {
            return {
                name: options.title,
                items: _.flatten(_.map(buttons, function (button) {
                    return button.add(new ContextMenu());
                }))
            };
        };

        /**
        * Context menus do not support layout buttons
        */
        ContextMenu.prototype.addLayoutButtons = function (layout) {
            return [];
        };

        ContextMenu.prototype.addColorButtons = function (currentColor) {
            return [];
        };

        ContextMenu.prototype.addMouseToolSettings = function (application) {
            return [];
        };

        /**
        * Apply a toolbar configuration, by attaching its items to that element's
        * context menu
        */
        ContextMenu.apply = function (selector, toolbarConfiguration, application) {
            $.contextMenu({
                selector: selector,
                build: function () {
                    var items = AbstractToolbars.createToolbarFromConfiguration(new ContextMenu(), toolbarConfiguration, application);

                    return {
                        items: new ContextMenuItemRenamer().rename(items)
                    };
                }
            });
        };
        return ContextMenu;
    })();
    Views.ContextMenu = ContextMenu;

    /**
    * This class is used to assign unique keys to submenu items to work around
    * the way in which jQuery.contextMenu handles arrays.
    */
    var ContextMenuItemRenamer = (function () {
        function ContextMenuItemRenamer() {
            this.index = 0;
        }
        ContextMenuItemRenamer.prototype.rename = function (items) {
            var _this = this;
            var result = {};

            _.each(items, function (item) {
                var key = "item" + _this.index;
                result[key] = item;

                if (item.items) {
                    item.items = _this.rename(item.items);
                }

                _this.index++;
            });

            return result;
        };
        return ContextMenuItemRenamer;
    })();

    var AbstractToolbars = (function () {
        function AbstractToolbars() {
        }
        /**
        * Add a dropdown button to an abstract toolbar.
        *
        * Note: this function is only needed here to avoid type inference issues in TypeScript.
        */
        AbstractToolbars.addDropDownButton = function (toolbar, options, buttons) {
            return toolbar.addDropDownButton(options, _.map(buttons, function (button) {
                return {
                    add: function (toolbar) {
                        return Either.either(button.action, function (onClick) {
                            return toolbar.addToolbarButton({
                                title: button.title,
                                tooltip: button.tooltip,
                                icon: button.icon,
                                click: onClick
                            });
                        }, function (tool) {
                            return AbstractToolbars.addMouseToolButton(toolbar, {
                                title: button.title,
                                tooltip: button.tooltip,
                                icon: button.icon,
                                tool: tool.tool,
                                selectedTool: tool.selectedTool,
                                selectedTool2: tool.selectedTool2
                            });
                        });
                    }
                };
            }));
        };

        /**
        * Add a button to a toolbar which allows the user to activate a mouse tool
        *
        * The button will appear selected when the mouse tool is active
        *
        * @param {$toolbar} The toolbar
        * @param {options.title} The text to display on the button
        * @param {options.tooltip} An optional tooltip
        * @param {options.icon} The name of the icon to display
        * @param {options.tool} The mouse tool to activate on clicking
        * @param {options.selectedTool} A writable subject which determines the currently selected tool
        *
        * @returns The button jQuery DOM wrapper object
        */
        AbstractToolbars.addMouseToolButton = function (toolbar, options) {
            return toolbar.addToggleButton({
                title: options.title,
                tooltip: options.tooltip,
                icon: options.icon,
                enabled: options.enabled,
                visible: options.visible,
                closePopupsOnClick: true,
                selected: Subjects.dimap(options.selectedTool, function (t) {
                    return t === options.tool;
                }, function (b) {
                    return b ? options.tool : 0 /* Move */;
                }),
                selected2: Subjects.dimap(options.selectedTool2, function (t) {
                    return t === options.tool;
                }, function (b) {
                    return b ? options.tool : 0 /* Move */;
                }),
                highlightParent: true
            });
        };

        /**
        * Create a toolbar from a configuration object
        */
        AbstractToolbars.createToolbarFromConfiguration = function (toolbar, config, application) {
            return _.flatten(_.map(config.items, function (item) {
                switch (item.itemType) {
                    case 0 /* Button */:
                        return AbstractToolbars.addStandardToolbarButton(toolbar, item.button, false, application);
                    case 1 /* Group */:
                        return [toolbar.addDropDownButton({
                                title: item.text_i18n && item.text ? application.terminology.lookup(new Classes.Term(item.text_i18n, item.text)) : item.text,
                                tooltip: item.tooltip_i18n && item.tooltip ? application.terminology.lookup(new Classes.Term(item.tooltip_i18n, item.tooltip)) : item.tooltip,
                                icon: item.icon
                            }, _.map(item.buttons, function (button) {
                                return {
                                    add: function (toolbar) {
                                        return AbstractToolbars.addStandardToolbarButton(toolbar, button, true, application);
                                    }
                                };
                            }))];
                }
            }));
        };

        /**
        * Add a standard toolbar button to the toolbar, or to a group
        */
        AbstractToolbars.addStandardToolbarButton = function (toolbar, buttonType, group, application) {
            var container = application.selectedSeriesContainer.read();
            var settings = application.settings.read();

            switch (buttonType) {
                case 9 /* Fit */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Fit),
                            tooltip: application.terminology.lookup(Terminology.Terms.FitTooltip),
                            icon: "w_fit",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.fit();
                                }
                            },
                            enabled: Subjects.map(application.selectedSeriesContainer, function (s) {
                                return s != null;
                            })
                        })];
                case 1 /* Zoom */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.Zoom),
                            tooltip: application.terminology.lookup(Terminology.Terms.ZoomTooltip),
                            icon: "w_zoom",
                            tool: 2 /* Zoom */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2
                        })];
                case 54 /* FreeRotate */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.FreeRotate),
                            tooltip: application.terminology.lookup(Terminology.Terms.FreeRotateTooltip),
                            icon: "w_rotate",
                            tool: 14 /* FreeRotate */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2
                        })];
                case 2 /* Move */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.Move),
                            tooltip: application.terminology.lookup(Terminology.Terms.MoveTooltip),
                            icon: "w_pan",
                            tool: 0 /* Move */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2
                        })];
                case 27 /* FlipV */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.FlipV),
                            tooltip: application.terminology.lookup(Terminology.Terms.FlipVTooltip),
                            icon: "w_flip_v",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series !== null) {
                                    var transform = series.transform;

                                    Subjects.modify(transform, function (t) {
                                        return {
                                            offsetX: t.offsetX,
                                            offsetY: t.offsetY,
                                            scale: t.scale,
                                            flipped: !t.flipped,
                                            rotation: (t.rotation + 2) % 4
                                        };
                                    });
                                }
                            }
                        })];
                case 3 /* Flip */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Flip),
                            tooltip: application.terminology.lookup(Terminology.Terms.FlipTooltip),
                            icon: "w_flip",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series !== null) {
                                    var transform = series.transform;

                                    Subjects.modify(transform, function (t) {
                                        return {
                                            offsetX: t.offsetX,
                                            offsetY: t.offsetY,
                                            scale: t.scale,
                                            flipped: !t.flipped,
                                            rotation: t.rotation
                                        };
                                    });
                                }
                            }
                        })];
                case 4 /* Rotate */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Rotate),
                            tooltip: application.terminology.lookup(Terminology.Terms.RotateTooltip),
                            icon: "w_rotate",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    Subjects.modify(series.transform, function (transform) {
                                        return {
                                            offsetX: transform.offsetX,
                                            offsetY: transform.offsetX,
                                            scale: transform.scale,
                                            flipped: transform.flipped,
                                            rotation: transform.rotation + 1
                                        };
                                    });
                                }
                            },
                            enabled: Subjects.map(application.selectedSeriesContainer, function (s) {
                                return s != null;
                            })
                        })];
                case 5 /* Scroll */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.Scroll),
                            tooltip: application.terminology.lookup(Terminology.Terms.ScrollTooltip),
                            icon: "w_scroll",
                            tool: 1 /* Scroll */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2
                        })];
                case 6 /* WindowLevel */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.SelectWindowLevel),
                            tooltip: application.terminology.lookup(Terminology.Terms.SelectWindowLevelTooltip),
                            icon: "w_pan",
                            tool: 3 /* Window */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2,
                            enabled: Subjects.bind(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.ret(null) : series.windowLevel;
                            }, function (_, windowLevel) {
                                return windowLevel !== null;
                            })
                        })];
                case 76 /* WindowLevelDirect */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.WindowLevel),
                            tooltip: application.terminology.lookup(Terminology.Terms.SelectWindowLevelTooltip),
                            icon: "w_window",
                            tool: 3 /* Window */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2,
                            enabled: Subjects.bind(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.ret(null) : series.windowLevel;
                            }, function (_, windowLevel) {
                                return windowLevel !== null;
                            })
                        })];
                case 28 /* ResetWindowLevel */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Reset),
                            tooltip: application.terminology.lookup(Terminology.Terms.ResetTooltip),
                            icon: "w_reload",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.resetWindowLevel();
                                }
                            },
                            enabled: Subjects.map(application.selectedSeriesContainer, function (s) {
                                return s != null;
                            })
                        })];
                case 24 /* SavePreset */:
                    if (!LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.SavePreset),
                                tooltip: application.terminology.lookup(Terminology.Terms.SavePresetTooltip),
                                icon: "w_save",
                                click: function () {
                                    application.saveWindowLevelPreset();
                                },
                                enabled: Subjects.zip(Subjects.map(application.selectedSeriesContainer, function (series) {
                                    if (series && series.series && series.series.instances.length > 0) {
                                        return WindowLevelPresets.shouldUse16BitWindowLevel(series.series.instances[0]);
                                    }

                                    return false;
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                })
                            })];
                    }
                    return [];
                case 29 /* WindowLevelPresets */:
                    if (container) {
                        var series = container.series;

                        if (series) {
                            var instance = series.instances[0];

                            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                                var modality = series.seriesAttributes.modality;
                                var modalitySettings;

                                if (settings.modalities) {
                                    modalitySettings = _.find(settings.modalities, function (m) {
                                        return m.modality === modality;
                                    });
                                }

                                var presets;

                                if (modalitySettings && modalitySettings.presets !== undefined && modalitySettings.presets.length > 0) {
                                    presets = modalitySettings.presets;
                                } else {
                                    var index = container.selectedInstanceIndex.read();
                                    if (container.series) {
                                        var instance = container.series.instances[index];
                                        presets = WindowLevelPresets.defaults(modality, application.terminology);
                                    } else {
                                        presets = [];
                                    }
                                }

                                return _.map(_.take(presets, 10), function (preset, index) {
                                    return toolbar.addToolbarButton({
                                        title: preset.name.substr(0, 8),
                                        tooltip: preset.name,
                                        icon: "w_window",
                                        closePopupsOnClick: true,
                                        click: function () {
                                            container.applyWindowLevelPreset(index);
                                        }
                                    });
                                });
                            }
                        }
                    }
                    return [];
                case 103 /* ColorTablePresets */:
                    if (container) {
                        var series = container.series;

                        if (series) {
                            var modality = series.seriesAttributes.modality;
                            var modalitySettings;

                            if (settings.modalities) {
                                modalitySettings = _.find(settings.modalities, function (m) {
                                    return m.modality === modality;
                                });
                            }

                            var colorTablePresets;

                            if (modalitySettings && modalitySettings.colorTablePresets !== undefined && modalitySettings.colorTablePresets.length > 0) {
                                colorTablePresets = modalitySettings.colorTablePresets;
                            } else {
                                if (container.series) {
                                    colorTablePresets = ColorTablePresets.defaults(modality, application.terminology);
                                } else {
                                    colorTablePresets = [];
                                }
                            }

                            return _.map(_.take(colorTablePresets, 10), function (preset, index) {
                                return toolbar.addToolbarButton({
                                    title: preset.name.substr(0, 8),
                                    tooltip: preset.name,
                                    icon: "w_colors",
                                    closePopupsOnClick: true,
                                    click: function () {
                                        container.applyColorTablePreset(index);
                                    }
                                });
                            });
                        }
                    }
                    return [];
                case 73 /* DetectWindowLevel */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.DetectWindowLevel),
                                tooltip: application.terminology.lookup(Terminology.Terms.DetectWindowLevelTooltip),
                                icon: "w_window",
                                closePopupsOnClick: true,
                                click: function () {
                                    container.detectWindowLevel();
                                }
                            })];
                    }
                    return [];
                case 30 /* SelectAnnotation */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.SelectAnnotation),
                                tooltip: application.terminology.lookup(Terminology.Terms.SelectAnnotationTooltip),
                                icon: "w_select",
                                tool: 4 /* Select */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2,
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 31 /* DeleteAnnotation */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.DeleteAnnotation),
                                tooltip: application.terminology.lookup(Terminology.Terms.DeleteAnnotationTooltip),
                                icon: "w_delete",
                                click: function () {
                                    var series = application.selectedSeriesContainer.read();
                                    if (series != null) {
                                        series.deleteSelectedMeasurement();
                                    }
                                },
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement !== null && measurement.editable;
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                })
                            })];
                    }
                    return [];
                case 32 /* FillAnnotation */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Filled),
                                tooltip: application.terminology.lookup(Terminology.Terms.FilledTooltip),
                                icon: "w_fill",
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement != null && measurement.editable;
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                }),
                                selected: Subjects.lens(Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(null) : series.selectedMeasurement;
                                }), function (measurement) {
                                    return measurement != null && measurement.filled;
                                }, function (measurement, value) {
                                    if (measurement) {
                                        measurement.filled = value;

                                        var series = application.selectedSeriesContainer.read();
                                        series.editMeasurement(measurement);
                                        series.clearSelection();
                                    }
                                    application.renderAllFrames();
                                })
                            })];
                    }
                    return [];
                case 33 /* ExportGSPS */:
                    if (application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportGSPS),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportGSPSTooltip),
                                icon: "w_save",
                                click: function () {
                                    var series = application.selectedSeriesContainer.read();
                                    if (series != null) {
                                        series.saveGSPS();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 34 /* Line */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Line),
                                tooltip: application.terminology.lookup(Terminology.Terms.LineTooltip),
                                icon: "w_line",
                                tool: 5 /* Measure */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 97 /* LineAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.LineAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.LineAnnotateTooltip),
                                icon: "w_line",
                                tool: 25 /* LineAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 107 /* CalibrateLine */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.LineCalibrate),
                                tooltip: application.terminology.lookup(Terminology.Terms.LineCalibrateTooltip),
                                icon: "w_line",
                                tool: 30 /* CalibrateLine */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 35 /* Arrow */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Arrow),
                                tooltip: application.terminology.lookup(Terminology.Terms.ArrowTooltip),
                                icon: "w_arrow",
                                tool: 12 /* Arrow */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 94 /* ArrowAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.ArrowAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.ArrowAnnotateTooltip),
                                icon: "w_arrow",
                                tool: 22 /* ArrowAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 53 /* Angle */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Angle),
                                tooltip: application.terminology.lookup(Terminology.Terms.AngleTooltip),
                                icon: "w_angle",
                                tool: 13 /* Angle */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 36 /* Cobb */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Cobb),
                                tooltip: application.terminology.lookup(Terminology.Terms.CobbTooltip),
                                icon: "w_cobb",
                                tool: 7 /* CobbAngle */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 37 /* Rectangle */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Rectangle),
                                tooltip: application.terminology.lookup(Terminology.Terms.RectangleTooltip),
                                icon: "w_rect",
                                tool: 6 /* Rectangle */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 98 /* RectangleAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.RectangleAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.RectangleAnnotateTooltip),
                                icon: "w_rect",
                                tool: 26 /* RectangleAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 38 /* Ellipse */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Ellipse),
                                tooltip: application.terminology.lookup(Terminology.Terms.EllipseTooltip),
                                icon: "w_ellipse",
                                tool: 8 /* Ellipse */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 96 /* EllipseAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.EllipseAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.EllipseAnnotateTooltip),
                                icon: "w_ellipse",
                                tool: 24 /* EllipseAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 58 /* Radius */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Radius),
                                tooltip: application.terminology.lookup(Terminology.Terms.RadiusTooltip),
                                icon: "w_ellipse",
                                tool: 16 /* Circle */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 85 /* Circle */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Circle),
                                tooltip: application.terminology.lookup(Terminology.Terms.CircleTooltip),
                                icon: "w_ellipse",
                                tool: 18 /* DropCircle */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                    return [];
                case 95 /* CircleAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.CircleAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.CircleAnnotateTooltip),
                                icon: "w_ellipse",
                                tool: 23 /* CircleAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 86 /* Square */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Square),
                                tooltip: application.terminology.lookup(Terminology.Terms.SquareTooltip),
                                icon: "w_rect",
                                tool: 19 /* DropSquare */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 99 /* SquareAnnotate */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.SquareAnnotate),
                                tooltip: application.terminology.lookup(Terminology.Terms.SquareAnnotateTooltip),
                                icon: "w_rect",
                                tool: 27 /* SquareAnnotate */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 88 /* OrthoAxes */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.OrthoAxes),
                                tooltip: application.terminology.lookup(Terminology.Terms.OrthoAxesTooltip),
                                icon: "w_axes",
                                tool: 20 /* OrthoAxes */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 93 /* FemoralHead */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.FemoralHead),
                                tooltip: application.terminology.lookup(Terminology.Terms.FemoralHeadTooltip),
                                icon: "w_ellipse",
                                tool: 21 /* FemoralHead */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 105 /* Polygon */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Polygon),
                                tooltip: application.terminology.lookup(Terminology.Terms.PolygonTooltip),
                                icon: "w_trace",
                                tool: 28 /* Polygon */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 106 /* Trace */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Trace),
                                tooltip: application.terminology.lookup(Terminology.Terms.TraceTooltip),
                                icon: "w_trace",
                                tool: 29 /* Trace */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 140 /* ProstateTool */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.ProstateTool),
                                tooltip: application.terminology.lookup(Terminology.Terms.ProstateToolTooltip),
                                icon: "w_line",
                                tool: 33 /* ProstateTool */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 127 /* Area */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Area),
                                tooltip: application.terminology.lookup(Terminology.Terms.PaintTooltip),
                                icon: "w_paint",
                                tool: 32 /* Area */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 39 /* Text */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Text),
                                tooltip: application.terminology.lookup(Terminology.Terms.TextTooltip),
                                icon: "w_text",
                                tool: 10 /* Text */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 121 /* Stamp */:
                    if (application.permissions.annotation_edit !== 0) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Stamp),
                                tooltip: application.terminology.lookup(Terminology.Terms.StampTooltip),
                                icon: "w_lock",
                                tool: 31 /* Stamp */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2
                            })];
                    }
                    return [];
                case 119 /* Propagate */:
                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.Propagate),
                            tooltip: application.terminology.lookup(Terminology.Terms.PropagateTooltip),
                            icon: "w_copy",
                            enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                            }, function (_, measurement) {
                                return measurement != null && measurement.editable && (measurement instanceof Measurements.Text);
                            }), application.isRecording, function (b1, b2) {
                                return b1 && !b2;
                            }),
                            selected: application.propagateMode
                        })];
                case 120 /* PropagateAll */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Propagate),
                            tooltip: application.terminology.lookup(Terminology.Terms.PropagateAllTooltip),
                            icon: "w_copy",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.propapateToSeries();
                                }
                            },
                            enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                            }, function (_, measurement) {
                                return measurement != null && measurement.editable && (measurement instanceof Measurements.Text);
                            }), application.isRecording, function (b1, b2) {
                                return b1 && !b2;
                            })
                        })];
                case 128 /* ThresholdToArea */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Threshold),
                            tooltip: application.terminology.lookup(Terminology.Terms.ThresholdTooltip),
                            icon: "w_threshold",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.thresholdToArea(false, false);
                                }
                            }
                        })];
                case 132 /* ThresholdRangeToArea */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Range),
                            tooltip: application.terminology.lookup(Terminology.Terms.RangeTooltip),
                            icon: "w_threshold",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.thresholdToArea(false, true);
                                }
                            }
                        })];
                case 129 /* ShrinkWrapToArea */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.ShrinkWrap),
                            tooltip: application.terminology.lookup(Terminology.Terms.ShrinkWrapTooltip),
                            icon: "w_shrinkwrap",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.thresholdToArea(true, false);
                                }
                            }
                        })];
                case 8 /* Probe */:
                    if (Rendering.getRenderingMode() !== 2 /* Simple */) {
                        return [AbstractToolbars.addMouseToolButton(toolbar, {
                                title: application.terminology.lookup(Terminology.Terms.Probe),
                                tooltip: application.terminology.lookup(Terminology.Terms.ProbeTooltip),
                                icon: "w_select",
                                tool: 11 /* Probe */,
                                selectedTool: application.selectedTool,
                                selectedTool2: application.selectedTool2,
                                enabled: Subjects.zip(Subjects.map(application.selectedSeriesContainer, function (series) {
                                    if (series && series.series && series.series.instances.length > 0) {
                                        return WindowLevelPresets.shouldUse16BitWindowLevel(series.series.instances[0]);
                                    }

                                    return false;
                                }), application.recordingMode, function (b1, b2) {
                                    return b1 && !b2;
                                })
                            })];
                    }
                    return [];
                case 10 /* Reset */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Reset),
                            tooltip: application.terminology.lookup(Terminology.Terms.ResetTooltip),
                            icon: "w_reload",
                            click: function () {
                                var series = application.selectedSeriesContainer.read();

                                if (series != null) {
                                    series.reset();
                                }
                            },
                            enabled: Subjects.map(application.selectedSeriesContainer, function (s) {
                                return s != null;
                            })
                        })];
                case 40 /* ReferenceLines */:
                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.ReferenceLines),
                            tooltip: application.terminology.lookup(Terminology.Terms.ReferenceLinesTooltip),
                            icon: "w_reflines",
                            selected: application.referenceLinesActive
                        })];
                case 41 /* LinkedSeries */:
                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.LinkedSeries),
                            tooltip: application.terminology.lookup(Terminology.Terms.LinkedSeriesTooltip),
                            icon: "w_link",
                            selected: application.linkSeries
                        })];
                case 42 /* PlaneLocalization */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.PlaneLocalization),
                            tooltip: application.terminology.lookup(Terminology.Terms.PlaneLocalizationTooltip),
                            icon: "w_3d",
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2,
                            tool: 9 /* Localization */,
                            enabled: Subjects.map(application.selectedInstance(), function (instance) {
                                return instance !== null && SeriesGeometry.hasGeometricMetadata(instance.instance);
                            })
                        })];
                case 70 /* MPR */:
                    var enableMPR = application.accountSettings.viewer_enable_mpr;
                    if (enableMPR !== undefined && enableMPR > 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.MPR),
                                tooltip: application.terminology.lookup(Terminology.Terms.MPRTooltip),
                                icon: "w_mpr",
                                click: function () {
                                    application.mpr();
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 43 /* Maximize */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Maximize),
                            tooltip: application.terminology.lookup(Terminology.Terms.MaximizeTooltip),
                            icon: "w_max",
                            enabled: Subjects.zip(Subjects.map(application.layout, function (layout) {
                                return layout.rows > 1 || layout.columns > 1;
                            }), application.isRecording, function (b1, b2) {
                                return b1 && !b2;
                            }),
                            click: function () {
                                var selectedSeries = application.selectedSeriesContainer.read();

                                if (selectedSeries) {
                                    application.magnifyMinify(selectedSeries);
                                }
                            }
                        })];
                case 44 /* NewWindow */:
                    if (!LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NewWindow),
                                tooltip: application.terminology.lookup(Terminology.Terms.NewWindowTooltip),
                                icon: "w_tab",
                                click: function () {
                                    application.openNewWindow(0);
                                }
                            })];
                    }
                    return [];
                case 45 /* LayoutButtons */:
                    return toolbar.addLayoutButtons(application.layout);
                case 130 /* ColorButtons */:
                    return toolbar.addColorButtons(application.currentColor);
                case 13 /* Print */:
                    var enablePrinting = application.accountSettings.enable_viewer_print;

                    if (!LocalViewer.isStandardLocalViewer() && (enablePrinting === undefined || enablePrinting > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Print),
                                tooltip: application.terminology.lookup(Terminology.Terms.PrintTooltip),
                                icon: "w_print",
                                click: function () {
                                    application.print();
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 15 /* TextAnnotations */:
                    var selected;

                    if (application.globalTextToggle) {
                        selected = Subjects.bindW(application.selectedSeriesContainer, function (_) {
                            return application.globalTextVisible;
                        });
                    } else {
                        selected = Subjects.bindW(application.selectedSeriesContainer, function (series) {
                            return series === null ? Subjects.retW(false) : series.infoVisible;
                        });
                    }

                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.Show),
                            tooltip: application.terminology.lookup(Terminology.Terms.InfoTooltip),
                            icon: "w_info",
                            selected: selected,
                            toggledData: {
                                title: application.terminology.lookup(Terminology.Terms.Hide),
                                tooltip: application.terminology.lookup(Terminology.Terms.InfoTooltip),
                                icon: "w_info",
                                baseClass: 'showHide'
                            }
                        })];
                case 16 /* Measurements */:
                    var enableToggleAnnotations = application.accountSettings.enable_viewer_toggle_annotations;

                    if (enableToggleAnnotations === undefined || enableToggleAnnotations > 0) {
                        var selected;

                        if (application.globalMeasurementsToggle) {
                            selected = Subjects.bindW(application.selectedSeriesContainer, function (_) {
                                return application.globalMeasurementsVisible;
                            });
                        } else {
                            selected = Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.retW(false) : series.measurementsVisible;
                            });
                        }

                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Show),
                                tooltip: application.terminology.lookup(Terminology.Terms.ShowAnnotationsTooltip),
                                icon: "w_measurements",
                                selected: selected,
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Hide),
                                    tooltip: application.terminology.lookup(Terminology.Terms.ShowAnnotationsTooltip),
                                    icon: "w_measurements",
                                    baseClass: 'showHide'
                                }
                            })];
                    }
                    return [];
                case 104 /* UltrasoundRegions */:
                    var selected = Subjects.bindW(application.selectedSeriesContainer, function (series) {
                        return series === null ? Subjects.retW(false) : series.ultrasoundRegionsVisible;
                    });

                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.ShowUltrasoundRegion),
                            tooltip: application.terminology.lookup(Terminology.Terms.UltrasoundRegionTooltip),
                            icon: "w_rect",
                            selected: selected,
                            toggledData: {
                                title: application.terminology.lookup(Terminology.Terms.HideUltrasoundRegion),
                                tooltip: application.terminology.lookup(Terminology.Terms.UltrasoundRegionTooltip),
                                icon: "w_rect",
                                baseClass: 'showHide'
                            }
                        })];
                case 84 /* Ruler */:
                    var enableToggleAnnotations = application.accountSettings.enable_viewer_toggle_annotations;

                    if (enableToggleAnnotations === undefined || enableToggleAnnotations > 0) {
                        var selected;

                        if (application.globalRulerToggle) {
                            selected = Subjects.bindW(application.selectedSeriesContainer, function (_) {
                                return application.globalRulerVisible;
                            });
                        } else {
                            selected = Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                return series === null ? Subjects.retW(false) : series.rulerVisible;
                            });
                        }

                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Show),
                                tooltip: application.terminology.lookup(Terminology.Terms.ShowRulerTooltip),
                                icon: "w_ruler",
                                selected: selected,
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Hide),
                                    tooltip: application.terminology.lookup(Terminology.Terms.ShowRulerTooltip),
                                    icon: "w_ruler",
                                    baseClass: 'showHide'
                                }
                            })];
                    }
                    return [];
                case 74 /* AnnotationsDetailToggle */:
                    var enableToggleAnnotations = application.accountSettings.enable_viewer_toggle_annotations;

                    if (enableToggleAnnotations === undefined || enableToggleAnnotations > 0) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Show),
                                tooltip: application.terminology.lookup(Terminology.Terms.ShowAnnotationsDetailTooltip),
                                icon: "w_ann_detail_toggle",
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.measurementsDetailsVisible;
                                }),
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        var measurementsVisible = selectedSeries.measurementsVisible.read();
                                        var measurementsDetailsVisible = selectedSeries.measurementsDetailsVisible.read();
                                        if (!measurementsVisible && measurementsDetailsVisible) {
                                            selectedSeries.measurementsVisible.write(true);
                                        }
                                    }
                                },
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Hide),
                                    tooltip: application.terminology.lookup(Terminology.Terms.ShowAnnotationsDetailTooltip),
                                    icon: "w_ann_detail_toggle",
                                    baseClass: 'showHide'
                                }
                            })];
                    }
                    return [];
                case 139 /* AnnotationsCreatedByOthersToggle */:
                    var enableToggleAnnotations = application.accountSettings.enable_viewer_toggle_annotations;

                    if (enableToggleAnnotations === undefined || enableToggleAnnotations > 0) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Show),
                                tooltip: application.terminology.lookup(Terminology.Terms.ToggleCreatedByOthersAnnotationsTooltip),
                                icon: "w_ann_detail_toggle",
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.annotationsCreatedByOtherUsersVisible;
                                }),
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        var measurementsVisible = selectedSeries.measurementsVisible.read();
                                        var measurementsCreatedByOthersVisible = selectedSeries.annotationsCreatedByOtherUsersVisible.read();
                                        if (!measurementsVisible && measurementsCreatedByOthersVisible) {
                                            selectedSeries.measurementsVisible.write(true);
                                        }
                                    }
                                },
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Hide),
                                    tooltip: application.terminology.lookup(Terminology.Terms.ToggleCreatedByOthersAnnotationsTooltip),
                                    icon: "w_ann_detail_toggle",
                                    baseClass: 'showHide'
                                }
                            })];
                    }
                    return [];
                case 17 /* Invert */:
                    if (Rendering.getRenderingMode() !== 2 /* Simple */) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Invert),
                                tooltip: application.terminology.lookup(Terminology.Terms.InvertTooltip),
                                icon: "w_invert",
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.invertActive;
                                })
                            })];
                    }
                    return [];
                case 118 /* Subtraction */:
                    if (Rendering.getRenderingMode() !== 2 /* Simple */) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Subtract),
                                tooltip: application.terminology.lookup(Terminology.Terms.SubtractTooltip),
                                icon: "w_calc",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(series.supportsSubtraction());
                                }),
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.subtractionActive;
                                })
                            })];
                    }
                    return [];
                case 71 /* Enhance */:
                    if (Rendering.getRenderingMode() === 1 /* WebGL */) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Enhance),
                                tooltip: application.terminology.lookup(Terminology.Terms.EnhanceTooltip),
                                icon: "w_enhance",
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.enhance;
                                })
                            })];
                    }
                    return [];
                case 46 /* ExportPNG */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportPNG),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportPNGTooltip),
                                icon: "w_png",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportCurrentImages();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 92 /* StorePNG */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (!LocalViewer.isLocalViewer() && ((enableExport === undefined || enableExport > 0))) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.StorePNG),
                                tooltip: application.terminology.lookup(Terminology.Terms.StorePNGTooltip),
                                icon: "w_png",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.storeCurrentImage();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 72 /* ExportAllPNG */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (!LocalViewer.isLocalViewer() && (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportAllPNG),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportAllPNGTooltip),
                                icon: "w_png",
                                click: function () {
                                    application.exportCurrentImages();
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 47 /* SecondaryCapture */:
                    var enableExport = application.accountSettings.enable_viewer_export;
                    var uploadPermssion = application.permissions.study_upload !== 0;

                    if (!LocalViewer.isStandardLocalViewer() && (enableExport === undefined || enableExport > 0) && uploadPermssion) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.SecondaryCapture),
                                tooltip: application.terminology.lookup(Terminology.Terms.SecondaryCaptureTooltip),
                                icon: "w_dcm",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.secondaryCapture();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 48 /* Metadata */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (!LocalViewer.isLocalViewer() && (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.EditMetadata),
                                tooltip: application.terminology.lookup(Terminology.Terms.EditMetadataTooltip),
                                icon: "w_dump",
                                click: function () {
                                    return application.editMetadata();
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 21 /* KeyImage */:
                    if (!LocalViewer.isStandardLocalViewer() && application.permissions.keyimage_edit !== 0) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.KeyImage),
                                tooltip: application.terminology.lookup(Terminology.Terms.KeyImageTooltip),
                                icon: "w_key",
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.onWrite(Subjects.bindW(series.selectedInstanceIndex, function (index) {
                                        if (series.series) {
                                            var instance = series.series.instances[index];

                                            return instance.instanceAttributes.isKeyImage;
                                        }
                                        return Subjects.retW(false);
                                    }), function (_) {
                                        return series.updateKeyImageStatusInServices();
                                    });
                                }),
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 14 /* Thumbs */:
                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.Show),
                            tooltip: application.terminology.lookup(Terminology.Terms.ThumbnailsTooltip),
                            icon: "w_thumb",
                            selected: application.thumbnailsVisible,
                            toggledData: {
                                title: application.terminology.lookup(Terminology.Terms.Hide),
                                tooltip: application.terminology.lookup(Terminology.Terms.ThumbnailsTooltip),
                                icon: "w_thumb",
                                baseClass: 'showHide'
                            }
                        })];
                case 23 /* Actions */:
                    if (!LocalViewer.isStandardLocalViewer() && application.studies[0].actions.length > 0) {
                        return _.map(application.studies[0].actions, function (action) {
                            return toolbar.addToolbarButton({
                                title: action.name.truncate(10),
                                tooltip: action.name,
                                icon: "w_action",
                                click: function () {
                                    application.invokeStudyAction(application.studies[0], action);
                                }
                            });
                        });
                    }
                    return [];
                case 25 /* DeleteImage */:
                    if (!LocalViewer.isStandardLocalViewer() && application.permissions.study_delete_image !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.DeleteImage),
                                icon: "w_delete",
                                tooltip: application.terminology.lookup(Terminology.Terms.DeleteImageTooltip),
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.deleteSelectedImage();
                                    }
                                }
                            })];
                    }
                    return [];
                case 26 /* DeleteSeries */:
                    if (!LocalViewer.isStandardLocalViewer() && application.permissions.study_delete_image !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.DeleteSeries),
                                icon: "w_delete",
                                tooltip: application.terminology.lookup(Terminology.Terms.DeleteSeriesTooltip),
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries && selectedSeries.series) {
                                        application.deleteSeries(selectedSeries.series);
                                    }
                                }
                            })];
                    }
                    return [];
                case 22 /* Settings */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Settings),
                                tooltip: application.terminology.lookup(Terminology.Terms.SettingsTooltip),
                                icon: "w_settings",
                                click: function () {
                                    application.editSettings();
                                }
                            })];
                    }
                    return [];
                case 19 /* Cine */:
                    if (container && container.series && !LocalViewer.isLocalViewer() && !Cine.isBrowserSupported() && Multiframe.isMultiframe(container.series)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Cine),
                                tooltip: application.terminology.lookup(Terminology.Terms.CineTooltip),
                                icon: "w_cine",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.openMultiframeCineInNewTab();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 49 /* Play */:
                    if (container && container.series && (LocalViewer.isLocalViewer() || Cine.isBrowserSupported() || !Multiframe.isMultiframe(container.series))) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Play),
                                tooltip: application.terminology.lookup(Terminology.Terms.PlayTooltip),
                                icon: "w_play",
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Pause),
                                    tooltip: application.terminology.lookup(Terminology.Terms.PauseTooltip),
                                    icon: "w_pause",
                                    baseClass: null
                                },
                                selected: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.cineActive;
                                })
                            })];
                    }
                    return [];
                case 50 /* Faster */:
                    if (container && container.series && (LocalViewer.isLocalViewer() || Cine.isBrowserSupported() || !Multiframe.isMultiframe(container.series))) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Faster),
                                tooltip: application.terminology.lookup(Terminology.Terms.FasterTooltip),
                                icon: "w_cine_faster",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.increaseCineSpeed();
                                    }
                                },
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.cineActive;
                                })
                            })];
                    }
                    return [];
                case 51 /* Slower */:
                    if (container && container.series && (LocalViewer.isLocalViewer() || Cine.isBrowserSupported() || !Multiframe.isMultiframe(container.series))) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Slower),
                                tooltip: application.terminology.lookup(Terminology.Terms.SlowerTooltip),
                                icon: "w_cine_slower",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.decreaseCineSpeed();
                                    }
                                },
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.cineActive;
                                })
                            })];
                    }
                    return [];
                case 52 /* FPSLabel */:
                    if (container && container.series && (LocalViewer.isLocalViewer() || Cine.isBrowserSupported() || !Multiframe.isMultiframe(container.series))) {
                        return [toolbar.addLabel({
                                text: Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret("") : Subjects.map(series.cineSpeed, function (fps) {
                                        return "{0} {fps}".replace("{fps}", application.terminology.lookup(Terminology.Terms.FPS)).replace("{0}", fps.toFixed(2));
                                    });
                                }, function (_, text) {
                                    return text;
                                }),
                                visible: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : series.cineActive;
                                })
                            })];
                    }
                    return [];
                case 20 /* Record */:
                    if (!LocalViewer.isLocalViewer() && application.permissions.study_audio_record) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Record),
                                tooltip: application.terminology.lookup(Terminology.Terms.RecordTooltip),
                                icon: "w_record",
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Stop),
                                    tooltip: application.terminology.lookup(Terminology.Terms.StopTooltip),
                                    icon: "w_stop",
                                    baseClass: null
                                },
                                selected: application.recordingMode,
                                enabled: Subjects.map(application.meetingHost, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 116 /* ShowRecordings */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Recordings),
                                tooltip: application.terminology.lookup(Terminology.Terms.RecordingsTooltip),
                                icon: "w_recordings",
                                click: function () {
                                    container.application.toggleRecordingsInfo();
                                }
                            })];
                    }
                    return [];
                case 131 /* ShowAttachments */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Reports),
                                tooltip: application.terminology.lookup(Terminology.Terms.ReportsTooltip),
                                icon: "w_attachment",
                                click: function () {
                                    container.application.toggleAttachmentInfo();
                                }
                            })];
                    }
                    return [];
                case 141 /* ShowAllGSPS */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.GSPS),
                                tooltip: application.terminology.lookup(Terminology.Terms.GSPSTooltip),
                                icon: "w_annotations",
                                click: function () {
                                    container.application.toggleGSPSInfo();
                                }
                            })];
                    }
                    return [];
                case 109 /* PlayRecording */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.Play),
                                tooltip: application.terminology.lookup(Terminology.Terms.PlayTooltip),
                                icon: "w_play",
                                toggledData: {
                                    title: application.terminology.lookup(Terminology.Terms.Pause),
                                    tooltip: application.terminology.lookup(Terminology.Terms.PauseTooltip),
                                    icon: "w_pause",
                                    baseClass: null
                                },
                                selected: application.playbackMode,
                                enabled: Subjects.retW(true)
                            })];
                    }
                    return [];
                case 110 /* StopPlayback */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Stop),
                                tooltip: application.terminology.lookup(Terminology.Terms.StopTooltip),
                                icon: "w_stop",
                                click: function () {
                                    container.application.playbackStopped();
                                }
                            })];
                    }
                    return [];
                case 111 /* RewindPlayback */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Rewind),
                                tooltip: application.terminology.lookup(Terminology.Terms.RewindTooltip),
                                icon: "w_rewind",
                                click: function () {
                                    container.application.rewindPlayback();
                                }
                            })];
                    }
                    return [];
                case 112 /* FastForwardPlayback */:
                    if (container && container.series && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Forward),
                                tooltip: application.terminology.lookup(Terminology.Terms.ForwardTooltip),
                                icon: "w_forward",
                                click: function () {
                                    container.application.fastForwardPlayback();
                                }
                            })];
                    }
                    return [];
                case 55 /* GSPSLayers */:
                    var series = container.series;

                    if (series && series.studyAttributes && series.studyAttributes.presentationStateIODs) {
                        return _.map(series.studyAttributes.presentationStateIODs, function (gsps) {
                            var title = gsps.identificationModule.presentationCreationDate;
                            var tooltip = title + " (" + gsps.graphicAnnotationModule.graphicAnnotationSequence.length + ")";

                            return toolbar.addToggleButton({
                                title: title,
                                tooltip: tooltip,
                                icon: "w_measurements",
                                selected: Subjects.lens(container.hiddenGSPSLayers, function (layers) {
                                    return !_.contains(layers, gsps);
                                }, function (layers, shown) {
                                    if (shown) {
                                        return _.without(layers, gsps);
                                    } else {
                                        return [gsps].concat(layers);
                                    }
                                })
                            });
                        });
                    }

                    return [];
                case 56 /* Magnify */:
                    return [AbstractToolbars.addMouseToolButton(toolbar, {
                            title: application.terminology.lookup(Terminology.Terms.Magnify),
                            tooltip: application.terminology.lookup(Terminology.Terms.MagnifyTooltip),
                            icon: "w_zoom",
                            tool: 15 /* Magnify */,
                            selectedTool: application.selectedTool,
                            selectedTool2: application.selectedTool2
                        })];
                case 57 /* Anonymize */:
                    if (container && application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Anonymize),
                                tooltip: application.terminology.lookup(Terminology.Terms.AnonymizeTooltip),
                                icon: "w_dcm",
                                click: function () {
                                    container.application.anonymize(1 /* Series */);
                                }
                            })];
                    }
                    return [];
                case 59 /* AnonymizeStudy */:
                    if (container && application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.AnonymizeStudy),
                                tooltip: application.terminology.lookup(Terminology.Terms.AnonymizeStudyTooltip),
                                icon: "w_dcm",
                                click: function () {
                                    container.application.anonymize(0 /* Study */);
                                }
                            })];
                    }
                    return [];
                case 60 /* AnonymizeImage */:
                    if (container && application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.AnonymizeImage),
                                tooltip: application.terminology.lookup(Terminology.Terms.AnonymizeImageTooltip),
                                icon: "w_dcm",
                                click: function () {
                                    container.application.anonymize(2 /* Image */);
                                }
                            })];
                    }
                    return [];
                case 125 /* CropSeries */:
                    if (container && application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.CropSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.CropSeriesTooltip),
                                icon: "w_crop",
                                click: function () {
                                    container.application.cropStudy();
                                }
                            })];
                    }
                    return [];
                case 108 /* SplitStudy */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.SplitStudy),
                                tooltip: application.terminology.lookup(Terminology.Terms.SplitStudyTooltip),
                                icon: "w_cut",
                                selected: application.splitStudyEnabled
                            })];
                    }
                    return [];
                case 133 /* ReverseSeries */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ReverseSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.ReverseSeriesTooltip),
                                icon: "w_random",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                click: function () {
                                    container.application.reverseSeries();
                                }
                            })];
                    }
                    return [];
                case 134 /* UnweaveSeries */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.UnweaveSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.UnweaveSeriesTooltip),
                                icon: "w_random",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                click: function () {
                                    container.application.unweaveSeries();
                                }
                            })];
                    }
                    return [];
                case 135 /* RearrangeSeries */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.RearrangeSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.RearrangeSeriesTooltip),
                                icon: "w_random",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                click: function () {
                                    container.application.rearrangeSeries();
                                }
                            })];
                    }
                    return [];
                case 136 /* PartSeries */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PartSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.PartSeriesTooltip),
                                icon: "w_random",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                click: function () {
                                    container.application.partSeries();
                                }
                            })];
                    }
                    return [];
                case 138 /* MergeSeries */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.MergeSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.MergeSeriesTooltip),
                                icon: "w_random",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                selected: application.mergeSeriesEnabled
                            })];
                    }
                    return [];
                case 137 /* ResetStudy */:
                    if (container && application.permissions.study_split !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ResetStudyArrangement),
                                tooltip: application.terminology.lookup(Terminology.Terms.ResetStudyArrangementTooltip),
                                icon: "w_undo",
                                enabled: Subjects.bindW(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.retW(false) : Subjects.retW(!Multiframe.isMultiframe(series.series));
                                }),
                                click: function () {
                                    container.application.resetArrangement();
                                }
                            })];
                    }
                    return [];
                case 61 /* PreviousSeriesSet */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PreviousSeriesSet),
                                tooltip: application.terminology.lookup(Terminology.Terms.PreviousSeriesSetTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.loadSeriesSet(false);
                                }
                            })];
                    }
                    return [];
                case 62 /* NextSeriesSet */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NextSeriesSet),
                                tooltip: application.terminology.lookup(Terminology.Terms.NextSeriesSetTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.loadSeriesSet(true);
                                }
                            })];
                    }
                    return [];
                case 63 /* CineAll */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PreviousSeriesSet),
                                tooltip: application.terminology.lookup(Terminology.Terms.PreviousSeriesSetTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.toggleCineAll();
                                }
                            })];
                    }
                    return [];
                case 64 /* DuplicateAnnotation */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.DuplicateAnnotation),
                                tooltip: application.terminology.lookup(Terminology.Terms.DuplicateAnnotationTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.duplicateShape();
                                }
                            })];
                    }
                    return [];
                case 65 /* CopyAnnotation */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.CopyAnnotation),
                                tooltip: application.terminology.lookup(Terminology.Terms.CopyAnnotationTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.copyShape();
                                }
                            })];
                    }
                    return [];
                case 66 /* PasteAnnotation */:
                    if (container) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PasteAnnotation),
                                tooltip: application.terminology.lookup(Terminology.Terms.PasteAnnotationTooltip),
                                icon: "w_action",
                                click: function () {
                                    container.application.pasteShape();
                                }
                            })];
                    }
                    return [];
                case 87 /* CoLocalization */:
                    if (application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.CoLocalization),
                                tooltip: application.terminology.lookup(Terminology.Terms.CoLocalizationTooltip),
                                icon: "w_colocate",
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement != null && measurement.editable;
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                }),
                                click: function () {
                                    container.application.copyShapeToAllSeries();
                                }
                            })];
                    }
                    return [];
                case 89 /* Label */:
                    if (application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.Label),
                                tooltip: application.terminology.lookup(Terminology.Terms.LabelTooltip),
                                icon: "w_header",
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement != null && measurement.editable && !(measurement instanceof Measurements.Text);
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                }),
                                click: function () {
                                    container.application.labelAnnotation();
                                }
                            })];
                    }
                    return [];
                case 122 /* PixelSpacingUser */:
                    if (application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.AssignPixelSpacing),
                                tooltip: application.terminology.lookup(Terminology.Terms.AssignPixelSpacingTooltip),
                                icon: "w_edit",
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement != null && measurement.editable && !(measurement instanceof Measurements.Text);
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                }),
                                click: function () {
                                    container.application.assignPixelSpacing();
                                }
                            })];
                    }
                    return [];
                case 123 /* SliceSpacingUser */:
                    if (application.permissions.annotation_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.AssignSliceSpacing),
                                tooltip: application.terminology.lookup(Terminology.Terms.AssignSliceSpacingTooltip),
                                icon: "w_edit",
                                enabled: Subjects.zip(Subjects.bind(application.selectedSeriesContainer, function (series) {
                                    return series === null ? Subjects.ret(null) : series.selectedMeasurement;
                                }, function (_, measurement) {
                                    return measurement != null && measurement.editable && !(measurement instanceof Measurements.Text);
                                }), application.isRecording, function (b1, b2) {
                                    return b1 && !b2;
                                }),
                                click: function () {
                                    container.application.assignSliceSpacing();
                                }
                            })];
                    }
                    return [];
                case 67 /* StartMeeting */:
                    if (container && application.permissions.meeting_edit !== 0 && !LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.StartMeeting),
                                tooltip: application.terminology.lookup(Terminology.Terms.StartMeetingTooltip),
                                icon: "w_meeting",
                                click: function () {
                                    application.startMeeting();
                                },
                                enabled: Subjects.map(application.recordingMode, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 68 /* UseDiagnosticQuality */:
                    if (!LocalViewer.isLocalViewer() && application.accountSettings.viewer_diagnostic_quality != 1 && application.accountSettings.viewer_diagnostic_quality_always != 1) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.UseDiagnosticQuality),
                                tooltip: application.terminology.lookup(Terminology.Terms.UseDiagnosticQualityTooltip),
                                icon: "w_dcm",
                                click: function () {
                                    application.useDiagosticQualityViewer();
                                },
                                enabled: Subjects.map(application.recordingMode, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 69 /* RecordAudio */:
                    if (!LocalViewer.isLocalViewer() && application.accountSettings.viewer_diagnostic_quality != 1 && application.accountSettings.viewer_diagnostic_quality_always != 1) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.RecordAudio),
                                tooltip: application.terminology.lookup(Terminology.Terms.RecordAudio),
                                icon: "w_audio",
                                click: function () {
                                    application.recordAudio();
                                },
                                enabled: Subjects.map(application.recordingMode, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 75 /* Blank */:
                    return [toolbar.addToolbarButton({ icon: 'w_blank' })];
                case 77 /* MouseToolSettings */:
                    return [toolbar.addToolbarButton({
                            title: application.terminology.lookup(Terminology.Terms.Mouse),
                            tooltip: application.terminology.lookup(Terminology.Terms.MouseTooltip),
                            icon: "w_pointer",
                            click: function () {
                                toolbar.addMouseToolSettings(application);
                            }
                        })];
                case 78 /* ShowOnlyKeyImages */:
                    if (application.permissions.keyimage_view !== 0) {
                        return [toolbar.addToggleButton({
                                title: application.terminology.lookup(Terminology.Terms.ShowOnlyKeyImages),
                                tooltip: application.terminology.lookup(Terminology.Terms.ShowOnlyKeyImagesTooltip),
                                icon: "w_key",
                                selected: application.keyImageSeriesEnabled
                            })];
                    }
                    return [];
                case 126 /* SaveKeyImageLayout */:
                    if (application.permissions.keyimage_view !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.SaveKeyImageLayout),
                                tooltip: application.terminology.lookup(Terminology.Terms.SaveKeyImageLayoutTooltip),
                                icon: "w_key",
                                click: function () {
                                    application.saveKeyImageLayoutState();
                                }
                            })];
                    }
                    return [];
                case 79 /* ExportAVI */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportAVI),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportAVITooltip),
                                icon: "w_export_video",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportVideo('avi');
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 80 /* ExportMP4 */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportMP4),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportMP4Tooltip),
                                icon: "w_export_video",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportVideo('mp4');
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 81 /* ExportSeries */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportSeries),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportSeriesTooltip),
                                icon: "w_download",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportSeries();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 113 /* ExportStudy */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportStudy),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportStudyTooltip),
                                icon: "w_download",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportStudy();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 114 /* ExportISO */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportISO),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportISOTooltip),
                                icon: "w_download",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportISO();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 115 /* ExportLocalViewer */:
                    var enableExport = application.accountSettings.enable_viewer_export;

                    if (LocalViewer.isLocalViewer() || (enableExport === undefined || enableExport > 0)) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.ExportViewer),
                                tooltip: application.terminology.lookup(Terminology.Terms.ExportViewerTooltip),
                                icon: "w_download",
                                click: function () {
                                    var selectedSeries = application.selectedSeriesContainer.read();

                                    if (selectedSeries) {
                                        selectedSeries.exportLocalViewer();
                                    }
                                },
                                enabled: Subjects.map(application.isRecording, function (b) {
                                    return !b;
                                })
                            })];
                    }
                    return [];
                case 82 /* NextStudy */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NextStudy),
                                tooltip: application.terminology.lookup(Terminology.Terms.NextStudyTooltip),
                                icon: "w_chevron_right",
                                click: function () {
                                    application.nextWorklistStudy(false);
                                },
                                enabled: Subjects.ret(application.hasNextWorklistStudy(false))
                            })];
                    }
                    return [];
                case 83 /* PreviousStudy */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PreviousStudy),
                                tooltip: application.terminology.lookup(Terminology.Terms.PreviousStudyTooltip),
                                icon: "w_chevron_left",
                                click: function () {
                                    application.previousWorklistStudy(false);
                                },
                                enabled: Subjects.ret(application.hasPreviousWorklistStudy(false))
                            })];
                    }
                    return [];
                case 90 /* NextStudyByMRN */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NextStudyByMRN),
                                tooltip: application.terminology.lookup(Terminology.Terms.NextStudyByMRNTooltip),
                                icon: "w_chevron_right",
                                click: function () {
                                    application.nextWorklistStudy(true);
                                },
                                enabled: Subjects.ret(application.hasNextWorklistStudy(true))
                            })];
                    }
                    return [];
                case 91 /* PreviousStudyByMRN */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PreviousStudyByMRN),
                                tooltip: application.terminology.lookup(Terminology.Terms.PreviousStudyByMRNTooltip),
                                icon: "w_chevron_left",
                                click: function () {
                                    application.previousWorklistStudy(true);
                                },
                                enabled: Subjects.ret(application.hasPreviousWorklistStudy(true))
                            })];
                    }
                    return [];
                case 101 /* NextImage */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NextImage),
                                tooltip: application.terminology.lookup(Terminology.Terms.NextImageTooltip),
                                icon: "w_chevron_right",
                                click: function () {
                                    application.nextSingleSeriesImage();
                                },
                                enabled: Subjects.ret(application.hasNextSingleSeriesImage())
                            })];
                    }
                    return [];
                case 100 /* PreviousImage */:
                    if (!LocalViewer.isStandardLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.PreviousImage),
                                tooltip: application.terminology.lookup(Terminology.Terms.PreviousImageTooltip),
                                icon: "w_chevron_left",
                                click: function () {
                                    application.previousSingleSeriesImage();
                                },
                                enabled: Subjects.ret(application.hasPreviousSingleSeriesImage())
                            })];
                    }
                    return [];
                case 102 /* LayoutSingleSeries */:
                    return [toolbar.addToggleButton({
                            title: application.terminology.lookup(Terminology.Terms.LayoutSingleSeries),
                            tooltip: application.terminology.lookup(Terminology.Terms.LayoutSingleSeriesTooltip),
                            icon: "w_series_view",
                            selected: application.singleSeriesEnabled,
                            enabled: Subjects.bindW(application.layout, function (layout) {
                                return Subjects.retW(application.canDisplaySingleSeries(layout));
                            })
                        })];
                case 117 /* LoadReport */:
                    if (!LocalViewer.isLocalViewer()) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.NewReport),
                                tooltip: application.terminology.lookup(Terminology.Terms.NewReportTooltip),
                                icon: "w_dump",
                                click: function () {
                                    application.loadReport(container.series.studyAttributes.uuid);
                                },
                                enabled: Subjects.ret(true)
                            })];
                    }
                    return [];
                case 124 /* RemoveImages */:
                    if (!LocalViewer.isStandardLocalViewer() && application.permissions.study_delete_image !== 0) {
                        return [toolbar.addToolbarButton({
                                title: application.terminology.lookup(Terminology.Terms.RemoveImages),
                                icon: "w_delete",
                                tooltip: application.terminology.lookup(Terminology.Terms.RemoveImagesTooltip),
                                click: function () {
                                    application.removeImages();
                                }
                            })];
                    }
                    return [];
            }
        };
        return AbstractToolbars;
    })();
    Views.AbstractToolbars = AbstractToolbars;

    /**
    * The main application toolbar
    */
    var Toolbar = (function () {
        function Toolbar(el, application) {
            this.el = el;
            this.application = application;
        }
        /**
        * Get an appropriate toolbar configuration for a modality
        */
        Toolbar.prototype.readToolbarConfiguration = function (settings, modality) {
            var modalitySettings;

            if (settings.modalities) {
                modalitySettings = _.find(settings.modalities, function (m) {
                    return m.modality === modality;
                });
            }

            if (LocalViewer.isStandardLocalViewer()) {
                return ToolbarButtons.localViewerToolbar(modality);
            } else if (modalitySettings && modalitySettings.toolbar !== undefined) {
                return modalitySettings.toolbar;
            } else if (settings.toolbar !== undefined) {
                return settings.toolbar;
            } else {
                return ToolbarButtons.createDefaultToolbar(modality);
            }
        };

        /**
        * Create the toolbar based on the user settings, for the specified modality
        */
        Toolbar.prototype.configure = function (settings, modality) {
            var toolbarConfiguration = this.readToolbarConfiguration(settings, modality);
            var $toolbar = $('<div>').addClass('toolbar-scroll');

            if (this.menuBar) {
                _.each(this.menuBar.listeners, function (l) {
                    l.cancel();
                });

                this.menuBar.listeners = [];
                this.menuBar = null;
            }

            this.menuBar = new MenuBar($toolbar);

            AbstractToolbars.createToolbarFromConfiguration(this.menuBar, toolbarConfiguration, this.application);

            this.el.empty();
            this.el.append($toolbar);

            // special handling of color control icon
            $(".colorControl").parents('.dropdown-icon').css("color", Annotations.COLORS[this.application.currentColor.read()]);
        };

        /**
        * Create necessary elements, add them to the DOM and hook up event handlers
        */
        Toolbar.prototype.render = function () {
            var _this = this;
            var container = this.application.selectedSeriesContainer.read();
            var settings = this.application.settings.read();

            if (container && container.series && settings) {
                if (container.playbackMode.read()) {
                    this.configure({ toolbar: ToolbarButtons.createPlaybackToolbar() }, null);
                } else {
                    var modality = container.series.seriesAttributes.modality;
                    this.configure(settings, modality);
                }
            }

            if (this.subscription) {
                this.subscription.cancel();
                this.subscription = null;
            }

            this.subscription = this.application.selectedSeriesContainer.updates.subscribe({
                next: function (container) {
                    var settings = _this.application.settings.read();
                    if (container.series && container.series.seriesAttributes.modality) {
                        if (container.playbackMode.read()) {
                            _this.configure({ toolbar: ToolbarButtons.createPlaybackToolbar() }, null);
                            _this.application.showPlaybackBanner(_this.application.terminology.lookup(Terminology.Terms.PlaybackInProgress));
                        } else {
                            _this.configure(settings, container.series.seriesAttributes.modality);
                        }
                    }
                },
                done: function () {
                },
                fail: function (_) {
                }
            });
        };
        return Toolbar;
    })();
    Views.Toolbar = Toolbar;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../typings/underscore/underscore.d.ts' />
///<reference path='../typings/virtual-dom/virtual-dom.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Multiframe.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Touch.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Series.ts' />
var Views;
(function (Views) {
    var FoldInHook = (function () {
        function FoldInHook() {
        }
        FoldInHook.prototype.hook = function (node, propertyName, previousValue) {
            if (!previousValue) {
                // Use animate instead of fadeIn so that the display property is
                // still inline-block when the animation completes.
                $(node).css({ opacity: 0.0 }).animate({ opacity: 1.0 }, 500);
            }
        };
        return FoldInHook;
    })();
    Views.FoldInHook = FoldInHook;

    /**
    * View which displays study information, priors, and thumbnails for all available series
    */
    var Thumbnails = (function () {
        function Thumbnails(el, application) {
            this.el = el;
            this.application = application;
        }
        Thumbnails.prototype.toVNode = function (studies, priors) {
            var vdom = virtualDom;

            var dateFormat = this.application.settings.read().dateFormat;

            return vdom.h('div', {}, [
                this.studyInfo(studies[0], dateFormat),
                this.thumbnailIcons(studies, priors, dateFormat),
                this.placementIcon()
            ]);
        };

        Thumbnails.prototype.studyInfo = function (study, dateFormat) {
            var _this = this;
            var vdom = virtualDom;
            var term = function (t) {
                return _this.application.terminology.lookup(t);
            };
            var hideDateTime = this.application.settings.read().hideThumbnailsDateTime;
            var elements = [];

            elements.push(vdom.h('div', {}, [study.studyAttributes.patientName || term(Terminology.Terms.Patient)]));
            elements.push(vdom.h('div', {}, [this.patientText(study, dateFormat)]));
            elements.push(vdom.h('div', {}, (study.studyAttributes.accelerated || this.application.studyStorage.localAccelerator ? [vdom.h('span', { className: 'fa fa-flash', title: 'This study is accelerated.' }, []), " "] : []).concat([study.studyAttributes.studyDescription || term(Terminology.Terms.Study)])));

            if (!hideDateTime) {
                elements.push(vdom.h('div', {}, [this.studyText(study, dateFormat)]));
            }

            elements.push(vdom.h('div', {}, [study.series.length + ' ' + term(Terminology.Terms.SeriesPlural)]));
            elements.push(this.studyPageLink(study));

            return vdom.h('div.study-thumbnails-container-info', {}, elements, 'study-info');
        };

        Thumbnails.prototype.studyPageLink = function (study) {
            var vdom = virtualDom;

            if (LocalViewer.isLocalViewer()) {
                return vdom.h('div', {}, []);
            } else if (this.application.accountSettings.viewer_study_page_link_visible == null || this.application.accountSettings.viewer_study_page_link_visible == 1) {
                var studyUri = null;
                if (this.application.accountSettings.viewer_study_page_link_url != null && this.application.accountSettings.viewer_study_page_link_url.length > 0) {
                    studyUri = this.application.accountSettings.viewer_study_page_link_url;
                } else {
                    var query = study.studyAttributes.queryObject;
                    studyUri = '/?route=view_study_meta_with_sid' + '&namespace_id=' + encodeURIComponent(query.storageNamespace.value) + '&study_uid=' + encodeURIComponent(query.studyUid.value) + '&phi_namespace=' + encodeURIComponent(query.phiNamespace.value) + '&sid=' + encodeURIComponent(this.application.sessionId.value);
                }

                return vdom.h('p.study-page-link', {}, [
                    vdom.h('a', {
                        href: studyUri,
                        target: '_blank',
                        style: {
                            'text-shadow': 'none'
                        }
                    }, [
                        this.application.terminology.lookup(Terminology.Terms.StudyPage)
                    ])
                ]);
            } else {
                return vdom.h('div', {}, []);
            }
        };

        Thumbnails.prototype.patientText = function (study, dateFormat) {
            var patientText = "(" + (study.studyAttributes.patientSex || "-") + ") ";

            if (study.studyAttributes.patientBirthDate) {
                patientText += study.studyAttributes.patientBirthDate.toShortDateString(dateFormat);
            }

            return patientText;
        };

        Thumbnails.prototype.studyText = function (study, dateFormat) {
            var hideThumbnailsDateTime = this.application.settings.read().hideThumbnailsDateTime || (this.application.accountSettings.viewer_hide_thumbnails_datetime != 0);

            if (study.studyAttributes.studyCreateDate && !hideThumbnailsDateTime) {
                return study.studyAttributes.studyCreateDate.toShortDateString(dateFormat);
            }

            return "-";
        };

        Thumbnails.prototype.thumbnailIcons = function (studies, priors, dateFormat) {
            var _this = this;
            var vdom = virtualDom;

            var term = function (t) {
                return _this.application.terminology.lookup(t);
            };

            var keyImageSeriesVisible = false;
            var stackSeriesVisible = false;
            var stackSeriesPosition = 0 /* None */;

            var settings = this.application.settings.read();

            // Check if we should display key image series
            if (this.application.keyImageSeriesEnabled.read()) {
                keyImageSeriesVisible = true;
            }

            // Look for modality setting based on first series in first study
            if (settings.modalities && studies[0] != null && studies[0].series[0] != null && studies[0].series[0].seriesAttributes != null) {
                var modalitySettings = _.find(settings.modalities, function (m) {
                    return m.modality === studies[0].series[0].seriesAttributes.modality;
                });

                if (modalitySettings && modalitySettings.stackSeriesVisible) {
                    stackSeriesVisible = modalitySettings.stackSeriesVisible;
                }

                if (modalitySettings && modalitySettings.stackSeriesPosition) {
                    stackSeriesPosition = modalitySettings.stackSeriesPosition;
                }
            }

            // Default legacy setting, overrides modality specific settings, should only be set at a modality level going forward
            if (settings.showStackSeries) {
                stackSeriesVisible = true;
                stackSeriesPosition = 2 /* Last */;
            }

            var priorsNotLoaded = _.filter(priors, function (prior) {
                return !_.any(_this.application.studies, function (study) {
                    return study.studyAttributes.queryObject.studyUid.value === prior.study_uid || study.studyAttributes.uuid.value === prior.uuid;
                });
            });

            var allStudies = _.map(studies, function (s) {
                return Either.Left(s);
            }).concat(_.map(priorsNotLoaded, function (s) {
                return Either.Right(s);
            }));

            var sortedStudies = _.sortBy(allStudies, function (e) {
                var maybeDate = Either.either(e, function (study) {
                    return study === _this.application.studies[0] ? Maybe.Nothing() : Maybe.fromNull(study.studyAttributes.studyCreateDate);
                }, function (prior) {
                    return Maybe.fromNull(prior.study_date_value);
                });

                return Maybe.fromMaybe(Maybe.fmap(maybeDate, function (d) {
                    return -d.getTime();
                }), -Infinity);
            });

            // Display report series before other series
            if (settings.displayReportsFirst) {
                sortedStudies = _.map(sortedStudies, function (e) {
                    return Either.either(e, function (study) {
                        study.series = _.sortBy(study.series, function (series) {
                            // For sorting purposes, return a zero for report series and a 1 for other series
                            return (Study.isPDFSeries(series) || Study.isImagedORUSeries(series)) ? 0 : 1;
                        });
                        return Either.Left(study);
                    }, function (prior) {
                        return Either.Right(prior);
                    });
                });
            }

            var icons = [];

            var keyImageOrder = settings.keyImageOrder;
            if (keyImageSeriesVisible && keyImageOrder) {
                var allKeyImageSeries = _.toArray(_.flatten(_.map(this.application.studies, function (study) {
                    return study.keyImageSeries;
                })));

                if (allKeyImageSeries && allKeyImageSeries.length && allKeyImageSeries[0]) {
                    var allSeries = Study.orderSeries(allKeyImageSeries, keyImageOrder == "desc");
                    var keyimageSeries = _.map(allSeries, function (series) {
                        return _this.keyImageSeriesIcon(series);
                    });
                    icons = _.flatten([keyimageSeries]);
                }
            }

            if (icons.length == 0) {
                icons = _.chain(sortedStudies).map(function (e, studyIndex, list) {
                    return Either.either(e, function (study) {
                        var studyIcons = _.map(study.series, function (series) {
                            return _this.thumbnailIcon(study, studyIndex, series, dateFormat);
                        });

                        var spacer = vdom.h('li.series-thumbnail-spacer', {}, []);

                        if (keyImageSeriesVisible && study.keyImageSeries && study.keyImageSeries.length > 0) {
                            var keyimageSeries = _.map(study.keyImageSeries, function (series) {
                                return _this.keyImageSeriesIcon(series);
                            });
                            studyIcons = _.flatten([keyimageSeries]);
                        }

                        if (stackSeriesVisible) {
                            var stackSeries = _this.stackSeriesIcon(study);

                            if (stackSeriesPosition == 1 /* First */) {
                                studyIcons = _.flatten([stackSeries, spacer, studyIcons]);
                            } else if (stackSeriesPosition == 2 /* Last */) {
                                studyIcons = _.flatten([studyIcons, stackSeries, spacer]);
                            }
                        }

                        return studyIcons;
                    }, function (prior) {
                        return _this.relatedStudy(prior, studies[0]);
                    });
                }).flatten().value();
            }

            return vdom.h('ul.thumbnail-icons', {}, icons, 'thumbnail-icons');
        };

        Thumbnails.prototype.thumbnailIcon = function (study, studyIndex, series, dateFormat) {
            var _this = this;
            var vdom = virtualDom;

            var instance = series.instances[0];
            var first = instance;

            var modality = series.seriesAttributes.modality;

            var settings = this.application.settings.read();

            if (settings.modalities) {
                var modalitySettings = _.find(settings.modalities, function (m) {
                    return m.modality === modality;
                });

                if (modalitySettings && modalitySettings.displayMiddleSlice) {
                    instance = series.instances[Math.floor(series.instances.length / 2)];
                }
            }

            var uri;

            // Cache the thumbnail URI to avoid unnecessary patching when using
            // multiple image hosts.
            if (series.seriesAttributes.documentType) {
                uri = '';
            } else if (instance.instanceAttributes.thumbnailUri) {
                uri = instance.instanceAttributes.thumbnailUri;
            } else {
                instance.instanceAttributes.thumbnailUri = uri = Routes.ImageData(this.application.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(0), 0 /* Thumbnail */, 8, this.application.accountSettings.cache == 1, true);
            }

            var selectedSeries = this.application.selectedSeriesContainer.read();

            var layout = this.application.layout.read();
            var displayedSeries = _.take(this.application.seriesViews.read(), layout.rows * layout.columns);

            var selected = selectedSeries && selectedSeries.series && selectedSeries.series.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value && selectedSeries.series.instances[0].id.value === series.instances[0].id.value;

            if (this.application.singleSeriesEnabled.read() && !selected && selectedSeries && selectedSeries.series && selectedSeries.series.parent) {
                selected = selectedSeries.series.parent.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value;
            }

            var displayed = _.any(displayedSeries, function (container) {
                return container && container.series && container.series.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value && container.series.instances[0].id.value === series.instances[0].id.value;
            });

            function formatDateTime(date, time, hide) {
                // Use MST timezone for imaged ORU series
                var timeZone = Study.isImagedORUSeries(series) ? "America/Phoenix" : null;

                if (!hide) {
                    // Combine fields and return formatted datetime
                    if (date && time) {
                        var datetime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), time.getHours(), time.getMinutes(), time.getSeconds());

                        return datetime.toShortDateTimeString(dateFormat, timeZone);
                    }

                    if (date && !time) {
                        return date.toShortDateString(dateFormat);
                    }

                    if (!date && time) {
                        return time.toShortTimeString();
                    }
                }

                return '';
            }

            var dynamicThumbnail = false;
            var backgroungImageStyleDynamic;

            // make thumbnail appear with same orientation as series
            var seriesView = this.application.findSeriesView(series);
            if (!Browser.isIE9()) {
                var transform;
                if (seriesView && seriesView.canvas) {
                    transform = _.clone(seriesView.transform.read());
                } else if (series.displayOptions) {
                    transform = {
                        offsetX: 0, offsetY: 0, scale: 1,
                        flipped: series.displayOptions.flipped,
                        rotation: series.displayOptions.rotation };
                }

                if (transform) {
                    dynamicThumbnail = true;

                    backgroungImageStyleDynamic = {
                        'background-image': 'url(' + uri + ')'
                    };

                    transform.scale = 1.0; // ignore zoom
                    var DEFAULT_SIZE = 100;
                    var matrix = Transform.transformToInverseMatrix(transform, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE, DEFAULT_SIZE);
                    backgroungImageStyleDynamic['transform'] = "matrix({0},{1},{2},{3},0,0)".replace("{0}", matrix.entries[0].toString()).replace("{1}", matrix.entries[1].toString()).replace("{2}", matrix.entries[3].toString()).replace("{3}", matrix.entries[4].toString());
                }
            }

            var seriesLoaded = this.application.seriesLoaded(series);
            var preloadAccelerated = this.application.supportsAcceleratedPreload();

            var loadedStatusStyle = "hide-icon";
            if (seriesLoaded == 3 /* Diagnostic */) {
                loadedStatusStyle = "loaded-hd-icon";
            } else if (seriesLoaded == 2 /* Thumbnail */) {
                loadedStatusStyle = "loaded-sd-icon";
            }

            var loadedStatusStyleAccelerated = preloadAccelerated && (seriesLoaded == 3 /* Diagnostic */) ? "loaded-hd-icon" : "";

            var hideThumbnailsDateTime = this.application.settings.read().hideThumbnailsDateTime || (this.application.accountSettings.viewer_hide_thumbnails_datetime != 0);

            return vdom.h('li.box', {
                onclick: function (e) {
                    var $thumbnail = $("#series-" + series.uuid);

                    if (_this.application.splitStudyEnabled.read()) {
                        // only allow splitting primary study
                        if (series.studyAttributes.uuid.value == _this.application.studies[0].studyAttributes.uuid.value) {
                            $thumbnail.toggleClass("splitting");
                            series.splitSelected = $thumbnail.hasClass("splitting");
                        }
                    } else if (_this.application.mergeSeriesEnabled.read()) {
                        // only allow merging primary study
                        if (series.studyAttributes.uuid.value == _this.application.studies[0].studyAttributes.uuid.value) {
                            $thumbnail.toggleClass("merging");
                            series.mergeSelected = $thumbnail.hasClass("merging");
                        }
                    } else {
                        _this.application.replaceSelectedSeries(series);
                    }

                    e.preventDefault();
                },
                ondragstart: function (e) {
                    if (e.dataTransfer) {
                        e.dataTransfer.setData("text", series.uuid);
                    }
                },
                foldIn: series.studyAttributes.queryObject.studyUid.value !== this.application.queryObject.studyUid.value ? new FoldInHook() : null
            }, [
                vdom.h('div', {
                    className: ['series-thumbnail-container'].concat(selected ? ['selected'] : []).concat(displayed ? ['displayed'] : []).join(' '),
                    draggable: true,
                    id: "series-" + series.uuid,
                    style: {
                        'background-image': dynamicThumbnail ? 'none' : 'url(' + uri + ')'
                    }
                }, [
                    dynamicThumbnail ? vdom.h('div', {
                        className: 'series-thumbnail-container-image',
                        style: backgroungImageStyleDynamic
                    }, []) : null,
                    vdom.h('span', { className: 'fa fa-3x thumb-icon ' + ((series.seriesAttributes.documentType) ? series.seriesAttributes.documentType.thumbnailIcon : '') }, []),
                    vdom.h('div.series-thumbnail-container-info', {}, (studyIndex > 0 && study.studyAttributes.studyCreateDate ? [
                        vdom.h('div', {}, [
                            study.studyAttributes.priorNumber > 0 ? this.application.terminology.lookup(Terminology.Terms.Prior) + " #" + study.studyAttributes.priorNumber : this.application.terminology.lookup(Terminology.Terms.Current)
                        ]),
                        vdom.h('div', {}, [hideThumbnailsDateTime ? "" : "(" + study.studyAttributes.studyCreateDate.toShortDateString(dateFormat) + ")"])
                    ] : []).concat([
                        vdom.h('div', {}, [first.instanceAttributes.seriesDescription]),
                        vdom.h('div', { className: 'series-info-normal' }, [formatDateTime(first.instanceAttributes.seriesCreateDate, first.instanceAttributes.seriesCreateTime, hideThumbnailsDateTime)]),
                        vdom.h('div', { className: 'series-info-small' }, [formatDateTime(first.instanceAttributes.seriesCreateDate, null, hideThumbnailsDateTime)]),
                        vdom.h('div', { className: 'series-info-small' }, [formatDateTime(null, first.instanceAttributes.seriesCreateTime, hideThumbnailsDateTime)]),
                        vdom.h('div', {}, [(!series.seriesAttributes.documentType) ? series.instances.length + ' ' + this.application.terminology.lookup(Terminology.Terms.Instances) : '']),
                        vdom.h('div.series-thumbnail-document-type', {}, [(series.seriesAttributes.documentType) ? series.seriesAttributes.documentType.thumbnailText : ''])
                    ])),
                    vdom.h('div', {}, (study.studyAttributes.accelerated || study.studyAttributes.studyStorage.localAccelerator ? [vdom.h('span', { className: 'fa fa-flash accelerated-icon ' + loadedStatusStyleAccelerated, title: 'This series is accelerated.' }, []), " "] : [vdom.h('span', { className: 'fa fa-check accelerated-icon load-icon ' + loadedStatusStyle, title: 'This series is loaded.' }, []), " "]))
                ])
            ], 'series-' + series.seriesAttributes.seriesUid.value);
        };

        Thumbnails.prototype.keyImageSeriesIcon = function (series) {
            var _this = this;
            var vdom = virtualDom;
            var term = function (t) {
                return _this.application.terminology.lookup(t);
            };
            var selectedSeries = this.application.selectedSeriesContainer.read();
            var selected = selectedSeries && selectedSeries.series && selectedSeries.series.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value;

            if (this.application.singleSeriesEnabled.read() && !selected && selectedSeries && selectedSeries.series && selectedSeries.series.parent) {
                selected = selectedSeries.series.parent.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value;
            }

            var instance = series.instances[0];
            var uri = '';
            if (instance.instanceAttributes.thumbnailUri) {
                uri = instance.instanceAttributes.thumbnailUri;
            } else {
                instance.instanceAttributes.thumbnailUri = uri = Routes.ImageData(this.application.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(0), 0 /* Thumbnail */, 8, this.application.accountSettings.cache == 1, true);
            }

            var icon = vdom.h('li.box', {
                onclick: function (e) {
                    _this.application.replaceSelectedSeries(series);
                    e.preventDefault();
                },
                ondragstart: function (e) {
                    if (e.dataTransfer) {
                        e.dataTransfer.setData("text", series.uuid);
                    }
                }
            }, [
                vdom.h('div', {
                    className: ['series-thumbnail-container'].concat(selected ? ['selected'] : []).join(' '),
                    draggable: true,
                    style: {
                        'background-image': 'url(' + uri + ')'
                    }
                }, [
                    vdom.h('span', { className: 'fa fa-3x fa-star thumb-icon' }, []),
                    vdom.h('div.series-thumbnail-container-info', {}, [
                        vdom.h('div', {}, [term(Terminology.Terms.KeyImageSeries)]),
                        vdom.h('div', {}, [series.instances.length + ' ' + term(Terminology.Terms.Instances)])
                    ])
                ])
            ], 'stack-series');

            return icon;
        };

        Thumbnails.prototype.stackSeriesIcon = function (study) {
            var _this = this;
            var vdom = virtualDom;
            var term = function (t) {
                return _this.application.terminology.lookup(t);
            };
            var selectedSeries = this.application.selectedSeriesContainer.read();
            var selected = selectedSeries && selectedSeries.series && study.stackSeries && selectedSeries.series.seriesAttributes.seriesUid.value === study.stackSeries.seriesAttributes.seriesUid.value;

            if (this.application.singleSeriesEnabled.read() && !selected && selectedSeries && selectedSeries.series && selectedSeries.series.parent) {
                selected = selectedSeries.series.parent.seriesAttributes.seriesUid.value === study.stackSeries.seriesAttributes.seriesUid.value;
            }

            var icon = vdom.h('li.box', {
                onclick: function (e) {
                    _this.application.replaceSelectedSeries(study.stackSeries);
                    e.preventDefault();
                }
            }, [
                vdom.h('div', {
                    className: ['series-thumbnail-container'].concat(selected ? ['selected'] : []).join(' ')
                }, [
                    vdom.h('span', { className: 'fa fa-3x fa-bars thumb-icon' }, []),
                    vdom.h('div.series-thumbnail-container-info', {}, [
                        vdom.h('div', {}, [study.studyAttributes.studyDescription]),
                        vdom.h('div', {}, [term(Terminology.Terms.StackSeries)])
                    ])
                ])
            ], 'stack-series');

            return icon;
        };

        Thumbnails.prototype.relatedStudy = function (prior, primary) {
            var _this = this;
            var enablePriors = this.application.settings.read().showPriors;
            var thin = prior.thin;

            if (LocalViewer.isLocalViewer() || (enablePriors !== undefined && enablePriors === false)) {
                return [];
            } else {
                if (!!this.application.settings.read().showOnlyPriors) {
                    var primaryStudyDate = primary.studyAttributes.studyCreateDate;
                    if (primaryStudyDate && prior.study_date_value) {
                        if (prior.study_date_value.getTime() > primaryStudyDate.getTime()) {
                            return [];
                        }
                    }
                }

                var vdom = virtualDom;
                var term = function (t) {
                    return _this.application.terminology.lookup(t);
                };
                var hideThumbnailsDateTime = this.application.settings.read().hideThumbnailsDateTime || (this.application.accountSettings.viewer_hide_thumbnails_datetime != 0);

                return [
                    vdom.h('li.box', {}, [
                        vdom.h('div.series-thumbnail-container', {
                            onclick: function (e) {
                                if (thin) {
                                    var app = _this.application;
                                    var sid = app.sessionId;

                                    var studyPoll = function () {
                                        Services.getStudyInfo(sid, new Classes.StudyUUID(prior.uuid)).subscribe({
                                            done: function () {
                                            },
                                            next: function (result) {
                                                if (result.thin == 1) {
                                                    setTimeout(studyPoll, 10000);
                                                } else {
                                                    app.loadPrior({
                                                        storage_namespace: result.storage_namespace,
                                                        phi_namespace: result.phi_namespace,
                                                        study_uid: result.study_uid,
                                                        prior_number: prior.prior_number
                                                    });
                                                }
                                            },
                                            fail: function (err) {
                                                Services.AuditLog("study poll call failed: " + err, "Thumbnails", sid);
                                            }
                                        });
                                    };

                                    Services.retrieveStudy(sid, new Classes.StudyUUID(prior.uuid)).subscribe({
                                        done: function () {
                                        },
                                        next: function (_) {
                                            $(e.target).find("span").removeClass("fa-arrow-circle-o-down").addClass("fa-gear fa-spin");
                                            setTimeout(studyPoll, 10000);
                                        },
                                        fail: function (err) {
                                            Services.AuditLog("study/retrieve call of prior failed: " + err, "Thumbnails", sid);
                                            window.alert(_this.application.terminology.lookup(Terminology.Terms.CannotRetrieveThinStudy));
                                        }
                                    });
                                } else {
                                    _this.application.loadPrior(prior);
                                }

                                e.preventDefault();
                            }
                        }, [
                            thin ? vdom.h('span', { className: 'fa fa-3x fa-arrow-circle-o-down thumb-icon' }, []) : vdom.h('span', { className: 'fa fa-3x fa-clock-o thumb-icon' }, []),
                            vdom.h('div.series-thumbnail-container-info', {}, [
                                vdom.h('div', {}, [prior.study_description]),
                                vdom.h('div', {}, [term(Terminology.Terms.AccessionNumberShort) + ": " + prior.accession_number]),
                                vdom.h('div', {}, [hideThumbnailsDateTime ? "" : term(Terminology.Terms.Date) + ": " + prior.study_date])
                            ])
                        ])
                    ], 'prior-' + prior.study_uid)
                ];
            }
        };

        Thumbnails.prototype.placementIcon = function () {
            var _this = this;
            var vdom = virtualDom;
            var $body = $('body');
            var left = $body.is(".thumbs-on-left");
            var top = $body.is(".thumbs-on-top");
            var bottom = !left && !top;
            var placementIcon;

            if (top) {
                placementIcon = "fa-caret-square-o-down";
            } else if (bottom) {
                placementIcon = "fa-caret-square-o-left";
            } else {
                placementIcon = "fa-caret-square-o-up";
            }

            return vdom.h('div.placement-button.glow', {}, [
                vdom.h('i', {
                    className: 'fa ' + placementIcon,
                    onclick: function (e) {
                        var small = _this.application.settings.read().smallThumbnails;
                        var $placementButton = $(".placement-button > i");

                        $body.removeClass("thumbs-small");
                        $placementButton.removeClass("fa-caret-square-o-down fa-caret-square-o-left fa-caret-square-o-up");

                        if ($body.is(".thumbs-on-left")) {
                            $body.toggleClass("thumbs-on-left thumbs-on-top"); // top

                            if (small) {
                                $body.addClass("thumbs-small");
                            }

                            $placementButton.addClass("fa-caret-square-o-down");
                        } else if ($body.is(".thumbs-on-top")) {
                            $body.toggleClass("thumbs-on-top"); // bottom

                            if (small) {
                                $body.addClass("thumbs-small");
                            }

                            $placementButton.addClass("fa-caret-square-o-left");
                        } else {
                            $body.toggleClass("thumbs-on-left"); // left
                            $placementButton.addClass("fa-caret-square-o-up");
                        }

                        _this.application.renderAllFrames();
                        e.preventDefault();
                    }
                }, [])
            ], 'placement-button');
        };

        /**
        * Create necessary elements, add them to the DOM and hook up event handlers.
        *
        * If the component has already been rendered, we use virtual-dom to apply the differences
        * to the existing node.
        */
        Thumbnails.prototype.render = function () {
            var priors = this.application.priors;
            var vnode = this.toVNode(this.application.studies, priors);

            if (this.rendered) {
                var patches = virtualDom.diff(this.rendered.vnode, vnode);

                virtualDom.patch(this.rendered.el, patches);

                this.rendered.vnode = vnode;
            } else {
                var initial = virtualDom.create(vnode);

                this.el.empty().append($(initial));

                this.rendered = {
                    el: initial,
                    vnode: vnode
                };
            }
        };
        return Thumbnails;
    })();
    Views.Thumbnails = Thumbnails;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path="../libs/Queue.ts" />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/Browser.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Multiframe.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/GridLayout.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Series.ts' />
///<reference path='Toolbar.ts' />
///<reference path='Thumbnails.ts' />
var Views;
(function (Views) {
    

    

    

    /**
    * The main application view
    *
    * Contains a grid of several series views
    *
    * Coordination between views takes place via subjects defined here
    */
    var Application = (function () {
        function Application(el, studyStorage, queryObject, sessionId, user, settings, accountSettings, permissions, terminology, keyImagesOnly, owner, shaders, hideStudyUUID, showStudyUUID) {
            var _this = this;
            this.studies = [];
            this.priors = [];
            this.stackedStudies = [];
            this.singleSeriesIndex = 0;
            /**
            * The series which is current magnified
            */
            this.magnifiedSeries = new Subjects.ObservableValue(null);
            /**
            * Cine frame rate
            */
            this.cineSpeed = new Subjects.ObservableValue(0);
            /**
            * True if playback should start automatically
            */
            this.startCinePlaybackAutomatically = new Subjects.ObservableValue(false);
            /**
            * True if the hanging protocol should override the frame time tag
            */
            this.ignoreFrameTimeTag = false;
            /**
            * Taken if keyboard shortcuts should be ignored
            */
            this.noKeyboardShortcuts = new Subjects.ObservableValue(Classes.Resource.unused());
            /**
            * Records viewer events
            */
            this.recorder = new Subjects.ObservableValue(new Recording.NullRecorder());
            /**
            * Indicates whether or not a script is being recorded
            */
            this.recordingMode = new Subjects.ObservableValue(false);
            /**
            * Indicates whether or not we are hosting a meeting
            */
            this.meetingHost = new Subjects.ObservableValue(false);
            /**
            * Indicates whether or not we are viewing a meeting
            */
            this.inMeeting = new Subjects.ObservableValue(false);
            /**
            * Subscriptions registered while render the UI
            * @type {any[]}
            */
            this.renderSubscriptions = [];
            /**
            * True if the recorder is recording
            */
            this.isRecording = Subjects.zip(this.recordingMode, this.meetingHost, function (b1, b2) {
                return b1 || b2;
            });
            /**
            * Indicates whether or not a script is being replayed
            */
            this.playbackMode = new Subjects.ObservableValue(false);
            // This object holds the status of the arrow keys (pressed or not)
            this.keyStatus = {};
            /**
            * True if the UI has been rendered
            * @type {boolean}
            */
            this.initializedLayout = false;
            /**
            * Toggle application keyboard listener, which is not focus-based.
            * @type {Subjects.ObservableValue<boolean>}
            */
            this.listenForKeyboardInput = new Subjects.ObservableValue(true);
            this.localAccelerator = false;
            this.splitStudyMode = false;
            this.mergeSeriesMode = false;
            this.keyImageLayout = [];
            /**
            * True if the GSPSInfo panel is visible
            */
            this.gspsInfoVisible = new Subjects.ObservableValue(false);
            this.highlightedGraphic = new Subjects.ObservableValue(null);
            this.el = el;
            this.studyStorage = studyStorage;
            this.queryObject = queryObject;
            this.sessionId = sessionId;
            this.user = user;
            this.settings = new Subjects.ObservableValue(settings);
            this.accountSettings = accountSettings;
            this.permissions = permissions;
            this.terminology = terminology;
            this.keyImagesOnly = keyImagesOnly;
            this.owner = owner;
            this.shaders = shaders;
            this.hideStudyUUID = hideStudyUUID;
            this.showStudyUUID = showStudyUUID;

            var viewer_local_accelerator = Query.findParameter(window.location, "viewer_local_accelerator");
            if (viewer_local_accelerator) {
                var local_acceleartor = parseInt(viewer_local_accelerator);
                if (local_acceleartor == 1) {
                    this.localAccelerator = true;
                    studyStorage.localAccelerator = true;
                    accountSettings.viewer_diagnostic_quality = 1;
                    accountSettings.viewer_diagnostic_quality_always = 1;
                }
            }

            this.layout = new Subjects.ObservableValue({ rows: 1, columns: 1 });
            this.currentColor = new Subjects.ObservableValue(0);
            this.selectedTool = new MultiMonitor.SharedSubject("selectedTool", owner, new Subjects.ObservableValue(this.getDefaultMouseTool(settings, accountSettings)));

            this.selectedTool2 = new MultiMonitor.SharedSubject("selectedTool2", owner, new Subjects.ObservableValue(this.getDefaultMouseTool2(settings, accountSettings)));

            this.selectedToolWheel = new MultiMonitor.SharedSubject("selectedToolWheel", owner, new Subjects.ObservableValue(this.getDefaultMouseToolWheel(settings, accountSettings)));

            this.globalMeasurementsToggle = settings.toggleAllSeriesMeasurements;
            this.globalMeasurementsVisible = new MultiMonitor.SharedSubject("globalMeasurementsVisible", owner, new Subjects.ObservableValue(true));

            this.globalTextToggle = settings.toggleAllSeriesText;
            this.globalTextVisible = new MultiMonitor.SharedSubject("globalTextVisible", owner, new Subjects.ObservableValue(true));

            this.globalRulerToggle = settings.toggleAllSeriesRuler;
            this.globalRulerVisible = new MultiMonitor.SharedSubject("globalRulerVisible", owner, new Subjects.ObservableValue(true));

            Subjects.listen(this.currentColor, function (color) {
                _.each(_this.seriesViews.value, function (series) {
                    if (series.series) {
                        series.updateAreaColorSelection(color);
                    }
                });

                _this.renderAllFrames();
            });

            Subjects.listen(this.layout, function (layout) {
                _this.recorder.read().append({
                    type: 4 /* LayoutChanged */,
                    newLayout: layout
                });

                if (_this.singleSeriesEnabled.read() && _this.seriesViews && _this.seriesViews.value.length) {
                    if (_this.canDisplaySingleSeries(layout)) {
                        if (_this.singleSeriesSingleLayout) {
                            _this.singleSeries = _this.seriesViews.value[0].series;
                            _this.seriesViews.value[0].series = _this.singleSeriesSingleLayout;
                            _this.seriesViews.value[0].selectedInstanceIndex.write(_this.singleSeriesIndex);
                            _this.selectedSeriesKey.write(_this.seriesViews.value[0].viewKey);
                            _this.seriesViews.value[0].renderAll();
                        }
                    } else {
                        if (_this.singleSeries) {
                            _this.singleSeriesSingleLayout = _this.seriesViews.value[0].series;
                            _this.seriesViews.value[0].series = _this.singleSeries;
                            _this.seriesViews.value[0].selectedInstanceIndex.write(0);
                            _this.selectedSeriesKey.write(_this.seriesViews.value[0].viewKey);
                            _this.seriesViews.value[0].renderAll();
                        }
                    }

                    _this.seriesViews.raiseChangedEvent(_this.seriesViews.value);
                }

                _this.updateSeriesHandles((layout.rows * layout.columns) > 1);
                _this.thumbnails.render();
            });

            Subjects.listen(this.globalMeasurementsVisible, function (show) {
                _.each(_this.seriesViews.value, function (series) {
                    series.measurementsVisible.write(show);
                });
            });

            Subjects.listen(this.globalRulerVisible, function (show) {
                _.each(_this.seriesViews.value, function (series) {
                    series.rulerVisible.write(show);
                });
            });

            Subjects.listen(this.globalTextVisible, function (show) {
                _.each(_this.seriesViews.value, function (series) {
                    series.infoVisible.write(show);
                });
            });

            Subjects.listen(this.selectedTool, function (tool) {
                _this.recorder.read().append({
                    type: 3 /* MouseToolChanged */,
                    newMouseTool: tool
                });
            });

            Subjects.listen(this.settings, function (settings) {
                Services.putSettings(_this.sessionId, settings).subscribe({
                    done: function () {
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        _this.recordError("Unable to save settings to services: " + err);
                    }
                });
            });

            Subjects.listen(this.playbackMode, function (playing) {
                _this.playbackChanged(playing);
            });

            this.seriesViews = new Subjects.ObservableValue([]);

            this.seriesViewsSubscription = this.seriesViews.updates.subscribe({
                done: function () {
                },
                fail: function (_) {
                },
                next: function (_) {
                    _this.thumbnails.render();
                }
            });

            /* Shared subjects */
            this.selectedSeriesKey = new MultiMonitor.SharedSubject("selectedSeries", owner, new Subjects.ObservableValue(Views.SeriesViewKey.Null));
            this.selectedSeriesContainer = Subjects.map(this.selectedSeriesKey, function (key) {
                return _.find(_this.seriesViews.value, function (sc) {
                    return sc.viewKey.value === key.value;
                }) || null;
            });

            this.selectedSeriesContainer.updates.subscribe({
                done: function () {
                },
                fail: function (_) {
                },
                next: function (_) {
                    _this.thumbnails.render();
                }
            });

            this.selectedInstanceGeometry = new MultiMonitor.SharedSubject("selectedInstanceGeometry", owner, new Subjects.ObservableValue(null));

            this.clipboard = new MultiMonitor.SharedSubject("clipboard", owner, new Subjects.ObservableValue(null));

            this.thumbnailsVisible = new MultiMonitor.SharedSubject("thumbnailsVisible", owner, new Subjects.ObservableValue(true));

            this.keyImageSeriesEnabled = new MultiMonitor.SharedSubject("keyImageSeriesEnabled", owner, new Subjects.ObservableValue(false));

            this.splitStudyEnabled = new Subjects.ObservableValue(false);
            this.mergeSeriesEnabled = new Subjects.ObservableValue(false);

            this.singleSeriesEnabled = new Subjects.ObservableValue(false);

            this.planeLocalizationCursor = new MultiMonitor.SharedSubject("planeLocalizationCursor", owner, new Subjects.ObservableValue(null));

            this.referenceLinesActive = new MultiMonitor.SharedSubject("referenceLinesActive", owner, new Subjects.ObservableValue(true));

            this.linkSeries = new MultiMonitor.SharedSubject("linkSeries", owner, new Subjects.ObservableValue(accountSettings.viewer_link_series == 1));

            this.propagateMode = new MultiMonitor.SharedSubject("propagateMode", owner, new Subjects.ObservableValue(false));

            Subjects.listen(this.selectedInstance(), function (value) {
                if (value && SeriesGeometry.hasGeometricMetadata(value.instance)) {
                    var instance = value.instance;

                    var instancePlane = SeriesGeometry.normalPlaneForInstance(instance.instanceAttributes);

                    var instanceGeometry = {
                        originator: value.container.viewKey.value,
                        seriesUid: instance.seriesAttributes.seriesUid,
                        studyUid: instance.studyAttributes.queryObject.studyUid,
                        imagePositionPatient: instance.instanceAttributes.imagePositionPatient,
                        imageOrientationPatient: instance.instanceAttributes.imageOrientationPatient,
                        pixelSpacing: instance.instanceAttributes.pixelSpacing,
                        rows: instance.instanceAttributes.rows,
                        columns: instance.instanceAttributes.columns,
                        normal: Vectors.toArray(instancePlane.n)
                    };

                    _this.selectedInstanceGeometry.write(instanceGeometry);
                } else {
                    _this.selectedInstanceGeometry.write(null);
                }

                if (_this.gspsInfoVisible.read()) {
                    _this.gspsInfo.render();
                }
            });

            this.imagePreloadQueue = new Queue.ObservablePriorityQueue();

            Observable.queue(this.imagePreloadQueue, 16, new Views.PreloadQueueKeyIsKey()).subscribe({
                next: function (_) {
                },
                done: function () {
                    throw new Error("The impossible happened in Observable.queue");
                },
                fail: function (_) {
                    throw new Error("The impossible happened in Observable.queue");
                }
            });

            this.imagePreloadStore = {};

            $(document).on("keydown", function (e) {
                if (!_this.noKeyboardShortcuts.read().used()) {
                    _this.handleKeyboardShortcut(e);
                }
            }).on("keyup", function (e) {
                if (!_this.noKeyboardShortcuts.read().used()) {
                    _this.handleKeyupEvent(e);
                }
            });

            if (Browser.isMobile()) {
                var message = this.terminology.lookup(Terminology.Terms.NotForDiagnosticUse);
                $('<div>').addClass('banner').append(message).appendTo($(document.body));
                $('.applicationFrame').css({ top: '6px' });
            }
        }
        /**
        * Load a study, and subscribe for updates to annotations and key images using websockets
        */
        Application.prototype.loadStudyAndSubscribeForUpdates = function (studyStorage, query) {
            var _this = this;
            return Observable.bind2(Study.loadStudy(this.sessionId, studyStorage, query, this.keyImagesOnly, this.permissions, this.settings.read(), this.accountSettings), function (study) {
                return Observable.take(1, Observable.catchError(Observable.timeout(_this.subscribeToStudyUpdates(study), 1000), function () {
                    return {};
                }));
            }, function (study, _) {
                return study;
            });
        };

        Application.prototype.loadPriorAndSubscribeForUpdates = function (studyStorage, query) {
            var _this = this;
            return Observable.bind2(Study.loadStudy(this.sessionId, studyStorage, query, this.keyImagesOnly, this.permissions, this.settings.read(), null), function (study) {
                return Observable.take(1, Observable.catchError(Observable.timeout(_this.subscribeToStudyUpdates(study), 1000), function () {
                    return {};
                }));
            }, function (study, _) {
                return study;
            });
        };

        /**
        * Modify a study-loading computation to also load annotations
        */
        Application.prototype.withAnnotations = function (query, loadStudy) {
            var _this = this;
            var loadAnnotations = this.permissions.annotation_view !== 0 ? Observable.catchError(Observable.timeout(Services.listImageAnnotations(this.sessionId, query), 2000), function () {
                return { annotations: [] };
            }) : Observable.ret({ annotations: [] });

            return Observable.map(Observable.invoke(Observable.zip(loadStudy, loadAnnotations, function (study, annotations) {
                return { study: study, annotations: annotations };
            }), function (output) {
                Annotations.applyAll(output.annotations, output.study, _this.user, _this.settings.read());
            }), function (output) {
                return output.study;
            });
        };

        /**
        * Modify a study-loading computation to also load GSPS data
        */
        Application.prototype.withGSPSData = function (loadStudy, f) {
            var _this = this;
            return Observable.bind(loadStudy, function (t) {
                var study = f(t);
                return Observable.map(Study.loadAndApplyAllGSPSData(_this.sessionId, study, _this.settings.read().supportsSRAnnotations), function (_) {
                    return study;
                });
            });
        };

        /**
        * Initialize the view
        *
        * Loads study and metadata from storage, and calls the render method
        */
        Application.prototype.load = function () {
            var _this = this;
            var loadStudy = Observable.bind2(this.loadStudyAndSubscribeForUpdates(this.studyStorage, this.queryObject), function (study) {
                if (LocalViewer.isLocalViewer()) {
                    // Local viewer returns a mock of the getStudyList call
                    return Observable.ret([LocalViewer.makePrior(study)]);
                } else if (_this.showStudyUUID.length > 0) {
                    return Services.getStudyListByUuid(_this.sessionId, _this.showStudyUUID);
                } else if (study.studyAttributes.patientId == null || (study.studyAttributes.patientId && study.studyAttributes.patientId.value != null && study.studyAttributes.patientId.value.length == 0)) {
                    // If we don't have an MRN / Patient Id we cannot find priors so just return null
                    return Observable.ret(null);
                } else {
                    var worklistFilter;

                    if (_this.accountSettings.viewer_show_priors_worklist_only == 1) {
                        worklistFilter = _this.getWorklistFilter();
                    }

                    // Attempt to query services for a list of related studies (priors), filtering out this study from that list
                    if (worklistFilter) {
                        return Services.getStudyListFiltered(_this.sessionId, _this.queryObject.phiNamespace, study.studyAttributes.patientId, null, null, _this.hideStudyUUID, worklistFilter);
                    } else {
                        return Services.getStudyList(_this.sessionId, _this.queryObject.phiNamespace, study.studyAttributes.patientId, null, null, _this.hideStudyUUID);
                    }
                }
            }, function (study, priors) {
                return {
                    study: study,
                    priors: priors
                };
            });

            return Observable.invoke(this.withAnnotations(this.queryObject, this.withGSPSData(Observable.invoke(loadStudy, function (output) {
                output.study.originalSeries = output.study.series;
                output.study.series = _.flatten(_.map(output.study.series, function (series) {
                    return Multiframe.split(series, _this.accountSettings);
                }));

                _this.setPriorNumbers(output.study, _.filter(output.priors, function (prior) {
                    return prior.study_uid !== output.study.studyAttributes.queryObject.studyUid.value;
                }));
                _this.studies.push(output.study);
                _this.priors = output.priors;

                var settings = _this.settings.read();

                // The stack series is kept separate to make it easy to find and work with later (ie in thumbnails)
                if (_this.isStackSeriesEnabled()) {
                    output.study.stackSeries = Study.createStackSeries(output.study);
                }

                // Key Images
                var priorsStack;

                _this.keyImageSeriesEnabled.write(settings.hangKeyImageSeriesFirst);
                _this.singleSeriesEnabled.write(settings.layoutSeries);
                _this.showingKeyImagesOnly = _this.keyImageSeriesEnabled.read();

                Subjects.listen(_this.singleSeriesEnabled, function (enabled) {
                    var seriesView = _this.selectedSeriesContainer.read();
                    var layout = _this.layout.read();
                    var layoutCount = layout.columns * layout.rows;
                    _this.resetSeriesViews();

                    if (enabled && seriesView) {
                        if (seriesView.series.instances.length <= layoutCount) {
                            _this.singleSeriesIndex = 0;
                        } else {
                            _this.singleSeriesIndex = seriesView.selectedInstanceIndex.read();
                        }

                        _this.render(seriesView.series);
                        _this.selectedSeriesKey.write(_this.seriesViews.value[0].viewKey);
                    } else {
                        var series;

                        if (_this.singleSeries) {
                            series = _this.singleSeries;
                        }

                        _this.singleSeries = null;
                        _this.render();

                        for (var ctr = 0; ctr < _this.seriesViews.value.length; ctr++) {
                            if (_this.seriesViews.value[ctr].series == series) {
                                _this.selectedSeriesKey.write(_this.seriesViews.value[ctr].viewKey);
                                _this.seriesViews.value[ctr].selectedInstanceIndex.write(_this.singleSeriesIndex);
                                break;
                            }
                        }
                    }

                    _this.seriesViews.raiseChangedEvent(_this.seriesViews.value);
                });

                if (!_this.keyImagesOnly && _this.keyImageSeriesEnabled.read()) {
                    if (_this.settings.read().splitKeyImageSeries) {
                        var keyImageSeries = Study.createKeyImageSeries(output.study, true);
                        if (keyImageSeries.length && keyImageSeries[0].instances.length) {
                            output.study.keyImageSeries = Study.splitSeries(keyImageSeries[0]);
                        } else {
                            output.study.keyImageSeries = [];
                        }
                    } else {
                        var stackKeyImageSeries = _this.settings.read().stackKeyImageSeries;
                        output.study.keyImageSeries = Study.createKeyImageSeries(output.study, stackKeyImageSeries);

                        if (stackKeyImageSeries && _this.settings.read().stackKeyImageSeriesPriors) {
                            priorsStack = output.study.keyImageSeries[0];
                        }
                    }
                }

                if (settings.expandPriors) {
                    var showOnlyPriors = !!settings.showOnlyPriors;
                    var thins = [];
                    _.each(_this.priors, function (prior) {
                        if (_this.studies[0].studyAttributes.queryObject.studyUid.value != prior.study_uid) {
                            var usePrior = true;

                            if (showOnlyPriors) {
                                var primaryStudyDate = _this.studies[0].studyAttributes.studyCreateDate;
                                if (primaryStudyDate && prior.study_date_value) {
                                    if (prior.study_date_value.getTime() > primaryStudyDate.getTime()) {
                                        usePrior = false;
                                    }
                                }
                            }

                            if (usePrior) {
                                if (prior.thin) {
                                    thins.push(prior);
                                } else {
                                    _this.loadPrior(prior, priorsStack);
                                }
                            }
                        }
                    });

                    _this.priors = thins;
                }

                var isReloadable = parseInt(Query.findParameter(window.location, "reloadable") || "0");
                var needsReport = parseInt(Query.findParameter(window.location, "openreport") || "0");
                if (isReloadable) {
                    if (needsReport) {
                        isReloadable = 2;
                    }

                    var app = _this;
                    function reloadListener(evt) {
                        var data = JSON.parse(evt.data);
                        window.location.href = "/viewer/#study/" + data.storageNs + "/" + data.studyUID + "/" + data.phiNs + "?sid=" + app.sessionId.value + "&reloadable=" + isReloadable;
                        window.location.reload();
                    }
                    window.addEventListener("message", reloadListener, false);
                    if (isReloadable === 2) {
                        window.localStorage.setItem("reportEvent", JSON.stringify({
                            studyId: app.studies[0].studyAttributes.uuid.value
                        }));
                    }
                }

                if (needsReport) {
                    _this.loadReport(_this.studies[0].studyAttributes.uuid, true);
                }
            }), function (output) {
                return output.study;
            })), function (_) {
                _this.render();
            });
        };

        Application.prototype.localAcceleratorCached = function (storage_namespace, study_uid, study) {
            if (this.isWorklistAccelerated()) {
                return {
                    subscribe: function (ob) {
                        var img = new Image();

                        img.onload = function () {
                            study.studyAttributes.studyStorage.localAccelerator = (this.width == Routes.LocalAccelerator.LOCAL_ACCELERATOR_TRUE);
                            ob.next(study);
                            ob.done();
                        };

                        img.onerror = function () {
                            study.studyAttributes.studyStorage.localAccelerator = false;
                            ob.next(study);
                            ob.done();
                        };

                        var data = {
                            namespace: storage_namespace,
                            studyUID: study_uid
                        };

                        var params = $.param({ data: JSON.stringify(data) });

                        img.src = Routes.LocalAccelerator.LOCAL_ACCELERATOR_PROTOCOL + Routes.LocalAccelerator.LOCAL_ACCELERATOR_HOSTNAME + ":" + Routes.LocalAccelerator.LOCAL_ACCELERATOR_PORT + "/cached" + "?" + params + "&a=" + +(new Date());

                        return Observable.memptySubscription;
                    }
                };
            } else {
                return Observable.ret(study);
            }
        };

        Application.prototype.checkAcceleration = function (storage_namespace, study_uid, withGSPSData, f) {
            var _this = this;
            return Observable.bind(withGSPSData, function (t) {
                var study = f(t);
                return Observable.invoke(_this.localAcceleratorCached(storage_namespace, study_uid, study), function (_) {
                    return study;
                });
            });
        };

        /**
        * Load a prior and make its series available
        */
        Application.prototype.loadPrior = function (prior, stack) {
            var _this = this;
            var priorQuery = new Classes.QueryObject(new Classes.StorageNamespace(prior.storage_namespace), new Classes.PhiNamespace(prior.phi_namespace), new Classes.StudyUid(prior.study_uid));

            var loadPrior = Observable.bind(Services.getStudyStorageInfo(this.sessionId, priorQuery), function (studyStorage) {
                return _this.loadPriorAndSubscribeForUpdates(studyStorage, priorQuery);
            });

            $('.overlay').addClass('application-loading');

            var loadAll = Observable._finally(this.withAnnotations(priorQuery, Observable.invoke(this.checkAcceleration(prior.storage_namespace, prior.study_uid, this.withGSPSData(loadPrior, function (study) {
                return study;
            }), function (study) {
                return study;
            }), function (study) {
                study.studyAttributes.isPrimary = false;
                study.studyAttributes.priorNumber = prior.prior_number;
                study.originalSeries = study.series;
                study.series = _.flatten(_.map(study.series, function (series) {
                    return Multiframe.split(series, _this.accountSettings);
                }));

                // The stack series is kept separate to make it easy to find and work with later (ie in thumbnails)
                if (_this.isStackSeriesEnabled()) {
                    study.stackSeries = Study.createStackSeries(study);
                }

                if (!_this.keyImagesOnly && _this.keyImageSeriesEnabled.read()) {
                    if (_this.settings.read().splitKeyImageSeries) {
                        var keyImageSeries = Study.createKeyImageSeries(study, true);
                        if (keyImageSeries.length && keyImageSeries[0].instances.length) {
                            study.keyImageSeries = Study.splitSeries(keyImageSeries[0]);
                        } else {
                            study.keyImageSeries = [];
                        }
                    } else {
                        var stackkeyimageSeries = _this.settings.read().stackKeyImageSeries;
                        study.keyImageSeries = Study.createKeyImageSeries(study, stackkeyimageSeries);
                    }
                }

                if (stack) {
                    stack.instances = _.uniq(_.union(study.keyImageSeries[0].instances, stack.instances), false, function (instance) {
                        return instance.id.value;
                    });
                    _this.stackedStudies.push(study);
                } else {
                    _this.studies.push(study);
                }

                _this.thumbnails.render();
            })), function () {
                $('.overlay').removeClass('application-loading');
            });

            loadAll.subscribe({
                next: function (_) {
                    if (_this.settings.read().expandPriors) {
                        _this.resetSeriesViews();
                        _this.render();
                        _this.selectedSeriesKey.write(_this.seriesViews.value[0].viewKey);
                    }
                },
                done: function () {
                },
                fail: function (err) {
                    _this.recordError("Unable to load prior study: " + err);
                }
            });
        };

        /**
        * Should the stack series be available
        */
        Application.prototype.isStackSeriesEnabled = function () {
            var _this = this;
            var settings = this.settings.read();

            var stackSeriesVisible = false;

            // Look for modality setting based on first series in first study
            if (settings.modalities && this.studies != null && this.studies[0] != null && this.studies[0].series[0] != null && this.studies[0].series[0].seriesAttributes != null) {
                var modalitySettings = _.find(settings.modalities, function (m) {
                    return m.modality === _this.studies[0].series[0].seriesAttributes.modality;
                });

                if (modalitySettings && modalitySettings.stackSeriesVisible) {
                    stackSeriesVisible = modalitySettings.stackSeriesVisible;
                }
            }

            // Default legacy setting, overrides modality specific settings, should only be set at a modality level going forward
            if (settings.showStackSeries) {
                stackSeriesVisible = true;
            }

            return stackSeriesVisible;
        };

        /**
        * Should the stack series be shown when the page loads
        */
        Application.prototype.isStackSeriesDisplayedFirst = function () {
            var _this = this;
            var settings = this.settings.read();

            var stackSeriesStartSelected = false;

            // Look for modality setting based on first series in first study
            if (settings.modalities && this.studies != null && this.studies[0] != null && this.studies[0].series[0] != null && this.studies[0].series[0].seriesAttributes != null) {
                var modalitySettings = _.find(settings.modalities, function (m) {
                    return m.modality === _this.studies[0].series[0].seriesAttributes.modality;
                });

                if (modalitySettings && modalitySettings.stackSeriesStartSelected) {
                    stackSeriesStartSelected = modalitySettings.stackSeriesStartSelected;
                }
            }

            return stackSeriesStartSelected;
        };

        /**
        * Get the default mouse tool based on settings
        */
        Application.prototype.getDefaultMouseTool = function (settings, accountSettings) {
            if (settings.toolSelection != null) {
                return settings.toolSelection;
            }

            if (accountSettings.viewer_default_mouse_tool != null) {
                return Classes.MouseTool[accountSettings.viewer_default_mouse_tool];
            }

            return 1 /* Scroll */;
        };

        /**
        * Get the default mouse tool based on settings
        */
        Application.prototype.getDefaultMouseTool2 = function (settings, accountSettings) {
            if (settings.toolSelection2 != null) {
                return settings.toolSelection2;
            }

            if (accountSettings.viewer_default_mouse_tool2 != null) {
                return Classes.MouseTool[accountSettings.viewer_default_mouse_tool2];
            }

            return 0 /* Move */;
        };

        /**
        * Get the default mouse tool based on settings
        */
        Application.prototype.getDefaultMouseToolWheel = function (settings, accountSettings) {
            if (settings.toolSelectionWheel != null) {
                return settings.toolSelectionWheel;
            }

            if (accountSettings.viewer_default_mouse_tool_wheel != null) {
                return Classes.MouseTool[accountSettings.viewer_default_mouse_tool_wheel];
            }

            return 0 /* Move */;
        };

        /**
        * Subscribe to study updates for the specified study
        */
        Application.prototype.subscribeToStudyUpdates = function (study) {
            var _this = this;
            var request = Messaging.studyChannel(this.sessionId, study.studyAttributes.queryObject);

            return Observable.forget(Observable.invoke(Messaging.channel(request), function (channel) {
                channel.subscribe({
                    done: function () {
                    },
                    next: function (message) {
                        _this.handleStudyChannelMessage(study, message);
                    },
                    fail: function (err) {
                        _this.recordError("Unable to subscribe for study updates: " + err);
                    }
                });
            }));
        };

        /**
        * Handle an asynchronous study message
        */
        Application.prototype.handleStudyChannelMessage = function (study, message) {
            if (message.sid_md5 !== this.user.sid_md5) {
                if (/^study\.annotation/.test(message.channel) || /^study\.annotation/.test(message.primary_channel)) {
                    this.handleAnnotationEvent(study, message);
                } else if (/^study\.keyimage/.test(message.channel) || /^study\.keyimage/.test(message.primary_channel)) {
                    this.handleKeyImageEvent(study, message);
                } else if (/^study\./.test(message.channel)) {
                    switch (message.event) {
                        case "NEW_MEETING":
                            this.addMeeting(study, message.data);
                            break;
                    }
                }
            }
        };

        /**
        * Handle an annotation event
        */
        Application.prototype.handleAnnotationEvent = function (study, message) {
            var _this = this;
            if (message.event === "DELETE") {
                Annotations.merge(study, message.event, { uuid: message.id }, this.user);
                this.renderAllFrames();
            } else {
                var loadAnnotation = Services.getImageAnnotation(this.sessionId, this.queryObject, new Classes.AnnotationId(message.id));

                loadAnnotation.subscribe({
                    done: function () {
                    },
                    next: function (ann) {
                        Annotations.merge(study, message.event, ann, _this.user);
                        _this.renderAllFrames();
                    },
                    fail: function (err) {
                        _this.recordError("Unable to retrieve annotation data: " + err);
                    }
                });
            }
        };

        /**
        * Handle a key image event
        */
        Application.prototype.handleKeyImageEvent = function (study, message) {
            var _this = this;
            if (message.event === "DELETE") {
                _.each(study.series, function (series) {
                    _.each(series.instances, function (instance) {
                        if (instance.instanceAttributes.keyImageId && instance.instanceAttributes.isKeyImage.read() && instance.instanceAttributes.keyImageId.value === message.id) {
                            instance.instanceAttributes.isKeyImage.write(false);
                            instance.instanceAttributes.keyImageId = null;
                        }
                    });
                });
            } else {
                var loadKeyImage = Services.getKeyImage(this.sessionId, new Classes.KeyImageId(message.id));

                loadKeyImage.subscribe({
                    done: function () {
                    },
                    next: function (keyImage) {
                        var series = _.find(study.series, function (s) {
                            return s.seriesAttributes.seriesUid.value === keyImage.series_uid;
                        });

                        if (series) {
                            var instance = _.find(series.instances, function (i) {
                                return i.id.value === keyImage.instance_uid;
                            });

                            if (instance) {
                                instance.instanceAttributes.isKeyImage.write(true);
                                instance.instanceAttributes.keyImageId = new Classes.KeyImageId(keyImage.uuid);
                            }
                        }
                    },
                    fail: function (err) {
                        _this.recordError("Unable to load key image data: " + err);
                    }
                });
            }
        };

        /**
        * Add a meeting to the thumbnails area
        */
        Application.prototype.addMeeting = function (study, meeting) {
            var newMeeting = new Classes.StudyMeeting();

            newMeeting.id = new Classes.MeetingId(meeting.uuid);
            newMeeting.name = meeting.name;
            newMeeting.host = meeting.user_name;

            study.meetings.push(newMeeting);

            this.thumbnails.render();
        };

        /**
        * Set the priorNumber field on prior study records related to their relative age
        */
        Application.prototype.setPriorNumbers = function (study, priors) {
            var _this = this;
            _.each(priors, function (prior) {
                prior.study_date_value = Parsers.parseDicomDate(prior.study_date);
                if (prior.study_date_value) {
                    var dateFormat = _this.settings.read().dateFormat;
                    prior.study_date = prior.study_date_value.toShortDateString(dateFormat);
                }
            });

            var currentStudyTime = study.studyAttributes.studyCreateDate ? study.studyAttributes.studyCreateDate.getTime() : 0;

            var sortedPriors = _.sortBy(priors, function (prior) {
                return prior.study_date_value ? -prior.study_date_value.getTime() : 0;
            });

            _.each(sortedPriors, function (prior, index, list) {
                var priorDate = prior.study_date_value;
                prior.prior_number = !priorDate || currentStudyTime > priorDate.getTime() ? index + 1 : index;
            });

            study.studyAttributes.isPrimary = true;
            study.studyAttributes.priorNumber = _.filter(priors, function (prior) {
                var priorDate = prior.study_date_value;
                return priorDate ? priorDate.getTime() > currentStudyTime : false;
            }).length;
        };

        Application.prototype.resetSeriesViews = function () {
            var _this = this;
            _.each(this.seriesViews.read(), function (s) {
                s.unload();
            });

            if (this.seriesViewsSubscription) {
                this.seriesViewsSubscription.cancel();
                this.seriesViewsSubscription = null;
            }

            this.seriesViews = new Subjects.ObservableValue([]);

            this.seriesViewsSubscription = this.seriesViews.updates.subscribe({
                done: function () {
                },
                fail: function (_) {
                },
                next: function (_) {
                    _this.thumbnails.render();
                }
            });
        };

        /**
        * Layout the main application grid. This method is only called when the collection of available series changes.
        */
        Application.prototype.render = function (seriesToLoad) {
            var _this = this;
            // clear listeners
            _.each(this.renderSubscriptions, function (sub) {
                return sub.cancel();
            });
            this.renderSubscriptions = [];

            // build toolbar UI
            var $toolbar;
            if (this.toolbar) {
                $toolbar = this.toolbar.el;
            } else {
                $toolbar = $('<div>').addClass('toolbar');
                this.toolbar = new Views.Toolbar($toolbar, this);
            }

            this.toolbar.render();

            // build application UI
            this.$applicationFrame = $('<div>').addClass('applicationFrame');
            this.$applicationFrame.append($toolbar);
            this.$applicationFrameInner = $('<div>').addClass('applicationFrame-inner').appendTo(this.$applicationFrame);

            // build thumbnails UI
            if (!this.thumbnails) {
                var $thumbnails = $('<div>').addClass('thumbs');
                this.thumbnails = new Views.Thumbnails($thumbnails, this);
                this.$applicationFrameInner.append($thumbnails);
            } else {
                this.$applicationFrameInner.append(this.thumbnails.el);
            }

            this.thumbnails.render();

            var $applicationFrameInner2 = $('<div>').addClass('applicationFrame-inner-2').appendTo(this.$applicationFrameInner);

            // build side panel UI
            var $meeting = $('<div>').addClass('meeting');
            this.meetingInfo = new Views.MeetingInfo($meeting, this.sessionId, this.permissions, this.terminology, this.meetingHost, function (meetingId) {
                return _this.endMeeting(meetingId);
            }, function (meetingId) {
                return _this.leaveMeeting(meetingId);
            }, function (meetingId, userUuid) {
                return _this.changeMeetingPresenter(meetingId, userUuid);
            }, this.listenForKeyboardInput);
            this.recordingsInfo = new Views.RecordingsInfo($meeting, this.sessionId, this.permissions, this.terminology, this.listenForKeyboardInput);
            this.attachmentInfo = new Views.AttachmentInfo($meeting, this.sessionId, this.permissions, this.terminology, this.listenForKeyboardInput);
            this.gspsInfo = new Views.GSPSInfo($meeting, this.sessionId, this.terminology, this);
            $applicationFrameInner2.append($meeting);

            // configure application instance
            var viewerSettings = this.settings.read();
            _.each(this.studies, function (study) {
                study.series = Study.filterSeries(study.series, viewerSettings);
            });

            this.renderSubscriptions.push(Subjects.listen(Subjects.zip(this.thumbnailsVisible, this.inMeeting, function (b1, b2) {
                return b1 && !b2;
            }), function (visible) {
                _this.updateThumbnailsVisibility(visible);
                _this.renderAllFrames();
            }));

            this.renderSubscriptions.push(Subjects.listen(Subjects.zip(this.inMeeting, this.meetingHost, function (b1, b2) {
                return b1 || b2;
            }), function (visible) {
                _this.updateMeetingInfoVisibility(visible);
                _this.renderAllFrames();
            }));

            this.renderSubscriptions.push(Subjects.listen(this.inMeeting, function (inMeeting) {
                if (inMeeting) {
                    _this.showMeetingGlassPane();
                } else {
                    _this.hideMeetingGlassPane();
                }
            }));

            // Hide thumbnails if necessary
            var hideThumbnails = (this.accountSettings.viewer_hide_thumbnails == 1) || this.settings.read().hideThumbnails;
            this.thumbnailsVisible.write(!hideThumbnails);

            this.updateThumbnailsVisibility(this.thumbnailsVisible.read());

            this.renderSubscriptions.push(Subjects.listen(this.splitStudyEnabled, function (enabled) {
                if (enabled) {
                    _this.splitStudyMode = true;
                    _this.showWarningBanner(_this.terminology.lookup(Terminology.Terms.SplitInstructions));
                } else {
                    if (_this.splitStudyMode) {
                        _this.hideWarningBanner();
                        _this.splitStudyMode = false;
                        var splitting = _.map(_.filter(_this.studies[0].series, function (series) {
                            return series.splitSelected;
                        }), function (series) {
                            return series.seriesAttributes.seriesUid.value;
                        });
                        _.each(_this.studies[0].series, function (series) {
                            series.splitSelected = false;
                        });
                        $(".series-thumbnail-container").removeClass("splitting");
                        _this.splitStudy(_this.studies[0], splitting);
                    }
                }
            }));

            this.renderSubscriptions.push(Subjects.listen(this.mergeSeriesEnabled, function (enabled) {
                if (enabled) {
                    _this.mergeSeriesMode = true;
                    _this.showWarningBanner(_this.terminology.lookup(Terminology.Terms.MergeInstructions));
                } else {
                    if (_this.mergeSeriesMode) {
                        _this.hideWarningBanner();
                        _this.mergeSeriesMode = false;
                        var merging = _.filter(_this.studies[0].series, function (series) {
                            return series.mergeSelected;
                        });
                        _.each(_this.studies[0].series, function (series) {
                            series.mergeSelected = false;
                        });
                        $(".series-thumbnail-container").removeClass("merging");
                        _this.mergeSeries(_this.studies[0], merging);
                    }
                }
            }));

            this.renderSubscriptions.push(Subjects.listen(this.keyImageSeriesEnabled, function (enabled) {
                var studies = _this.studies;
                var stackedStudies = _this.stackedStudies;

                // it's possible for touch/click events to trigger this multiple times very rapidly leading to a bad state
                if (_this.showingKeyImagesOnly != enabled) {
                    if (enabled) {
                        var stackKeyImageSeries = _this.settings.read().stackKeyImageSeries;
                        var splitKeyImageSeries = _this.settings.read().splitKeyImageSeries;
                        var stackKeyImageSeriesPriors = _this.settings.read().stackKeyImageSeriesPriors;

                        _.each(_this.studies, function (study) {
                            if (splitKeyImageSeries) {
                                var keyImageSeries = Study.createKeyImageSeries(study, true);
                                if (keyImageSeries.length && keyImageSeries[0].instances.length) {
                                    study.keyImageSeries = Study.splitSeries(keyImageSeries[0]);
                                } else {
                                    study.keyImageSeries = [];
                                }
                            } else {
                                study.keyImageSeries = Study.createKeyImageSeries(study, stackKeyImageSeries);
                            }
                        });

                        if (stackKeyImageSeriesPriors && stackKeyImageSeries && !splitKeyImageSeries) {
                            var stack = _this.studies[0].keyImageSeries[0];

                            _.each(_this.studies, function (study, index) {
                                if (index > 0) {
                                    study.keyImageSeries = Study.createKeyImageSeries(study, stackKeyImageSeries);
                                    stack.instances = _.uniq(_.union(study.keyImageSeries[0].instances, stack.instances), false, function (instance) {
                                        return instance.id.value;
                                    });
                                    stackedStudies.push(study);
                                }
                            });

                            _.each(_this.stackedStudies, function (study) {
                                studies = _.without(studies, study);
                            });

                            _this.studies = studies;
                            _this.priors = [];
                        }
                    } else {
                        _.each(_this.stackedStudies, function (study) {
                            studies.push(study);
                        });

                        _this.stackedStudies = [];
                    }

                    _this.resetSeriesViews();
                    _this.render();
                    _this.selectedSeriesKey.write(_this.seriesViews.value[0].viewKey);
                    _this.thumbnails.render();
                }

                _this.showingKeyImagesOnly = enabled;
            }));

            var allSeries = _.toArray(_.flatten(_.map(this.studies, function (study) {
                return study.series;
            })));

            // If the key images series is displayed do not display the regular series
            if (this.keyImageSeriesEnabled.read()) {
                var allKeyImageSeries = _.toArray(_.flatten(_.map(this.studies, function (study) {
                    return study.keyImageSeries;
                })));

                if (allKeyImageSeries && allKeyImageSeries.length && allKeyImageSeries[0]) {
                    allSeries = allKeyImageSeries;

                    var keyImageOrder = viewerSettings.keyImageOrder;

                    if (keyImageOrder) {
                        allSeries = Study.orderSeries(allSeries, keyImageOrder == "desc");
                    }
                }
            }

            // Hang the stack series in the first available screen if requested to in modality settings
            if (this.isStackSeriesDisplayedFirst() && this.studies[0].stackSeries) {
                allSeries = [this.studies[0].stackSeries].concat(allSeries);
            }

            var $allSeries = $('<div>').addClass('study-series');
            var firstLayout = false;

            if (_.any(allSeries)) {
                var screenIndex = parseInt(Query.findParameter(window.location, "screen") || "0");
                var hangingProtocol;

                if (this.singleSeriesEnabled.read()) {
                    this.singleSeries = seriesToLoad || allSeries[0];
                    allSeries = Study.splitSeries(this.singleSeries).slice(this.singleSeriesIndex);

                    if (this.initializedLayout) {
                        var layout = this.layout.read();
                        hangingProtocol = HangingProtocols.selectDefault(this.studies[0], this.settings.read(), { rows: layout.rows, columns: layout.columns });
                    } else {
                        firstLayout = true;
                        hangingProtocol = HangingProtocols.selectDefault(this.studies[0], this.settings.read());
                    }

                    this.hang(allSeries, $allSeries, hangingProtocol, screenIndex);
                } else {
                    hangingProtocol = HangingProtocols.select(this.studies[0], this.settings.read());
                    this.hang(allSeries, $allSeries, hangingProtocol, screenIndex);

                    if (screenIndex == 0) {
                        var isChildWindow = parseInt(Query.findParameter(window.location, "isChildWindow") || "0");

                        if (isChildWindow == 0) {
                            if (window.self == window.top) {
                                if (this.accountSettings.fullscreen) {
                                    this.fullscreenOnDisplay(0, hangingProtocol.screens.length);
                                } else if (!Browser.uiFound()) {
                                    this.needsDirectFullscreen(function (fullscreen) {
                                        if (fullscreen) {
                                            _this.fullscreenOnDisplay(0, hangingProtocol.screens.length, null, true);
                                        }
                                    });
                                }

                                window.addEventListener("storage", function (e) {
                                    try  {
                                        if (e.key === "viewerReloadEvent") {
                                            var data = JSON.parse(e.newValue);
                                            window.location.href = data.url;
                                        }
                                    } catch (ex) {
                                    }
                                });
                            }

                            for (var screen = 1; screen < hangingProtocol.screens.length; screen++) {
                                this.openNewWindow(screen, hangingProtocol.screens.length);
                            }
                        }
                    }
                }
            } else {
                $allSeries.append(this.terminology.lookup(Terminology.Terms.NoImageSeries));
            }

            if (firstLayout && this.singleSeriesEnabled.read() && !this.canDisplaySingleSeries(this.layout.read())) {
                this.layout.write({ columns: 1, rows: 1 });
            }

            $applicationFrameInner2.append($allSeries);

            this.renderSubscriptions.push(Subjects.listen(this.linkSeries, function (link) {
                if (link) {
                    _this.loadVisibleImageAttributes(_this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
                }
            }));

            this.renderSubscriptions.push(Subjects.listen(this.propagateMode, function (enabled) {
                if (enabled) {
                    var series = _this.selectedSeriesContainer.read();
                    if (series) {
                        series.startPropagation();
                        _this.showWarningBanner(_this.terminology.lookup(Terminology.Terms.PropagateInstructions));
                    }
                } else {
                    _this.hideWarningBanner();
                }
            }));

            this.renderSubscriptions.push(Subjects.listen(Subjects.zip(this.selectedTool, this.layout, function (tool, _) {
                return tool;
            }), function (tool) {
                if (tool === 9 /* Localization */) {
                    _this.loadVisibleImageAttributes(_this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
                    if (Browser.isChrome()) {
                        _this.loadVisibleThumbnails(_this.imagePreloadQueue, -2 /* ImageDataBackgroundPreloading */);
                    }
                }
            }));

            if (this.selectedTool.read() === 9 /* Localization */ || LocalViewer.isLocalViewer()) {
                this.loadVisibleImageAttributes(this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
            }

            this.renderSubscriptions.push(Subjects.listen(this.recordingMode, function (recording) {
                if (recording) {
                    _this.startRecording();
                }
            }));

            // build context menu UI
            var contextMenu = (function () {
                var settings = _this.settings.read();

                if (settings.contextMenu !== undefined) {
                    return settings.contextMenu;
                } else {
                    return ContextMenus.createDefaultContextMenu();
                }
            })();

            Views.ContextMenu.apply(".series", contextMenu, this);

            // draw application
            this.el.empty();
            this.el.append(this.$applicationFrame);

            if (!!this.studyStorage.is_frozen) {
                this.showWarningBanner(this.terminology.lookup(Terminology.Terms.FrozenStudyWarning));
            }

            if (this.keyImageSeriesEnabled.read()) {
                this.layoutKeyImages();
            }
        };

        Application.prototype.layoutKeyImages = function () {
            var app = this;

            if (this.hangKeyImageHandlerId) {
                clearTimeout(this.hangKeyImageHandlerId);
            }

            this.hangKeyImageHandlerId = setTimeout(function () {
                Services.getKeyImageSettings(app.sessionId).subscribe({
                    done: function () {
                    },
                    next: function (res) {
                        if (res) {
                            app.keyImageLayout = res;
                            var keyImageLayout = _.find(res, function (k) {
                                return app.studies[0].studyAttributes.patientId.value == k.patientId;
                            });
                            if (keyImageLayout) {
                                app.applyKeyImageLayoutState(keyImageLayout);
                            }
                        }
                    },
                    fail: function (err) {
                        console.error("Unable to get key image settings: " + err);
                    }
                });
            }, 500);
        };

        /**
        * Hang series as described by a single screen of a hanging protocol object
        */
        Application.prototype.hang = function (allSeries, $allSeries, hangingProtocol, screenIndex) {
            var _this = this;
            var screen = hangingProtocol.screens[screenIndex];
            this.layout.write({ rows: screen.rows, columns: screen.cols });

            if (hangingProtocol.cineOptions) {
                this.cineSpeed.write(hangingProtocol.cineOptions.defaultCineFrameRate);
                this.ignoreFrameTimeTag = hangingProtocol.cineOptions.ignoreFrameTimeTag || false;
                this.startCinePlaybackAutomatically.write(hangingProtocol.cineOptions.startCinePlaybackAutomatically);
            }

            var protocolSeries = HangingProtocols.apply(allSeries, hangingProtocol);
            var screenSeries = protocolSeries.matches[screenIndex];
            var layoutOriginal = this.layout.read();
            var maxViews = Math.max(layoutOriginal.rows * layoutOriginal.columns, 16);

            for (var i = 0; i < maxViews; i++) {
                (function (i) {
                    var series = null;
                    var displayOptions = null;

                    if (i < screenSeries.length) {
                        var match = screenSeries[i];
                        if (match) {
                            series = match.series;
                            displayOptions = match.displayOptions;
                            series.displayOptions = match.displayOptions;
                        }
                    } else if (i - screenSeries.length < protocolSeries.rest.length) {
                        series = protocolSeries.rest[i - screenSeries.length];
                    }

                    var $series = $('<div>').addClass('series');

                    var seriesView = new Views.Series(_this, _this.sessionId, $series, series, _this.settings, _this.accountSettings, _this.selectedSeriesKey, _this.selectedTool, _this.selectedTool2, _this.selectedToolWheel, _this.selectedInstanceGeometry, _this.referenceLinesActive, _this.planeLocalizationCursor, _this.linkSeries, _this.imagePreloadQueue, _this.terminology, _this.cineSpeed, _this.ignoreFrameTimeTag, _this.startCinePlaybackAutomatically, _this.noKeyboardShortcuts, Subjects.map(_this.recorder, function (r) {
                        return _this.modifyRecorder(r, i);
                    }), _this.playbackMode);

                    seriesView.applyDisplayOptions(displayOptions);

                    // if locally accelerated but not currently displayed, preload the series (queues storage calls)
                    if (!displayOptions && _this.supportsAcceleratedPreload()) {
                        if (series != null && series.seriesAttributes != null && series.seriesAttributes.seriesUid != null && series.seriesAttributes.seriesUid.value != null) {
                            seriesView.init(true);
                        }
                    }

                    _this.seriesViews.value.push(seriesView);

                    if (series != null && series.seriesAttributes != null && series.seriesAttributes.seriesUid != null && series.seriesAttributes.seriesUid.value != null) {
                        if (_this.imagePreloadStore[series.seriesAttributes.seriesUid.value] == null) {
                            _this.imagePreloadStore[series.seriesAttributes.seriesUid.value] = new Views.ByImageTypeAndInstanceNumber();
                        }
                    }

                    if (series != null && series.seriesAttributes != null && Multiframe.shouldSplitInstances(series.seriesAttributes.modality)) {
                        seriesView.loadImageAttributes(_this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
                    }
                })(i);
            }

            this.thumbnails.render();

            var seriesElements = Subjects.zip(this.seriesViews, this.magnifiedSeries, function (ss, mag) {
                return mag === null ? _.map(ss, function (s) {
                    return s.el;
                }) : [mag.el];
            });

            var layout = Subjects.zip(this.layout, this.magnifiedSeries, function (layout, series) {
                return series === null ? layout : { rows: 1, columns: 1 };
            });

            var startingSeriesUid = Query.findParameter(window.location, "showSeriesUID");

            // Initial series specified
            if (this.selectedSeriesKey.read() === Views.SeriesViewKey.Null && startingSeriesUid != null) {
                var series = this.seriesViews.read();
                for (var i = 0; i < allSeries.length; i++) {
                    if (series[i].series && series[i].series.seriesAttributes && series[i].series.seriesAttributes.seriesUid && series[i].series.seriesAttributes.seriesUid.value == startingSeriesUid) {
                        this.loadSeriesAt(series[i].series, 0);
                        this.seriesViews.raiseChangedEvent(series);
                        this.selectedSeriesKey.write(series[i].viewKey);
                        break;
                    }
                }
            }

            // Either no starting series selected or starting series not found
            if (this.selectedSeriesKey.read() === Views.SeriesViewKey.Null) {
                var firstSeries = _.find(this.seriesViews.read(), function (s) {
                    return s.series != null;
                });
                if (firstSeries) {
                    this.selectedSeriesKey.write(firstSeries.viewKey);
                } else {
                    this.selectedSeriesKey.write(_.first(this.seriesViews.read()).viewKey);
                }
            }

            $allSeries.grid(layout, Subjects.ret(0), seriesElements, function () {
                _.delay(function () {
                    return _this.renderAllFrames();
                }, 10);
            });

            this.initializedLayout = true;
        };

        Application.prototype.supportsAcceleratedPreload = function () {
            return this.settings.read().preloadAccelerated && this.studyStorage.localAccelerator && this.isDiagnosticQualityAlways();
        };

        /**
        * Create a recorder which inserts the correct value for seriesContainerIndex
        */
        Application.prototype.modifyRecorder = function (recorder, index) {
            return {
                append: function (e) {
                    e.seriesContainerIndex = index;
                    recorder.append(e);
                },
                getEvents: recorder.getEvents,
                setZeroTimestamp: recorder.setZeroTimestamp
            };
        };

        /**
        * Load image attributes for all visible series to enable linking
        */
        Application.prototype.loadVisibleImageAttributes = function (queue, priority) {
            var _this = this;
            var layout = this.layout.read();

            _.each(_.range(0, Math.min(this.seriesViews.value.length, layout.rows * layout.columns)), function (index) {
                _this.seriesViews.value[index].loadImageAttributes(queue, priority);
            });
        };

        /**
        * Start loading thumbnails for all visible series
        */
        Application.prototype.loadVisibleThumbnails = function (queue, priority) {
            var _this = this;
            var layout = this.layout.read();

            _.each(_.range(0, Math.min(this.seriesViews.value.length, layout.rows * layout.columns)), function (index) {
                _this.seriesViews.value[index].loadAllImageData(queue, priority);
            });
        };

        Application.prototype.handleKeyupEvent = function (e) {
            switch (e.keyCode) {
                case 38:
                    if (this.keyStatus.up !== undefined) {
                        window.clearInterval(this.keyStatus.up);
                        delete this.keyStatus.up;
                    }
                    if (this.keyStatus.upWait !== undefined) {
                        window.clearTimeout(this.keyStatus.upWait);
                        delete this.keyStatus.upWait;
                    }
                    break;
                case 40:
                    if (this.keyStatus.down !== undefined) {
                        window.clearInterval(this.keyStatus.down);
                        delete this.keyStatus.down;
                    }
                    if (this.keyStatus.downWait !== undefined) {
                        window.clearTimeout(this.keyStatus.downWait);
                        delete this.keyStatus.downWait;
                    }
                    break;
            }
        };

        /**
        * Handle a key-press event
        */
        Application.prototype.handleKeyboardShortcut = function (e) {
            var _this = this;
            if (!this.listenForKeyboardInput.read()) {
                return;
            }

            var series = this.selectedSeriesContainer.read();

            if (series != null) {
                var handled = true;

                switch (e.keyCode) {
                    case 8:
                    case 46:
                        if ((this.permissions.annotation_edit !== 0) && series.hasSelectedMeasurement()) {
                            series.deleteSelectedMeasurement();
                        } else if (this.keyImageSeriesEnabled.read()) {
                            this.clearKeyImageLayoutState();
                        }
                        break;
                    case 33:
                        series.scrollBy(-10);
                        break;
                    case 34:
                        series.scrollBy(10);
                        break;
                    case 38:
                        series.scrollBy(-1);
                        if (this.keyStatus.upWait === undefined) {
                            this.keyStatus.upWait = window.setTimeout(function () {
                                delete _this.keyStatus.upWait;
                                if (_this.keyStatus.up === undefined) {
                                    _this.keyStatus.up = window.setInterval(function () {
                                        series.scrollBy(-1);
                                    }, 10);
                                }
                            }, 250);
                        }
                        break;
                    case 40:
                        series.scrollBy(1);
                        if (this.keyStatus.downWait === undefined) {
                            this.keyStatus.downWait = window.setTimeout(function () {
                                delete _this.keyStatus.downWait;
                                if (_this.keyStatus.down === undefined) {
                                    _this.keyStatus.down = window.setInterval(function () {
                                        series.scrollBy(1);
                                    }, 10);
                                }
                            }, 250);
                        }
                        break;
                    case 49:
                    case 50:
                    case 51:
                    case 52:
                    case 53:
                    case 54:
                    case 55:
                    case 56:
                    case 57:
                        series.applyWindowLevelPreset(e.keyCode - 49);
                        break;
                    case 97:
                    case 98:
                    case 99:
                    case 100:
                    case 101:
                    case 102:
                    case 103:
                    case 104:
                    case 105:
                        series.applyWindowLevelPreset(e.keyCode - 97);
                        break;
                    default:
                        var settings = this.settings.read();

                        handled = (function () {
                            var bindings = settings.keyBindings || Keyboard.createDefaultKeyConfiguration();

                            if (bindings && bindings.items) {
                                var key = _.find(bindings.items, function (item) {
                                    return item.keyCode === e.keyCode && !!item.ctrl == e.ctrlKey && !!item.alt == e.altKey && !!item.meta == e.metaKey && !!item.shift == e.shiftKey;
                                });

                                if (key) {
                                    _this.invokeToolbarAction(key.action);
                                    return true;
                                }

                                switch (e.keyCode) {
                                    case 37:
                                        if (_this.gspsInfoVisible.read()) {
                                            _this.gspsInfo.previousGSPS();
                                        }
                                        break;
                                    case 39:
                                        if (_this.gspsInfoVisible.read()) {
                                            _this.gspsInfo.nextGSPS();
                                        }
                                        break;
                                }
                            }

                            return false;
                        })();
                }

                if (handled) {
                    e.preventDefault();
                }
            }
        };

        /**
        * Invoke a toolbar action manually
        */
        Application.prototype.invokeToolbarAction = function (action) {
            Views.AbstractToolbars.addStandardToolbarButton({
                addLabel: function (_) {
                    return {};
                },
                addToolbarButton: function (opts) {
                    opts.click();
                    return {};
                },
                addToggleButton: function (opts) {
                    Subjects.modify(opts.selected, function (b) {
                        return !b;
                    });
                    return {};
                },
                addDropDownButton: function (_1, _2) {
                    return {};
                },
                addLayoutButtons: function (_) {
                    return [];
                },
                addColorButtons: function (_) {
                    return [];
                },
                addMouseToolSettings: function (_) {
                    return [];
                }
            }, action, false, this);
        };

        /**
        * Synchronize the visibility of the thumbnails with the current setting
        */
        Application.prototype.updateThumbnailsVisibility = function (visible) {
            if (visible) {
                this.$applicationFrameInner.addClass('withthumbs');
            } else {
                this.$applicationFrameInner.removeClass('withthumbs');
            }
        };

        /**
        * Synchronize the visibility of the meetings panel with the current setting
        */
        Application.prototype.updateMeetingInfoVisibility = function (visible) {
            if (visible) {
                this.$applicationFrameInner.addClass('withmeeting');
            } else {
                this.$applicationFrameInner.removeClass('withmeeting');
            }
        };

        Application.prototype.updateRecordingsInfoVisibility = function (visible) {
            if (visible) {
                this.$applicationFrameInner.addClass('withmeeting withrecording');
            } else {
                this.$applicationFrameInner.removeClass('withmeeting withrecording');
            }
        };

        Application.prototype.updateAttachmentInfoVisibility = function (visible) {
            if (visible) {
                this.$applicationFrameInner.addClass('withmeeting withattachments');
            } else {
                this.$applicationFrameInner.removeClass('withmeeting withattachments');
            }
        };

        Application.prototype.updateGSPSInfoVisibility = function (visible) {
            this.gspsInfoVisible.write(visible);

            if (visible) {
                this.$applicationFrameInner.addClass('withmeeting withgsps');
            } else {
                this.$applicationFrameInner.removeClass('withmeeting withgsps');
            }
        };

        /**
        * Render all visible series
        */
        Application.prototype.renderAllFrames = function () {
            for (var i = 0; i < this.seriesViews.value.length; i++) {
                var series = this.seriesViews.value[i];
                series.renderAll();
            }
        };

        /**
        * A subject which returns the model of the first visible instance in the selected series
        */
        Application.prototype.selectedInstance = function () {
            return Subjects.bind(this.selectedSeriesContainer, function (series) {
                return series === null ? Subjects.ret(null) : series.selectedInstanceIndex;
            }, function (series, index) {
                if (!series || !series.series) {
                    return null;
                } else {
                    var instance = series.series.instances[index % series.series.instances.length];
                    return { instance: instance, container: series };
                }
            });
        };

        /**
        *  Replace the selected series with the previous series in study.series
        */
        Application.prototype.loadPrevSeries = function (startAtLastInstance) {
            if (typeof startAtLastInstance === "undefined") { startAtLastInstance = false; }
            var selectedSeriesContainer = this.selectedSeriesContainer.read();

            if (selectedSeriesContainer) {
                var currentUID = selectedSeriesContainer.series.seriesAttributes.seriesUid;
                var currentIndex = null;
                var nextSeries = null;

                var currentStudy = _.filter(this.studies, function (study) {
                    return study.studyAttributes.uuid.value == selectedSeriesContainer.series.studyAttributes.uuid.value;
                });
                var allSeries = _.toArray(_.flatten(_.map(currentStudy, function (study) {
                    return study.series;
                })));

                for (var i = 0; i < allSeries.length; i++) {
                    if (allSeries[i].seriesAttributes.seriesUid.value == currentUID.value) {
                        currentIndex = i;
                        break;
                    }
                }

                if (currentIndex != null) {
                    // Loop to the last series if we are viewing the first series, otherwise load the previous series
                    nextSeries = (currentIndex == 0) ? allSeries[allSeries.length - 1] : allSeries[currentIndex - 1];
                }

                if (nextSeries != null) {
                    this.replaceSelectedSeries(nextSeries);

                    var loadedSeries = this.selectedSeriesContainer.read();
                    loadedSeries.init();
                    loadedSeries.arrow('prev-series');

                    if (startAtLastInstance) {
                        loadedSeries.scrollBy(loadedSeries.series.instances.length - 1);
                    }
                }
            }
        };

        /**
        *  Replace the selected series with the next series in study.series
        */
        Application.prototype.loadNextSeries = function () {
            var selectedSeriesContainer = this.selectedSeriesContainer.read();

            if (selectedSeriesContainer) {
                var currentUID = selectedSeriesContainer.series.seriesAttributes.seriesUid;
                var currentIndex = null;
                var nextSeries = null;

                var currentStudy = _.filter(this.studies, function (study) {
                    return study.studyAttributes.uuid.value == selectedSeriesContainer.series.studyAttributes.uuid.value;
                });
                var allSeries = _.toArray(_.flatten(_.map(currentStudy, function (study) {
                    return study.series;
                })));

                for (var i = 0; i < allSeries.length; i++) {
                    if (allSeries[i].seriesAttributes.seriesUid.value == currentUID.value) {
                        currentIndex = i;
                        break;
                    }
                }

                if (currentIndex != null) {
                    // Loop to the first series if we are viewing the last series, otherwise load the next series
                    nextSeries = (currentIndex == allSeries.length - 1) ? allSeries[0] : allSeries[currentIndex + 1];
                }

                if (nextSeries != null) {
                    this.replaceSelectedSeries(nextSeries);

                    var loadedSeries = this.selectedSeriesContainer.read();
                    loadedSeries.init();
                    loadedSeries.arrow('next-series');
                }
            }
        };

        /**
        * Replace the selected series with the provided series
        */
        Application.prototype.replaceSelectedSeries = function (series) {
            var views = this.seriesViews.value;
            var selectedSeriesContainer = this.selectedSeriesContainer.read();

            if (!selectedSeriesContainer) {
                this.selectedSeriesKey.write(this.seriesViews.read()[0].viewKey);
                selectedSeriesContainer = this.selectedSeriesContainer.read();
            }

            if (selectedSeriesContainer) {
                if (this.isMagnified()) {
                    this.magnifyMinify(selectedSeriesContainer);
                }

                selectedSeriesContainer.unload();

                for (var i = 0; i < views.length; i++) {
                    if (views[i].viewKey.value === selectedSeriesContainer.viewKey.value) {
                        if (this.singleSeriesEnabled.read()) {
                            this.resetSeriesViews();

                            if (this.singleSeries != series) {
                                this.singleSeriesIndex = 0;
                            }

                            this.render(series);
                            this.selectedSeriesKey.write(this.seriesViews.read()[0].viewKey);
                        } else {
                            var seriesView = this.loadSeriesAt(series, i);
                            this.selectedSeriesKey.write(seriesView.viewKey);
                        }

                        this.seriesViews.raiseChangedEvent(views);

                        break;
                    }
                }

                if (this.selectedTool.read() === 9 /* Localization */) {
                    this.loadVisibleImageAttributes(this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
                }
            }
        };

        /**
        * Load a series at a particular location
        */
        Application.prototype.loadSeriesAt = function (series, index) {
            var _this = this;
            var $series = $('<div>').addClass('series');

            var seriesView = new Views.Series(this, this.sessionId, $series, series, this.settings, this.accountSettings, this.selectedSeriesKey, this.selectedTool, this.selectedTool2, this.selectedToolWheel, this.selectedInstanceGeometry, this.referenceLinesActive, this.planeLocalizationCursor, this.linkSeries, this.imagePreloadQueue, this.terminology, this.cineSpeed, this.ignoreFrameTimeTag, this.startCinePlaybackAutomatically, this.noKeyboardShortcuts, Subjects.map(this.recorder, function (r) {
                return _this.modifyRecorder(r, index);
            }), this.playbackMode);

            this.seriesViews.value[index] = seriesView;
            seriesView.init();
            if (series) {
                seriesView.applyDisplayOptions(series.displayOptions);
            } else {
                seriesView.applyDisplayOptions(null);
            }

            this.recorder.read().append({
                type: 5 /* SeriesChanged */,
                seriesContainerIndex: index,
                seriesInstanceUid: series === null || typeof series == "undefined" ? null : series.seriesAttributes.seriesUid
            });

            return seriesView;
        };

        /**
        * Add a new series
        */
        Application.prototype.addSeries = function (series) {
            var study = _.find(this.studies, function (study) {
                return study.studyAttributes.queryObject.studyUid === series.studyAttributes.queryObject.studyUid;
            });

            if (!study) {
                debugger;
            }

            study.series.push(series);

            this.render();
        };

        /**
        * Open the print view in a new window
        */
        Application.prototype.print = function () {
            var study = this.studies[0];
            var id = study.studyAttributes.queryObject.toString();
            var sid = this.sessionId.value;
            window.open("/viewer/print.html#print/{0}?sid={1}".replace("{0}", encodeURIComponent(id)).replace("{1}", encodeURIComponent(sid)));
        };

        /**
        * Open the metadata editor
        */
        Application.prototype.editMetadata = function () {
            var selectedSeriesContainer = this.selectedSeriesContainer.read();

            if (selectedSeriesContainer && selectedSeriesContainer.series) {
                var id = selectedSeriesContainer.series.studyAttributes.queryObject;
                var selectedInstanceIndex = selectedSeriesContainer.selectedInstanceIndex.read();

                var instance = selectedSeriesContainer.series.instances[selectedInstanceIndex];

                window.open("/studies/metadata.html?namespace_id={0}&study_uid={1}&phi_namespace={2}&image_uid={3}&version={4}".replace("{0}", encodeURIComponent(id.storageNamespace.value)).replace("{1}", encodeURIComponent(id.studyUid.value)).replace("{2}", encodeURIComponent(id.phiNamespace.value)).replace("{3}", encodeURIComponent(instance.id.value)).replace("{4}", encodeURIComponent(instance.instanceAttributes.version.value)));
            }
        };

        /**
        * Open the MPR view in a new window
        */
        Application.prototype.mpr = function () {
            var study = this.studies[0];
            var id = study.studyAttributes.queryObject.toString();
            var sid = this.sessionId.value;
            var series = this.selectedSeriesContainer.read();

            if (series) {
                var seriesUid = series.series.seriesAttributes.seriesUid;

                window.open("/viewer/mpr.html#mpr/{0}?sid={1}&seriesUid={2}".replace("{0}", encodeURIComponent(id)).replace("{1}", encodeURIComponent(sid)).replace("{2}", encodeURIComponent(seriesUid.value)));
            }
        };

        /**
        * Open the viewer in diagnostic quality mode
        */
        Application.prototype.useDiagosticQualityViewer = function () {
            var study = this.studies[0];
            var id = study.studyAttributes.queryObject.toString();
            var sid = this.sessionId.value;
            window.open("/viewer/#study/{0}?sid={1}&viewer_diagnostic_quality=1&viewer_diagnostic_quality_always=1".replace("{0}", encodeURIComponent(id)).replace("{1}", encodeURIComponent(sid)));
        };

        /**
        * Open the recorder in a new window
        */
        Application.prototype.recordAudio = function () {
            var study = this.studies[0];
            var id = study.studyAttributes.queryObject;
            var sid = this.sessionId.value;
            window.open("/studies/recorder.html?namespace_id={0}&study_uid={1}&phi_namespace={2}".replace("{0}", encodeURIComponent(id.storageNamespace.value)).replace("{1}", encodeURIComponent(id.studyUid.value)).replace("{2}", encodeURIComponent(id.phiNamespace.value)));
        };

        /**
        * Open the settings editor in a new window
        */
        Application.prototype.editSettings = function () {
            window.open("/viewer/settings/#?sid=" + encodeURIComponent(this.sessionId.value));
        };

        Application.prototype.getWorklistData = function (byMRN) {
            if (window.sessionStorage) {
                var data = window.sessionStorage.getItem(byMRN ? "worklist:mrn-data" : "worklist:data");
                if (data) {
                    var unstringify = JSON.parse(data);
                    if (unstringify) {
                        return JSON.parse(unstringify);
                    }
                }
            }

            return null;
        };

        Application.prototype.getWorklistFilter = function () {
            var worklistFilter = null;

            if (window.sessionStorage) {
                var data = window.sessionStorage.getItem("worklist:currentSearch");
                if (data) {
                    var unstringify = JSON.parse(data);
                    if (unstringify) {
                        worklistFilter = _.pairs(JSON.parse(unstringify));
                    }
                }
            }

            return worklistFilter;
        };

        Application.prototype.getCurrentWorklistIndex = function (worklist, byMRN) {
            var currentIndex = -1;
            for (var ctr = 0; ctr < worklist.length; ctr += 1) {
                if ((this.queryObject.studyUid.value == worklist[ctr].studyUID) || (byMRN && (this.studyStorage.patientid == worklist[ctr].patientId))) {
                    currentIndex = ctr;
                    break;
                }
            }

            return currentIndex;
        };

        Application.prototype.hasNextWorklistStudy = function (byMRN) {
            var worklist = this.getWorklistData(byMRN);
            if (worklist) {
                var currentIndex = this.getCurrentWorklistIndex(worklist, byMRN);

                if (currentIndex != -1) {
                    return currentIndex < (worklist.length - 1);
                }
            }

            return false;
        };

        Application.prototype.hasPreviousWorklistStudy = function (byMRN) {
            var worklist = this.getWorklistData(byMRN);
            if (worklist) {
                var currentIndex = this.getCurrentWorklistIndex(worklist, byMRN);

                if (currentIndex != -1) {
                    return currentIndex > 0;
                }
            }

            return false;
        };

        Application.prototype.nextWorklistStudy = function (byMRN) {
            this.changeWorklistStudy(false, byMRN);
        };

        Application.prototype.previousWorklistStudy = function (byMRN) {
            this.changeWorklistStudy(true, byMRN);
        };

        Application.prototype.isWorklistAccelerated = function () {
            if (window.sessionStorage) {
                var data = window.sessionStorage.getItem("worklist:acceleration");
                if (data) {
                    var unstringify = JSON.parse(data);
                    if (unstringify) {
                        return JSON.parse(unstringify);
                    }
                }
            }

            return false;
        };

        Application.prototype.changeWorklistStudy = function (decrement, byMRN) {
            var worklist = this.getWorklistData(byMRN);
            if (worklist) {
                var currentIndex = this.getCurrentWorklistIndex(worklist, byMRN);

                if (currentIndex != -1) {
                    var nextIndex = currentIndex + (decrement ? -1 : 1);
                    if ((nextIndex >= 0) && (nextIndex < worklist.length)) {
                        var params = "";

                        if (worklist[nextIndex].accelerated) {
                            params += "&viewer_local_accelerator=1";
                        }

                        if (!this.keyImagesOnly && this.keyImageSeriesEnabled.read()) {
                            params += "&showKeyOnly=true";
                        }

                        window.location.href = "https://" + window.location.hostname + worklist[nextIndex].studyURL + params;
                        window.location.reload();
                    }
                }
            }
        };

        Application.prototype.hasPreviousSingleSeriesImage = function () {
            if (this.singleSeriesEnabled.read() && (this.singleSeries != null) && this.canDisplaySingleSeries(this.layout.read())) {
                return this.singleSeriesIndex > 0;
            }

            return false;
        };

        Application.prototype.previousSingleSeriesImage = function () {
            if (this.hasPreviousSingleSeriesImage()) {
                var selected = this.selectedSeriesKey.read().value;
                var allSeriesViews = this.seriesViews.value;

                var foundSelected = 0;
                for (var ctr = 0; ctr < allSeriesViews.length; ctr += 1) {
                    if (allSeriesViews[ctr].viewKey.value == selected) {
                        foundSelected = ctr;
                        break;
                    }
                }

                allSeriesViews.unshift(allSeriesViews.splice(allSeriesViews.length - 1, 1)[0]);

                var series = Study.splitSeries(this.singleSeries)[this.singleSeriesIndex - 1];
                this.singleSeriesIndex -= 1;

                var seriesToUnload = this.seriesViews.value[0];
                var seriesNew = this.loadSeriesAt(series, 0);

                if (seriesToUnload) {
                    seriesToUnload.unload();
                }

                this.seriesViews.raiseChangedEvent(allSeriesViews);
                this.selectedSeriesKey.write(this.seriesViews.read()[foundSelected].viewKey);
                seriesNew.selectedInstanceIndex.write(0);
            }
        };

        Application.prototype.hasNextSingleSeriesImage = function () {
            if (this.singleSeriesEnabled.read() && (this.singleSeries != null) && this.canDisplaySingleSeries(this.layout.read())) {
                var layout = this.layout.read();
                var available = layout.columns * layout.rows;
                return (this.singleSeriesIndex + available) < this.singleSeries.instances.length;
            }

            return false;
        };

        Application.prototype.nextSingleSeriesImage = function () {
            if (this.hasNextSingleSeriesImage()) {
                var selected = this.selectedSeriesKey.read().value;
                var allSeriesViews = this.seriesViews.value;
                var layout = this.layout.read();
                var available = layout.columns * layout.rows;

                var foundSelected = 0;
                for (var ctr = 0; ctr < allSeriesViews.length; ctr += 1) {
                    if (allSeriesViews[ctr].viewKey.value == selected) {
                        foundSelected = ctr;
                        break;
                    }
                }

                allSeriesViews.push(allSeriesViews.shift());
                var series = Study.splitSeries(this.singleSeries)[this.singleSeriesIndex + available];
                this.singleSeriesIndex += 1;

                var seriesToUnload = this.seriesViews.value[available - 1];
                var seriesNew = this.loadSeriesAt(series, available - 1);

                if (seriesToUnload) {
                    seriesToUnload.unload();
                }

                this.seriesViews.raiseChangedEvent(allSeriesViews);
                this.selectedSeriesKey.write(this.seriesViews.read()[foundSelected].viewKey);
                seriesNew.selectedInstanceIndex.write(0);
            }
        };

        /**
        * Run a study action, aka routing rule
        */
        Application.prototype.invokeStudyAction = function (study, action) {
            var _this = this;
            var studyStorage = Services.getStudyStorageInfo(this.sessionId, study.studyAttributes.queryObject);

            var run = function (task) {
                $('.overlay').addClass('application-loading');

                Observable._finally(task, function () {
                    $('.overlay').removeClass('application-loading');
                }).subscribe({
                    next: function (_) {
                        // Allow 200 ms for processing on the server before requesting the updated study
                        setTimeout(function () {
                            // On completion of routing rule, update study from /get/study and re-render
                            Observable.bind(studyStorage, function (storage) {
                                study = Study.updateStudyFromStorage(study, storage);
                                _this.renderAllFrames();

                                return Observable.ret({});
                            }).subscribe({
                                done: function () {
                                },
                                next: function (_) {
                                },
                                fail: function (err) {
                                    _this.recordError("Unable to update study: " + err);
                                }
                            });
                        }, 200);
                    },
                    done: function () {
                    },
                    fail: function (err) {
                        _this.recordError("Unable to invoke study action: " + err);
                        window.alert("Unable to run the selected study action.");
                    }
                });
            };

            if (action.requiresEmailAddress) {
                var email = window.prompt(this.terminology.lookup(Terminology.Terms.EnterEmailAddress));

                if (email) {
                    var message = window.prompt(this.terminology.lookup(Terminology.Terms.EnterShareMessage));

                    run(Services.runRoutingRule(this.sessionId, study.studyAttributes.uuid, action.id, new Classes.EmailAddress(email), message));
                }
            } else {
                run(Services.runRoutingRule(this.sessionId, study.studyAttributes.uuid, action.id));
            }
        };

        /**
        * Magnify/minify the series with the specified index
        */
        Application.prototype.magnifyMinify = function (series) {
            var layout = this.layout.read();

            if (layout.rows > 1 || layout.columns > 1) {
                var magnifiedSeries = this.magnifiedSeries.read();

                if (magnifiedSeries !== null) {
                    this.magnifiedSeries.write(null);
                    this.updateSeriesHandles(true);
                } else {
                    this.magnifiedSeries.write(series);
                    this.updateSeriesHandles(false);
                }
            }
        };

        Application.prototype.isMagnified = function () {
            return this.magnifiedSeries.read() != null;
        };

        Application.prototype.canDisplaySingleSeries = function (layout) {
            if (layout.rows > 1 || layout.columns > 1) {
                return this.magnifiedSeries.read() == null;
            }

            return false;
        };

        /**
        * Save the current window level setting as a preset
        */
        Application.prototype.saveWindowLevelPreset = function () {
            var _this = this;
            var container = this.selectedSeriesContainer.read();

            if (container) {
                var modality = container.series.seriesAttributes.modality;

                var presetName = window.prompt(this.terminology.lookup(Terminology.Terms.EnterPresetName), this.terminology.lookup(Terminology.Terms.Untitled));

                if (presetName) {
                    Subjects.modify(this.settings, function (settings) {
                        var copy = $.extend(true, settings, {
                            modalities: []
                        });

                        var modalitySettings = _.find(copy.modalities, function (m) {
                            return m.modality === modality;
                        });

                        if (!modalitySettings) {
                            copy.modalities.push(modalitySettings = { modality: modality });
                        }

                        if (!modalitySettings.presets || (modalitySettings.presets.length === 0)) {
                            modalitySettings.presets = WindowLevelPresets.defaults(modality, _this.terminology);
                        }

                        var windowLevel = container.combineWindowLevelFlags().read();

                        if (modalitySettings.presets.length >= 9) {
                            var presetNumberString = window.prompt(_this.terminology.lookup(Terminology.Terms.SelectPresetToReplace), "1");

                            var presetNumber;

                            if (!presetNumberString || presetNumberString.length != 1 || !_.contains(['1', '2', '3', '4', '5', '6', '7', '8', '9'], presetNumberString[0])) {
                                presetNumber = 1;
                            } else {
                                presetNumber = parseInt(presetNumberString);
                            }

                            modalitySettings.presets[presetNumber - 1] = {
                                name: presetName,
                                windowLevel: windowLevel
                            };
                        } else {
                            modalitySettings.presets.push({
                                name: presetName,
                                windowLevel: windowLevel
                            });
                        }

                        return copy;
                    });
                }
            }
        };

        /**
        * Display a new collection of series
        */
        Application.prototype.loadSeriesSet = function (forwards) {
            var layout = this.layout.read();
            var count = layout.columns * layout.rows;

            var seriesViews = this.seriesViews.read();

            var allSeries = _.toArray(_.map(_.flatten(_.map(this.studies, function (study) {
                return study.series;
            })), function (series, index, list) {
                return {
                    index: index,
                    series: series
                };
            }));

            var visibleSeries = _.filter(allSeries, function (pair) {
                return _.any(seriesViews, function (series, index, list) {
                    return series.series === pair.series && index < count;
                });
            });

            var invisibleSeries = _.filter(allSeries, function (pair) {
                return !_.any(seriesViews, function (series, index, list) {
                    return series.series === pair.series && index < count;
                });
            });

            var visibleSeriesNumbers = _.map(visibleSeries, function (pair) {
                return pair.index;
            });
            var minSeriesNumber = _.min(visibleSeriesNumbers);
            var maxSeriesNumber = _.max(visibleSeriesNumbers);

            var validSeries = _.filter(invisibleSeries, function (pair) {
                if (forwards) {
                    return pair.index > maxSeriesNumber;
                } else {
                    return pair.index < minSeriesNumber;
                }
            });

            if (!_.any(validSeries)) {
                validSeries = _.any(invisibleSeries) ? invisibleSeries : allSeries;
            }

            if (forwards) {
                validSeries = _.take(validSeries, count);
            } else {
                validSeries = _.take(validSeries.reverse(), count).reverse();
            }

            for (var i = 0; i < seriesViews.length; i++) {
                var seriesView = seriesViews[i];
                var series = i < validSeries.length ? validSeries[i].series : null;

                this.loadSeriesAt(series, i);
            }

            this.seriesViews.raiseChangedEvent(seriesViews);
            this.selectedSeriesKey.write(seriesViews[0].viewKey);

            if (this.selectedTool.read() === 9 /* Localization */) {
                this.loadVisibleImageAttributes(this.imagePreloadQueue, -1 /* ImageAttributeBackgroundPreloading */);
            }
        };

        /**
        * Toggle cine for all visible series
        */
        Application.prototype.toggleCineAll = function () {
            var layout = this.layout.read();
            var count = layout.columns * layout.rows;

            var currentSeriesViews = this.seriesViews.read();

            var cineIsActive = _.any(currentSeriesViews, function (series, index, list) {
                return series.cineActive.read() && index < count;
            });

            for (var i = 0; i < currentSeriesViews.length; i++) {
                var seriesView = currentSeriesViews[i];

                if (i < count) {
                    seriesView.cineActive.write(!cineIsActive);
                }
            }
        };

        Application.prototype.loadScript = function (studyStorage, queryObject, attachment) {
            var _this = this;
            var $overlay = $('.overlay');

            $overlay.addClass('application-loading');

            Observable._finally(V3Storage.GetAttachment(this.sessionId, studyStorage, queryObject, attachment.id, attachment.phiNamespace, attachment.version), function () {
                $overlay.removeClass('application-loading');
            }).subscribe({
                done: function () {
                },
                next: function (script) {
                    _this.playScript(script);
                },
                fail: function (err) {
                    _this.recordError("Unable to download attachment: " + err);
                    window.alert("Unable to download the attachment.");
                }
            });
        };

        /**
        * Download an attachment
        */
        Application.prototype.loadAttachment = function (study, attachment) {
            var uri = Routes.Attachment(this.sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, attachment.id, attachment.phiNamespace, attachment.version);

            window.open(uri);
        };

        /**
        * View a report
        */
        Application.prototype.loadHL7Report = function (study, report) {
            var uri = Routes.HL7Report(this.sessionId, study.studyAttributes.uuid, report.id);

            window.open(uri);
        };

        /**
        * View a structured report
        */
        Application.prototype.loadStructuredReport = function (study, report) {
            var id = study.studyAttributes.queryObject.toString();
            var sid = this.sessionId.value;
            window.open("/viewer/report.html#report/{0}?sid={1}&uuid={2}&version={3}".replace("{0}", encodeURIComponent(id)).replace("{1}", encodeURIComponent(sid)).replace("{2}", encodeURIComponent(report.id.value)).replace("{3}", encodeURIComponent(report.version.value)));
        };

        Application.prototype.loadReport = function (uuid, newWin) {
            if (typeof newWin === "undefined") { newWin = false; }
            var id = uuid.value;
            var sid = this.sessionId.value;

            if (newWin) {
                window.open("/?route=study_reports_with_sid&sid={0}&study_uuid={1}".replace("{0}", encodeURIComponent(sid)).replace("{1}", encodeURIComponent(id)), "_blank", "width=800,height=600");
            } else {
                window.open("/?route=study_reports_with_sid&sid={0}&study_uuid={1}".replace("{0}", encodeURIComponent(sid)).replace("{1}", encodeURIComponent(id)));
            }
        };

        /**
        * Duplicate the selected measurement
        */
        Application.prototype.duplicateShape = function () {
            var series = this.selectedSeriesContainer.read();

            if (series) {
                var measurement = series.selectedMeasurement.read();

                if (measurement) {
                    var copy = measurement.clone();
                    copy.editable = true;
                    copy.hideMeasurement = measurement.hideMeasurement;
                    series.addMeasurementToSelectedInstance(copy);

                    series.renderAll();
                }
            }
        };

        /**
        * Copy the selected measurement to the clipboard
        */
        Application.prototype.copyShape = function () {
            var series = this.selectedSeriesContainer.read();

            if (series) {
                var measurement = series.selectedMeasurement.read();

                if (measurement) {
                    this.clipboard.write(measurement.toAnnotationData());
                }
            }
        };

        /**
        * Paste the copied shape as a new measurement
        */
        Application.prototype.pasteShape = function () {
            var series = this.selectedSeriesContainer.read();

            if (series) {
                var data = this.clipboard.read();

                var measurement = Annotations.createMeasurement(data, "", this.user.name, this.user.uuid, true);

                if (measurement) {
                    var copy = measurement.clone();
                    copy.editable = true;
                    copy.hideMeasurement = measurement.hideMeasurement;

                    var instance = series.currentInstance().read();
                    if (this.settings.read().ultrasoundMeasurements && Dicom.Ultrasound.isUltrasound(instance)) {
                        var result = Dicom.Ultrasound.findSpacing(copy, instance);
                        if (result && result.valid) {
                            copy.ultrasoundPixelSpacing = [result.spacingX, result.spacingY];
                        }
                    }

                    measurement.calibration = false;
                    measurement.calibrationValue = 0;

                    series.addMeasurementToSelectedInstance(copy);

                    series.renderAll();
                }
            }
        };

        Application.prototype.geometryMatches = function (instanceFrom, instanceTo) {
            var pixelSpacingFrom = instanceFrom.instanceAttributes.pixelSpacing;
            var pixelSpacingTo = instanceTo.instanceAttributes.pixelSpacing;

            if (pixelSpacingFrom && pixelSpacingFrom[0] && pixelSpacingTo && pixelSpacingTo[0]) {
                var epsilon = Math.max(pixelSpacingFrom[0], pixelSpacingTo[0]) / 2.0;
                var widthFrom = instanceFrom.instanceAttributes.columns * pixelSpacingFrom[0];
                var heightFrom = instanceFrom.instanceAttributes.rows * pixelSpacingFrom[0];

                var widthTo = instanceTo.instanceAttributes.columns * pixelSpacingTo[0];
                var heightTo = instanceTo.instanceAttributes.rows * pixelSpacingTo[0];

                return (Math.abs(widthFrom - widthTo) < epsilon) && (Math.abs(heightFrom - heightTo) < epsilon);
            }

            return false;
        };

        /**
        * Handles co-located annotations across series.
        */
        Application.prototype.copyShapeToAllSeries = function () {
            var currentSeries = this.selectedSeriesContainer.read();

            if (currentSeries) {
                var measurement = currentSeries.selectedMeasurement.read();
                var currentSeriesInstance = currentSeries.currentInstance().read();
                var pixelSpacingFrom = currentSeriesInstance.instanceAttributes.pixelSpacing;
                var modality = currentSeriesInstance.seriesAttributes.modality;

                if (measurement) {
                    var app = this;
                    var userName = this.user.name;
                    var userId = this.user.uuid;
                    var colocationId = measurement.colocationId;
                    var foundColocationId = false;

                    if (colocationId) {
                        foundColocationId = true;
                    } else {
                        colocationId = Annotations.newRandomId();
                        measurement.colocationId = colocationId;
                    }

                    _.each(this.seriesViews.value, function (series) {
                        if (currentSeries != series) {
                            var currentInstance = series.currentInstance().read();

                            if (series.isActive() && currentInstance) {
                                var pixelSpacingTo = currentInstance.instanceAttributes.pixelSpacing;
                                var currentModality = currentInstance.seriesAttributes.modality;

                                var measurements = currentInstance.instanceAttributes.measurements;
                                var measurementCopy = _.find(measurements, function (item) {
                                    return (item.colocationId && (item.colocationId.value == colocationId.value));
                                });
                                var data = jQuery.extend(true, {}, measurement.toAnnotationData());

                                // images must match geometry; pixel location copy is also supported for US modality
                                var geometryMatch = false;
                                var modalityMatch = false;

                                if (pixelSpacingFrom && pixelSpacingFrom[0] && pixelSpacingTo && pixelSpacingTo[0]) {
                                    geometryMatch = app.geometryMatches(currentSeriesInstance, currentInstance);
                                } else if (!pixelSpacingFrom && !pixelSpacingTo) {
                                    modalityMatch = (modality == currentModality) && (modality == "US");
                                }

                                if (geometryMatch || modalityMatch) {
                                    if (geometryMatch) {
                                        Annotations.convertAnnotationLocation(data, pixelSpacingFrom[0], pixelSpacingTo[0]);
                                    }

                                    if (measurement.label) {
                                        data.label = measurement.label;
                                    }

                                    if (measurementCopy) {
                                        var dataOriginal = measurementCopy.toAnnotationData();
                                        _.each(data.points, function (item, index) {
                                            dataOriginal.points[index].x = item.x;
                                            dataOriginal.points[index].y = item.y;
                                        });

                                        series.editMeasurement(measurementCopy, currentInstance);
                                        series.renderAll();
                                    } else if (!foundColocationId) {
                                        var measurementCopy = Annotations.createMeasurement(data, "", userName, userId, true);
                                        measurementCopy.colocationId = colocationId;
                                        measurementCopy.hideMeasurement = measurement.hideMeasurement;
                                        measurementCopy.calibration = false;
                                        measurementCopy.calibrationValue = 0;
                                        series.addMeasurementToSelectedInstance(measurementCopy);
                                        series.renderAll();
                                    }
                                }
                            }
                        }
                    });
                }
            }
        };

        Application.prototype.labelAnnotation = function () {
            var currentSeries = this.selectedSeriesContainer.read();

            if (currentSeries) {
                var measurement = currentSeries.selectedMeasurement.read();
                var currentSeriesInstance = currentSeries.currentInstance().read();

                if (measurement && currentSeriesInstance) {
                    var text = window.prompt(this.terminology.lookup(Terminology.Terms.EnterText), measurement.label);

                    if (text != null) {
                        measurement.label = text;

                        if (measurement.volume) {
                            _.each(measurement.volume.slices, function (s) {
                                if (s.measurement.label != text) {
                                    s.measurement.label = text;
                                    currentSeries.editMeasurement(s.measurement, s.instance);
                                }
                            });
                        }
                    }

                    currentSeries.editMeasurement(measurement, currentSeriesInstance);
                    currentSeries.renderAll();
                }
            }
        };

        Application.prototype.assignPixelSpacing = function () {
            var currentSeries = this.selectedSeriesContainer.read();

            if (currentSeries) {
                var measurement = currentSeries.selectedMeasurement.read();
                var currentSeriesInstance = currentSeries.currentInstance().read();

                if (measurement && currentSeriesInstance) {
                    var spacingStr = "";
                    if (measurement.pixelSpacingUser) {
                        spacingStr = String(measurement.pixelSpacingUser);
                    }

                    var text = window.prompt(this.terminology.lookup(Terminology.Terms.AssignPixelSpacingDialog), spacingStr);
                    measurement.pixelSpacingUser = parseFloat(text);
                    var calibrationUser = !!measurement.sliceSpacingUser || !!measurement.pixelSpacingUser;
                    currentSeriesInstance.instanceAttributes.calibrationUser = calibrationUser;

                    if (measurement.volume) {
                        _.each(measurement.volume.slices, function (s) {
                            s.measurement.volume = null;
                            s.measurement.pixelSpacingUser = parseFloat(text);
                            currentSeries.editMeasurement(s.measurement, s.instance);
                            s.instance.instanceAttributes.calibrationUser = calibrationUser;
                        });
                    }

                    currentSeries.editMeasurement(measurement, currentSeriesInstance);
                    currentSeries.renderAll();
                }
            }
        };

        Application.prototype.assignSliceSpacing = function () {
            var currentSeries = this.selectedSeriesContainer.read();

            if (currentSeries) {
                var measurement = currentSeries.selectedMeasurement.read();
                var currentSeriesInstance = currentSeries.currentInstance().read();

                if (measurement && currentSeriesInstance) {
                    var spacingStr = "";
                    if (measurement.sliceSpacingUser) {
                        spacingStr = String(measurement.sliceSpacingUser);
                    }

                    var text = window.prompt(this.terminology.lookup(Terminology.Terms.AssignSliceSpacingDialog), spacingStr);
                    var value = parseFloat(text);
                    measurement.sliceSpacingUser = value;
                    var calibrationUser = !!measurement.sliceSpacingUser || !!measurement.pixelSpacingUser;
                    currentSeriesInstance.instanceAttributes.calibrationUser = calibrationUser;

                    if (measurement.volume) {
                        _.each(measurement.volume.slices, function (s) {
                            s.measurement.volume = null;
                            s.measurement.sliceSpacingUser = value;
                            currentSeries.editMeasurement(s.measurement, s.instance);
                            s.instance.instanceAttributes.calibrationUser = calibrationUser;
                        });
                    }

                    currentSeries.editMeasurement(measurement, currentSeriesInstance);
                    currentSeries.renderAll();
                }
            }
        };

        /**
        * Remove all temporary annotations
        */
        Application.prototype.removeTemporaryAnnotations = function () {
            _.each(this.studies, function (study) {
                _.each(study.series, function (series) {
                    _.each(series.instances, function (instance) {
                        instance.instanceAttributes.measurements = _.filter(instance.instanceAttributes.measurements, function (m) {
                            return !m.temporary;
                        });
                    });
                });
            });

            this.renderAllFrames();
        };

        /**
        * Delete a series
        */
        Application.prototype.deleteSeries = function (series) {
            var _this = this;
            if (series) {
                var message = this.terminology.lookup(Terminology.Terms.DeleteSeriesWarning) + "\n\n" + series.instances[0].instanceAttributes.seriesDescription;

                if (window.confirm(message)) {
                    // Do not pass a version here - delete all versions of each image
                    var deleteSeries = Observable.sequenceM(_.map(series.instances, function (instance, index, list) {
                        return Observable.invoke(V3Storage.deleteImage(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id), function (_) {
                            console.log("Deleted image #" + index + " from series" + series.seriesAttributes.seriesUid.value);
                        });
                    }));

                    $('.overlay').addClass('application-loading');

                    Observable._finally(deleteSeries, function () {
                        $('.overlay').removeClass('application-loading');
                    }).subscribe({
                        done: function () {
                            _.each(_this.studies, function (study) {
                                study.series = _.filter(study.series, function (studySeries) {
                                    return studySeries.seriesAttributes.seriesUid.value !== series.seriesAttributes.seriesUid.value;
                                });
                            });

                            for (var i = 0; i < _this.seriesViews.value.length; i++) {
                                var view = _this.seriesViews.value[i];

                                if (view.series && view.series.seriesAttributes.seriesUid.value === series.seriesAttributes.seriesUid.value) {
                                    _this.loadSeriesAt(null, i);
                                }
                            }

                            _this.render();
                        },
                        next: function (_) {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to delete series: " + err);
                            window.alert(_this.terminology.lookup(Terminology.Terms.ErrorDeletingImage));
                        }
                    });
                }
            }
        };

        Application.prototype.parseInstanceSelectionFormat = function (format) {
            var inputs = [];

            format = format.replace(/\s/g, '');
            var items = format.split(",");
            _.each(items, function (s) {
                if (s.indexOf("-") !== -1) {
                    var range = s.split("-");
                    if (range.length === 2) {
                        try  {
                            inputs.push(_.range(parseInt(range[0]), parseInt(range[1]) + 1));
                        } catch (e) {
                            inputs.push(Number.NaN);
                        }
                    }
                } else {
                    inputs.push(parseInt(s));
                }
            });

            return _.uniq(_.flatten(inputs)).sort();
        };

        Application.prototype.removeImages = function () {
            var _this = this;
            var selectedSeries;
            var selectedSeriesView = this.selectedSeriesContainer.read();

            if (this.singleSeriesEnabled.read()) {
                selectedSeries = this.singleSeries;
            } else if (selectedSeriesView) {
                selectedSeries = selectedSeriesView.series;
            }

            if (selectedSeries && selectedSeriesView) {
                var format = window.prompt(this.terminology.lookup(Terminology.Terms.RemoveImagesInstructions) + "\n\n" + selectedSeries.instances[0].instanceAttributes.seriesDescription, "");

                if (format) {
                    var numInstances = selectedSeries.instances.length;
                    var maxIndex = selectedSeries.instances.length;
                    var inputs = [];
                    format = format.replace(/\s/g, '');
                    var items = format.split(",");
                    _.each(items, function (s) {
                        if (s.indexOf("-") !== -1) {
                            var range = s.split("-");
                            if (range.length === 2) {
                                try  {
                                    inputs.push(_.range(parseInt(range[0]), parseInt(range[1]) + 1));
                                } catch (e) {
                                    inputs.push(Number.NaN);
                                }
                            }
                        } else {
                            inputs.push(parseInt(s));
                        }
                    });

                    var indices = _.uniq(_.flatten(inputs)).sort();
                    var valid = _.reduce(indices, function (t, n) {
                        return t + n;
                    });
                    if (isNaN(valid) || indices[0] < 1 || indices[indices.length - 1] > maxIndex) {
                        window.alert(this.terminology.lookup(Terminology.Terms.RemoveImagesWarning));
                    } else {
                        var seriesRemoved = indices.length === numInstances;
                        var deleteAll = Observable.sequenceM(_.map(indices, function (i) {
                            var instance = selectedSeries.instances[i - 1];
                            return V3Storage.deleteImage(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version);
                        }));

                        $('.overlay').addClass('application-loading');

                        Observable._finally(deleteAll, function () {
                            $('.overlay').removeClass('application-loading');
                        }).subscribe({
                            done: function () {
                                selectedSeries.instances = _.filter(selectedSeries.instances, function (i, n) {
                                    return _.indexOf(indices, n + 1) === -1;
                                });

                                if (seriesRemoved) {
                                    _this.removeEmptySeries(selectedSeries);
                                }

                                if (_this.singleSeriesEnabled.read()) {
                                    _this.resetSeriesViews();
                                    _this.render(_this.singleSeries);
                                }

                                selectedSeriesView.selectedInstanceIndex.write(0);
                                window.alert(_this.terminology.lookup(Terminology.Terms.RemoveImagesSuccess));
                            },
                            next: function (_) {
                            },
                            fail: function (err) {
                                _this.recordError("Unable to remove images: " + err);
                                window.alert(_this.terminology.lookup(Terminology.Terms.RemoveImagesError));
                            }
                        });
                    }
                }
            }
        };

        /**
        * Remove empty series (e.g., when series images were deleted)
        */
        Application.prototype.removeEmptySeries = function (deletedSeries) {
            var _this = this;
            var nextSeries;
            var foundSeries;
            var viewIndex = 0;
            var layout = this.layout.read();

            // find index of old series in layout
            _.each(_.range(0, Math.min(this.seriesViews.value.length, layout.rows * layout.columns)), function (index) {
                if (_this.seriesViews.value[index].series == deletedSeries) {
                    viewIndex = index;
                }
            });

            // find index of old series among all series
            _.each(this.studies, function (study) {
                _.each(study.series, function (series) {
                    if (foundSeries) {
                        foundSeries = false;
                        nextSeries = series;
                    }
                    if (deletedSeries == series) {
                        foundSeries = true;
                    }
                });
            });

            // filter out empty study
            _.each(this.studies, function (study) {
                study.series = _.filter(study.series, function (studySeries) {
                    return studySeries.instances.length > 0;
                });
            });

            // render views
            this.resetSeriesViews();
            this.render();

            // replace series with next one
            if (this.seriesViews.value.length) {
                this.selectedSeriesKey.write(this.seriesViews.value[viewIndex].viewKey);

                if (nextSeries) {
                    this.replaceSelectedSeries(nextSeries);
                }
            }
        };

        /**
        * Open a copy of the viewer in another window
        */
        Application.prototype.openNewWindow = function (screenId, numScreens) {
            var _this = this;
            var study = this.studies[0];
            var id = study.studyAttributes.queryObject.toString();
            var sid = this.sessionId.value;

            var url;

            if (location.hostname == Routes.LocalAccelerator.LOCAL_ACCELERATOR_HOSTNAME) {
                url = location.protocol + '//' + location.host + location.pathname + location.search + "&owner={1}&screen={2}&isChildWindow=1".replace("{1}", encodeURIComponent(this.owner.value)).replace("{2}", encodeURIComponent(screenId.toString()));
            } else {
                url = "/viewer/index.html#study/{0}?sid={1}&owner={2}&screen={3}&isChildWindow=1".replace("{0}", encodeURIComponent(id)).replace("{1}", encodeURIComponent(sid)).replace("{2}", encodeURIComponent(this.owner.value)).replace("{3}", encodeURIComponent(screenId.toString()));
            }

            if (this.accountSettings.fullscreen) {
                this.fullscreenOnDisplay(screenId, numScreens, url);
            } else if (!Browser.uiFound()) {
                this.needsDirectFullscreen(function (fullscreen) {
                    if (fullscreen) {
                        _this.fullscreenOnDisplay(screenId, numScreens, url, true);
                    } else {
                        window.open(url);
                    }
                });
            } else {
                window.open(url);
            }
        };

        /**
        * Positions viewer page at a specific display in a multi-monitor environment (see Local.js in v3ui)
        * @param {number} screenId
        * @param {number} numScreens
        * @param {string} url
        * @param {boolean} directFullscreen  to handle fullscreen directly with the PA, without studylist available
        */
        Application.prototype.fullscreenOnDisplay = function (screenId, numScreens, url, directFullscreen) {
            if (typeof directFullscreen === "undefined") { directFullscreen = false; }
            var title = "Viewer (Screen " + (screenId + 1) + ")";
            var specs;
            var name = "ambra-screen-" + screenId;
            var timestamp = (new Date().getTime());

            if (Browser.isIE() || Browser.isIE11()) {
                specs = "top=0,left=0,height=100,width=100,menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
            } else {
                specs = "top=0,left=0,height=100,width=100";
            }

            if (url && this.localAccelerator) {
                url += "&viewer_local_accelerator=1";
            }

            if (url) {
                url = url.replace("#study", "?foo=" + timestamp + "#study");
            }

            var win = url ? window.open(url, name, specs) : window;

            win.document.title = title;

            var app = this;
            setTimeout(function () {
                win.document.title = title;

                if (directFullscreen) {
                    app.doDirectFullscreen({
                        screen: screenId.toString(),
                        numScreens: numScreens.toString(),
                        name: title,
                        timestamp: timestamp
                    });
                } else {
                    window.localStorage.setItem("viewEvent", JSON.stringify({
                        screen: screenId.toString(),
                        numScreens: numScreens.toString(),
                        name: title,
                        timestamp: timestamp
                    }));
                }

                // Chrome does not always show the changed title on first attempt
                var titleTries = 0;
                var setTitle = setInterval(function () {
                    win.document.title = title;
                    titleTries++;

                    if (titleTries > 10) {
                        clearInterval(setTitle);
                    }
                }, 100);
            }, 100);
        };

        Application.prototype.needsDirectFullscreen = function (cb) {
            this.callDisplay({}, cb);
        };

        Application.prototype.doDirectFullscreen = function (data) {
            if (Browser.isChrome()) {
                data["browser"] = "Google Chrome";
            } else if (Browser.isFirefox()) {
                data["browser"] = "Firefox";
            } else if (Browser.isSafari()) {
                data["browser"] = "Safari";
            } else {
                data["browser"] = "Unknown";
            }

            data["listName"] = "Ambra | Studies";
            this.callDisplay(data);
        };

        Application.prototype.callDisplay = function (data, cb) {
            var params = $.param({ data: JSON.stringify(data) });

            var img = new Image();

            img.onload = function () {
                if (cb) {
                    cb(true);
                }
            };

            img.onerror = function () {
                if (cb) {
                    cb(false);
                }
            };

            img.src = "https://local.ambrahealth.com:" + 8021 + "/" + "display" + "?" + params + "&a=" + +(new Date());
        };

        /**
        * Export the visible images to PNG format
        */
        Application.prototype.exportCurrentImages = function () {
            var $allSeries = $('.study-series');

            var layout = this.layout.read();

            var width = $allSeries.width();
            var height = $allSeries.height();

            var seriesWidth = width / layout.columns;
            var seriesHeight = height / layout.rows;

            var canvas = Images.createCanvas(width, height);
            var ctx = canvas.getContext('2d');

            ctx.fillStyle = "black";
            ctx.fillRect(0, 0, width, height);

            _.each(this.seriesViews.read(), function (series, index, list) {
                if (index < layout.rows * layout.columns) {
                    var seriesCanvas = series.prepareCanvasForExport();

                    var x = (index * seriesWidth) % width;
                    var y = Math.floor(index / layout.columns) * seriesHeight;

                    ctx.drawImage(seriesCanvas, x, y, seriesWidth, seriesHeight);
                }
            });

            var uri = canvas.toDataURL("image/png");

            var html = $('<img>').attr('src', uri)[0].outerHTML;
            $(window.open().document.body).html(html);
        };

        /**
        * Add a visual marker on the screen to indicate a click
        */
        Application.prototype.addMarker = function (series, imageCoords, clazz) {
            if (series.series) {
                var index = series.selectedInstanceIndex.read();

                var instance = series.series.instances[index];

                var $instance = series.el.find('.instance');
                var screenCoords = series.mapFromImage(imageCoords, $instance.width(), $instance.height(), instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var $marker = $('<span>').addClass(clazz).css({
                    left: Math.round(screenCoords.x - 5) + "px",
                    top: Math.round(screenCoords.y - 5) + "px"
                });

                $instance.append($marker);

                $marker.animate({
                    opacity: 0.0
                }, 1000, function () {
                    $marker.remove();
                });
            }
        };

        /**
        * Handle an event from a recorded script or meeting
        */
        Application.prototype.handleEvent = function (event, timestamp, reverseEvent) {
            try  {
                var seriesView;

                if (event.seriesContainerIndex !== undefined) {
                    seriesView = this.seriesViews.value[event.seriesContainerIndex];
                }

                if (seriesView !== undefined && event.mousePositionImage) {
                    var clazz = event.type === 7 /* MouseMove */ ? "marker mouse-move" : "marker mouse-up";
                    this.addMarker(seriesView, event.mousePositionImage, clazz);
                }

                switch (event.type) {
                    case 0 /* ImageTransformationChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 0 /* ImageTransformationChanged */,
                                        newTransformation: seriesView.transform.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                seriesView.transform.write(event.newTransformation);
                            }
                        }
                        break;
                    case 1 /* WindowLevelChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 1 /* WindowLevelChanged */,
                                        newWindowLevel: seriesView.windowLevel.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                if (event.newWindowLevel === null) {
                                    seriesView.resetWindowLevel();
                                } else {
                                    seriesView.useOriginalWindowLevel.write(false);
                                    seriesView.windowLevel.write(event.newWindowLevel);
                                }
                            }
                        }
                        break;
                    case 2 /* AnnotationAdded */:
                         {
                            if (seriesView && seriesView.series) {
                                var instance = _.find(seriesView.series.instances, function (instance) {
                                    return instance.id.value === event.instanceUid.value;
                                });
                                if (instance) {
                                    var original = seriesView.selectedInstanceIndex.read();
                                    var annId = Annotations.newRandomId();
                                    seriesView.selectedInstanceIndex.write(event.instanceIndex);
                                    var measurement = Annotations.createMeasurement(event.annotation, annId.value, "", "", false);

                                    if (reverseEvent) {
                                        reverseEvent.appendWithTimestamp({
                                            type: 16 /* AnnotationDeleted */,
                                            measurement: measurement,
                                            seriesContainerIndex: event.seriesContainerIndex,
                                            instanceUid: event.instanceUid,
                                            event: null
                                        }, timestamp);

                                        reverseEvent.appendWithTimestamp({
                                            type: 6 /* SelectedImageChanged */,
                                            instanceIndex: original,
                                            seriesContainerIndex: event.seriesContainerIndex,
                                            event: event
                                        }, timestamp);
                                    }

                                    measurement.temporary = true;
                                    var measurements = instance.instanceAttributes.measurements;
                                    measurements.push(measurement);
                                    seriesView.renderAll();
                                }
                            }
                        }
                        break;
                    case 16 /* AnnotationDeleted */: {
                        if (seriesView && seriesView.series) {
                            var instance = _.find(seriesView.series.instances, function (instance) {
                                return instance.id.value === event.instanceUid.value;
                            });
                            instance.instanceAttributes.measurements = _.without(instance.instanceAttributes.measurements, event.measurement);
                            seriesView.renderAll();
                        }

                        break;
                    }
                    case 3 /* MouseToolChanged */:
                         {
                            if (reverseEvent) {
                                reverseEvent.appendWithTimestamp({
                                    type: 3 /* MouseToolChanged */,
                                    newMouseTool: this.selectedTool.read(),
                                    seriesContainerIndex: event.seriesContainerIndex,
                                    event: event
                                }, timestamp);
                            }
                            this.selectedTool.write(event.newMouseTool);
                        }
                        break;
                    case 4 /* LayoutChanged */:
                         {
                            if (reverseEvent) {
                                reverseEvent.appendWithTimestamp({
                                    type: 4 /* LayoutChanged */,
                                    newLayout: this.layout.read(),
                                    seriesContainerIndex: event.seriesContainerIndex,
                                    event: event
                                }, timestamp);
                            }

                            this.layout.write(event.newLayout);
                        }
                        break;
                    case 5 /* SeriesChanged */:
                         {
                            if (reverseEvent) {
                                reverseEvent.appendWithTimestamp({
                                    type: 5 /* SeriesChanged */,
                                    seriesInstanceUid: this.seriesViews.value[event.seriesContainerIndex].series.seriesAttributes.seriesUid,
                                    seriesContainerIndex: event.seriesContainerIndex,
                                    event: event
                                }, timestamp);
                            }

                            var series = _.find(_.flatten(_.map(this.studies, function (s) {
                                return s.series;
                            })), function (s) {
                                return s.seriesAttributes.seriesUid.value === event.seriesInstanceUid.value;
                            });
                            this.loadSeriesAt(series, event.seriesContainerIndex);
                            this.seriesViews.raiseChangedEvent(this.seriesViews.value);
                        }
                        break;
                    case 6 /* SelectedImageChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 6 /* SelectedImageChanged */,
                                        instanceIndex: seriesView.selectedInstanceIndex.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }
                                seriesView.selectedInstanceIndex.write(event.instanceIndex);
                            }
                        }
                        break;
                    case 9 /* InvertChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 9 /* InvertChanged */,
                                        inverted: seriesView.invertActive.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                seriesView.invertActive.write(event.inverted);
                            }
                        }
                        break;
                    case 15 /* EnhanceChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 15 /* EnhanceChanged */,
                                        enhance: seriesView.enhance.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }
                                seriesView.enhance.write(event.enhance);
                            }
                        }
                        break;
                    case 11 /* ToggleMeasurementsChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 11 /* ToggleMeasurementsChanged */,
                                        showMeasurements: seriesView.measurementsVisible.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                seriesView.measurementsVisible.write(event.showMeasurements);
                            }
                        }
                        break;
                    case 10 /* ToggleTextAnnotationsChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 10 /* ToggleTextAnnotationsChanged */,
                                        showTextAnnotations: seriesView.infoVisible.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                seriesView.infoVisible.write(event.showTextAnnotations);
                            }
                        }
                        break;
                    case 12 /* MagnifierPositionChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 12 /* MagnifierPositionChanged */,
                                        magnifierPosition: seriesView.magnifier.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }

                                seriesView.magnifier.write(event.magnifierPosition);
                            }
                        }
                        break;
                    case 13 /* ProbeToolChanged */:
                         {
                            if (seriesView) {
                                if (reverseEvent) {
                                    reverseEvent.appendWithTimestamp({
                                        type: 13 /* ProbeToolChanged */,
                                        probeTool: seriesView.probeTool.read(),
                                        seriesContainerIndex: event.seriesContainerIndex,
                                        event: event
                                    }, timestamp);
                                }
                                seriesView.probeTool.write(event.probeTool);
                            }
                        }
                        break;
                    case 14 /* PlaneLocalizationChanged */:
                         {
                            if (reverseEvent) {
                                reverseEvent.appendWithTimestamp({
                                    type: 14 /* PlaneLocalizationChanged */,
                                    planeLocalizationData: this.planeLocalizationCursor.read(),
                                    seriesContainerIndex: event.seriesContainerIndex,
                                    event: event
                                }, timestamp);
                            }
                            this.planeLocalizationCursor.write(event.planeLocalizationData);
                        }
                        break;
                }
            } catch (err) {
                if (err instanceof Error) {
                    this.recordError("Error while handling event: " + err.message);
                }
            }
        };

        /**
        * Play back a recorded script
        */
        Application.prototype.playScript = function (script) {
            var _this = this;
            this.playbackMode.write(true);

            this.render();
            this.showPlaybackBanner(this.terminology.lookup(Terminology.Terms.PlaybackInProgress));

            _.each(script.series, function (s, index, context) {
                var series = _.find(_.flatten(_.map(_this.studies, function (s) {
                    return s.series;
                })), function (series) {
                    return series.seriesAttributes.seriesUid.value === s.seriesUid;
                });
                var seriesView = _this.loadSeriesAt(series, index);
                seriesView.selectedInstanceIndex.write(s.instanceIndex);
            });

            this.layout.write(script.layout);

            this.playbackAudio = new Audio(script.audio);
            $(this.playbackAudio).hide().appendTo($(document.body));

            this.playbackAudio.addEventListener("ended", function () {
                _this.playbackStopped();
            });

            var playPromise = this.playbackAudio.play();

            playPromise.then(function (_) {
                var reverseRecorder = new Recording.ArrayScriptRecorder();
                _this.reversiblePlayback = { script: script, recorder: reverseRecorder };

                Recording.play(script.events, function (event, timestamp) {
                    _this.handleEvent(event, timestamp, reverseRecorder);
                }, _this.playbackAudio, _this.reversiblePlayback);
            });
        };

        Application.prototype.rewindPlayback = function () {
            var _this = this;
            var current = this.playbackAudio.currentTime;
            var newTime = current - 5;

            if (current < 5) {
                newTime = 0;
            }

            this.playbackAudio.pause();

            var eventsToReverse = this.reversiblePlayback.recorder.getEvents();

            if (eventsToReverse) {
                for (var ctr = eventsToReverse.length - 1; ctr >= 0; ctr -= 1) {
                    if (eventsToReverse[ctr].timestamp > (newTime * 1000)) {
                        this.handleEvent(eventsToReverse[ctr].event);
                    }
                }
            }

            this.playbackAudio.currentTime = newTime;
            var playPromise = this.playbackAudio.play();

            playPromise.then(function (_) {
                Recording.play(_this.reversiblePlayback.script.events, function (event, timestamp) {
                    _this.handleEvent(event, timestamp, _this.reversiblePlayback.recorder);
                }, _this.playbackAudio, _this.reversiblePlayback, newTime);
            });
        };

        Application.prototype.fastForwardPlayback = function () {
            var newTime = this.playbackAudio.currentTime + 5;
            if (newTime < this.playbackAudio.duration) {
                this.playbackAudio.currentTime = newTime;
            } else {
                this.playbackStopped();
            }
        };

        Application.prototype.playbackChanged = function (playing) {
            if (this.playbackAudio) {
                if (playing) {
                    this.playbackAudio.play();
                } else {
                    this.playbackAudio.pause();
                }
            }

            this.toolbar.render();
        };

        Application.prototype.playbackStopped = function () {
            $(this.playbackAudio).remove();
            this.hidePlaybackBanner();
            this.removeTemporaryAnnotations();
            this.playbackMode.write(false);
            this.resetSeriesViews();
            this.render();
            this.selectedSeriesKey.write(this.seriesViews.value[0].viewKey);
        };

        Application.prototype.showGlassPane = function (playback) {
            if (typeof playback === "undefined") { playback = false; }
            $('<div>').addClass('overlay' + (playback ? " playback" : "")).addClass('glasspane').appendTo($(document.body));
        };

        Application.prototype.hideGlassPane = function () {
            $('.overlay').remove();
        };

        Application.prototype.showMeetingGlassPane = function () {
            $('.applicationFrame').addClass('toolbar-hidden');

            $('<div>').addClass('overlay').addClass('glasspane').appendTo($('.study-series'));
        };

        Application.prototype.hideMeetingGlassPane = function () {
            $('.applicationFrame').removeClass('toolbar-hidden');
            $('.study-series > .overlay').remove();
        };

        Application.prototype.showPlaybackBanner = function (message) {
            this.hidePlaybackBanner();
            this.showGlassPane(true);
            $(".toolbar-scroll").css({ width: '275px', margin: "auto" });
            this.showWarningBanner(message);
        };

        Application.prototype.hidePlaybackBanner = function () {
            this.hideGlassPane();
            $(".toolbar-scroll").css({ width: '', margin: "" });
            this.hideWarningBanner();
        };

        Application.prototype.showRecordingBanner = function (message) {
            this.showWarningBanner(message);
        };

        Application.prototype.hideRecordingBanner = function () {
            this.hideWarningBanner();
        };

        Application.prototype.showWarningBanner = function (message) {
            if ($("#series-warning-banner").length) {
                $('#series-warning-banner').remove();
            }

            $('<div>').attr('id', 'series-warning-banner').addClass('banner').append(message).prependTo(".applicationFrame-inner");

            $('.study-series').css({ top: '12px' });
            var $body = $("body");

            if ($body.is(".thumbs-on-left") || $body.is(".thumbs-on-top")) {
                $('.thumbs').css({ top: '12px' });
            }

            this.renderAllFrames();
        };

        Application.prototype.updateWarningBanner = function (message) {
            $('#series-warning-banner').html(message);
        };

        Application.prototype.hideWarningBanner = function () {
            $('#series-warning-banner').remove();
            $('.study-series').css({ top: '0' });
            var $body = $("body");

            if ($body.is(".thumbs-on-left") || $body.is(".thumbs-on-top")) {
                $('.thumbs').css({ top: '0' });
            }

            this.renderAllFrames();
        };

        /**
        * Start sending events to the recorder
        */
        Application.prototype.startRecording = function () {
            var _this = this;
            if (this.$applicationFrameInner.hasClass("withrecording")) {
                this.toggleRecordingsInfo();
            }

            Recording.startAudioRecording(function (audioRecorder) {
                _this.showRecordingBanner(_this.terminology.lookup(Terminology.Terms.RecordingInProgress));

                var layout = _this.layout.read();
                var series = _.map(_this.seriesViews.value, function (s) {
                    return {
                        seriesUid: s.series ? s.series.seriesAttributes.seriesUid.value : null,
                        instanceIndex: s.selectedInstanceIndex.read()
                    };
                });

                var recorder = new Recording.ArrayScriptRecorder();
                recorder.setZeroTimestamp(new Date().getTime());
                _this.recorder.write(recorder);

                var stopRecordingSubscription = Subjects.listen(_this.recordingMode, function (recording) {
                    if (!recording) {
                        audioRecorder.stop();

                        var $overlay = $('.overlay');
                        $overlay.addClass('application-loading');

                        var exportData = Observable._finally(Observable.timeout(Recording.exportWaveAsBase64(audioRecorder), 5000), function () {
                            _this.hideRecordingBanner();
                            _this.removeTemporaryAnnotations();
                            stopRecordingSubscription.cancel();
                        });

                        var recordingName = window.prompt(_this.terminology.lookup(Terminology.Terms.RecordingName), _this.terminology.lookup(Terminology.Terms.RecordingUntitled));

                        if (recordingName) {
                            var exportAndUploadScript = Observable._finally(Observable.bind(exportData, function (base64Data) {
                                var script = {
                                    layout: layout,
                                    series: series,
                                    audio: base64Data,
                                    events: _this.recorder.read().getEvents()
                                };

                                return V3Storage.PostAttachment(_this.sessionId, _this.studyStorage, _this.queryObject, Recording.MIME_TYPE, _this.user.name, recordingName + ".json", script);
                            }), function () {
                                _this.recorder.write(new Recording.NullRecorder());
                                $overlay.removeClass('application-loading');
                            });

                            exportAndUploadScript.subscribe({
                                done: function () {
                                },
                                next: function (attachmentResult) {
                                    var attachment = {
                                        id: new Classes.AttachmentID(attachmentResult.attachment_guid),
                                        stored: 0,
                                        mime: Recording.MIME_TYPE,
                                        version: new Classes.ImageVersion(attachmentResult.attachment_version),
                                        phiNamespace: new Classes.PhiNamespace(attachmentResult.namespace),
                                        filename: recordingName + ".json"
                                    };

                                    _this.studies[0].attachments.push(attachment);
                                    window.alert(_this.terminology.lookup(Terminology.Terms.ScriptSaved));
                                },
                                fail: function (err) {
                                    _this.recordError("Unable to save recorded script: " + err);
                                    window.alert(_this.terminology.lookup(Terminology.Terms.ScriptSaveError));
                                }
                            });
                        } else {
                            _this.hideRecordingBanner();
                            _this.removeTemporaryAnnotations();
                            stopRecordingSubscription.cancel();
                            _this.recorder.write(new Recording.NullRecorder());
                            $overlay.removeClass('application-loading');
                        }
                    }
                });
            }, function (err) {
                window.alert(_this.terminology.lookup(Terminology.Terms.AudioRecordingNotSupported));
                _this.recordingMode.write(false);
            });
        };

        Application.prototype.toggleRecordingsInfo = function () {
            var isShowing = this.$applicationFrameInner.hasClass("withrecording");

            if (isShowing) {
                this.updateRecordingsInfoVisibility(false);
            } else {
                this.recordingsInfo.render(this.studies[0], this.studyStorage, this.queryObject);
                this.updateRecordingsInfoVisibility(true);
            }

            this.renderAllFrames();
        };

        Application.prototype.toggleAttachmentInfo = function () {
            var isShowing = this.$applicationFrameInner.hasClass("withattachments");

            if (isShowing) {
                this.updateAttachmentInfoVisibility(false);
            } else {
                this.attachmentInfo.render(this.studies[0], this.studyStorage, this.queryObject);
                this.updateAttachmentInfoVisibility(true);
            }

            this.renderAllFrames();
        };

        Application.prototype.toggleGSPSInfo = function () {
            var isShowing = this.$applicationFrameInner.hasClass("withgsps");

            if (isShowing) {
                this.updateGSPSInfoVisibility(false);
            } else {
                this.updateGSPSInfoVisibility(true);
                this.gspsInfo.render();
            }

            this.renderAllFrames();
        };

        /**
        * Become the meeting presenter for a meeting
        */
        Application.prototype.becomeMeetingPresenter = function (meetingId) {
            var _this = this;
            this.inMeeting.write(false);
            this.meetingHost.write(true);

            this.recorder.write(new Meeting.MeetingRecorder(this.sessionId, meetingId, function () {
                return _this.getLatestMeetingState();
            }));
        };

        /**
        * Become a regular meeting attendee
        */
        Application.prototype.becomeMeetingAttendee = function () {
            this.inMeeting.write(true);
            this.meetingHost.write(false);

            this.recorder.write(new Recording.NullRecorder());
        };

        /**
        * Apply the current meeting state to the application
        */
        Application.prototype.applyMeetingState = function (state) {
            var _this = this;
            _.each(state.series, function (s, index, context) {
                var series = null;

                if (s !== null) {
                    series = _.find(_.flatten(_.map(_this.studies, function (s) {
                        return s.series;
                    })), function (series) {
                        return series.seriesAttributes.seriesUid.value === s.seriesInstanceUid && _.any(series.instances, function (i) {
                            return i.id.value == s.instanceUid;
                        });
                    });
                }

                var seriesView = _this.loadSeriesAt(series, index);

                if (series) {
                    var instanceIndex = series.instances.indexWhere(function (i) {
                        return i.id.value === s.instanceUid && i.frameNumber.value === s.frameNumber;
                    });

                    seriesView.selectedInstanceIndex.write(instanceIndex);
                    seriesView.applyDisplayOptions(s.displayOptions);
                }
            });

            this.seriesViews.raiseChangedEvent(this.seriesViews.read());
            this.layout.write(state.layout);
        };

        Application.prototype.applyKeyImageLayoutState = function (state) {
            var _this = this;
            var original = this.seriesViews.read();
            var ordered = new Array(original.length);
            var empties = _.filter(original, function (s) {
                return s.series == null;
            });

            _.each(state.series, function (s, index) {
                var series = null;
                var seriesView = null;

                if (s !== null) {
                    series = _.find(_.flatten(_.map(_this.studies, function (s) {
                        return s.keyImageSeries;
                    })), function (series) {
                        return _.any(series.instances, function (i) {
                            return i.id.value == s.instanceUid;
                        });
                    });

                    if (series) {
                        seriesView = _this.findSeriesView(series, ordered);

                        if (seriesView) {
                            ordered[index] = seriesView;
                            seriesView.applyDisplayOptions(s.displayOptions);
                        }
                    }
                }
            });

            for (var ctr = 0; ctr < ordered.length; ctr += 1) {
                if (ordered[ctr] == null) {
                    if (empties.length) {
                        ordered[ctr] = empties.pop();
                    } else {
                        var $series = $('<div>').addClass('series');
                        ordered[ctr] = new Views.Series(this, this.sessionId, $series, null, this.settings, this.accountSettings, this.selectedSeriesKey, this.selectedTool, this.selectedTool2, this.selectedToolWheel, this.selectedInstanceGeometry, this.referenceLinesActive, this.planeLocalizationCursor, this.linkSeries, this.imagePreloadQueue, this.terminology, this.cineSpeed, this.ignoreFrameTimeTag, this.startCinePlaybackAutomatically, this.noKeyboardShortcuts, Subjects.map(this.recorder, function (r) {
                            return _this.modifyRecorder(r, ctr);
                        }), this.playbackMode);
                    }
                }
            }

            this.seriesViews.write(ordered);
            this.layout.write(state.layout);
        };

        /**
        * Enter meeting mode - start the ping to the server, and listen for events
        */
        Application.prototype.enterMeetingMode = function (meetingId) {
            var _this = this;
            var ping = this.startMeetingPing(meetingId).subscribe({
                next: function (_) {
                },
                fail: function (_) {
                },
                done: function () {
                }
            });

            var eventStream = Observable.join(Messaging.channel(Messaging.meeting(this.sessionId, meetingId)));

            var eventSubscription = eventStream.subscribe({
                done: function () {
                },
                fail: function (err) {
                    window.alert(_this.terminology.lookup(Terminology.Terms.CannotJoinMeeting));
                    _this.recordError("Unable to join meeting: " + err);
                },
                next: function (event) {
                    switch (event.event) {
                        case "EVENT":
                             {
                                if (!_this.meetingHost.read()) {
                                    var eventData = JSON.parse(event.data);
                                    _this.handleEvent(eventData);
                                }
                            }
                            break;
                        case "FINISHED":
                             {
                                _this.leaveMeetingMode();
                                window.alert(_this.terminology.lookup(Terminology.Terms.MeetingEnded));
                            }
                            break;
                        case "JOIN":
                        case "LEAVE":
                             {
                                _this.meetingInfo.updateRoster(_this.sessionId, meetingId);
                            }
                            break;
                        case "NEW_PRESENTER":
                             {
                                var presenterData = event.data;

                                if (presenterData.uuid === _this.user.uuid) {
                                    window.alert(_this.terminology.lookup(Terminology.Terms.YouAreThePresenter));
                                    _this.becomeMeetingPresenter(meetingId);
                                }

                                _this.meetingInfo.updateRoster(_this.sessionId, meetingId);
                            }
                            break;
                    }
                }
            });

            return Observable.mappendSubscription(ping, eventSubscription);
        };

        /**
        * Leave meeting mode
        */
        Application.prototype.leaveMeetingMode = function () {
            this.meetingHost.write(false);
            this.inMeeting.write(false);

            if (this.meetingSubscription) {
                this.meetingSubscription.cancel();
                this.meetingSubscription = null;
            }
        };

        /**
        * Join a meeting which is in progress
        */
        Application.prototype.joinMeeting = function (meetingId) {
            var _this = this;
            var joinAndGetMeetingInfo = Observable.zip(Services.JoinMeeting(this.sessionId, meetingId), Services.GetMeeting(this.sessionId, meetingId), function (meeting, roster) {
                return {
                    meeting: meeting,
                    roster: roster.users
                };
            });

            var $overlay = $('.overlay');

            $overlay.addClass('application-loading');

            Observable._finally(joinAndGetMeetingInfo, function () {
                $overlay.removeClass('application-loading');
            }).subscribe({
                done: function () {
                },
                fail: function (err) {
                    _this.recordError("Unable to join meeting: " + err);
                    window.alert(_this.terminology.lookup(Terminology.Terms.CannotJoinMeeting));
                },
                next: function (meeting) {
                    _this.inMeeting.write(true);
                    _this.meetingHost.write(false);

                    _this.meetingInfo.render(_this.queryObject, _this.studies[0].studyAttributes.uuid, meetingId);

                    var state = JSON.parse(meeting.meeting.state);

                    _this.applyMeetingState(state);

                    _this.meetingSubscription = _this.enterMeetingMode(meetingId);
                }
            });
        };

        /**
        * Start a new meeting
        */
        Application.prototype.startMeeting = function () {
            var _this = this;
            var meetingName = window.prompt(this.terminology.lookup(Terminology.Terms.EnterMeetingName), this.terminology.lookup(Terminology.Terms.DefaultMeetingName));

            if (meetingName) {
                var initialState = this.getLatestMeetingState();

                Services.StartMeeting(this.sessionId, this.queryObject, meetingName, initialState).subscribe({
                    done: function () {
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.CannotStartMeeting));
                        _this.recordError("Unable to start meeting: " + err);
                    },
                    next: function (meeting) {
                        var meetingId = new Classes.MeetingId(meeting.uuid);

                        _this.meetingInfo.render(_this.queryObject, _this.studies[0].studyAttributes.uuid, meetingId, meetingName);

                        _this.meetingSubscription = _this.enterMeetingMode(meetingId);

                        _this.becomeMeetingPresenter(meetingId);
                    }
                });
            }
        };

        /**
        * End the current meeting
        */
        Application.prototype.endMeeting = function (meetingId) {
            var _this = this;
            var $overlay = $('.overlay');

            $overlay.addClass('application-loading');

            this.leaveMeetingMode();

            var closeMeeting = Observable._finally(Services.EndMeeting(this.sessionId, meetingId), function () {
                _this.recorder.write(new Recording.NullRecorder());
                $overlay.removeClass('application-loading');
            });

            closeMeeting.subscribe({
                done: function () {
                },
                next: function (_) {
                },
                fail: function (err) {
                    _this.recordError("Unable to end meeting: " + err);
                    window.alert(_this.terminology.lookup(Terminology.Terms.CannotEndMeeting));
                }
            });
        };

        /**
        * Leave the current meeting
        */
        Application.prototype.leaveMeeting = function (meetingId) {
            var _this = this;
            var $overlay = $('.overlay');

            $overlay.addClass('application-loading');

            var leaveMeeting = Observable._finally(Services.LeaveMeeting(this.sessionId, meetingId), function () {
                _this.leaveMeetingMode();

                $overlay.removeClass('application-loading');
            });

            leaveMeeting.subscribe({
                done: function () {
                },
                next: function (_) {
                },
                fail: function (err) {
                    _this.recordError("Unable to leave meeting: " + err);
                    window.alert(_this.terminology.lookup(Terminology.Terms.CannotLeaveMeeting));
                }
            });
        };

        /**
        * Change the presenter to the selected user
        */
        Application.prototype.changeMeetingPresenter = function (meetingId, userUuid) {
            var _this = this;
            if (window.confirm(this.terminology.lookup(Terminology.Terms.ConfirmChangeOfPresenter))) {
                var $overlay = $('.overlay');

                $overlay.addClass('application-loading');

                var changePresenter = Observable._finally(Services.ChangeMeetingPresenter(this.sessionId, meetingId, userUuid), function () {
                    $overlay.removeClass('application-loading');
                });

                changePresenter.subscribe({
                    done: function () {
                    },
                    next: function (_) {
                        _this.becomeMeetingAttendee();
                    },
                    fail: function (err) {
                        _this.recordError("Unable to change the meeting presenter: " + err);
                        window.alert(_this.terminology.lookup(Terminology.Terms.CannotChangePresenter));
                    }
                });
            }
        };

        /**
        * Start a regular ping to the server to keep a meeting alive
        */
        Application.prototype.startMeetingPing = function (meetingId) {
            var _this = this;
            return Observable.bind(Observable.interval(10000), function (_) {
                return Observable.catchError(Services.PingMeeting(_this.sessionId, meetingId), function (err) {
                    _this.recordError("Unable to ping meeting: " + err);
                    return {};
                });
            });
        };

        /**
        * Get the meeting state
        */
        Application.prototype.getLatestMeetingState = function () {
            return {
                layout: this.layout.read(),
                series: _.map(this.seriesViews.value, function (s) {
                    if (s.series) {
                        var instance = s.series.instances[s.selectedInstanceIndex.read()];

                        return {
                            seriesInstanceUid: instance.seriesAttributes.seriesUid.value,
                            instanceUid: instance.id.value,
                            frameNumber: instance.frameNumber.value,
                            studyUUID: instance.studyAttributes.uuid.value,
                            displayOptions: s.getDisplayOptions()
                        };
                    } else {
                        return null;
                    }
                })
            };
        };

        Application.prototype.getKeyImageLayoutState = function () {
            return {
                layout: this.layout.read(),
                series: _.map(this.seriesViews.value, function (s) {
                    if (s.series) {
                        var instance = s.series.instances[s.selectedInstanceIndex.read()];

                        return {
                            seriesInstanceUid: instance.seriesAttributes.seriesUid.value,
                            instanceUid: instance.id.value,
                            frameNumber: instance.frameNumber.value,
                            studyUUID: instance.studyAttributes.uuid.value,
                            displayOptions: s.getExtendedDisplayOptions()
                        };
                    } else {
                        return null;
                    }
                })
            };
        };

        Application.prototype.saveKeyImageLayoutState = function () {
            var _this = this;
            var current = this.getKeyImageLayoutState();
            current["created"] = new Date().getTime();
            current["patientId"] = this.studies[0].studyAttributes.patientId.value;

            var keyImageLayouts = _.filter(this.keyImageLayout, function (k) {
                return current.patientId != k.patientId;
            });
            keyImageLayouts.push(current);

            var now = (new Date()).getTime();
            keyImageLayouts = _.filter(keyImageLayouts, function (k) {
                return (now - k.created) < 5256000000;
            }); // filter data older than two months

            Services.putKeyImageSettings(this.sessionId, keyImageLayouts).subscribe({
                done: function () {
                },
                next: function (_) {
                    window.alert(_this.terminology.lookup(Terminology.Terms.SaveKeyImageLayoutSuccess));
                },
                fail: function (err) {
                    window.alert(_this.terminology.lookup(Terminology.Terms.SaveKeyImageLayoutError));
                }
            });
        };

        Application.prototype.clearKeyImageLayoutState = function () {
            var _this = this;
            var patientId = this.studies[0].studyAttributes.patientId.value;

            var foundLayout = _.find(this.keyImageLayout, function (k) {
                return patientId == k.patientId;
            });

            if (foundLayout) {
                this.keyImageLayout = _.filter(this.keyImageLayout, function (k) {
                    return patientId != k.patientId;
                });
                this.render();

                Services.putKeyImageSettings(this.sessionId, this.keyImageLayout).subscribe({
                    done: function () {
                    },
                    next: function (_) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.ClearedKeyImageLayout));
                    },
                    fail: function (err) {
                        console.error("Unable to save key image settings to services: " + err);
                    }
                });
            }
        };

        /**
        * Anonymize the study
        */
        Application.prototype.anonymize = function (level) {
            var _this = this;
            var regionData = {};

            var series = this.selectedSeriesContainer.read().series;
            var studyUid = series.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (!study) {
                throw new Error("Unable to determine study to anonymize.");
            }

            var measurementsToRegions = function (ms) {
                return _.chain(ms).map(function (m) {
                    return m.toAnnotationData();
                }).filter(function (a) {
                    return a.type === Classes.MouseTool[6 /* Rectangle */];
                }).map(function (a) {
                    var minX = Math.min(a.points[0].x, a.points[1].x);
                    var minY = Math.min(a.points[0].y, a.points[1].y);
                    var maxX = Math.max(a.points[0].x, a.points[1].x);
                    var maxY = Math.max(a.points[0].y, a.points[1].y);

                    return {
                        x: minX,
                        y: minY,
                        width: maxX - minX,
                        height: maxY - minY
                    };
                }).value();
            };

            var haveRegions = false;

            switch (level) {
                case 0 /* Study */:
                     {
                        regionData.regions = measurementsToRegions(_.chain(series.instances).map(function (i) {
                            return i.instanceAttributes.measurements;
                        }).flatten().value());

                        if (_.any(regionData.regions)) {
                            haveRegions = true;
                        }
                    }
                    break;
                case 1 /* Series */:
                     {
                        var regions = {};

                        _.each(study.originalSeries, function (s) {
                            var seriesRegions = {};

                            if (Multiframe.isMultiframe(s)) {
                                var instanceRegions = {};

                                _.each(s.instances, function (inst) {
                                    var newSeries = _.find(study.series, function (s) {
                                        return s.seriesAttributes.seriesUid.value === inst.seriesAttributes.seriesUid.value && _.any(s.instances, function (i) {
                                            return i.id.value === inst.id.value;
                                        });
                                    });

                                    var instanceRegionData = {};
                                    instanceRegionData.regions = measurementsToRegions(_.chain(newSeries.instances).map(function (i) {
                                        return i.instanceAttributes.measurements;
                                    }).flatten().value());

                                    if (_.any(instanceRegionData.regions)) {
                                        haveRegions = true;
                                    }

                                    instanceRegions[inst.id.value] = instanceRegionData;
                                });

                                seriesRegions.instances = instanceRegions;
                            } else {
                                seriesRegions.regions = measurementsToRegions(_.chain(s.instances).map(function (i) {
                                    return i.instanceAttributes.measurements;
                                }).flatten().value());

                                if (_.any(seriesRegions.regions)) {
                                    haveRegions = true;
                                }
                            }

                            regions[s.seriesAttributes.seriesUid.value] = seriesRegions;
                        });

                        regionData.series = regions;
                    }
                    break;
                case 2 /* Image */:
                     {
                        var regions = {};

                        var selectedInstance = this.selectedInstance().read().instance;

                        _.each(study.originalSeries, function (s) {
                            var instanceRegions = {};

                            _.each(s.instances, function (inst) {
                                var measurements = _.chain(study.series).filter(function (s) {
                                    return s.seriesAttributes.seriesUid.value === inst.seriesAttributes.seriesUid.value;
                                }).filter(function (s) {
                                    return s.seriesAttributes.seriesUid.value === selectedInstance.seriesAttributes.seriesUid.value;
                                }).map(function (s) {
                                    return s.instances;
                                }).flatten().filter(function (i) {
                                    return i.id.value === inst.id.value;
                                }).filter(function (i) {
                                    return i.id.value === selectedInstance.id.value;
                                }).map(function (i) {
                                    return i.instanceAttributes.measurements;
                                }).flatten().value();

                                var instanceRegionData = {
                                    regions: measurementsToRegions(measurements)
                                };

                                if (_.any(instanceRegionData.regions)) {
                                    haveRegions = true;
                                }

                                instanceRegions[inst.id.value] = instanceRegionData;
                            });

                            regions[s.seriesAttributes.seriesUid.value] = {
                                instances: instanceRegions
                            };
                        });

                        regionData.series = regions;
                    }
                    break;
            }

            if (!haveRegions) {
                window.alert(this.terminology.lookup(Terminology.Terms.CannotAnonymize));
            } else {
                if (window.confirm(this.terminology.lookup(Terminology.Terms.AnonymizeWarning))) {
                    var anonymize = Modify.anonymize(this.sessionId, study, regionData);

                    anonymize.subscribe({
                        next: function (_) {
                            window.alert(_this.terminology.lookup(Terminology.Terms.AnonymizeSuccess));
                        },
                        done: function () {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to anonymize study: " + err);
                            window.alert(_this.terminology.lookup(Terminology.Terms.AnonymizeFailed));
                        }
                    });
                }
            }
        };

        Application.prototype.splitStudy = function (study, seriesUIDs) {
            var _this = this;
            if (seriesUIDs && seriesUIDs.length) {
                if (study.series.length == seriesUIDs.length) {
                    window.alert(this.terminology.lookup(Terminology.Terms.SplitWarningAllSeries));
                } else if (window.confirm(this.terminology.lookup(Terminology.Terms.SplitWarning))) {
                    var anonymize = Modify.split(this.sessionId, study, seriesUIDs);

                    anonymize.subscribe({
                        next: function (_) {
                            _this.splitStudyEnd(seriesUIDs);
                            window.alert(_this.terminology.lookup(Terminology.Terms.SplitSuccess));
                        },
                        done: function () {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to split study: " + err);
                            window.alert(_this.terminology.lookup(Terminology.Terms.SplitFailed));
                        }
                    });
                }
            } else {
                window.alert(this.terminology.lookup(Terminology.Terms.SplitWarningNoSeries));
            }
        };

        Application.prototype.cropStudy = function () {
            var _this = this;
            var regionData = {};
            regionData.series = {};
            var seriesDescriptions = "\n";

            var foundData = false;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (!study) {
                throw new Error("Unable to determine study to crop.");
            }

            for (var i = 0; i < this.seriesViews.value.length; i++) {
                var view = this.seriesViews.value[i];
                if (view.series) {
                    if (view.series.studyAttributes.uuid.value === selectedSeries.studyAttributes.uuid.value) {
                        var selectedMeasurement = view.selectedMeasurement.read();
                        if (selectedMeasurement) {
                            var a = selectedMeasurement.toAnnotationData();
                            if (a.type === Classes.MouseTool[6 /* Rectangle */]) {
                                foundData = true;

                                seriesDescriptions += ("\n" + view.series.instances[0].instanceAttributes.seriesDescription);

                                var minX = Math.min(a.points[0].x, a.points[1].x);
                                var minY = Math.min(a.points[0].y, a.points[1].y);
                                var maxX = Math.max(a.points[0].x, a.points[1].x);
                                var maxY = Math.max(a.points[0].y, a.points[1].y);

                                regionData.series[view.series.seriesAttributes.seriesUid.value] = {
                                    region: {
                                        x: minX,
                                        y: minY,
                                        width: maxX - minX,
                                        height: maxY - minY
                                    }
                                };
                            }
                        }
                    }
                }
            }

            if (foundData) {
                if (window.confirm(this.terminology.lookup(Terminology.Terms.CropWarning) + seriesDescriptions)) {
                    var crop = Modify.crop(this.sessionId, study, regionData);

                    crop.subscribe({
                        next: function (_) {
                            window.alert(_this.terminology.lookup(Terminology.Terms.CropSuccess));
                        },
                        done: function () {
                        },
                        fail: function (err) {
                            _this.recordError("Unable to anonymize study: " + err);
                            window.alert(_this.terminology.lookup(Terminology.Terms.CropFailed));
                        }
                    });
                }
            } else {
                window.alert(this.terminology.lookup(Terminology.Terms.CannotCrop));
            }
        };

        Application.prototype.splitStudyEnd = function (seriesUIDs) {
            this.studies[0].series = _.difference(this.studies[0].series, _.filter(this.studies[0].series, function (series) {
                return _.contains(seriesUIDs, series.seriesAttributes.seriesUid.value);
            }));
            this.resetSeriesViews();
            this.render();
        };

        Application.prototype.reverseSeries = function () {
            var _this = this;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            selectedSeries.instances = selectedSeries.instances.reverse();

            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (study) {
                var seriesUID = selectedSeries.seriesAttributes.seriesUid.value;
                var reordered = [];

                _.each(selectedSeries.instances, function (i, index) {
                    i.instanceAttributes.instanceIndex = index;
                    i.instanceAttributes.instanceNumber = index;
                    reordered.push(i.id.value);
                });

                var extended = { series: {} };
                extended.series[seriesUID] = {
                    image_order: reordered
                };

                Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, JSON.stringify(extended)).subscribe({
                    done: function () {
                    },
                    next: function (_) {
                        _this.resetSeriesViews();
                        _this.render();
                        _this.replaceSelectedSeries(selectedSeries);

                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedSuccess));
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                        console.error("Unable to reverse series: " + err);
                    }
                });
            }
        };

        Application.prototype.unweaveSeries = function () {
            var _this = this;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (study) {
                var uidA = this.generateUID();
                var uidB = this.generateUID();
                var instancesA = [];
                var instancesB = [];
                var images = {};

                var extended = { study: { series_order: [uidA, uidB] } };

                _.each(selectedSeries.instances, function (i, index) {
                    if (index % 2 == 0) {
                        instancesA.push(i);
                    } else {
                        instancesB.push(i);
                    }
                });

                var seriesAttributesA = Multiframe.copySeriesAttributes(selectedSeries.seriesAttributes);
                seriesAttributesA.instanceCount = instancesA.length;
                var seriesA = {
                    uuid: uidA,
                    instances: _.map(instancesA, function (i, index) {
                        var copy = new Models.Instance();
                        copy.frameNumber = new Classes.FrameNumber(0);
                        copy.id = i.id;
                        copy.instanceAttributes = Multiframe.copyInstanceAttributes(i.instanceAttributes);
                        copy.instanceAttributes.measurements = [];
                        copy.instanceAttributes.instanceIndex = index;
                        copy.instanceAttributes.instanceNumber = index;
                        copy.seriesAttributes = seriesAttributesA;
                        copy.studyAttributes = study.studyAttributes;
                        images[i.id.value] = { "(0020,000E)": uidA };
                        return copy;
                    }),
                    seriesAttributes: seriesAttributesA,
                    studyAttributes: study.studyAttributes,
                    loadedStatus: 0 /* None */
                };
                study.series.push(seriesA);

                var seriesAttributesB = Multiframe.copySeriesAttributes(selectedSeries.seriesAttributes);
                seriesAttributesB.instanceCount = instancesB.length;
                var seriesB = {
                    uuid: uidB,
                    instances: _.map(instancesB, function (i, index) {
                        var copy = new Models.Instance();
                        copy.frameNumber = new Classes.FrameNumber(0);
                        copy.id = i.id;
                        copy.instanceAttributes = Multiframe.copyInstanceAttributes(i.instanceAttributes);
                        copy.instanceAttributes.measurements = [];
                        copy.instanceAttributes.instanceIndex = index;
                        copy.instanceAttributes.instanceNumber = index;
                        copy.seriesAttributes = seriesAttributesB;
                        copy.studyAttributes = study.studyAttributes;
                        images[i.id.value] = { "(0020,000E)": uidB };
                        return copy;
                    }),
                    seriesAttributes: seriesAttributesB,
                    studyAttributes: study.studyAttributes,
                    loadedStatus: 0 /* None */
                };
                study.series.push(seriesB);

                // set up series order
                extended["series"] = {};
                extended["series"][uidA] = {
                    image_order: _.map(seriesA.instances, function (i) {
                        return i.id.value;
                    })
                };

                extended["series"][uidB] = {
                    image_order: _.map(seriesB.instances, function (i) {
                        return i.id.value;
                    })
                };

                // set up images order
                extended["images"] = images;

                var success = function () {
                    study.series = _.filter(study.series, function (s) {
                        return s !== selectedSeries;
                    });
                    _this.resetSeriesViews();
                    _this.render();
                    _this.replaceSelectedSeries(seriesA);
                };

                Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, JSON.stringify(extended)).subscribe({
                    done: function () {
                    },
                    next: function (_) {
                        success();
                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedSuccess));
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                        console.error("Unable to unweave series: " + err);
                    }
                });
            }
        };

        Application.prototype.partSeries = function () {
            var _this = this;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (study) {
                var format = window.prompt(this.terminology.lookup(Terminology.Terms.PartImagesInstructions) + "\n\n" + selectedSeries.instances[0].instanceAttributes.seriesDescription, "");

                if (format) {
                    var formats = format.split(/(\s+)/).filter(function (e) {
                        return e.trim().length > 0;
                    });
                    var images = {};
                    var allMoved = [];
                    var partedSeries = [];

                    _.each(formats, function (format) {
                        var indices = _this.parseInstanceSelectionFormat(format);
                        var maxIndex = selectedSeries.instances.length;

                        var valid = _.reduce(indices, function (t, n) {
                            return t + n;
                        });
                        if (isNaN(valid) || indices[0] < 1 || indices[indices.length - 1] > maxIndex) {
                            window.alert(_this.terminology.lookup(Terminology.Terms.RearrangeImagesWarning));
                        } else {
                            var moving = [];

                            _.each(selectedSeries.instances, function (i, index) {
                                if (_.contains(indices, index + 1)) {
                                    moving.push(i);
                                }
                            });

                            var uidNew = _this.generateUID();
                            var seriesAttributesNew = Multiframe.copySeriesAttributes(selectedSeries.seriesAttributes);
                            seriesAttributesNew.instanceCount = moving.length;
                            var seriesNew = {
                                uuid: uidNew,
                                instances: _.map(moving, function (i, index) {
                                    var copy = new Models.Instance();
                                    copy.frameNumber = new Classes.FrameNumber(0);
                                    copy.id = i.id;
                                    copy.instanceAttributes = Multiframe.copyInstanceAttributes(i.instanceAttributes);
                                    copy.instanceAttributes.measurements = [];
                                    copy.instanceAttributes.instanceIndex = index;
                                    copy.instanceAttributes.instanceNumber = index;
                                    copy.seriesAttributes = seriesAttributesNew;
                                    copy.studyAttributes = study.studyAttributes;
                                    images[i.id.value] = { "(0020,000E)": uidNew };
                                    return copy;
                                }),
                                seriesAttributes: seriesAttributesNew,
                                studyAttributes: study.studyAttributes,
                                loadedStatus: 0 /* None */
                            };

                            partedSeries.push(seriesNew);
                            allMoved = allMoved.concat(moving);
                        }
                    });

                    var original = _.difference(selectedSeries.instances, allMoved);
                    _.each(original, function (i, index) {
                        i.instanceAttributes.instanceIndex = index;
                        i.instanceAttributes.instanceNumber = index;
                    });

                    var extended = { study: { series_order: [selectedSeries.seriesAttributes.seriesUid.value] } };

                    _.each(partedSeries, function (parted) {
                        extended["study"]["series_order"].push(parted.uuid);
                        study.series.push(parted);
                    });

                    selectedSeries.instances = original;
                    selectedSeries.seriesAttributes.instanceCount = original.length;
                    extended["images"] = images;

                    Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, JSON.stringify(extended)).subscribe({
                        done: function () {
                        },
                        next: function (_) {
                            var toSelect = selectedSeries;

                            if (original.length == 0) {
                                toSelect = partedSeries[0];
                                _this.removeEmptySeries(selectedSeries);
                            }

                            _this.resetSeriesViews();
                            _this.render();
                            _this.replaceSelectedSeries(toSelect);

                            window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedSuccess));
                        },
                        fail: function (err) {
                            window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                            console.error("Unable to part series: " + err);
                        }
                    });
                }
            }
        };

        Application.prototype.rearrangeSeries = function () {
            var _this = this;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            var selectedInstance = selectedSeriesView.currentInstance().read();

            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (study) {
                var format = window.prompt(this.terminology.lookup(Terminology.Terms.RearrangeImagesInstructions) + "\n\n" + selectedSeries.instances[0].instanceAttributes.seriesDescription, "");

                if (format) {
                    var indices = this.parseInstanceSelectionFormat(format);
                    var maxIndex = selectedSeries.instances.length;

                    var valid = _.reduce(indices, function (t, n) {
                        return t + n;
                    });
                    if (isNaN(valid) || indices[0] < 1 || indices[indices.length - 1] > maxIndex) {
                        window.alert(this.terminology.lookup(Terminology.Terms.RearrangeImagesWarning));
                    } else {
                        var moving = [];
                        var original = [];

                        _.each(selectedSeries.instances, function (i, index) {
                            if (_.contains(indices, index + 1)) {
                                moving.push(i);
                            } else {
                                original.push(i);
                            }
                        });

                        var currentIndex = _.indexOf(original, selectedInstance);
                        var rearranged = original.slice(0, currentIndex).concat(moving).concat(original.slice(currentIndex, original.length));
                        var seriesUID = selectedSeries.seriesAttributes.seriesUid.value;
                        var reordered = [];

                        _.each(rearranged, function (i, index) {
                            i.instanceAttributes.instanceIndex = index;
                            i.instanceAttributes.instanceNumber = index;
                            reordered.push(i.id.value);
                        });

                        selectedSeries.instances = rearranged;

                        var extended = { series: {} };
                        extended.series[seriesUID] = {
                            image_order: reordered
                        };

                        Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, JSON.stringify(extended)).subscribe({
                            done: function () {
                            },
                            next: function (_) {
                                _this.resetSeriesViews();
                                _this.render();
                                _this.replaceSelectedSeries(selectedSeries);

                                window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedSuccess));
                            },
                            fail: function (err) {
                                window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                                console.error("Unable to rearrange series: " + err);
                            }
                        });
                    }
                }
            }
        };

        Application.prototype.resetArrangement = function () {
            var _this = this;
            var selectedSeriesView = this.selectedSeriesContainer.read();
            var selectedSeries = selectedSeriesView.series;
            var studyUid = selectedSeries.studyAttributes.queryObject.studyUid;

            var study = _.find(this.studies, function (s) {
                return s.studyAttributes.queryObject.studyUid.value === studyUid.value;
            });

            if (study) {
                Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, "DELETE").subscribe({
                    done: function () {
                    },
                    next: function (_) {
                        _this.resetSeriesViews();
                        _this.render();

                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedResetSuccess));
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                        console.error("Unable to reset study: " + err);
                    }
                });
            }
        };

        Application.prototype.generateUID = function () {
            var S4 = function () {
                return Math.floor(Math.random() * 0x10000).toString(10);
            };

            return '1.3.6.1.4.1.34692.' + S4() + S4() + S4() + S4() + S4() + S4();
        };

        Application.prototype.mergeSeries = function (study, merging) {
            var _this = this;
            var uidNew = this.generateUID();
            var images = {};
            var extended = { series: {} };

            var seriesAttributesNew = Multiframe.copySeriesAttributes(merging[0].seriesAttributes);
            var instances = [];
            var index = 0;
            var reordered = [];

            _.each(merging, function (series) {
                _.each(series.instances, function (i) {
                    var copy = new Models.Instance();
                    copy.frameNumber = new Classes.FrameNumber(0);
                    copy.id = i.id;
                    copy.instanceAttributes = Multiframe.copyInstanceAttributes(i.instanceAttributes);
                    copy.instanceAttributes.measurements = [];
                    copy.instanceAttributes.instanceIndex = index;
                    copy.instanceAttributes.instanceNumber = index;
                    copy.seriesAttributes = seriesAttributesNew;
                    copy.seriesAttributes.instanceCount = i.instanceAttributes.frameCount;
                    copy.studyAttributes = study.studyAttributes;
                    images[i.id.value] = { "(0020,000E)": uidNew };
                    reordered.push(i.id.value);
                    instances.push(copy);
                    index += 1;
                });
            });

            var seriesNew = {
                uuid: uidNew,
                instances: instances,
                seriesAttributes: seriesAttributesNew,
                studyAttributes: study.studyAttributes,
                loadedStatus: 0 /* None */
            };

            seriesAttributesNew.instanceCount = instances.length;

            study.series = _.difference(study.series, merging);
            study.series.push(seriesNew);

            extended.series[uidNew] = {
                image_order: reordered
            };

            extended["images"] = images;

            Services.StudyPHIExtended(this.sessionId, study.studyAttributes.uuid, JSON.stringify(extended)).subscribe({
                done: function () {
                },
                next: function (_) {
                    _this.resetSeriesViews();
                    _this.render();
                    window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedSuccess));
                },
                fail: function (err) {
                    window.alert(_this.terminology.lookup(Terminology.Terms.StudyPHIExtendedError));
                    console.error("Unable to rearrange series: " + err);
                }
            });
        };

        Application.prototype.updateSeriesHandles = function (show) {
            var views = this.seriesViews.value;

            for (var i = 0; i < views.length; i++) {
                if (views[i].handle) {
                    if (show) {
                        views[i].handle.show();
                    } else {
                        views[i].handle.hide();
                    }
                }
            }
        };

        Application.prototype.recordError = function (message) {
            Services.AuditLog(message, "Application", this.sessionId, this.studies[0].studyAttributes.uuid);
        };

        Application.prototype.recordSeriesError = function (seriesUid, message, source) {
            Services.AuditLog(message, source, this.sessionId, this.studies[0].studyAttributes.uuid, seriesUid);
        };

        Application.prototype.findSeriesView = function (series, skip) {
            var views = this.seriesViews.read();

            for (var i = 0; i < views.length; i++) {
                if (views[i].series && (views[i].series == series) && (!skip || !_.contains(skip, views[i]))) {
                    return views[i];
                }
            }

            return null;
        };

        Application.prototype.isDiagnosticQualityAlways = function () {
            return (this.accountSettings.viewer_diagnostic_quality_always == 1) || this.settings.read().diagnosticQualityAlways;
        };

        Application.prototype.seriesLoaded = function (series) {
            var loaded = 0 /* None */;

            if (series) {
                loaded = series.loadedStatus || 0 /* None */;

                var instances = series.instances;
                var diagnosticAlways = this.isDiagnosticQualityAlways();
                var attributesLoaded = true;

                // check attributes
                if (loaded < 1 /* Attributes */) {
                    for (var ctr = 0; ctr < instances.length; ctr += 1) {
                        var instance = instances[ctr];
                        if (instance.instanceAttributes.attributesDownloadState != 3 /* Success */) {
                            attributesLoaded = false;
                            break;
                        }
                    }

                    if (attributesLoaded) {
                        loaded = 1 /* Attributes */;
                    }
                }

                if (attributesLoaded) {
                    var imageElements = this.imagePreloadStore[series.seriesAttributes.seriesUid.value];

                    if (imageElements) {
                        if (!diagnosticAlways) {
                            if (loaded < 2 /* Thumbnail */) {
                                var thumbnailsLoaded = true;

                                for (var ctr = 0; ctr < instances.length; ctr += 1) {
                                    var instance = instances[ctr];
                                    var instanceKey = instance.id.value + ':' + instance.frameNumber.value;
                                    var image = imageElements.get(instanceKey, 0 /* Thumbnail */);

                                    if (!image || (image.imageDownloadState != 3 /* Success */)) {
                                        thumbnailsLoaded = false;
                                        break;
                                    }
                                }

                                if (thumbnailsLoaded) {
                                    loaded = 2 /* Thumbnail */;
                                }
                            }
                        }

                        if (loaded < 3 /* Diagnostic */) {
                            var diagnosticLoaded = true;
                            var useDiagnostic = this.accountSettings.viewer_diagnostic_quality == 1 || this.settings.read().diagnosticQualityAlways;
                            var hiResImageType = useDiagnostic ? 2 /* Diagnostic */ : 1 /* FullResolution */;

                            for (var ctr = 0; ctr < instances.length; ctr += 1) {
                                var instance = instances[ctr];
                                var instanceKey = instance.id.value + ':' + instance.frameNumber.value;
                                var image = imageElements.get(instanceKey, hiResImageType);

                                if (!image || (image.imageDownloadState != 3 /* Success */)) {
                                    diagnosticLoaded = false;
                                    break;
                                }
                            }

                            if (diagnosticLoaded) {
                                loaded = 3 /* Diagnostic */;
                            }
                        }
                    }
                }

                series.loadedStatus = loaded;
            }

            return loaded;
        };
        return Application;
    })();
    Views.Application = Application;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Array.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../models/Study.ts' />
var Views;
(function (Views) {
    /**
    * Contains all of the subviews required for the print page
    */
    (function (Print) {
        /**
        * Calculate the True Size dimensions of the image in millimeters
        */
        function calculatePrintDimensions(rows, columns, pixelSpacing) {
            return [columns * pixelSpacing[0], rows * pixelSpacing[0]];
        }

        /**
        * Calculate the Fit To Page dimensions of the image in inches.
        * For letter paper, we reserve an area 7.5x8.5 inches for the image, allowing for 0.5 inch margins and a 1.5 inch header
        */
        function calculateFitDimensions(paper, rows, columns) {
            var height = paper.height - 1.5;
            var width = paper.width;
            var margin = paper.margin;

            var aspect = (width - margin) / (height - margin);

            if (columns / rows >= aspect) {
                // Wide image, fit to width
                return [width - margin, (width - margin) * rows / columns];
            } else {
                // Tall image, fit to height
                return [(height - margin) * columns / rows, height - margin];
            }
        }

        /**
        * Enumerates the ways in which an image can be displayed
        */
        (function (ImageMode) {
            ImageMode[ImageMode["Hidden"] = 0] = "Hidden";
            ImageMode[ImageMode["ShowFullSize"] = 1] = "ShowFullSize";
            ImageMode[ImageMode["ShowTrueSize"] = 2] = "ShowTrueSize";
            ImageMode[ImageMode["ShowFitToPage"] = 3] = "ShowFitToPage";
        })(Print.ImageMode || (Print.ImageMode = {}));
        var ImageMode = Print.ImageMode;

        

        /**
        * The view which shows the list of available images
        */
        var ImageList = (function () {
            function ImageList(el, study, terminology) {
                /**
                * A list of preview images
                */
                this.frames = [];
                this.el = el;
                this.study = study;
                this.terminology = terminology;
            }
            /**
            * Layout the screen, and hook up any event handlers
            */
            ImageList.prototype.render = function () {
                var _this = this;
                $('<a name="top">').appendTo(this.el);

                var framesIndex = 0;

                _.each(this.study.series, function (series) {
                    var seriesDescription = series.instances[0].instanceAttributes.seriesDescription;

                    if (!seriesDescription) {
                        seriesDescription = _this.terminology.lookup(Terminology.Terms.Untitled);
                    }

                    $('<h2>').text(seriesDescription + ' (' + series.seriesAttributes.modality + ')').appendTo(_this.el);

                    $('<a href="#top">').text(_this.terminology.lookup(Terminology.Terms.BackToTop)).appendTo(_this.el);

                    var $instances = $('<ul>').addClass('instances').appendTo(_this.el);

                    _.each(series.instances, function (instance, imageIndex, list) {
                        var imageText = _this.terminology.lookup(Terminology.Terms.Instance) + ' ' + (imageIndex + 1) + ' / ' + series.instances.length;

                        var $frames = $('<ul>').addClass('frames');

                        var $instance = $('<li>').addClass('instance').text(imageText).appendTo($instances);

                        _.each(_.range(0, instance.instanceAttributes.frameCount), function (frameIndex) {
                            var $frame = $('<li>').addClass('frame').appendTo($frames);

                            var mode = new Subjects.ObservableValue(0 /* Hidden */);

                            var previewImage = {
                                mode: mode,
                                instance: instance,
                                frameIndex: frameIndex,
                                imageIndex: imageIndex
                            };

                            Subjects.listen(mode, function (newMode) {
                                switch (newMode) {
                                    case 0 /* Hidden */:
                                        $frame.show();
                                        break;
                                    default:
                                        $frame.hide();
                                        break;
                                }
                            });

                            _this.frames.push(previewImage);

                            framesIndex++;

                            var $linksLocation;

                            if (instance.instanceAttributes.frameCount > 1) {
                                var frameText = _this.terminology.lookup(Terminology.Terms.Frame) + ' ' + (frameIndex + 1) + ' / ' + instance.instanceAttributes.frameCount;

                                $frame.append(frameText);
                                $instance.append($frames);

                                $linksLocation = $frame;
                            } else {
                                $linksLocation = $instance;
                            }

                            $linksLocation.append(' ').append($('<a>').attr('href', '#').text('(' + _this.terminology.lookup(Terminology.Terms.FitToPage) + ')').click(function (e) {
                                previewImage.mode.write(3 /* ShowFitToPage */);
                                e.preventDefault();
                            }));

                            $linksLocation.append(' ').append($('<a>').attr('href', '#').text('(' + _this.terminology.lookup(Terminology.Terms.FullSize) + ')').click(function (e) {
                                previewImage.mode.write(1 /* ShowFullSize */);
                                e.preventDefault();
                            }));

                            $linksLocation.append(' ').append($('<a>').attr('href', '#').text('(' + _this.terminology.lookup(Terminology.Terms.TrueSize) + ')').click(function (e) {
                                previewImage.mode.write(2 /* ShowTrueSize */);
                                e.preventDefault();
                            }));
                        });
                    });
                });
            };
            return ImageList;
        })();
        Print.ImageList = ImageList;

        /**
        * The view which shows a preview of those images which will be printed
        */
        var Preview = (function () {
            function Preview(sessionId, el, study, terminology, settings) {
                this.sessionId = sessionId;
                this.el = el;
                this.study = study;
                this.terminology = terminology;
                this.settings = settings;
            }
            /**
            * Layout the screen, and hook up any event handlers
            */
            Preview.prototype.render = function (frames) {
                var _this = this;
                var $images = $('.images').length ? $('.images') : $('<div>').addClass('images').appendTo(this.el);

                new PHI($images, this.study.studyAttributes, this.terminology, this.settings.dateFormat, "no-print").render();

                _.each(frames, function (frame, index, list) {
                    // $divWrapper makes sure the images are always shown in the right order
                    var $divWrapper = $('<div>').appendTo($images);

                    // Use the first description as a default if one isn't provided per frame
                    if (frame.instance.instanceAttributes.seriesDescription == null || frame.instance.instanceAttributes.seriesDescription.length == 0) {
                        frame.instance.instanceAttributes.seriesDescription = frames[0].instance.instanceAttributes.seriesDescription;
                    }

                    var image = new PreviewImage(_this.sessionId, $divWrapper, frame, index > 0 ? frames[index - 1] : null, index < frames.length - 1 ? frames[index + 1] : null, _this.settings, _this.terminology);

                    image.render();
                });
            };
            return Preview;
        })();
        Print.Preview = Preview;

        /**
        * A view which renders PHI
        */
        var PHI = (function () {
            function PHI(el, studyAttributes, terminology, dateFormat, className) {
                this.el = el;
                this.studyAttributes = studyAttributes;
                this.terminology = terminology;
                this.dateFormat = dateFormat;
                this.className = className;
            }
            /**
            * Render the patient information
            */
            PHI.prototype.render = function () {
                var $div = $('<div>').addClass("phi").addClass(this.className).appendTo(this.el);

                $('<h2>').text(this.studyAttributes.patientName).appendTo($div);

                if (this.studyAttributes.patientBirthDate) {
                    $div.append($('<div>').append(this.terminology.lookup(Terminology.Terms.PatientDOB) + ": ").append(this.studyAttributes.patientBirthDate.toShortDateString(this.dateFormat)));
                }

                $div.append($('<div>').append(this.terminology.lookup(Terminology.Terms.StudyDescription) + ": ").append(this.studyAttributes.studyDescription));

                if (this.studyAttributes.studyCreateDate) {
                    $div.append($('<div>').append(this.terminology.lookup(Terminology.Terms.StudyCreatedOn) + ": ").append(this.studyAttributes.studyCreateDate.toShortDateString(this.dateFormat)));
                }
            };
            return PHI;
        })();
        Print.PHI = PHI;

        /**
        * A view which displays an image in the preview pane
        */
        var PreviewImage = (function () {
            function PreviewImage(sessionId, el, imageCreationParams, previousFrame, nextFrame, settings, terminology) {
                this.loaded = false;
                this.sessionId = sessionId;
                this.el = el;
                this.imageCreationParams = imageCreationParams;
                this.previousFrame = previousFrame;
                this.nextFrame = nextFrame;
                this.settings = settings;
                this.terminology = terminology;
            }
            /**
            * Create elements, add them to the DOM, and hook up event handlers
            */
            PreviewImage.prototype.render = function () {
                var _this = this;
                var $div = $('<div>').addClass('together').appendTo(this.el);

                $div.hide();

                // Ideally, we would use a CSS selector here to say "add a page break after
                // all but the last images", but there is no selector which can do that, given
                // the way the page is laid out to preserve order of images, so we fix up the
                // CSS classes using jQuery after an update.
                var fixPageBreaks = function () {
                    var cousins = _this.el.parent().children().children();
                    cousins.removeClass('break-after');
                    cousins.filter(':visible').filter(':not(:last)').addClass('break-after');
                };

                new PHI($div, this.imageCreationParams.instance.studyAttributes, this.terminology, this.settings.dateFormat, "print-only").render();

                var instance = this.imageCreationParams.instance;

                var frameTitle = (instance.instanceAttributes.seriesDescription || instance.instanceAttributes.seriesDescription) + ' (' + this.terminology.lookup(Terminology.Terms.Instance) + ' ' + (this.imageCreationParams.imageIndex + 1) + '/' + instance.seriesAttributes.instanceCount + ', ' + this.terminology.lookup(Terminology.Terms.Frame) + ' ' + (this.imageCreationParams.frameIndex + 1) + '/' + instance.instanceAttributes.frameCount + ')';

                $('<h3>').addClass('frame-title').text(frameTitle).appendTo($div);

                this.img = $('<img>').appendTo($div);

                var $menu = $('<div>').addClass('no-print').append($('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.Remove)).click(function (e) {
                    _this.imageCreationParams.mode.write(0 /* Hidden */);
                    e.preventDefault();
                })).appendTo($div);

                if (this.previousFrame) {
                    $menu.append(' | ').append($('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.Prev)).click(function (e) {
                        _this.previousFrame.mode.write(_this.imageCreationParams.mode.read());
                        _this.imageCreationParams.mode.write(0 /* Hidden */);
                        e.preventDefault();
                    }));
                }

                if (this.nextFrame) {
                    $menu.append(' | ').append($('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.Next)).click(function (e) {
                        _this.nextFrame.mode.write(_this.imageCreationParams.mode.read());
                        _this.imageCreationParams.mode.write(0 /* Hidden */);
                        e.preventDefault();
                    }));
                }

                Subjects.listen(this.imageCreationParams.mode, function (newMode) {
                    switch (newMode) {
                        case 0 /* Hidden */:
                            $div.hide();
                            fixPageBreaks();
                            break;
                        default:
                            _this.load(newMode).subscribe({
                                next: function (_) {
                                },
                                done: function () {
                                    var src = Routes.ImageData(_this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(_this.imageCreationParams.frameIndex), 1 /* FullResolution */, 8, false);

                                    _this.img.attr('src', src);

                                    switch (newMode) {
                                        case 1 /* ShowFullSize */:
                                            _this.img.css({
                                                width: instance.instanceAttributes.columns,
                                                height: instance.instanceAttributes.rows
                                            });
                                            break;
                                        case 3 /* ShowFitToPage */:
                                            if (instance.instanceAttributes.rows && instance.instanceAttributes.columns && instance.instanceAttributes.pixelSpacing) {
                                                var letter = { width: 8.5, height: 11.0, margin: 1.0 };

                                                var dimensions = calculateFitDimensions(_this.settings.paperSize || letter, instance.instanceAttributes.rows, instance.instanceAttributes.columns);

                                                _this.img.css({ width: dimensions[0] + 'in', height: dimensions[1] + 'in' });
                                            }
                                            break;
                                        case 2 /* ShowTrueSize */:
                                            if (instance.instanceAttributes.rows && instance.instanceAttributes.columns && instance.instanceAttributes.pixelSpacing) {
                                                var dimensions = calculatePrintDimensions(instance.instanceAttributes.rows, instance.instanceAttributes.columns, instance.instanceAttributes.pixelSpacing);

                                                _this.img.css({ width: dimensions[0] + 'mm', height: dimensions[1] + 'mm' });
                                            } else {
                                                _this.img.css({
                                                    width: instance.instanceAttributes.columns,
                                                    height: instance.instanceAttributes.rows
                                                });
                                            }
                                            break;
                                    }

                                    $div.show();

                                    fixPageBreaks();
                                },
                                fail: function (err) {
                                    Services.AuditLog("Unable to load image data: " + err, "Print", _this.sessionId);
                                    window.alert('Unable to load image data.');
                                }
                            });
                            break;
                    }
                });
            };

            /**
            * Load image attributes if necessary
            */
            PreviewImage.prototype.load = function (mode) {
                var _this = this;
                if (this.loaded) {
                    return Observable.ret({});
                } else {
                    return Observable.invoke(Observable.forget(Study.loadImageAttributes(this.sessionId, this.imageCreationParams.instance)), function (_) {
                        _this.loaded = true;
                    });
                }
            };
            return PreviewImage;
        })();
        Print.PreviewImage = PreviewImage;

        /**
        * The Print view
        *
        * This view also doubles as the main viewer in browsers which do not support Canvas.
        *
        * Displays a list of all available series with links to individual frames. Images may be displayed at
        * Full Size (one image pixel equals one pixel on screen) or True Size (one cm in the patient geometry
        * equals one cm on the printed or displayed media)
        *
        * Basic image navigation (previous, next) functions are also provided.
        */
        var Application = (function () {
            function Application(el, studyStorage, queryObject, sessionId, terminology, settings, permissions) {
                this.study = null;
                this.imageLists = [];
                this.el = el;
                this.studyStorage = studyStorage;
                this.queryObject = queryObject;
                this.sessionId = sessionId;
                this.terminology = terminology;
                this.settings = settings;
                this.permissions = permissions;
            }
            /**
            * Load the study from storage and call the render method
            */
            Application.prototype.load = function () {
                var _this = this;
                var loadStudy = Study.loadStudy(this.sessionId, this.studyStorage, this.queryObject, false, this.permissions);

                return Observable.invoke(loadStudy, function (study) {
                    _this.study = study;
                    _this.renderInit();
                    _this.renderStudy(_this.el, study);

                    var getPriors = Services.getStudyList(_this.sessionId, _this.queryObject.phiNamespace, study.studyAttributes.patientId, null, null, study.studyAttributes.uuid);

                    Observable.invoke(getPriors, function (list) {
                        _this.renderList(_this.el, list);
                    }).subscribe({
                        done: function () {
                        },
                        next: function (_) {
                        },
                        fail: function (err) {
                            Services.AuditLog("Unable to load priors: " + err, "Print", _this.sessionId);
                        }
                    });
                });
            };

            Application.prototype.createInstructions = function () {
                var $div = $('<div>').addClass('instructions');

                var redirected = Query.findParameter(window.location, 'redirected');

                if (redirected === 'true') {
                    $div.append($('<p>').text(this.terminology.lookup(Terminology.Terms.PrintViewerRedirectMessageLine1)));
                    $div.append($('<p>').append($('<a>').attr({ href: '/requirements.html', target: '_blank' }).text(this.terminology.lookup(Terminology.Terms.PrintViewerRedirectMessageLine2))));
                } else {
                    $div.append($('<p>').text(this.terminology.lookup(Terminology.Terms.PrintViewerInstructions)));
                }

                return $div;
            };

            /**
            * Create the print toolbar
            */
            Application.prototype.createToolbar = function () {
                var _this = this;
                var $toolbar = $('<p>').append($('<a href="#">Print</a>').click(function (e) {
                    window.print();
                    e.preventDefault();
                })).append(' | ').append($('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.Clear)).click(function (e) {
                    _.each(_this.imageLists, function (list) {
                        _.each(list.frames, function (frame) {
                            return frame.mode.write(0 /* Hidden */);
                        });
                    });
                    e.preventDefault();
                }));

                var totalNumberOfFrames = this.study.series.sum(function (series) {
                    return series.instances.sum(function (instance) {
                        return instance.instanceAttributes.frameCount;
                    });
                });

                if (totalNumberOfFrames <= 20) {
                    var $showAllFitToPage = $('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.ShowAllFitToPage)).click(function (e) {
                        _.each(_this.imageLists, function (list) {
                            _.each(list.frames, function (frame) {
                                return frame.mode.write(3 /* ShowFitToPage */);
                            });
                        });
                        e.preventDefault();
                    });
                    var $showAllFullSize = $('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.ShowAllFullSize)).click(function (e) {
                        _.each(_this.imageLists, function (list) {
                            _.each(list.frames, function (frame) {
                                return frame.mode.write(1 /* ShowFullSize */);
                            });
                        });
                        e.preventDefault();
                    });

                    var $showAllTrueSize = $('<a>').attr('href', '#').text(this.terminology.lookup(Terminology.Terms.ShowAllTrueSize)).click(function (e) {
                        _.each(_this.imageLists, function (list) {
                            _.each(list.frames, function (frame) {
                                return frame.mode.write(2 /* ShowTrueSize */);
                            });
                        });
                        e.preventDefault();
                    });

                    $toolbar.append(' | ').append($showAllFitToPage).append(' | ').append($showAllFullSize).append(' | ').append($showAllTrueSize);
                }

                return $toolbar;
            };

            Application.prototype.renderInit = function () {
                if ($('.instructions').length == 0) {
                    var $study = $('<div>').addClass('study');

                    var $column1 = $('<div>').addClass('content with-margin padded full-height scrolls no-print').appendTo($('<div>').addClass('column1').appendTo($study));
                    $('<div>').addClass('content with-margin padded full-height scrolls bordered').appendTo($('<div>').addClass('column2').appendTo($study));

                    this.el.append($study);

                    this.createInstructions().appendTo($column1);

                    this.createToolbar().appendTo($column1);
                }
            };

            /**
            * Layout the screen, and hook up any event handlers
            */
            Application.prototype.renderStudy = function (el, study) {
                var $column1 = $('.column1 .content');
                var $column2 = $('.column2 .content');

                var imageList = new ImageList($column1, study, this.terminology);
                this.imageLists.push(imageList);

                imageList.render();

                var preview = new Preview(this.sessionId, $column2, study, this.terminology, this.settings);

                preview.render(imageList.frames);
            };

            Application.prototype.renderList = function (el, list) {
                var _this = this;
                for (var i = 0; i < list.length; ++i) {
                    var queryObject = new Classes.QueryObject(new Classes.StorageNamespace(list[i].storage_namespace), new Classes.PhiNamespace(list[i].phi_namespace), new Classes.StudyUid(list[i].study_uid));
                    var studyStorage = Services.getStudyStorageInfo(this.sessionId, queryObject);

                    Observable.bind(studyStorage, function (storage) {
                        var loadStudy = Study.loadStudy(_this.sessionId, storage, queryObject, false, _this.permissions);

                        return Observable.invoke(loadStudy, function (study) {
                            _this.study = study;
                            _this.renderStudy(el, study);
                        });
                    }).subscribe({
                        done: function () {
                        },
                        next: function (_) {
                        },
                        fail: function (err) {
                            Services.AuditLog("Unable to load priors: " + err, "Print", _this.sessionId);
                        }
                    });
                }
            };
            return Application;
        })();
        Print.Application = Application;
    })(Views.Print || (Views.Print = {}));
    var Print = Views.Print;
})(Views || (Views = {}));
///<reference path='typings/jquery/jquery.d.ts' />
///<reference path='libs/Query.ts' />
///<reference path='libs/Services.ts' />
///<reference path='libs/Terminology.ts' />
///<reference path='views/Application.ts' />
///<reference path='views/Print.ts' />

/**
* Bootstrapping module which provides the global onload handler
*/
var Main;
(function (_Main) {
    /**
    * The main application class. Responsible for initializing the application and delegating to the appropriate view
    * based on the route in the query string.
    */
    var Main = (function () {
        function Main() {
        }
        /**
        * Try to get the session ID from the session storage. If it is not provided, attempt to load it from the query string.
        */
        Main.ensureSessionId = function () {
            var sessionId = Query.getSessionId(window.location);

            if (sessionId === null) {
                var sid = sessionStorage.getItem('sid');

                if (!sid) {
                    // Go back to domain root to log back in for SID, then redirect back to viewer
                    window.location.assign("/?route=login_redirect&redirect_url=" + encodeURIComponent(window.location.href));

                    throw new Classes.ExpectedSIDException("");
                }

                sessionId = new Classes.SessionId(sid);
            }

            return sessionId;
        };

        Main.recordMetric = function (name, additionalMetrics) {
            if (!Date.now)
                Date.now = function () {
                    return +new Date();
                };

            function detectBrowser() {
                var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
                if (/trident/i.test(M[1])) {
                    tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
                    return 'IE ' + (tem[1] || '');
                }
                if (M[1] === 'Chrome') {
                    tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
                    if (tem != null)
                        return tem.slice(1).join(' ').replace('OPR', 'Opera');
                }
                M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
                if ((tem = ua.match(/version\/(\d+)/i)) != null)
                    M.splice(1, 1, tem[1]);
                return { name: M[0], version: M[1] };
            }

            var uuid = (this.studyStorage) ? this.studyStorage.uuid : null;

            var elapsed = Date.now() - this.mainInitTime;

            var userEmail = (this.user && this.user.email) ? this.user.email : '';
            var userName = (this.user && this.user.name) ? this.user.name : '';

            var browser = detectBrowser();

            var renderingTypes = ['canvas', 'webgl', 'simple'];
            var renderMode = renderingTypes[Rendering.getRenderingMode()];

            var metrics = {
                elapsed: elapsed,
                userEmail: userEmail,
                userName: userName,
                hostname: window.location.hostname,
                browserName: browser.name,
                browserVersion: browser.version,
                screenWidth: window.screen.width,
                screenHeight: window.screen.height,
                renderMode: renderMode,
                uuid: uuid
            };

            if (additionalMetrics) {
                metrics["studyUID"] = additionalMetrics.studyUID.value;
                metrics["accelerated"] = additionalMetrics.accelerated;
                metrics["imageSize"] = additionalMetrics.imageSize;
                metrics["imageWidth"] = additionalMetrics.imageWidth;
                metrics["imageHeight"] = additionalMetrics.imageHeight;
                metrics["renderTime"] = additionalMetrics.renderTime;
            }

            Services.AuditMetric(name, metrics);
        };

        /**
        * Determine if the print viewer should be used since Canvas is not supported
        */
        Main.usePrintViewer = function () {
            return document.createElement('canvas').getContext === undefined;
        };

        /**
        * Redirect to the print viewer
        */
        Main.redirectToPrintViewer = function (query, sessionId) {
            window.location.replace("/viewer/print.html#print/" + encodeURIComponent(query.toString()) + "?redirected=true&sid=" + encodeURIComponent(sessionId.value));
        };

        /**
        * Redirect to view just the priors
        */
        Main.redirectToPriorsView = function (query, sessionId) {
            var originalStudy = null;
            var getFirstPrior = Observable.bind(Services.getStudyStorageInfo(sessionId, query), function (study) {
                originalStudy = study;
                return Services.getStudyList(sessionId, query.phiNamespace, new Classes.PatientID(study.patientid), null, null, new Classes.StudyUUID(study.uuid));
            });

            getFirstPrior.subscribe({
                done: function () {
                },
                fail: function (err) {
                    Services.AuditLog("Error with priors view", "Main", sessionId);
                },
                next: function (priors) {
                    if (priors.length > 0) {
                        var priorQuery = new Classes.QueryObject(new Classes.StorageNamespace(priors[0].storage_namespace), new Classes.PhiNamespace(priors[0].phi_namespace), new Classes.StudyUid(priors[0].study_uid));

                        var newLocation = "/viewer/?foo=" + (new Date().getTime()) + "#study/" + priorQuery.toString() + "?hideStudyUUID=" + originalStudy.uuid;

                        var expandPriors = Query.findParameter(window.location, "expandPriors");
                        if (expandPriors) {
                            newLocation += "&expandPriors=true";
                        }

                        var localAcceleration = Query.findParameter(window.location, "viewer_local_accelerator");
                        if (localAcceleration) {
                            newLocation += "&viewer_local_accelerator=1";
                        }

                        window.location.assign(newLocation);
                    } else {
                        window.location.replace("/blank.html");
                    }
                }
            });
        };

        Main.getDateFormat = function () {
            if (LocalViewer.dateFormat) {
                return LocalViewer.dateFormat;
            } else if (window.sessionStorage) {
                var dateFormat = window.sessionStorage.getItem("viewer:dateFormat");
                if (dateFormat) {
                    var parsedDateFormat = JSON.parse(dateFormat);
                    if (parsedDateFormat) {
                        return parsedDateFormat;
                    }
                }
            }

            return 'MM-DD-YYYY';
        };

        /**
        * Initialize the application.
        *
        * Displays the "Application Loading" message and loads the appropriate view.
        */
        Main.initialize = function () {
            // Time the page is downloaded and parsed
            this.mainInitTime = +new Date();

            var $frame = $('<div>').addClass('applicationFrame').appendTo($(document.body));

            var $overlay = $('<div>').addClass('overlay').appendTo($(document.body));
            $('<div>').addClass('fa fa-gear fa-spin fa-5x').appendTo($overlay);

            try  {
                var query;

                if (LocalViewer.isLocalViewer()) {
                    if (LocalViewer.isPersonalAccelerator()) {
                        query = Query.parsePersonalAcceleratorPath(window.location);
                    } else {
                        var storageNamespace = new Classes.StorageNamespace("default");
                        var studyUid = new Classes.StudyUid("default");
                        var phiNamespace = new Classes.PhiNamespace("default");

                        query = {
                            route: "study",
                            queryObject: new Classes.QueryObject(storageNamespace, phiNamespace, studyUid)
                        };
                    }
                } else {
                    query = Query.parseQueryString(window.location);
                }

                var sessionId = LocalViewer.isStandardLocalViewer() ? new Classes.SessionId(null) : Main.ensureSessionId();

                if (query.route !== "print" && Main.usePrintViewer()) {
                    Main.redirectToPrintViewer(query.queryObject, sessionId);
                } else if (Query.findParameter(window.location, "priorsOnly") == "true") {
                    Main.redirectToPriorsView(query.queryObject, sessionId);
                } else {
                    $overlay.addClass('application-loading');

                    var terminology = Terminology.loadAllTerminology(sessionId, query.queryObject);

                    terminology.subscribe({
                        done: function () {
                        },
                        fail: function (err) {
                            Services.AuditLog("Unable to load terminology: " + err, "Main", sessionId);
                            $frame.append("Error loading terminology");
                            $overlay.removeClass('application-loading');
                        },
                        next: function (terminology) {
                            Main.terminologyLoaded(sessionId, query, terminology);
                        }
                    });
                }
            } catch (ex) {
                if (ex instanceof Classes.ExpectedSIDException) {
                    $frame.append("Redirecting... ");
                    setTimeout(function () {
                        return $frame.append("failed.  No SID found.");
                    }, 5000);
                } else if (ex instanceof Classes.InvalidQueryStringException) {
                    $frame.append(ex.message);
                } else {
                    $frame.append("Unknown error");
                }

                $overlay.removeClass('application-loading');
            }
        };

        /**
        * Called after terminology is loaded (so other errors during loading can be
        * customized).
        */
        Main.terminologyLoaded = function (sessionId, query, terminology) {
            var _this = this;
            var acceleratorUsed = Services.AcceleratorUsed(sessionId);
            acceleratorUsed.subscribe({
                done: function () {
                },
                next: function (result) {
                    if (result && result.used === 1) {
                        _this.acceleratorUsed = true;
                    }
                },
                fail: function (err) {
                    Services.AuditLog("accelerator/used failed: " + err, "Main", sessionId);
                }
            });

            var userInfo = Services.getUser(sessionId);

            var studyStorage = Services.getStudyStorageInfo(sessionId, query.queryObject);

            var accountSettings = Observable.bind2(Observable.map(Observable.catchError(Services.getAccountSettings(sessionId, query.queryObject), function () {
                return {};
            }), function (settings) {
                return Main.applySettingsOverrides(settings);
            }), function (settings) {
                return Accelerator.checkAccelerators(sessionId, query.queryObject, settings.accelerators);
            }, function (settings, accelerators) {
                settings.accelerators = accelerators;
                return settings;
            });

            var permissions = Services.getPermissions(sessionId, query.queryObject.phiNamespace);
            if (query.queryObject.phiNamespace.value !== query.queryObject.storageNamespace.value) {
                var permissionsStorage = Services.getStoragePermissions(sessionId, query.queryObject.storageNamespace);
                permissions = Observable.zip(permissions, permissionsStorage, function (p, s) {
                    p.study_upload = s.study_upload;
                    return p;
                });
            }

            var shaders = Rendering.getRenderingMode() === 1 /* WebGL */ ? Observable.zip(AJAX.ajaxGet("resources/shaders/fragment_2d.glsl", "text"), AJAX.ajaxGet("resources/shaders/vertex_2d.glsl", "text"), function (fragment, vertex) {
                return {
                    fragment: fragment,
                    vertex: vertex
                };
            }) : Observable.ret(null);

            var loadAllSettings = Observable.ap(Observable.ap(Observable.ap(Observable.ap(Observable.map(userInfo, function (userInfo) {
                return function (shaders) {
                    return function (permissions) {
                        return function (studyStorage) {
                            return function (account) {
                                return {
                                    user: userInfo,
                                    shaders: shaders,
                                    permissions: permissions,
                                    studyStorage: studyStorage,
                                    account: account,
                                    terminology: terminology
                                };
                            };
                        };
                    };
                };
            }), shaders), permissions), studyStorage), accountSettings);

            loadAllSettings.subscribe({
                done: function () {
                },
                next: function (settings) {
                    // If the user comes in on a file:// protocol show message that says to use the native application
                    if (LocalViewer.isFileSystemViewer()) {
                        Main.showLocalViewerInstructions(sessionId, query, terminology, settings);
                        return;
                    }

                    // If necessary show the end user agreement
                    if (settings.account.viewer_setting_not_diagnostic == 1) {
                        Main.showEndUserAgreement(sessionId, query, terminology, settings);
                    } else {
                        Main.initializeApplicationView(sessionId, query, terminology, settings);
                    }
                },
                fail: function (err) {
                    Services.AuditLog("Unable to load settings: " + err, "Main", sessionId);

                    if (err && (err.indexOf("NOT_FOUND") !== -1)) {
                        $('.applicationFrame').append(terminology.lookup(Terminology.Terms.ErrorStudyNotFound));
                    } else {
                        $('.applicationFrame').append(terminology.lookup(Terminology.Terms.ErrorLoadingSettings));
                    }

                    $('.overlay').removeClass('application-loading');
                }
            });
        };

        Main.showLocalViewerInstructions = function (sessionId, query, terminology, settings) {
            $('.overlay').removeClass('application-loading');

            var txtTitle = terminology.lookup(Terminology.Terms.LocalViewerInstructionsTitle);
            var txtBody = terminology.lookup(Terminology.Terms.LocalViewerInstructionsBody);

            var txtWinHead = terminology.lookup(Terminology.Terms.LocalViewerInstructionsWinHeader);
            var txtWinBody = terminology.lookup(Terminology.Terms.LocalViewerInstructionsWinBody);
            txtWinBody = txtWinBody.replace('viewer.exe', '<b>viewer.exe</b>');

            var txtMacHead = terminology.lookup(Terminology.Terms.LocalViewerInstructionsMacHeader);
            var txtMacBody = terminology.lookup(Terminology.Terms.LocalViewerInstructionsMacBody);
            txtMacBody = txtMacBody.replace('viewer.', '<b>viewer</b>.');

            var endUserAgreementPanel = $('<div id="endUserAgreementPanel">');
            var title = $('<h3>').text(txtTitle);
            var body = $('<p>').text(txtBody);
            var winHead = $('<h5>').text(txtWinHead);
            var winBody = $('<span>').html(txtWinBody);
            var macHead = $('<h5>').text(txtMacHead);
            var macBody = $('<span>').html(txtMacBody);
            var footer = $('<div class="footer">');

            body.append(winHead).append(winBody).append(macHead).append(macBody);

            endUserAgreementPanel.append(title).append(body).append(footer);
            $('.applicationFrame').append(endUserAgreementPanel);
        };

        Main.showEndUserAgreement = function (sessionId, query, terminology, settings) {
            $('.overlay').removeClass('application-loading');

            var txtTitle = terminology.lookup(Terminology.Terms.EndUserTitle);
            var txtBody = terminology.lookup(Terminology.Terms.EndUserBody);
            var txtDecline = terminology.lookup(Terminology.Terms.EndUserDecline);
            var txtAccept = terminology.lookup(Terminology.Terms.EndUserAccept);

            var endUserAgreementPanel = $('<div id="endUserAgreementPanel">');
            var title = $('<h3>').text(txtTitle);
            var body = $('<p>').text(txtBody);
            var footer = $('<div class="footer">');

            var btnDecline = $('<button id="endUserDecline" data-dismiss="modal">').text(txtDecline);
            btnDecline.on('click', function () {
                Services.setStudyAudit(sessionId, new Classes.StudyUUID(settings.studyStorage.uuid), 'CANCELED_NOT_DIAGNOSTIC').subscribe({
                    done: function () {
                        if (window.parent.$('#viewer-dialog').length > 0) {
                            window.parent.$('#viewer-dialog').modal('hide');
                        } else {
                            window.close();
                        }
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        Services.AuditLog("Unable to record audit event for action 'CANCELED_NOT_DIAGNOSTIC': " + err, "Main", sessionId);
                    }
                });
            });

            var btnAccept = $('<button id="endUserAccept">').text(txtAccept);
            btnAccept.on('click', function () {
                Services.setStudyAudit(sessionId, new Classes.StudyUUID(settings.studyStorage.uuid), 'ACCEPTED_NOT_DIAGNOSTIC').subscribe({
                    done: function () {
                        $('#endUserAgreementPanel').hide();
                        $('.overlay').addClass('application-loading');
                        Main.initializeApplicationView(sessionId, query, terminology, settings);
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        Services.AuditLog("Unable to record audit event for action 'ACCEPTED_NOT_DIAGNOSTIC': " + err, "Main", sessionId);
                    }
                });
            });

            footer.append(btnAccept).append(btnDecline);

            endUserAgreementPanel.append(title).append(body).append(footer);
            $('.applicationFrame').append(endUserAgreementPanel);
        };

        Main.handleMobilePrompt = function () {
            if (Browser.isiOS()) {
                window.location.replace("https://apps.apple.com/us/app/ambra/id1437945158?ls=1");
            } else {
                window.location.replace("https://play.google.com/store/apps/details?id=com.ambrahealth.ambra");
            }
        };

        Main.initializeApplicationView = function (sessionId, query, terminology, settings) {
            var _this = this;
            // Do not display thin studies
            if (settings.studyStorage.thin == 1) {
                $('.applicationFrame').append(terminology.lookup(Terminology.Terms.StudyBeingRetrieved));
                $('.overlay').removeClass('application-loading');

                // Set the global study information
                this.studyStorage = settings.studyStorage;

                // Record how long it took to load the page
                this.recordMetric('pageLoadedThin');

                // poll Services until thin flag is false, then reload
                function studyPoll() {
                    Services.getStudyStorageInfo(sessionId, query.queryObject).subscribe({
                        done: function () {
                        },
                        next: function (result) {
                            if (result.thin == 1) {
                                setTimeout(studyPoll, 10000);
                            } else {
                                window.location.reload();
                            }
                        },
                        fail: function (err) {
                            Services.AuditLog("study poll call failed: " + err, "Main", sessionId);
                        }
                    });
                }

                Services.retrieveStudy(sessionId, new Classes.StudyUUID(settings.studyStorage.uuid)).subscribe({
                    done: function () {
                        setTimeout(studyPoll, 10000);
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        Services.AuditLog("study/retrieve call failed: " + err, "Main", sessionId);
                        window.alert(terminology.lookup(Terminology.Terms.CannotRetrieveThinStudy));
                    }
                });
            } else {
                // Redirect to the accelerator
                if (settings.account.accelerators && settings.account.accelerators.length > 0) {
                    window.location.host = settings.account.accelerators[0];
                }

                // Set the global study information
                this.studyStorage = settings.studyStorage;

                var view;

                // Get user settings
                var userSettings;
                if (!LocalViewer.isStandardLocalViewer()) {
                    try  {
                        userSettings = JSON.parse(settings.account.viewer_config);
                    } catch (ex) {
                        console.log("Warning: Could not parse user settings, using default settings instead. \n" + ex);
                        userSettings = {};
                    } finally {
                        userSettings = Main.applyUserSettingsOverrides(userSettings);
                    }
                } else if (LocalViewer.settings) {
                    userSettings = {
                        modalities: LocalViewer.settings.modalities,
                        version: Classes.CURRENT_SETTINGS_VERSION
                    };
                } else {
                    userSettings = {
                        version: Classes.CURRENT_SETTINGS_VERSION
                    };
                }

                userSettings.dateFormat = Main.getDateFormat();

                if (userSettings.thumbs === "left") {
                    $('body').addClass('thumbs-on-left');
                } else if (userSettings.thumbs === "top") {
                    $('body').addClass('thumbs-on-top');
                }

                if (userSettings.smallThumbnails && (userSettings.thumbs !== "left")) {
                    $("body").addClass("thumbs-small");
                }

                if (settings.user) {
                    this.user = settings.user;
                }

                // automatically reload as mobile viewer if it appears we're on a handheld device
                if (Browser.isMobile() && Browser.isPhone() && (query.route === "study") && !userSettings.preferDesktopViewer) {
                    window.location.href = window.location.href.replace("/viewer/#study", "/viewer/mobile.html#mobile");
                }

                if (userSettings.mobilePrompt && ((query.route === "mobile") || Browser.isMobile()) && (window.self === window.top)) {
                    Main.handleMobilePrompt();
                } else {
                    // log STUDY_VIEW action
                    Services.setStudyAudit(sessionId, new Classes.StudyUUID(settings.studyStorage.uuid), 'STUDY_VIEW').subscribe({
                        done: function () {
                        },
                        next: function (_) {
                        },
                        fail: function (err) {
                            Services.AuditLog("Unable to record audit event for action 'STUDY_VIEW': " + err, "Main", sessionId);
                        }
                    });

                    this.checkFrozenStatus(settings.studyStorage, settings.permissions);

                    switch (query.route) {
                        case "print":
                            view = new Views.Print.Application($('.applicationFrame'), settings.studyStorage, query.queryObject, sessionId, settings.terminology, userSettings, settings.permissions);
                            break;
                        case "report":
                            view = new Views.Report.Application($('.applicationFrame'), settings.studyStorage, query.queryObject, sessionId, settings.terminology);
                            break;
                        case "study":
                        case "script":
                        case "meeting":
                        case "keyimages":
                            var owner = Query.findParameter(window.location, "owner");

                            if (!owner) {
                                owner = Math.round(Math.random() * 1000000).toString();
                            }

                            var keyImagesOnly = query.route === "keyimages";

                            var hideStudyUUID = new Classes.StudyUUID(Query.findParameter(window.location, "hideStudyUUID"));
                            var showStudyUUID = _.map((Query.findParameter(window.location, "showStudyUUID") || '').split('+').filter(Boolean), function (uuid) {
                                return new Classes.StudyUUID(uuid);
                            });

                            view = Main.application = new Views.Application($('.applicationFrame'), settings.studyStorage, query.queryObject, sessionId, settings.user, userSettings, settings.account, settings.permissions, settings.terminology, keyImagesOnly, new Classes.OwnerId(owner), settings.shaders, hideStudyUUID, showStudyUUID);
                            break;
                        case "mobile":
                            view = new Views.Mobile.Application($('.applicationFrame'), settings.studyStorage, query.queryObject, sessionId, settings.terminology, userSettings.dateFormat, userSettings, settings.permissions);
                            break;
                        case "mpr":
                            view = new Views.MPR.Application($('.applicationFrame'), settings.studyStorage, query.queryObject, sessionId, settings.terminology, userSettings, settings.account, settings.permissions);
                            break;
                    }

                    view.load().subscribe({
                        done: function () {
                        },
                        next: function (_) {
                            $('.overlay').removeClass('application-loading');

                            // Record how long it took to load the page
                            _this.recordMetric('pageLoaded');

                            switch (query.route) {
                                case "script": {
                                    var attachmentId = Query.findParameter(window.location, "attachmentId");
                                    var attachmentVersion = Query.findParameter(window.location, "attachmentVersion");
                                    var phiNamespace = Query.findParameter(window.location, "attachmentPhiNs");

                                    view.loadScript(settings.studyStorage, query.queryObject, {
                                        id: new Classes.AttachmentID(attachmentId),
                                        version: new Classes.ImageVersion(attachmentVersion),
                                        phiNamespace: new Classes.PhiNamespace(phiNamespace)
                                    });
                                    break;
                                }
                                case "meeting": {
                                    var meetingId = Query.findParameter(window.location, "meetingId");

                                    view.joinMeeting(new Classes.MeetingId(meetingId));
                                    break;
                                }
                            }
                        },
                        fail: function (err) {
                            Services.AuditLog("Error loading study : " + err, "Main", sessionId);

                            if (err && (err.indexOf("(404)") !== -1)) {
                                $('.applicationFrame').append(terminology.lookup(Terminology.Terms.ErrorStudyNotFound));
                            } else {
                                $('.applicationFrame').append(terminology.lookup(Terminology.Terms.ErrorLoadingStudy));
                            }

                            $('.overlay').removeClass('application-loading');
                        }
                    });
                }
            }
        };

        Main.checkFrozenStatus = function (studyStorage, permissions) {
            // if study is frozen, unset all edit-based permissions
            if (!!studyStorage.is_frozen) {
                permissions.annotation_edit = 0;
                permissions.keyimage_edit = 0;
                permissions.link_edit = 0;
                permissions.meeting_edit = 0;
                permissions.study_delete_image = 0;
                permissions.study_split = 0;
                permissions.study_audio_record = 0;
            }
        };

        /**
        * Apply overrides to account settings based on the query string
        */
        Main.applySettingsOverrides = function (settings) {
            var viewer_diagnostic_quality = Query.findParameter(window.location, "viewer_diagnostic_quality");

            if (viewer_diagnostic_quality) {
                settings.viewer_diagnostic_quality = parseInt(viewer_diagnostic_quality);
            }

            var viewer_diagnostic_quality_always = Query.findParameter(window.location, "viewer_diagnostic_quality_always");

            if (viewer_diagnostic_quality_always) {
                settings.viewer_diagnostic_quality_always = parseInt(viewer_diagnostic_quality_always);
            }

            var viewer_preload_diagnostic_images = Query.findParameter(window.location, "viewer_preload_diagnostic_images");

            if (viewer_preload_diagnostic_images) {
                settings.viewer_preload_diagnostic_images = parseInt(viewer_preload_diagnostic_images);
            }

            var viewer_show_reports = Query.findParameter(window.location, "viewer_show_reports");

            if (viewer_show_reports) {
                settings.viewer_show_reports = parseInt(viewer_show_reports);
            }

            var viewer_single_instance_series = Query.findParameter(window.location, "viewer_single_instance_series");

            if (viewer_single_instance_series) {
                settings.viewer_single_instance_series = parseInt(viewer_single_instance_series);
            }

            var viewer_multiframe_split_method = Query.findParameter(window.location, "viewer_multiframe_split_method");

            if (viewer_multiframe_split_method) {
                settings.viewer_multiframe_split_method = parseInt(viewer_multiframe_split_method);
            }

            var viewer_study_page_link_visible = Query.findParameter(window.location, "viewer_study_page_link_visible");

            if (viewer_study_page_link_visible) {
                settings.viewer_study_page_link_visible = parseInt(viewer_study_page_link_visible);
            }

            var viewer_study_page_link_url = Query.findParameter(window.location, "viewer_study_page_link_url");

            if (viewer_study_page_link_url) {
                settings.viewer_study_page_link_url = viewer_study_page_link_url;
            }

            var viewer_setting_not_diagnostic = Query.findParameter(window.location, "viewer_setting_not_diagnostic");

            if (viewer_setting_not_diagnostic) {
                settings.viewer_setting_not_diagnostic = parseInt(viewer_setting_not_diagnostic);
            }

            var viewer_enable_tap_navigation = Query.findParameter(window.location, "viewer_enable_tap_navigation");

            if (viewer_enable_tap_navigation) {
                settings.viewer_enable_tap_navigation = parseInt(viewer_enable_tap_navigation);
            }

            var viewer_show_std_dev = Query.findParameter(window.location, "viewer_show_std_dev");

            if (viewer_show_std_dev) {
                settings.viewer_show_std_dev = parseInt(viewer_show_std_dev);
            }

            var viewer_hide_thumbnails = Query.findParameter(window.location, "viewer_hide_thumbnails");

            if (viewer_hide_thumbnails) {
                settings.viewer_hide_thumbnails = parseInt(viewer_hide_thumbnails);
            }

            var viewer_hide_thumbnails_datetime = Query.findParameter(window.location, "viewer_hide_thumbnails_datetime");

            if (viewer_hide_thumbnails_datetime) {
                settings.viewer_hide_thumbnails_datetime = parseInt(viewer_hide_thumbnails_datetime);
            }

            var viewer_show_priors_worklist_only = Query.findParameter(window.location, "viewer_show_priors_worklist_only");

            if (viewer_show_priors_worklist_only) {
                settings.viewer_show_priors_worklist_only = parseInt(viewer_show_priors_worklist_only);
            }

            var viewer_hide_ruler = Query.findParameter(window.location, "viewer_hide_ruler");

            if (viewer_hide_ruler) {
                settings.viewer_hide_ruler = parseInt(viewer_hide_ruler);
            }

            var viewer_hide_active_measurement_info = Query.findParameter(window.location, "viewer_hide_active_measurement_info");

            if (viewer_hide_active_measurement_info) {
                settings.viewer_hide_active_measurement_info = parseInt(viewer_hide_active_measurement_info);
            }

            var viewer_store_extra_annotation_data = Query.findParameter(window.location, "viewer_store_extra_annotation_data");

            if (viewer_store_extra_annotation_data) {
                settings.viewer_store_extra_annotation_data = parseInt(viewer_store_extra_annotation_data);
            }

            var viewer_default_drop_shape_width = Query.findParameter(window.location, "viewer_default_drop_shape_width");

            if (viewer_default_drop_shape_width) {
                settings.viewer_default_drop_shape_width = parseInt(viewer_default_drop_shape_width);
            }

            var viewer_link_series = Query.findParameter(window.location, "viewer_link_series");

            if (viewer_link_series) {
                settings.viewer_link_series = parseInt(viewer_link_series);
            }

            var fullscreen = Query.findParameter(window.location, "fullscreen");

            if (fullscreen) {
                settings.fullscreen = parseInt(fullscreen) == 1;
            }

            return settings;
        };

        /**
        * Apply overrides to user settings based on the query string
        */
        Main.applyUserSettingsOverrides = function (settings) {
            var showPriors = Query.findParameter(window.location, "showPriors");

            if (showPriors) {
                settings.showPriors = showPriors.toLowerCase() === 'true';
            }

            var expandPriors = Query.findParameter(window.location, "expandPriors");

            if (expandPriors) {
                settings.expandPriors = expandPriors.toLowerCase() === 'true';
            }

            var showKeyOnly = Query.findParameter(window.location, "showKeyOnly");

            if (showKeyOnly) {
                settings.hangKeyImageSeriesFirst = showKeyOnly.toLowerCase() === 'true';
            }

            if (settings.simplePreload) {
                Images.useSimplePreloading = settings.simplePreload;
            }

            return settings;
        };

        /**
        * Used by v3ui to load related studies into the viewer
        */
        Main.loadRelatedStudy = function (storageNamespace, studyUid, phiNamespace) {
            if (Main.application) {
                Main.application.loadPrior({
                    storage_namespace: storageNamespace,
                    study_uid: studyUid,
                    phi_namespace: phiNamespace
                });
            }
        };
        Main.acceleratorUsed = false;
        return Main;
    })();
    _Main.Main = Main;

    $(function () {
        return Main.initialize();
    });
})(Main || (Main = {}));
///<reference path="Observable.ts"/>
/**
* Methods for working with messaging channels
*/
var Messaging;
(function (Messaging) {
    // Channels
    var CHANNEL_MEETING = "meeting";
    var CHANNEL_STUDY = "study";

    

    

    /**
    * Create a connect request for a study channel
    */
    function studyChannel(sessionId, queryObject) {
        return {
            sid: sessionId.value,
            channel: [CHANNEL_STUDY, queryObject.storageNamespace.value, queryObject.phiNamespace.value, queryObject.studyUid.value].join(".")
        };
    }
    Messaging.studyChannel = studyChannel;

    /**
    * Create a connect request for a key images channel
    */
    function meeting(sessionId, meetingId) {
        return {
            sid: sessionId.value,
            channel: [CHANNEL_MEETING, meetingId.value].join(".")
        };
    }
    Messaging.meeting = meeting;

    function hostName() {
        var loc = window.location.hostname;
        loc += (window.location.port.length > 0 ? ":" + window.location.port : "");
        return loc;
    }

    function ws() {
        var _ws;
        var _wsevents = {};
        var originalRequest;
        var pinger;

        var PING_INTERVAL = 30000;
        var RESTART_INTERVAL = 10000;
        var MAX_RESTARTS = 10;

        var closed = false;
        var initialized = false;
        var subscribed = false;
        var restarts = 0;

        function init() {
            if (_ws) {
                _ws.close();
                _ws = null;
            }

            subscribed = false;

            _ws = new WebSocket("wss://" + hostName() + "/api/v3/channel/websocket");

            _ws.onmessage = function (ev) {
                var data = JSON.parse(ev.data);

                if (data.status && !initialized) {
                    if (data.status == "OK") {
                        initialized = true;
                        _wsevent("onsubscribe", data);
                    } else {
                        _wsevent("onerror", data);
                    }
                } else {
                    _wsevent("onmessage", data);
                }
            };

            _ws.onclose = function (ev) {
                restart();
            };

            _ws.onerror = function (ev) {
                _ws.close();
            };
        }

        function _ping() {
            if (_ws && (_ws.readyState === WebSocket.OPEN)) {
                if (subscribed) {
                    _ws.send(JSON.stringify({ "action": "ping" }));
                }
            } else if (initialized) {
                restart();
            }
        }

        function _wsevent(eventType, data) {
            if (_wsevents[eventType]) {
                _wsevents[eventType](data);
            }
        }

        function _close() {
            closed = true;

            if (pinger) {
                clearInterval(pinger);
                pinger = null;
            }

            _ws.close();
        }

        function _subscribe(request) {
            _ws.onopen = function () {
                _ws.send(JSON.stringify(_.extend(request, { "action": "subscribe" })));
                subscribed = true;
                restarts = 0;
            };
        }

        var restart = _.throttle(function () {
            if (restarts < MAX_RESTARTS) {
                if (!closed && (_ws.readyState !== WebSocket.CONNECTING)) {
                    restarts++;
                    init();
                    _subscribe(originalRequest);
                }
            } else {
                console.error("Websocket service is unavailable.");
                _close();
            }
        }, RESTART_INTERVAL);

        pinger = setInterval(_ping, PING_INTERVAL); // start ping

        return {
            subscribe: function (request) {
                if (!originalRequest) {
                    originalRequest = request;
                    init();
                    _subscribe(originalRequest);
                }
            },
            onsubscribe: function (cb) {
                _wsevents["onsubscribe"] = cb;
            },
            onmessage: function (cb) {
                _wsevents["onmessage"] = cb;
            },
            onerror: function (cb) {
                _wsevents["onerror"] = cb;
            },
            disconnect: function () {
                _close();
            }
        };
    }

    /**
    * Connect to a channel and receive updates asynchronously
    */
    function channel(request) {
        if (LocalViewer.isLocalViewer() || Browser.isIE9()) {
            return Observable.ret(Observable.empty());
        } else {
            return {
                subscribe: function (ob) {
                    var socket = ws();

                    socket.onsubscribe(function (_) {
                        ob.next({
                            subscribe: function (ob) {
                                socket.onmessage(function (t) {
                                    ob.next(t);
                                });
                                return {
                                    cancel: function () {
                                        socket.disconnect();
                                    }
                                };
                            }
                        });
                        ob.done();
                    });

                    socket.onerror(function (message) {
                        ob.fail(message);
                    });

                    socket.subscribe(request);

                    return Observable.memptySubscription;
                }
            };
        }
    }
    Messaging.channel = channel;
})(Messaging || (Messaging = {}));
///<reference path="Terminology.ts" />
/**
* Helper functions for working with the keyboard configurations
*/
var Keyboard;
(function (Keyboard) {
    /**
    * Create a default keyboard configurations
    */
    function createDefaultKeyConfiguration() {
        return {
            items: [
                {
                    action: 61 /* PreviousSeriesSet */,
                    keyCode: 66
                }, {
                    action: 62 /* NextSeriesSet */,
                    keyCode: 78
                }, {
                    action: 63 /* CineAll */,
                    keyCode: 80
                }, {
                    action: 64 /* DuplicateAnnotation */,
                    keyCode: 68,
                    ctrl: true
                }, {
                    action: 65 /* CopyAnnotation */,
                    keyCode: 67,
                    ctrl: true
                }, {
                    action: 66 /* PasteAnnotation */,
                    keyCode: 86,
                    ctrl: true
                }
            ]
        };
    }
    Keyboard.createDefaultKeyConfiguration = createDefaultKeyConfiguration;
})(Keyboard || (Keyboard = {}));
///<reference path="Terminology.ts" />
/**
* Helper functions for working with the toolbar button user settings
*/
var ToolbarButtons;
(function (ToolbarButtons) {
    /**
    * Toolbar buttons for the local viewer
    */
    function localViewerToolbar(modality) {
        var items = [];

        items.push({
            itemType: 1 /* Group */,
            icon: "w_pan",
            text: Terminology.Terms.Transform.defaultValue,
            text_i18n: Terminology.Terms.Transform.code,
            tooltip: Terminology.Terms.TransformTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.TransformTooltip.code,
            buttons: [
                9 /* Fit */,
                1 /* Zoom */,
                2 /* Move */,
                27 /* FlipV */,
                3 /* Flip */,
                4 /* Rotate */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 5 /* Scroll */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 76 /* WindowLevelDirect */
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_arrow_down",
            text: null,
            text_i18n: null,
            tooltip: Terminology.Terms.WindowLevelTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.WindowLevelTooltip.code,
            buttons: [
                28 /* ResetWindowLevel */,
                29 /* WindowLevelPresets */
            ]
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_annotations",
            text: Terminology.Terms.Annotations.defaultValue,
            text_i18n: Terminology.Terms.Annotations.code,
            tooltip: Terminology.Terms.AnnotationsTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.AnnotationsTooltip.code,
            buttons: [
                31 /* DeleteAnnotation */,
                32 /* FillAnnotation */,
                34 /* Line */,
                35 /* Arrow */,
                53 /* Angle */,
                36 /* Cobb */,
                37 /* Rectangle */,
                38 /* Ellipse */,
                39 /* Text */
            ]
        });

        if (modality === "CT" || modality === "MR") {
            items.push({
                itemType: 0 /* Button */,
                button: 8 /* Probe */
            });
        }

        items.push({
            itemType: 0 /* Button */,
            button: 10 /* Reset */
        });

        if (modality === "CT" || modality === "MR" || modality === "PT") {
            items.push({
                itemType: 1 /* Group */,
                icon: "w_3d",
                text: Terminology.Terms.ThreeD.defaultValue,
                text_i18n: Terminology.Terms.ThreeD.code,
                tooltip: Terminology.Terms.ThreeDTooltip.defaultValue,
                tooltip_i18n: Terminology.Terms.ThreeDTooltip.code,
                buttons: [
                    40 /* ReferenceLines */,
                    41 /* LinkedSeries */,
                    42 /* PlaneLocalization */
                ]
            });
        }

        items.push({
            itemType: 1 /* Group */,
            icon: "w_layout",
            text: Terminology.Terms.Layout.defaultValue,
            text_i18n: Terminology.Terms.Layout.code,
            tooltip: Terminology.Terms.LayoutTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.LayoutTooltip.code,
            buttons: [
                43 /* Maximize */,
                45 /* LayoutButtons */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 15 /* TextAnnotations */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 16 /* Measurements */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 17 /* Invert */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 46 /* ExportPNG */,
            text: Terminology.Terms.Export.defaultValue,
            text_i18n: Terminology.Terms.Export.code,
            tooltip: Terminology.Terms.ExportTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.ExportTooltip.code
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_cine",
            text: Terminology.Terms.Cine.defaultValue,
            text_i18n: Terminology.Terms.Cine.code,
            tooltip: Terminology.Terms.CineTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.CineTooltip.code,
            buttons: [
                19 /* Cine */,
                49 /* Play */,
                50 /* Faster */,
                51 /* Slower */,
                52 /* FPSLabel */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 14 /* Thumbs */
        });

        return {
            items: items
        };
    }
    ToolbarButtons.localViewerToolbar = localViewerToolbar;

    /**
    * Create a default toolbar for a modality
    */
    function createDefaultToolbar(modality) {
        var items = [];

        items.push({
            itemType: 1 /* Group */,
            icon: "w_pan",
            text: Terminology.Terms.Transform.defaultValue,
            text_i18n: Terminology.Terms.Transform.code,
            tooltip: Terminology.Terms.TransformTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.TransformTooltip.code,
            buttons: [
                9 /* Fit */,
                1 /* Zoom */,
                2 /* Move */,
                27 /* FlipV */,
                3 /* Flip */,
                4 /* Rotate */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 5 /* Scroll */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 76 /* WindowLevelDirect */
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_arrow_down",
            text: null,
            text_i18n: null,
            tooltip: Terminology.Terms.WindowLevelTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.WindowLevelTooltip.code,
            buttons: [
                28 /* ResetWindowLevel */,
                24 /* SavePreset */,
                29 /* WindowLevelPresets */
            ]
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_annotations",
            text: Terminology.Terms.Annotations.defaultValue,
            text_i18n: Terminology.Terms.Annotations.code,
            tooltip: Terminology.Terms.AnnotationsTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.AnnotationsTooltip.code,
            buttons: [
                31 /* DeleteAnnotation */,
                32 /* FillAnnotation */,
                33 /* ExportGSPS */,
                34 /* Line */,
                35 /* Arrow */,
                53 /* Angle */,
                36 /* Cobb */,
                37 /* Rectangle */,
                38 /* Ellipse */,
                39 /* Text */
            ]
        });

        if (modality === "CT" || modality === "MR") {
            items.push({
                itemType: 0 /* Button */,
                button: 8 /* Probe */
            });
        }

        items.push({
            itemType: 0 /* Button */,
            button: 10 /* Reset */
        });

        if (modality === "CT" || modality === "MR" || modality === "PT") {
            items.push({
                itemType: 1 /* Group */,
                icon: "w_3d",
                text: Terminology.Terms.ThreeD.defaultValue,
                text_i18n: Terminology.Terms.ThreeD.code,
                tooltip: Terminology.Terms.ThreeDTooltip.defaultValue,
                tooltip_i18n: Terminology.Terms.ThreeDTooltip.code,
                buttons: [
                    40 /* ReferenceLines */,
                    41 /* LinkedSeries */,
                    42 /* PlaneLocalization */
                ]
            });
        }

        items.push({
            itemType: 1 /* Group */,
            icon: "w_layout",
            text: Terminology.Terms.Layout.defaultValue,
            text_i18n: Terminology.Terms.Layout.code,
            tooltip: Terminology.Terms.LayoutTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.LayoutTooltip.code,
            buttons: [
                43 /* Maximize */,
                44 /* NewWindow */,
                45 /* LayoutButtons */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 13 /* Print */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 15 /* TextAnnotations */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 16 /* Measurements */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 17 /* Invert */
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_export",
            text: Terminology.Terms.Export.defaultValue,
            text_i18n: Terminology.Terms.Layout.code,
            tooltip: Terminology.Terms.ExportTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.ExportTooltip.code,
            buttons: [
                46 /* ExportPNG */,
                47 /* SecondaryCapture */,
                48 /* Metadata */
            ]
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_cine",
            text: Terminology.Terms.Cine.defaultValue,
            text_i18n: Terminology.Terms.Cine.code,
            tooltip: Terminology.Terms.CineTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.CineTooltip.code,
            buttons: [
                19 /* Cine */,
                49 /* Play */,
                50 /* Faster */,
                51 /* Slower */,
                52 /* FPSLabel */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 21 /* KeyImage */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 14 /* Thumbs */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 67 /* StartMeeting */
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_action",
            text: Terminology.Terms.Actions.defaultValue,
            text_i18n: Terminology.Terms.Actions.code,
            tooltip: Terminology.Terms.ActionsTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.ActionsTooltip.code,
            buttons: [
                23 /* Actions */
            ]
        });

        items.push({
            itemType: 1 /* Group */,
            icon: "w_delete",
            text: Terminology.Terms.Delete.defaultValue,
            text_i18n: Terminology.Terms.Delete.code,
            tooltip: Terminology.Terms.DeleteTooltip.defaultValue,
            tooltip_i18n: Terminology.Terms.DeleteTooltip.code,
            buttons: [
                25 /* DeleteImage */,
                26 /* DeleteSeries */
            ]
        });

        items.push({
            itemType: 0 /* Button */,
            button: 22 /* Settings */
        });

        return {
            items: items
        };
    }
    ToolbarButtons.createDefaultToolbar = createDefaultToolbar;

    function createPlaybackToolbar() {
        var items = [];

        items.push({
            itemType: 0 /* Button */,
            button: 111 /* RewindPlayback */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 109 /* PlayRecording */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 112 /* FastForwardPlayback */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 75 /* Blank */
        });

        items.push({
            itemType: 0 /* Button */,
            button: 110 /* StopPlayback */
        });

        return {
            items: items
        };
    }
    ToolbarButtons.createPlaybackToolbar = createPlaybackToolbar;

    /**
    * Turn Ambra icon names into FontAwesome classes
    */
    function iconFor(name) {
        switch (name) {
            case "w_3d":
                return "fa-cube";
            case "w_action":
                return "fa-exclamation-circle";
            case "w_angle":
                return "fa-angle-left";
            case "w_annotations":
                return "fa-pencil";
            case "w_arrow":
                return "fa-location-arrow";
            case "w_pan":
                return "fa-arrows";
            case "w_dcm":
                return "fa-file-image-o";
            case "w_cine":
                return "fa-film";
            case "w_cine_faster":
                return "fa-caret-square-o-up";
            case "w_cine_slower":
                return "fa-caret-square-o-down";
            case "w_cobb":
                return "fa-angle-left";
            case "w_delete":
                return "fa-trash-o";
            case "w_draw":
                return "fa-pencil";
            case "w_dump":
                return "fa-file-text-o";
            case "w_edit":
                return "fa-pencil";
            case "w_ellipse":
                return "fa-circle-o";
            case "w_export":
                return "fa-share-square-o";
            case "w_enhance":
                return "fa-magic";
            case "w_fill":
                return "fa-tint";
            case "w_fit":
                return "fa-arrows-alt";
            case "w_flip":
                return "fa-arrows-h";
            case "w_flip_v":
                return "fa-arrows-v";
            case "w_png":
                return "fa-file-image-o";
            case "w_info":
                return "fa-info";
            case "w_invert":
                return "fa-adjust";
            case "w_key":
                return "fa-star";
            case "w_layout":
                return "fa-th-large";
            case "w_line":
                return "fa-pencil";
            case "w_link":
                return "fa-link";
            case "w_max":
                return "fa-expand";
            case "w_measurements":
                return "fa-pencil";
            case "w_mpr":
                return "fa-cubes";
            case "w_pause":
                return "fa-pause";
            case "w_play":
                return "fa-play";
            case "w_print":
                return "fa-print";
            case "w_priors":
                return "fa-clock-o";
            case "w_record":
                return "fa-microphone";
            case "w_rect":
                return "fa-square-o";
            case "w_reflines":
                return "fa-bars";
            case "w_reload":
                return "fa-repeat";
            case "w_rotate":
                return "fa-repeat";
            case "w_save":
                return "fa-floppy-o";
            case "w_scroll":
                return "fa-arrows-v";
            case "w_select":
                return "fa-crosshairs";
            case "w_settings":
                return "fa-cogs";
            case "w_tab":
                return "fa-plus";
            case "w_text":
                return "fa-keyboard-o";
            case "w_thumb":
                return "fa-thumbs-up";
            case "w_window":
                return "fa-sun-o";
            case "w_colors":
                return "fa-tint";
            case "w_zoom":
                return "fa-search";
            case "w_audio":
                return "fa-microphone";
            case "w_ann_detail_toggle":
                return "fa-pencil";
            case "w_trace":
                return "fa-pencil-square-o";
            case "w_blank":
                return "fa-fw";
            case "w_arrow_down":
                return "fa-caret-down";
            case "w_pointer":
                return "fa-mouse-pointer";
            case "w_export_video":
                return "fa-file-video-o";
            case "w_chevron_right":
                return "fa-chevron-right";
            case "w_chevron_left":
                return "fa-chevron-left";
            case "w_ruler":
                return "fa-minus";
            case "w_colocate":
                return "fa-files-o";
            case "w_header":
                return "fa-font";
            case "w_axes":
                return "fa-arrows";
            case "w_series_view":
                return "fa-th-large";
            case "w_cut":
                return "fa-scissors";
            case "w_stop":
                return "fa-stop";
            case "w_rewind":
                return "fa-backward";
            case "w_forward":
                return "fa-forward";
            case "w_download":
                return "fa-download";
            case "w_recordings":
                return "fa-file-video-o";
            case "w_meeting":
                return "fa-users";
            case "w_calc":
                return "fa-calculator";
            case "w_copy":
                return "fa-clone";
            case "w_lock":
                return "fa-lock";
            case "w_crop":
                return "fa-crop";
            case "w_paint":
                return "fa-paint-brush";
            case "w_threshold":
                return "fa-plus-square";
            case "w_shrinkwrap":
                return "fa-plus-circle";
            case "w_square":
                return "fa-square";
            case "w_attachment":
                return "fa-paperclip";
            case "w_random":
                return "fa-random";
            case "w_undo":
                return "fa-undo";

            default:
                return "";
        }
    }
    ToolbarButtons.iconFor = iconFor;
})(ToolbarButtons || (ToolbarButtons = {}));
///<reference path="Terminology.ts" />
/**
* Helper functions for working with context menus
*/
var ContextMenus;
(function (ContextMenus) {
    /**
    * The default set of menu items
    */
    ContextMenus.defaultSet = [
        9 /* Fit */,
        1 /* Zoom */,
        2 /* Move */,
        5 /* Scroll */,
        6 /* WindowLevel */,
        34 /* Line */,
        35 /* Arrow */,
        53 /* Angle */,
        36 /* Cobb */,
        37 /* Rectangle */,
        38 /* Ellipse */,
        58 /* Radius */,
        42 /* PlaneLocalization */,
        56 /* Magnify */
    ];

    /**
    * Create a context menu
    */
    function createContextMenu(list) {
        var items = _.map(list, function (button) {
            return {
                itemType: 0 /* Button */,
                button: button
            };
        });

        return {
            trigger: 'none',
            items: items
        };
    }
    ContextMenus.createContextMenu = createContextMenu;

    /**
    * Create a default context menu
    */
    function createDefaultContextMenu() {
        if (LocalViewer.isLocalViewer()) {
            return createContextMenu(ContextMenus.defaultSet);
        } else {
            return createContextMenu(ContextMenus.defaultSet.concat([70 /* MPR */]));
        }
    }
    ContextMenus.createDefaultContextMenu = createDefaultContextMenu;
})(ContextMenus || (ContextMenus = {}));
/**
* Helper functions for working with measurement annotations
*/
var Annotations;
(function (Annotations) {
    Annotations.COLORS = [
        "#e6194B",
        "#3cb44b",
        "#ffe119",
        "#4363d8",
        "#f58231",
        "#911eb4",
        "#42d4f4",
        "#f032e6",
        "#bfef45",
        "#fabebe"
    ];

    Annotations.OVERLY_COLORS = [
        Annotations.COLORS[0],
        Annotations.COLORS[3]
    ];

    Annotations.fromHex = function (hex) {
        hex = hex.replace("#", "");
        var result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? {
            r: parseInt(result[1], 16),
            g: parseInt(result[2], 16),
            b: parseInt(result[3], 16)
        } : null;
    };

    /**
    * Create a measurement from an annotation in services
    */
    function createMeasurement(annotation, id, creator, creatorId, editable) {
        var measurement;

        switch (Classes.MouseTool[annotation.type]) {
            case 5 /* Measure */:
                measurement = new Measurements.LineMeasurement(annotation.points[0], annotation.points[1], false);
                break;
            case 12 /* Arrow */:
                measurement = new Measurements.LineMeasurement(annotation.points[0], annotation.points[1], true);
                break;
            case 13 /* Angle */:
                measurement = new Measurements.Angle(annotation.points);
                break;
            case 7 /* CobbAngle */:
                measurement = new Measurements.Cobb({
                    start: annotation.points[0],
                    end: annotation.points[1]
                }, {
                    start: annotation.points[2],
                    end: annotation.points[3]
                });
                break;
            case 6 /* Rectangle */:
                measurement = new Measurements.Rectangle(annotation.points[0], annotation.points[1], annotation.filled || false);
                break;
            case 8 /* Ellipse */:
                measurement = new Measurements.Ellipse(annotation.points[0], annotation.points[1], annotation.filled || false);
                break;
            case 16 /* Circle */:
                measurement = new Measurements.Circle(annotation.points[0], annotation.points[1], annotation.filled || false);
                break;
            case 18 /* DropCircle */:
                measurement = new Measurements.DropCircle(annotation.points[0], annotation.points[1], annotation.filled || false);
                break;
            case 19 /* DropSquare */:
                measurement = new Measurements.DropSquare(annotation.points[0], annotation.points[1], annotation.filled || false);
                break;
            case 20 /* OrthoAxes */:
                measurement = new Measurements.OrthogonalAxes(annotation.points);
                break;
            case 21 /* FemoralHead */:
                measurement = new Measurements.FemoralHead(annotation.points);
                break;
            case 28 /* Polygon */:
                measurement = new Measurements.Polygon(annotation.points, annotation.closed, annotation.valid, annotation.filled || false);
                break;
            case 29 /* Trace */:
                measurement = new Measurements.Trace(annotation.points, true, annotation.valid, annotation.filled || false);
                break;
            case 10 /* Text */:
                if (annotation.points.length == 1) {
                    measurement = new Measurements.Text(annotation.points[0], { x: annotation.points[0].x, y: annotation.points[0].y }, annotation.text);
                } else {
                    measurement = new Measurements.Text(annotation.points[0], annotation.points[1], annotation.text);
                }
                break;
            case 31 /* Stamp */:
                if (annotation.points.length == 1) {
                    measurement = new Measurements.Stamp(annotation.points[0], { x: annotation.points[0].x, y: annotation.points[0].y }, annotation.text);
                } else {
                    measurement = new Measurements.Stamp(annotation.points[0], annotation.points[1], annotation.text);
                }
                break;
            case 32 /* Area */:
                measurement = new Measurements.Area(annotation.points, annotation.width, annotation.height, annotation.cursorSize, 0, annotation.area, null, annotation.filled || false);
                break;
            case 33 /* ProstateTool */:
                measurement = new Measurements.ProstateTool(annotation.points[0], annotation.points[1], annotation.groupId);
                break;
            default:
                throw new Classes.Exception("Unsupported measurement type");
        }

        measurement.id = new Classes.AnnotationId(id);
        measurement.editable = editable;
        measurement.creator = creator;
        measurement.creatorId = creatorId;

        if (annotation.colocationId) {
            measurement.colocationId = annotation.colocationId;
        }

        if (annotation.label) {
            measurement.label = annotation.label;
        }

        if (annotation.hideMeasurement) {
            measurement.hideMeasurement = annotation.hideMeasurement;
        }

        if (annotation.calibration) {
            measurement.calibration = annotation.calibration;
            measurement.calibrationValue = annotation.calibrationValue;
        }

        if (annotation.propagation) {
            measurement.propagation = {
                startIndex: annotation.propagation.startIndex,
                originalIndex: annotation.propagation.originalIndex,
                endIndex: annotation.propagation.endIndex
            };
        }

        if (annotation.pixelSpacing) {
            measurement.pixelSpacingUser = annotation.pixelSpacing;
        }

        if (annotation.sliceSpacing) {
            measurement.sliceSpacingUser = annotation.sliceSpacing;
        }

        if (annotation.color) {
            measurement.color = annotation.color;
        }

        return measurement;
    }
    Annotations.createMeasurement = createMeasurement;

    /**
    * Apply a message to the study
    */
    function merge(study, action, annotation, user) {
        switch (action) {
            case "CREATE":
                var instance = findInstance(study, annotation);

                if (instance) {
                    instance.instanceAttributes.measurements.push(createMeasurement(JSON.parse(annotation.json), annotation.uuid, annotation.user_name, annotation.user_id, user.uuid === annotation.user_id));
                }
                break;
            case "EDIT":
                var instance = findInstance(study, annotation);

                if (instance) {
                    var measurement = _.find(instance.instanceAttributes.measurements, function (m) {
                        return m.id.value === annotation.uuid;
                    });

                    if (measurement) {
                        measurement.edit(JSON.parse(annotation.json));

                        if (measurement.propagation) {
                            var series = findSeries(study, annotation);
                            if (series) {
                                propagateAnnotation(measurement, series, measurement.propagation.originalIndex, measurement.propagation.startIndex, measurement.propagation.endIndex, true);
                            }
                        }
                    }
                }
                break;
            case "DELETE":
                _.each(study.series, function (series) {
                    _.each(series.instances, function (instance) {
                        instance.instanceAttributes.measurements = _.filter(instance.instanceAttributes.measurements, function (m) {
                            return m.id.value !== annotation.uuid;
                        });
                    });
                });
                break;
        }
    }
    Annotations.merge = merge;

    /**
    * Find the instance to which an annotation belongs
    */
    function findInstance(study, annotation) {
        var series = findSeries(study, annotation);

        if (series) {
            return _.find(series.instances, function (i) {
                return annotation.instance_uid === i.id.value && annotation.frame_number === i.frameNumber.value.toString();
            });
        }

        return null;
    }
    Annotations.findInstance = findInstance;

    function findSeries(study, annotation) {
        return _.find(study.series, function (s) {
            return s.seriesAttributes.seriesUid.value === annotation.series_uid;
        });
    }
    Annotations.findSeries = findSeries;

    /**
    * Create a measurement from an annotation in services
    */
    function applyAll(annotations, study, user, settings) {
        var allowUltrasound = settings.ultrasoundMeasurements;

        _.each(study.series, function (series) {
            var isMultiframe = Multiframe.isMultiframe(series);

            _.each(series.instances, function (instance) {
                instance.instanceAttributes.measurements = _.map(_.filter(annotations.annotations, function (a) {
                    return a.instance_uid === instance.id.value && a.frame_number === instance.frameNumber.value.toString();
                }), function (a) {
                    var measurement = createMeasurement(JSON.parse(a.json), a.uuid, a.user_name, a.user_id, user.uuid === a.user_id);

                    if (isMultiframe && allowUltrasound && Dicom.Ultrasound.isUltrasoundSeries(instance.seriesAttributes)) {
                        var result = Dicom.Ultrasound.findSpacing(measurement, instance, instance.seriesAttributes.json);
                        if (result && result.valid) {
                            measurement.ultrasoundPixelSpacing = [result.spacingX, result.spacingY];
                        }
                    }

                    if (measurement.calibration) {
                        instance.instanceAttributes.calibration = measurement;
                    }

                    if (measurement.pixelSpacingUser) {
                        instance.instanceAttributes.calibrationUser = true;
                    }

                    if (measurement.sliceSpacingUser) {
                        instance.instanceAttributes.calibrationUser = true;
                    }

                    return measurement;
                });
            });
        });

        _.each(study.series, function (series) {
            var checked = [];

            _.each(series.instances, function (instance) {
                _.each(instance.instanceAttributes.measurements, function (m) {
                    if (!_.contains(checked, m)) {
                        checked.push(m);

                        if (m.propagation) {
                            propagateAnnotation(m, series, m.propagation.originalIndex, m.propagation.startIndex, m.propagation.endIndex);
                        }
                    }
                });
            });
        });
    }
    Annotations.applyAll = applyAll;

    /**
    * Propagate an annotation across slices.
    * @param m
    * @param series
    * @param originalIndex
    * @param propagateStart
    * @param propagateEnd
    * @param update
    */
    function propagateAnnotation(m, series, originalIndex, propagateStart, propagateEnd, update) {
        var length = series.instances.length;
        propagateStart = Math.max(0, Math.min(propagateStart, originalIndex));
        propagateEnd = Math.min(length - 1, Math.max(propagateEnd, originalIndex));

        if (update) {
            for (var ctr = 0; ctr < length; ctr += 1) {
                if (ctr !== originalIndex) {
                    series.instances[ctr].instanceAttributes.measurements = _.without(series.instances[ctr].instanceAttributes.measurements, m);
                }
            }

            m.propagation = {
                startIndex: propagateStart,
                originalIndex: originalIndex,
                endIndex: propagateEnd
            };
        }

        for (var ctr = propagateStart; ctr < originalIndex; ctr += 1) {
            series.instances[ctr].instanceAttributes.measurements.push(m);
        }

        for (var ctr = originalIndex + 1; ctr <= propagateEnd; ctr += 1) {
            series.instances[ctr].instanceAttributes.measurements.push(m);
        }
    }
    Annotations.propagateAnnotation = propagateAnnotation;

    /**
    * Create a new random ID for temporary use
    */
    function newRandomId() {
        var s = "";
        var chars = "abcdefghijklmnopqrstuvwxyz1234567890";

        for (var i = 0; i < 20; i++) {
            s += chars[Math.floor(Math.random() * 36)];
        }

        return new Classes.AnnotationId(s);
    }
    Annotations.newRandomId = newRandomId;

    /**
    * Convert annotation point data to a different image space.
    * @param {Models.AnnotationData} data
    * @param {number} pixelSpacingFrom
    * @param {number} pixelSpacingTo
    */
    function convertAnnotationLocation(data, pixelSpacingFrom, pixelSpacingTo) {
        var ratio = pixelSpacingFrom / pixelSpacingTo;

        if (data && data.points) {
            _.each(data.points, function (point) {
                point.x *= ratio;
                point.y *= ratio;
            });
        }
    }
    Annotations.convertAnnotationLocation = convertAnnotationLocation;
})(Annotations || (Annotations = {}));
/**
* Helper functions for working with Ambra accelerator.
*/
var Accelerator;
(function (Accelerator) {
    /**
    * Check a list of accelerators in parallel. Returns those accelerators which are available.
    */
    function checkAccelerators(sessionId, queryObject, accelerators) {
        if (!accelerators || !accelerators.length) {
            return Observable.ret(accelerators);
        }

        return Observable.map(Observable.sequenceA(_.map(accelerators, function (a) {
            return checkAccelerator(sessionId, queryObject, a);
        })), function (ms) {
            return Maybe.catMaybes(ms);
        });
    }
    Accelerator.checkAccelerators = checkAccelerators;

    /**
    * Check an accelerator for connectivity.
    */
    function checkAccelerator(sessionId, queryObject, fqdn) {
        return Observable.catchError(Observable.map(Observable.timeout(Services.getAccountSettings(sessionId, queryObject, fqdn), 500), function (settings) {
            return settings.is_accelerated == 1 ? Maybe.Just(fqdn) : Maybe.Nothing();
        }), function (_) {
            return Maybe.Nothing();
        });
    }
    Accelerator.checkAccelerator = checkAccelerator;
})(Accelerator || (Accelerator = {}));
///<reference path='../classes/Types.ts' />
///<reference path='../models/Study.ts' />
///<reference path='Terminology.ts' />
// http://dicom.nema.org/DICOM/2013/output/chtml/part06/chapter_B.html
/**
* Preset window level defaults
*/
var ColorTablePresets;
(function (ColorTablePresets) {
    // 1.2.840.10008.1.​5.​2
    ColorTablePresets.PET = {
        knots: [
            { value: 0, red: 0, green: 0, blue: 0 },
            { value: 0.25, red: 0, green: 0.5019607843137255, blue: 0.49019607843137253 },
            { value: 0.50, red: 0.5019607843137255, green: 0, blue: 1 },
            { value: 0.75, red: 1, green: 0.5019607843137255, blue: 0 },
            { value: 1, red: 1, green: 1, blue: 1 }
        ],
        gradient: true
    };

    // 1.2.840.10008.1.​5.​4
    ColorTablePresets.PET20 = {
        knots: [
            { value: 0, red: 0, green: 0, blue: 0 },
            { value: 0.05, red: 0.3764705882352941, green: 0, blue: 0.3137254901960784 },
            { value: 0.10, red: 0.18823529411764706, green: 0.18823529411764706, blue: 0.3137254901960784 },
            { value: 0.15, red: 0.18823529411764706, green: 0.18823529411764706, blue: 0.4392156862745098 },
            { value: 0.20, red: 0.3137254901960784, green: 0.3137254901960784, blue: 0.5019607843137255 },
            { value: 0.25, red: 0.3764705882352941, green: 0.3764705882352941, blue: 0.6901960784313725 },
            { value: 0.30, red: 0.4392156862745098, green: 0.4392156862745098, blue: 0.7529411764705882 },
            { value: 0.35, red: 0.5019607843137255, green: 0.5019607843137255, blue: 0.8784313725490196 },
            { value: 0.40, red: 0.18823529411764706, green: 0.3764705882352941, blue: 0.18823529411764706 },
            { value: 0.45, red: 0.18823529411764706, green: 0.5647058823529412, blue: 0.18823529411764706 },
            { value: 0.50, red: 0.3137254901960784, green: 0.7529411764705882, blue: 0.3137254901960784 },
            { value: 0.55, red: 0.25098039215686274, green: 0.8784313725490196, blue: 0.25098039215686274 },
            { value: 0.60, red: 0.8784313725490196, green: 0.8784313725490196, blue: 0.3137254901960784 },
            { value: 0.65, red: 0.8156862745098039, green: 0.8156862745098039, blue: 0.3764705882352941 },
            { value: 0.70, red: 0.8156862745098039, green: 0.6901960784313725, blue: 0.25098039215686274 },
            { value: 0.75, red: 0.8156862745098039, green: 0.5647058823529412, blue: 0 },
            { value: 0.80, red: 0.7529411764705882, green: 0.3764705882352941, blue: 0 },
            { value: 0.85, red: 0.6901960784313725, green: 0.18823529411764706, blue: 0 },
            { value: 0.90, red: 1, green: 0, blue: 0 },
            { value: 0.95, red: 1, green: 1, blue: 1 },
            { value: 1, red: 1, green: 1, blue: 1 }
        ],
        gradient: false
    };

    // 1.2.840.10008.1.​5.​1
    ColorTablePresets.HOT_IRON = {
        knots: [
            { value: 0, red: 0, green: 0, blue: 0 },
            { value: 0.50, red: 1, green: 0, blue: 0 },
            { value: 0.75, red: 1, green: 0.50, blue: 0 },
            { value: 1, red: 1, green: 1, blue: 1 }
        ],
        gradient: true
    };

    // 1.2.840.10008.1.​5.​3
    ColorTablePresets.HOT_BLUE = {
        knots: [
            { value: 0, red: 0, green: 0, blue: 0 },
            { value: 0.33, red: 0, green: 0, blue: 0.6549019607843137 },
            { value: 0.40, red: 0.19607843137254902, green: 0, blue: 0.7843137254901961 },
            { value: 0.47, red: 0.35294117647058826, green: 0, blue: 0.5882352941176471 },
            { value: 0.53, red: 0.5490196078431373, green: 0.12549019607843137, blue: 0.19607843137254902 },
            { value: 0.60, red: 0.7450980392156863, green: 0.25098039215686274, blue: 0 },
            { value: 0.67, red: 1, green: 0.3764705882352941, blue: 0 },
            { value: 1, red: 1, green: 1, blue: 1 }
        ],
        gradient: true
    };

    ColorTablePresets.FIRE = {
        knots: [
            { value: 0, red: 0, green: 0, blue: 0 },
            { value: 0.06, red: 0, green: 0, blue: 0.36 },
            { value: 0.16, red: 0.29, green: 0, blue: 0.75 },
            { value: 0.22, red: 0.48, green: 0, blue: 0.89 },
            { value: 0.31, red: 0.68, green: 0, blue: 0.6 },
            { value: 0.37, red: 0.76, green: 0, blue: 0.36 },
            { value: 0.5, red: 0.94, green: 0.31, blue: 0 },
            { value: 0.56, red: 1, green: 0.45, blue: 0 },
            { value: 0.81, red: 1, green: 0.91, blue: 0 },
            { value: 0.88, red: 1, green: 1, blue: 0.38 },
            { value: 1.0, red: 1, green: 1, blue: 1 }
        ],
        gradient: true
    };

    /**
    * Converts a ColorTable to a form usable by both canvas2D and webgl
    * @param {Classes.ColorTable} lut
    * @returns {Uint8Array}
    */
    function createPalette(lut) {
        var palette = new Array(256 * 4);

        var knotRange, threshold, nextThreshold = 0, currentKnot = 0, nextKnot = 0;

        for (var ctr = 0; ctr < 256; ctr += 1) {
            if ((ctr > nextThreshold) || (ctr == 0)) {
                currentKnot = nextKnot;
                nextKnot++;
                knotRange = 1.0 / (lut.knots[nextKnot].value - lut.knots[currentKnot].value);
                threshold = (255.0 * lut.knots[currentKnot].value);
                nextThreshold = (255.0 * lut.knots[nextKnot].value);
            }

            if (lut.gradient) {
                var knotRangeValue = ((ctr - threshold) * knotRange) / 255.0;
                var knotRangeValueInv = (1.0 - knotRangeValue);

                palette[ctr * 4] = Math.round(255 * ((knotRangeValueInv * lut.knots[currentKnot].red) + (knotRangeValue * lut.knots[nextKnot].red)));
                palette[ctr * 4 + 1] = Math.round(255 * ((knotRangeValueInv * lut.knots[currentKnot].green) + (knotRangeValue * lut.knots[nextKnot].green)));
                palette[ctr * 4 + 2] = Math.round(255 * ((knotRangeValueInv * lut.knots[currentKnot].blue) + (knotRangeValue * lut.knots[nextKnot].blue)));
            } else {
                palette[ctr * 4] = Math.round(255 * (lut.knots[currentKnot].red));
                palette[ctr * 4 + 1] = Math.round(255 * (lut.knots[currentKnot].green));
                palette[ctr * 4 + 2] = Math.round(255 * (lut.knots[currentKnot].blue));
            }

            palette[ctr * 4 + 3] = 255;
        }

        return palette;
    }
    ColorTablePresets.createPalette = createPalette;

    /**
    * Lookup default window level presets for a modality
    * @param {string} modality
    * @param {Func<Classes.Term, string>} terminology
    * @returns {Classes.ColorTablePreset[]}
    */
    function defaults(modality, terminology) {
        var presets = [];

        switch (modality.toUpperCase()) {
            case "PT":
            case "NM":
                presets.push({
                    name: terminology.lookup(Terminology.Terms.PET),
                    colorTable: ColorTablePresets.PET
                });

                presets.push({
                    name: terminology.lookup(Terminology.Terms.PET20),
                    colorTable: ColorTablePresets.PET20
                });

            default:
                presets.push({
                    name: terminology.lookup(Terminology.Terms.HotIron),
                    colorTable: ColorTablePresets.HOT_IRON
                });

                presets.push({
                    name: terminology.lookup(Terminology.Terms.HotBlue),
                    colorTable: ColorTablePresets.HOT_BLUE
                });

                presets.push({
                    name: terminology.lookup(Terminology.Terms.Fire),
                    colorTable: ColorTablePresets.FIRE
                });
        }

        return presets;
    }
    ColorTablePresets.defaults = defaults;
})(ColorTablePresets || (ColorTablePresets = {}));
///<reference path="../typings/jquery/jquery.d.ts" />
/**
* Helper methods for working with mouse events
*/
var Mouse;
(function (Mouse) {
    /**
    * Get the offset from the top left of the container at which a mouse event occurred
    */
    function getOffset(e) {
        if (e.offsetX && e.offsetY) {
            return { x: e.offsetX, y: e.offsetY };
        }

        var position = $(e.target).offset();

        return {
            x: e.pageX - position.left,
            y: e.pageY - position.top
        };
    }
    Mouse.getOffset = getOffset;

    /**
    * Get the offset of a mouse event relative to an element
    */
    function relativeTo(e, jq) {
        return {
            x: e.pageX - jq.position().left,
            y: e.pageY - jq.position().top
        };
    }
    Mouse.relativeTo = relativeTo;
})(Mouse || (Mouse = {}));
var Meeting;
(function (Meeting) {
    

    /**
    * A ScriptRecorder implementation which sends events to services to be broadcast
    */
    var MeetingRecorder = (function () {
        function MeetingRecorder(sessionId, meetingId, getLatestState) {
            this.sessionId = sessionId;
            this.meetingId = meetingId;

            this.getLatestState = getLatestState;
        }
        MeetingRecorder.prototype.append = function (event) {
            var _this = this;
            switch (event.type) {
                case 4 /* LayoutChanged */:
                case 6 /* SelectedImageChanged */:
                case 5 /* SeriesChanged */:
                case 9 /* InvertChanged */:
                case 1 /* WindowLevelChanged */:
                case 0 /* ImageTransformationChanged */:
                    this.sendState();
                    break;
            }

            // Don't send annotation events, they will be handled on the annotations
            // channels.
            if (event.type !== 2 /* AnnotationAdded */) {
                Services.BroadcastEvent(this.sessionId, this.meetingId, event).subscribe({
                    done: function () {
                    },
                    fail: function (err) {
                        Services.AuditLog("Unable to broadcast meeting event: " + err, "Meeting", _this.sessionId);
                    },
                    next: function (_) {
                    }
                });
            }
        };

        MeetingRecorder.prototype.sendState = function () {
            var _this = this;
            var state = this.getLatestState();

            Services.UpdateMeeting(this.sessionId, this.meetingId, state).subscribe({
                done: function () {
                },
                fail: function (err) {
                    Services.AuditLog("Unable to update meeting state: " + err, "Meeting", _this.sessionId);
                },
                next: function (_) {
                }
            });
        };

        MeetingRecorder.prototype.getEvents = function () {
            throw new Error("Not supported");
        };

        MeetingRecorder.prototype.setZeroTimestamp = function (timestamp) {
        };
        return MeetingRecorder;
    })();
    Meeting.MeetingRecorder = MeetingRecorder;
})(Meeting || (Meeting = {}));
var SecondaryCapture;
(function (SecondaryCapture) {
    /**
    * Store a secondary capture image, and load its newly created image attributes.
    */
    function secondaryCapture(sessionId, series, uri) {
        var header = "data:image/png;base64,";

        if (uri.indexOf(header) == 0) {
            var base64ImageData = uri.substr(header.length);

            $('.overlay').addClass('application-loading');

            return Observable._finally(Observable.bind(V3Storage.postSecondaryCaptureImage(sessionId, series.studyAttributes.studyStorage, series.studyAttributes.queryObject, base64ImageData), function (result) {
                var newSeries = new Models.Series();

                newSeries.seriesAttributes = series.seriesAttributes;
                newSeries.studyAttributes = series.studyAttributes;

                var newInstance = new Models.Instance();

                newInstance.frameNumber = new Classes.FrameNumber(0);
                newInstance.id = new Classes.InstanceUid(result.image_uid);

                newInstance.seriesAttributes = series.seriesAttributes;
                newInstance.studyAttributes = series.studyAttributes;

                newInstance.instanceAttributes = new Models.InstanceAttributes();

                newInstance.instanceAttributes.instanceIndex = 0;
                newInstance.instanceAttributes.attributesLoaded = false;
                newInstance.instanceAttributes.frameCount = 1;
                newInstance.instanceAttributes.version = new Classes.ImageVersion(result.image_version);
                newInstance.instanceAttributes.instanceNumber = 1;
                newInstance.instanceAttributes.isKeyImage = new Subjects.ObservableValue(false);

                newSeries.instances.push(newInstance);

                newSeries.instances = [newInstance];

                return Observable.map(Observable.retryWithDelay(Study.loadImageAttributes(sessionId, newInstance), 1000, 10), function (_) {
                    return newSeries;
                });
            }), function () {
                return $('.overlay').removeClass('application-loading');
            });
        } else {
            return Observable.fail("The image data was in an incorrect format.");
        }
    }
    SecondaryCapture.secondaryCapture = secondaryCapture;

    /**
    * Post captured image to storage
    * @param {Classes.SessionId} sessionId
    * @param {Views.Series} view
    * @param {string} uri
    */
    function postCurrentImage(sessionId, view, uri) {
        var blob = convertImageDataUrlToBlob(uri);

        $('.overlay').addClass('application-loading');

        var showMessage = function (msg) {
            setTimeout(function () {
                return window.alert(msg);
            }, 500);
        };

        Observable._finally(V3Storage.PostImageAttachment(sessionId, view.series.studyAttributes.studyStorage, view.series.studyAttributes.queryObject, view.application.user.name, view.currentInstance().read().id.value + ".png", blob), function () {
            $('.overlay').removeClass('application-loading');
        }).subscribe({
            done: function () {
                showMessage(view.application.terminology.lookup(Terminology.Terms.ImageCaptureSuccess));
            },
            next: function () {
            },
            fail: function (err) {
                view.recordError("Unable to save captured image: " + err);
                showMessage(view.application.terminology.lookup(Terminology.Terms.ImageCaptureFailed));
            }
        });
    }
    SecondaryCapture.postCurrentImage = postCurrentImage;

    function convertImageDataUrlToBlob(dataUrl) {
        var img = atob(dataUrl.split(',')[1]);
        var img_buffer = [];
        var ctr = 0;

        while (ctr < img.length) {
            img_buffer.push(img.charCodeAt(ctr));
            ctr++;
        }

        return new Blob([new Uint8Array(img_buffer)], { "type": "image/png" });
    }
})(SecondaryCapture || (SecondaryCapture = {}));
var TextAnnotations;
(function (TextAnnotations) {
    var nonModalitySpecificDefaults = [
        { type: 0 /* PatientName */, position: 0 /* TopLeft */ },
        { type: 1 /* PatientDOB */, position: 0 /* TopLeft */ },
        { type: 2 /* StudyDate */, position: 0 /* TopLeft */ },
        { type: 3 /* PriorNumber */, position: 0 /* TopLeft */ },
        { type: 4 /* ImageNumber */, position: 0 /* TopLeft */ },
        { type: 6 /* MeasurementCreator */, position: 1 /* BottomLeft */ },
        { type: 7 /* ReferringPhysician */, position: 1 /* BottomLeft */ },
        { type: 8 /* StudyDescription */, position: 1 /* BottomLeft */ },
        { type: 9 /* SeriesDescription */, position: 1 /* BottomLeft */ },
        { type: 5 /* ImageType */, position: 1 /* BottomLeft */ },
        { type: 10 /* Zoom */, position: 2 /* BottomRight */ },
        { type: 11 /* Quality */, position: 2 /* BottomRight */ },
        { type: 17 /* PixelSpacingMeaning */, position: 2 /* BottomRight */ },
        { type: 12 /* WindowLevel */, position: 2 /* BottomRight */ },
        { type: 39 /* PaintMeasurementLabel */, position: 3 /* TopRight */ },
        { type: 34 /* PaintMeasurementArea */, position: 3 /* TopRight */ },
        { type: 35 /* PaintMeasurementMin */, position: 3 /* TopRight */ },
        { type: 36 /* PaintMeasurementMax */, position: 3 /* TopRight */ },
        { type: 37 /* PaintMeasurementMean */, position: 3 /* TopRight */ },
        { type: 38 /* PaintMeasurementStDev */, position: 3 /* TopRight */ }
    ];

    var radiationModule = [
        { type: 13 /* RadiationMachineName */, position: 2 /* BottomRight */ },
        { type: 14 /* KVP */, position: 2 /* BottomRight */ },
        { type: 15 /* Exposure */, position: 2 /* BottomRight */ }
    ];

    var volumetricModule = [
        { type: 16 /* SliceThickness */, position: 2 /* BottomRight */ }
    ];

    var subtractionModule = [
        { type: 30 /* Subtraction */, position: 3 /* TopRight */ }
    ];

    TextAnnotations.cureMetrixModule = [
        { type: 31 /* CureMetrix */, position: 3 /* TopRight */ },
        { type: 33 /* CADSRCalcificationFindings */, position: 3 /* TopRight */ },
        { type: 32 /* CADSRDensityFindings */, position: 3 /* TopRight */ }
    ];

    /**
    * Default text annotations for each modality
    */
    function defaults(modality) {
        var result = nonModalitySpecificDefaults;

        if (_.contains(["CR", "DX", "CT", "RTIMAGE", "RTDOSE"], modality.toUpperCase())) {
            result = result.concat(radiationModule);
        }

        if (_.contains(["CT", "MR"], modality.toUpperCase())) {
            result = result.concat(volumetricModule);
        }

        if (_.contains(["XA"], modality.toUpperCase())) {
            result = result.concat(subtractionModule);
        }

        return result;
    }
    TextAnnotations.defaults = defaults;
})(TextAnnotations || (TextAnnotations = {}));
var Modify;
(function (Modify) {
    /**
    * Enumerates levels at which anonymization can occur
    */
    (function (AnonymizationType) {
        AnonymizationType[AnonymizationType["Study"] = 0] = "Study";
        AnonymizationType[AnonymizationType["Series"] = 1] = "Series";
        AnonymizationType[AnonymizationType["Image"] = 2] = "Image";
    })(Modify.AnonymizationType || (Modify.AnonymizationType = {}));
    var AnonymizationType = Modify.AnonymizationType;

    /**
    * Anonymize a series and return the new series
    */
    function anonymize(sessionId, study, regions) {
        $('.overlay').addClass('application-loading');

        return Observable.forget(Observable._finally(V3Storage.anonymizeStudy(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, regions), function () {
            return $('.overlay').removeClass('application-loading');
        }));
    }
    Modify.anonymize = anonymize;

    function split(sessionId, study, seriesUIDs) {
        $('.overlay').addClass('application-loading');

        return Observable.forget(Observable._finally(V3Storage.splitStudy(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, seriesUIDs), function () {
            return $('.overlay').removeClass('application-loading');
        }));
    }
    Modify.split = split;

    function crop(sessionId, study, regions) {
        $('.overlay').addClass('application-loading');

        return Observable.forget(Observable._finally(V3Storage.cropStudy(sessionId, study.studyAttributes.studyStorage, study.studyAttributes.queryObject, regions), function () {
            return $('.overlay').removeClass('application-loading');
        }));
    }
    Modify.crop = crop;
})(Modify || (Modify = {}));
///<reference path="../classes/Types.ts" />
/**
* Plane geometry helper methods
*/
var Geometry;
(function (Geometry) {
    /**
    * Convert a collection of points into a sequence of lines joining them together.
    */
    function pointsToLines(points) {
        var lines = [];

        for (var i = 0; i < points.length - 1; i++) {
            lines.push({
                start: {
                    x: points[i].x,
                    y: points[i].y
                },
                end: {
                    x: points[i + 1].x,
                    y: points[i + 1].y
                }
            });
        }

        return lines;
    }
    Geometry.pointsToLines = pointsToLines;

    /**
    * Convert rectangle points into lines.
    * @param points
    * @returns {Classes.Line[]}
    */
    function rectangleToLines(points) {
        var lines = [];

        lines.push({
            start: { x: points[0].x, y: points[0].y },
            end: { x: points[1].x, y: points[0].y }
        });

        lines.push({
            start: { x: points[1].x, y: points[0].y },
            end: { x: points[1].x, y: points[1].y }
        });

        lines.push({
            start: { x: points[1].x, y: points[1].y },
            end: { x: points[0].x, y: points[1].y }
        });

        lines.push({
            start: { x: points[0].x, y: points[1].y },
            end: { x: points[0].x, y: points[0].y }
        });

        return lines;
    }
    Geometry.rectangleToLines = rectangleToLines;

    /**
    * Compute the acute angle between two lines
    */
    function angleBetween(l1, l2) {
        var len1 = Math.sqrt((l1.start.x - l1.end.x) * (l1.start.x - l1.end.x) + (l1.start.y - l1.end.y) * (l1.start.y - l1.end.y));
        var len2 = Math.sqrt((l2.start.x - l2.end.x) * (l2.start.x - l2.end.x) + (l2.start.y - l2.end.y) * (l2.start.y - l2.end.y));
        var dot = (l1.start.x - l1.end.x) * (l2.start.x - l2.end.x) + (l1.start.y - l1.end.y) * (l2.start.y - l2.end.y);

        return Math.acos(dot / len1 / len2) / Math.PI * 180.0;
    }
    Geometry.angleBetween = angleBetween;

    /**
    * Create an approximation to an ellipse as a list of line segments
    */
    function pointsForEllipse(p1, p2, detail) {
        var points = [];

        var cx = (p1.x + p2.x) / 2;
        var cy = (p1.y + p2.y) / 2;

        var w = Math.abs(p1.x - p2.x);
        var h = Math.abs(p1.y - p2.y);

        for (var i = 0; i <= detail; i++) {
            var x = cx + Math.cos(i / detail * Math.PI * 2) * w / 2;
            var y = cy + Math.sin(i / detail * Math.PI * 2) * h / 2;
            points.push({ x: x, y: y });
        }

        return points;
    }
    Geometry.pointsForEllipse = pointsForEllipse;

    /**
    * Create an approximation to an ellipse as a list of line segments, based on a major and minor axis
    */
    function pointsForEllipseFromAxes(major, minor, detail) {
        var points = [];

        var cx = (major.start.x + major.end.x) / 2;
        var cy = (major.start.y + major.end.y) / 2;

        var dx1 = major.start.x - cx;
        var dy1 = major.start.y - cy;

        var dx2 = minor.start.x - cx;
        var dy2 = minor.start.y - cy;

        for (var i = 0; i <= detail; i++) {
            var c = Math.cos(i / detail * Math.PI * 2);
            var s = Math.sin(i / detail * Math.PI * 2);

            var x = cx + c * dx1 + s * dx2;
            var y = cy + c * dy1 + s * dy2;

            points.push({ x: x, y: y });
        }

        return points;
    }
    Geometry.pointsForEllipseFromAxes = pointsForEllipseFromAxes;

    /**
    * Interpolate between the two central control points
    */
    function interpolate(y, x) {
        var a = y[1] + y[3] - y[0] - y[2];
        var b = 2 * (y[0] - y[1]) + y[2] - y[3];
        var c = y[2] - y[0];
        var d = y[1];

        var x2 = x * x;
        var x3 = x * x2;

        return a * x3 + b * x2 + c * x + d;
    }
    Geometry.interpolate = interpolate;

    /**
    * Create an interpolated path
    */
    function pointsForInterpolatedPath(points, detail, closed) {
        var result = [];

        if (closed) {
            var lastPointRemoved = points.slice(0, -1);

            for (var i = 0; i < lastPointRemoved.length; i++) {
                var ctl = [
                    lastPointRemoved[(i + lastPointRemoved.length - 1) % lastPointRemoved.length],
                    lastPointRemoved[i],
                    lastPointRemoved[(i + lastPointRemoved.length + 1) % lastPointRemoved.length],
                    lastPointRemoved[(i + lastPointRemoved.length + 2) % lastPointRemoved.length]];

                for (var j = 0; j < detail; j++) {
                    var x = interpolate([ctl[0].x, ctl[1].x, ctl[2].x, ctl[3].x], j / detail);
                    var y = interpolate([ctl[0].y, ctl[1].y, ctl[2].y, ctl[3].y], j / detail);
                    result.push({ x: x, y: y });
                }
            }

            result.push(points[0]);
        } else {
            for (var i = 0; i < points.length; i++) {
                var ctl = [
                    points[Math.max(i - 1, 0)],
                    points[i],
                    points[Math.min(i + 1, points.length - 1)],
                    points[Math.min(i + 2, points.length - 1)]];

                for (var j = 0; j < detail; j++) {
                    var x = interpolate([ctl[0].x, ctl[1].x, ctl[2].x, ctl[3].x], j / detail);
                    var y = interpolate([ctl[0].y, ctl[1].y, ctl[2].y, ctl[3].y], j / detail);
                    result.push({ x: x, y: y });
                }
            }
        }

        return result;
    }
    Geometry.pointsForInterpolatedPath = pointsForInterpolatedPath;

    /**
    * Compute the length of a line
    */
    function lineLength(line) {
        var dx = line.start.x - line.end.x;
        var dy = line.start.y - line.end.y;

        return Math.sqrt(dx * dx + dy * dy);
    }
    Geometry.lineLength = lineLength;

    /**
    * Compute length of line, converting from pixels to units
    * @param line
    * @param deltaX
    * @param deltaY
    */
    function lineLength2(line, deltaX, deltaY) {
        var dx = (line.start.x - line.end.x) * deltaX;
        var dy = (line.start.y - line.end.y) * deltaY;

        return Math.sqrt(dx * dx + dy * dy);
    }
    Geometry.lineLength2 = lineLength2;

    /**
    * Compute the l_infinity distance between two points. Used for quick proximity tests when the user clicks.
    */
    function lengthInf(line) {
        return Math.abs(line.start.x - line.end.x) + Math.abs(line.start.y - line.end.y);
    }
    Geometry.lengthInf = lengthInf;

    /**
    * Distance between points
    */
    function distanceBetween(p1, p2) {
        var dx = p1.x - p2.x;
        var dy = p1.y - p2.y;

        return Math.sqrt(dx * dx + dy * dy);
    }
    Geometry.distanceBetween = distanceBetween;

    /**
    * Distance between points, converting points to units
    * @param p1
    * @param p2
    * @param deltaX
    * @param deltaY
    */
    function distanceBetween2(p1, p2, deltaX, deltaY) {
        var dx = (p1.x - p2.x) * deltaX;
        var dy = (p1.y - p2.y) * deltaY;

        return Math.sqrt(dx * dx + dy * dy);
    }
    Geometry.distanceBetween2 = distanceBetween2;

    /**
    * Distance between two points in 3D space.
    * @param p1
    * @param p2
    */
    function distance3(p1, p2) {
        var dx = p1.x - p2.x;
        var dy = p1.y - p2.y;
        var dz = p1.z - p2.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }
    Geometry.distance3 = distance3;

    /**
    * Copy a point structure
    */
    function copyPoint(p) {
        return {
            x: p.x,
            y: p.y
        };
    }
    Geometry.copyPoint = copyPoint;

    /**
    * Set point
    * @param {Classes.Point} p1
    * @param {Classes.Point} p2
    */
    function setPoint(p1, p2) {
        p1.x = p2.x;
        p1.y = p2.y;
    }
    Geometry.setPoint = setPoint;

    function displacePointBy(p, vec) {
        return {
            x: p.x + vec.x,
            y: p.y + vec.y
        };
    }
    Geometry.displacePointBy = displacePointBy;

    function pointDiff(p1, p2) {
        return {
            x: p2.x - p1.x,
            y: p2.y - p1.y
        };
    }
    Geometry.pointDiff = pointDiff;

    /**
    * Copy a line structure
    */
    function copyLine(line) {
        return {
            start: copyPoint(line.start),
            end: copyPoint(line.end)
        };
    }
    Geometry.copyLine = copyLine;

    /**
    * Set line (with option to swap endpoints)
    * @param {Classes.Line} line1
    * @param {Classes.Line} line2
    * @param {boolean} swap
    */
    function setLine(line1, line2, swap) {
        if (swap) {
            setPoint(line1.start, line2.end);
            setPoint(line1.end, line2.start);
        } else {
            setPoint(line1.start, line2.start);
            setPoint(line1.end, line2.end);
        }
    }
    Geometry.setLine = setLine;

    /**
    * Returns opposite endpoint
    * @param {Classes.Line} line
    * @param {Classes.Point} point
    * @returns {any}
    */
    function otherPoint(line, point) {
        if (Geometry.pointsEqual(line.start, point)) {
            return line.end;
        } else if (Geometry.pointsEqual(line.end, point)) {
            return line.start;
        }

        return null;
    }
    Geometry.otherPoint = otherPoint;

    /**
    * Displace a point structure
    */
    function displacePoint(p, x, y) {
        return {
            x: p.x + x,
            y: p.y + y
        };
    }
    Geometry.displacePoint = displacePoint;

    /**
    * Displace a line structure
    */
    function displaceLine(l, x, y) {
        return {
            start: displacePoint(l.start, x, y),
            end: displacePoint(l.end, x, y)
        };
    }
    Geometry.displaceLine = displaceLine;

    /**
    * Distance between a point and a line segment
    */
    function distanceToLine(line, p) {
        var dx1 = p.x - line.start.x;
        var dy1 = p.y - line.start.y;
        var dx2 = line.end.x - line.start.x;
        var dy2 = line.end.y - line.start.y;
        var dx3 = p.x - line.end.x;
        var dy3 = p.y - line.end.y;

        var d11 = dx1 * dx1 + dy1 * dy1;
        var d22 = dx2 * dx2 + dy2 * dy2;
        var d12 = dx1 * dx2 + dy1 * dy2;
        var d33 = dx3 * dx3 + dy3 * dy3;

        if (d12 < 0) {
            return Math.sqrt(d11);
        } else if (d12 > d22) {
            return Math.sqrt(d33);
        } else {
            return Math.sqrt(d11 - d12 * d12 / d22);
        }
    }
    Geometry.distanceToLine = distanceToLine;

    /**
    * Check if two points are equal
    */
    function pointsEqual(p1, p2) {
        return p1.x === p2.x && p1.y === p2.y;
    }
    Geometry.pointsEqual = pointsEqual;

    /**
    * Check if two lines are equal
    */
    function linesEqual(l1, l2) {
        return pointsEqual(l1.start, l2.start) && pointsEqual(l1.end, l2.end);
    }
    Geometry.linesEqual = linesEqual;

    /**
    * Returns true if two lines share a common point
    * @param l1
    * @param l2
    */
    function hasCommonPoint(l1, l2) {
        return pointsEqual(l1.start, l2.start) || pointsEqual(l1.end, l2.end) || pointsEqual(l1.start, l2.end) || pointsEqual(l1.end, l2.start);
    }
    Geometry.hasCommonPoint = hasCommonPoint;

    /**
    * Find the intersection of two line segments
    */
    function intersect(l1, l2) {
        var x00 = l1.start.x;
        var v00 = l1.end.x - l1.start.x;
        var x01 = l1.start.y;
        var v01 = l1.end.y - l1.start.y;
        var x10 = l2.start.x;
        var v10 = l2.end.x - l2.start.x;
        var x11 = l2.start.y;
        var v11 = l2.end.y - l2.start.y;

        // Matrix equation for intersection:
        //
        //   ( v00   -v10 ) ( lam ) = ( x10 - x00 )
        //   ( v01   -v11 ) (  mu ) = ( x11 - x01 )
        //
        // so
        //
        //   ( lam ) = _________1_________ ( -v11   v10 ) ( x10 - x00 )
        //   (  mu ) = (v10 v01 - v00 v11) ( -v01   v00 ) ( x11 - x01 )
        //
        // with solution when 0 <= lam, mu <= 1 and determinant is non-zero.
        var det = v01 * v10 - v00 * v11;

        if (Math.abs(det) > 1e-4) {
            var lam = (v10 * (x11 - x01) - v11 * (x10 - x00)) / det;
            var mu = (v00 * (x11 - x01) - v01 * (x10 - x00)) / det;

            if (0 <= lam && lam <= 1 && 0 <= mu && mu <= 1) {
                return Maybe.Just({
                    x: x00 + lam * v00,
                    y: x01 + lam * v01
                });
            }
        }

        return Maybe.Nothing();
    }
    Geometry.intersect = intersect;

    /**
    * Returns the southern-most point from an array of points.
    */
    function findSouthPoint(points, trans) {
        var pt = { x: -Number.MAX_VALUE, y: -Number.MAX_VALUE };
        var ptTrans = { x: -Number.MAX_VALUE, y: -Number.MAX_VALUE };

        for (var i = 0; i < points.length; i++) {
            var ptTransCurrent = trans ? Transform.mapFromImage(points[i], trans) : points[i];

            if (ptTransCurrent.y > ptTrans.y) {
                pt = points[i];
                ptTrans = ptTransCurrent;
            }
        }

        return pt;
    }
    Geometry.findSouthPoint = findSouthPoint;

    function findWestPoint(points, trans) {
        var pt = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };
        var ptTrans = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };

        for (var i = 0; i < points.length; i++) {
            var ptTransCurrent = trans ? Transform.mapFromImage(points[i], trans) : points[i];

            if (ptTransCurrent.x < ptTrans.x) {
                pt = points[i];
                ptTrans = ptTransCurrent;
            }
        }

        return pt;
    }
    Geometry.findWestPoint = findWestPoint;

    /**
    * Returns the northern-most point from an array of points.
    */
    function findNorthPoint(points, trans) {
        var pt = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };
        var ptTrans = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };

        for (var i = 0; i < points.length; i++) {
            var ptTransCurrent = trans ? Transform.mapFromImage(points[i], trans) : points[i];

            if (ptTransCurrent.y < ptTrans.y) {
                pt = points[i];
                ptTrans = ptTransCurrent;
            }
        }

        return pt;
    }
    Geometry.findNorthPoint = findNorthPoint;

    /**
    * Returns the southern-most point from an array of lines.
    */
    function findSouthPointFromLines(lines, trans) {
        var pt = { x: -Number.MAX_VALUE, y: -Number.MAX_VALUE };
        var ptTrans = { x: -Number.MAX_VALUE, y: -Number.MAX_VALUE };

        for (var i = 0; i < lines.length; i++) {
            var lineTrans = Transform.mapFromImageL(lines[i], trans);

            if (lineTrans.start.y > ptTrans.y) {
                pt.x = lines[i].start.x;
                pt.y = lines[i].start.y;
                ptTrans = { x: lineTrans.start.x, y: lineTrans.start.y };
            }

            if (lineTrans.end.y > ptTrans.y) {
                pt.x = lines[i].end.x;
                pt.y = lines[i].end.y;
                ptTrans = { x: lineTrans.end.x, y: lineTrans.end.y };
            }
        }

        return pt;
    }
    Geometry.findSouthPointFromLines = findSouthPointFromLines;

    /**
    * Returns the northern-most point from an array of lines.
    */
    function findNorthPointFromLines(lines, trans) {
        var pt = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };
        var ptTrans = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };

        for (var i = 0; i < lines.length; i++) {
            var lineTrans = Transform.mapFromImageL(lines[i], trans);

            if (lineTrans.start.y < ptTrans.y) {
                pt.x = lines[i].start.x;
                pt.y = lines[i].start.y;
                ptTrans = { x: lineTrans.start.x, y: lineTrans.start.y };
            }

            if (lineTrans.end.y < ptTrans.y) {
                pt.x = lines[i].end.x;
                pt.y = lines[i].end.y;
                ptTrans = { x: lineTrans.end.x, y: lineTrans.end.y };
            }
        }

        return pt;
    }
    Geometry.findNorthPointFromLines = findNorthPointFromLines;

    /**
    * Find the midpoint of a line.
    * @param {Classes.Line} line
    * @returns {Classes.Point}
    */
    function midpoint(line) {
        return {
            x: (line.start.x + line.end.x) / 2.0,
            y: (line.start.y + line.end.y) / 2.0
        };
    }
    Geometry.midpoint = midpoint;

    /**
    * Find the vector orthogonal to a line.
    * @param {Classes.Line} line
    * @returns {Vectors.Vector2d}
    */
    function orthogonalVector(line) {
        var dx = line.start.x - line.end.x;
        var dy = line.start.y - line.end.y;
        var dist = Math.sqrt(dx * dx + dy * dy);

        return { x: -dy / dist, y: dx / dist };
    }
    Geometry.orthogonalVector = orthogonalVector;

    /**
    * Find the line segment orthogonal to a line.
    * @param {Classes.Line} line
    * @param {Classes.Point} intersection point of intersection
    * @param {number} lengthStart length between the start endpoint and intersection
    * @param {number} lengthEnd length between the end endpoint and intersection
    * @returns {Classes.Line}
    */
    function orthogonalLineSegment(line, intersection, lengthStart, lengthEnd) {
        var vec = Geometry.orthogonalVector(line);
        var xStart = intersection.x + lengthStart * vec.x;
        var yStart = intersection.y + lengthStart * vec.y;
        var xEnd = intersection.x - lengthEnd * vec.x;
        var yEnd = intersection.y - lengthEnd * vec.y;

        return { start: { x: xStart, y: yStart }, end: { x: xEnd, y: yEnd } };
    }
    Geometry.orthogonalLineSegment = orthogonalLineSegment;

    /**
    * Calculate point on line some distance from starting point
    * @param {Classes.Point} point the starting point
    * @param {Classes.Point} opposite the other endpoint
    * @param {number} distance
    * @returns {{x: number; y: number}}
    */
    function interploatedPointAtDistance(point, opposite, distance) {
        var ratio = distance / Geometry.lineLength({ start: point, end: opposite });
        return {
            x: ratio * opposite.x + (1.0 - ratio) * point.x,
            y: ratio * opposite.y + (1.0 - ratio) * point.y
        };
    }
    Geometry.interploatedPointAtDistance = interploatedPointAtDistance;

    /**
    * Determine binary position of point relative to line
    * @param {Classes.Line} line
    * @param {Classes.Point} point
    * @returns {boolean}
    */
    function onLeft(line, point) {
        return ((line.end.x - line.start.x) * (point.y - line.start.y) > (line.end.y - line.start.y) * (point.x - line.start.x));
    }
    Geometry.onLeft = onLeft;

    /**
    * Returns area of a polygon
    * @param points
    */
    function polygonArea(points) {
        var area = 0;
        var previous = points.length - 1;

        for (var ctr = 0; ctr < points.length; ctr += 1) {
            area += ((points[previous].x + points[ctr].x) * (points[previous].y - points[ctr].y));
            previous = ctr;
        }

        return Math.abs(area / 2.0);
    }
    Geometry.polygonArea = polygonArea;

    /**
    * Returns the bounding box of a polygon
    * @param points
    */
    function findBoundingBox(points) {
        var minX, maxX, minY, maxY;
        minX = maxX = points[0].x;
        minY = maxY = points[0].y;

        for (var ctr = 1; ctr < points.length; ctr += 1) {
            if (points[ctr].x < minX) {
                minX = points[ctr].x;
            } else if (points[ctr].x > maxX) {
                maxX = points[ctr].x;
            }

            if (points[ctr].y < minY) {
                minY = points[ctr].y;
            } else if (points[ctr].y > maxY) {
                maxY = points[ctr].y;
            }
        }

        return [
            { x: minX, y: minY },
            { x: maxX, y: maxY }
        ];
    }
    Geometry.findBoundingBox = findBoundingBox;

    function boundBoxesIntersect(box1, box2) {
        return (box1[0].x < box2[1].x && box1[1].x > box2[0].x && box1[0].y < box2[1].y && box1[1].y > box2[0].y);
    }
    Geometry.boundBoxesIntersect = boundBoxesIntersect;

    /**
    * Returns true if the specified point is inside the rectangle
    * @param points
    * @param point
    */
    function insideRectangle(points, point) {
        var p1 = points[0];
        var p2 = points[1];

        return (point.x >= Math.min(p1.x, p2.x)) && (point.x <= Math.max(p1.x, p2.x)) && (point.y >= Math.min(p1.y, p2.y)) && (point.y <= Math.max(p1.y, p2.y));
    }
    Geometry.insideRectangle = insideRectangle;

    /**
    * Returns true if the specified point is inside the polygon
    * @param points
    * @param p
    */
    function insidePolygon(points, p) {
        var inside = false;

        for (var i = 0, j = points.length - 1; i < points.length; j = i++) {
            var xi = points[i].x;
            var yi = points[i].y;
            var xj = points[j].x;
            var yj = points[j].y;

            var intersect = ((yi > p.y) != (yj > p.y)) && (p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi);

            if (intersect) {
                inside = !inside;
            }
        }

        return inside;
    }
    Geometry.insidePolygon = insidePolygon;

    /**
    * Creates a rectangular path shape from p1 to p2 with a specified width
    * @param p1
    * @param p2
    * @param size
    */
    function createRectanglePath(p1, p2, size) {
        if (p1.x === p2.x) {
            return [
                { x: p1.x - (size / 2.0), y: p1.y },
                { x: p1.x + (size / 2.0), y: p1.y },
                { x: p2.x + (size / 2.0), y: p2.y },
                { x: p2.x - (size / 2.0), y: p2.y }
            ];
        } else if (p1.y === p2.y) {
            return [
                { x: p1.x, y: p1.y - (size / 2.0) },
                { x: p1.x, y: p1.y + (size / 2.0) },
                { x: p2.x, y: p2.y + (size / 2.0) },
                { x: p2.x, y: p2.y - (size / 2.0) }
            ];
        } else {
            var m = (p1.x - p2.x) / (p2.y - p1.y);
            var dx = ((size / Math.sqrt(1 + (m * m))) * 0.5);
            var dy = m * dx;

            return [
                { x: p1.x - dx, y: p1.y - dy },
                { x: p1.x + dx, y: p1.y + dy },
                { x: p2.x + dx, y: p2.y + dy },
                { x: p2.x - dx, y: p2.y - dy }
            ];
        }
    }
    Geometry.createRectanglePath = createRectanglePath;
})(Geometry || (Geometry = {}));
var Transform;
(function (Transform) {
    /**
    * Convert an image transformation to the image-to-screen matrix, based on a target canvas size
    */
    function transformToMatrix(transform, dw, dh, sw, sh) {
        var scale = transform.scale * Math.min(dw / sw, dh / sh);

        var result = Vectors.translate(-sw / 2, -sh / 2);

        result = Vectors.composeM(Vectors.scaleM(scale), result);

        if (transform.flipped) {
            result = Vectors.composeM(transform.rotation % 2 === 0 ? Vectors.flipH() : Vectors.flipV(), result);
        }

        result = Vectors.composeM(Vectors.rotate(Math.PI / 2 * transform.rotation), result);

        result = Vectors.composeM(Vectors.translate(dw / 2, dh / 2), result);
        result = Vectors.composeM(Vectors.translate(transform.offsetX, transform.offsetY), result);

        return result;
    }
    Transform.transformToMatrix = transformToMatrix;

    /**
    * Convert an image transformation to the screen-to-image, based on a target canvas size
    */
    function transformToInverseMatrix(transform, dw, dh, sw, sh) {
        var scale = transform.scale * Math.min(dw / sw, dh / sh);

        var result = Vectors.translate(-transform.offsetX, -transform.offsetY);
        result = Vectors.composeM(Vectors.translate(-dw / 2, -dh / 2), result);

        result = Vectors.composeM(Vectors.rotate(-Math.PI / 2 * transform.rotation), result);

        if (transform.flipped) {
            result = Vectors.composeM(transform.rotation % 2 === 0 ? Vectors.flipH() : Vectors.flipV(), result);
        }

        result = Vectors.composeM(Vectors.scaleM(1 / scale), result);

        result = Vectors.composeM(Vectors.translate(sw / 2, sh / 2), result);

        return result;
    }
    Transform.transformToInverseMatrix = transformToInverseMatrix;

    /**
    * Map image coordinates to canvas coordinates
    */
    function mapFromImage(p, transform) {
        return Vectors.multiplyM(transform, p);
    }
    Transform.mapFromImage = mapFromImage;

    /**
    * Map a line in image coordinates to canvas coordinates
    */
    function mapFromImageL(l, transform) {
        return {
            start: Vectors.multiplyM(transform, l.start),
            end: Vectors.multiplyM(transform, l.end)
        };
    }
    Transform.mapFromImageL = mapFromImageL;

    /**
    * Modify a transformation to zoom around a point
    */
    function zoomRotateAround(instance, center, transform, newScale, newRotation, dw, dh) {
        var tempTransform = {
            offsetX: transform.offsetX,
            offsetY: transform.offsetY,
            scale: newScale,
            flipped: transform.flipped,
            rotation: newRotation
        };

        var instanceAttributes = instance.instanceAttributes;

        var inverseTransform = Transform.transformToInverseMatrix(transform, dw, dh, instanceAttributes.columns, instanceAttributes.rows);
        var originalMousePositionImage = Vectors.multiplyM(inverseTransform, center);

        var tempMatrix = Transform.transformToMatrix(tempTransform, dw, dh, instanceAttributes.columns, instanceAttributes.rows);

        var current = Vectors.multiplyM(tempMatrix, originalMousePositionImage);

        var newTransform = {
            offsetX: tempTransform.offsetX + center.x - current.x,
            offsetY: tempTransform.offsetY + center.y - current.y,
            scale: tempTransform.scale,
            flipped: tempTransform.flipped,
            rotation: tempTransform.rotation
        };

        return newTransform;
    }
    Transform.zoomRotateAround = zoomRotateAround;

    function getScaleX(transform) {
        return transform.entries[0];
    }
    Transform.getScaleX = getScaleX;

    function getScaleY(transform) {
        return transform.entries[4];
    }
    Transform.getScaleY = getScaleY;
})(Transform || (Transform = {}));
///<reference path="Observable.ts" />

/**
* Helper methods for working with images
*/
var Images;
(function (Images) {
    Images.useSimplePreloading = false;

    /**
    * Download an image
    *
    * @see Observable.Observable
    */
    function loadImageOnly(image, uri, cachedImage, accelerated) {
        return {
            subscribe: function (ob) {
                var token = new Observable.CancellationToken();
                var accCacheStatus = '';

                if (!cachedImage) {
                    cachedImage = {
                        imageElement: image,
                        imageData: null,
                        imageDownloadState: 1 /* Queued */
                    };
                }

                if (!LocalViewer.isLocalViewer()) {
                    image.crossOrigin = "anonymous";
                }

                image.addEventListener("load", function (_) {
                    // Hack around IE9 bug where image disappears after changing to HD/Diagnostic
                    if (Browser.isIE9()) {
                        setTimeout(function () {
                            ob.next({ acceleratorCached: accCacheStatus });
                            ob.done();
                        }, 100);
                    } else {
                        ob.next({ acceleratorCached: accCacheStatus });
                        ob.done();
                    }
                });

                // AJAX request for image
                if (window.URL && window.URL.createObjectURL && !LocalViewer.isLocalViewer() && !Images.useSimplePreloading) {
                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function () {
                        if (this.readyState == 4 && this.status == 200) {
                            if (accelerated) {
                                accCacheStatus = xhr.getResponseHeader('X-Acc-Cached') || '';
                            }
                            var url = window.URL || window.webkitURL;
                            cachedImage.imageDownloadState = 3 /* Success */;
                            image.src = url.createObjectURL(this.response);
                        } else if (this.readyState == 4 && this.status != 200) {
                            cachedImage.imageDownloadState = 4 /* Failed */;
                            if (!token.cancelled) {
                                // Redirect on unauthorized
                                if (this.status == 401) {
                                    window.location.assign("/");
                                }
                                ob.fail("Image GET failed for URI (status=" + this.status + ", dga=" + accelerated + ") " + uri);
                            }
                        }
                    };
                    xhr.open('GET', uri);
                    xhr.responseType = 'blob';
                    xhr.send();
                    cachedImage.imageDownloadState = 2 /* Started */;
                } else {
                    // For browsers that do not support window.URL
                    image.onerror = function (e) {
                        cachedImage.imageDownloadState = 4 /* Failed */;
                        ob.fail("Image GET failed for URI " + uri);
                    };

                    image.addEventListener("load", function (e) {
                        cachedImage.imageDownloadState = 3 /* Success */;
                    });

                    cachedImage.imageDownloadState = 2 /* Started */;
                    image.src = uri;
                }

                return Observable.memptySubscription;
            }
        };
    }
    Images.loadImageOnly = loadImageOnly;

    /**
    * Download an image and return its ImageData asynchronously as an Observable
    *
    * @see Observable.Observable
    */
    function loadImage(uri) {
        var image = new Image();

        return Observable.map(loadImageOnly(image, uri), function (_) {
            return getImageData(image);
        });
    }
    Images.loadImage = loadImage;

    /**
    * Get image data from an image element
    */
    function getImageData(image) {
        if (image.complete && typeof image.naturalWidth !== "undefined" && image.naturalWidth !== 0) {
            var canvas = createCanvas(image.width, image.height);
            var ctx = canvas.getContext("2d");
            ctx.drawImage(image, 0, 0);
            var imageData = ctx.getImageData(0, 0, image.width, image.height);
            image.onload = null;
            image = null;
            canvas = null;
            return imageData;
        }
        return null;
    }
    Images.getImageData = getImageData;

    /**
    * Draw an Image onto a canvas with the specified with and height
    */
    function drawImageData(ctx, data, nn) {
        if (typeof nn === "undefined") { nn = false; }
        var intermediateCanvas = Images.createCanvas(data.width, data.height);
        intermediateCanvas.getContext("2d").putImageData(data, 0, 0);

        if (nn) {
            ctx.imageSmoothingEnabled = false;
        }

        ctx.drawImage(intermediateCanvas, 0, 0);
    }
    Images.drawImageData = drawImageData;

    /**
    * Create a canvas for rendering with the specified with and height
    */
    function createCanvas(width, height) {
        var canvas = document.createElement("canvas");
        canvas.width = width;
        canvas.height = height;
        return canvas;
    }
    Images.createCanvas = createCanvas;
})(Images || (Images = {}));
/**
* Helper functions for working with grayscale softcopy presentation state (GSPS) data
*/
var GSPS;
(function (GSPS) {
    /**
    * Apply a set of presentation states to a study
    */
    function apply(presentationStates, study) {
        study.studyAttributes.presentationStateIODs = presentationStates;

        _.each(study.series, function (series) {
            _.each(series.instances, function (instance) {
                instance.instanceAttributes.presentationStates = (instance.instanceAttributes.presentationStates || []).concat(_.filter(presentationStates, function (presentationState) {
                    return isRelatedTo(presentationState, series, instance);
                }));
            });
        });
    }
    GSPS.apply = apply;

    /**
    * Check if a presentation state is related to an instance
    */
    function isRelatedTo(presentationState, series, instance) {
        var relatedByRelationshipModule = _.any(presentationState.relationshipModule.referencedSeriesSequence, function (referencedSeries) {
            return referencedSeries.referencedSeriesUID === series.seriesAttributes.seriesUid.value && _.any(referencedSeries.referencedImageSequence, function (referencedInstance) {
                return referencedInstance.referencedSOPInstanceUID === instance.id.value && ((referencedInstance.referencedFrameNumber === 0 && series.seriesAttributes.multiframe) || Math.max(0, referencedInstance.referencedFrameNumber - 1) === instance.frameNumber.value);
            });
        });

        if (relatedByRelationshipModule) {
            if (presentationState.graphicAnnotationModule.graphicAnnotationSequence) {
                var relatedByGraphicAnnotation = _.any(presentationState.graphicAnnotationModule.graphicAnnotationSequence, function (graphicAnnotation) {
                    if (graphicAnnotation.referencedImageSequence && graphicAnnotation.referencedImageSequence.length > 0) {
                        return _.any(graphicAnnotation.referencedImageSequence, function (referencedInstance) {
                            return referencedInstance.referencedSOPInstanceUID === instance.id.value && Math.max(0, referencedInstance.referencedFrameNumber - 1) === instance.frameNumber.value;
                        });
                    } else {
                        return true;
                    }
                });

                return relatedByGraphicAnnotation;
            } else {
                return true;
            }
        }

        return false;
    }
    GSPS.isRelatedTo = isRelatedTo;

    /**
    * Create a presentation state from a collection of annotations
    */
    function createGSPS(annotations, renderer, instanceUID, instance, showStdDev) {
        var now = new Date();

        return {
            identificationModule: {
                presentationCreationDate: now.toDicomDate(),
                presentationCreationTime: now.toDicomTime()
            },
            relationshipModule: {
                referencedSeriesSequence: [{
                        referencedSeriesUID: instance.seriesAttributes.seriesUid.value,
                        referencedImageSequence: [{
                                referencedFrameNumber: instance.frameNumber.value + 1,
                                referencedSOPClassUID: "",
                                referencedSOPInstanceUID: instance.id.value
                            }]
                    }]
            },
            graphicAnnotationModule: {
                graphicAnnotationSequence: [{
                        referencedImageSequence: [],
                        textObjectSequence: _.flatten(_.map(annotations, function (ann) {
                            return ann.toTextObjects(renderer, instanceUID, instance, showStdDev);
                        })),
                        graphicObjectSequence: _.flatten(_.map(annotations, function (ann) {
                            return ann.toGraphicObjects(instance);
                        }))
                    }]
            }
        };
    }
    GSPS.createGSPS = createGSPS;
})(GSPS || (GSPS = {}));
var CADSR;
(function (CADSR) {
    function apply(cadsrs, study) {
        var mappedFindings = {};

        if (cadsrs && cadsrs.length) {
            _.each(cadsrs, function (list) {
                _.each(list, function (cadsr) {
                    processCADSR(cadsr, mappedFindings);
                });
            });
        }

        _.each(study.series, function (series) {
            _.each(series.instances, function (instance) {
                if (mappedFindings[instance.id.value]) {
                    _.each(mappedFindings[instance.id.value], function (cadFindings) {
                        _.each(cadFindings.findings, function (finding) {
                            var numPoints = finding.outline.numberOfGraphicPoints;
                            var points = [];
                            for (var ctr = 0; ctr < numPoints; ctr += 1) {
                                points.push({ x: finding.outline.graphicData[(ctr * 2)], y: finding.outline.graphicData[(ctr * 2 + 1)] });
                            }

                            var color = cadFindings.color;
                            var score = finding.neuScore;
                            var center = { x: finding.center.graphicData[0], y: finding.center.graphicData[1] };
                            var density = cadFindings.glyph == "&#9679;";
                            var calcification = cadFindings.glyph == "&#9632;";
                            var summary = _.map(cadFindings.cadResultSummary.text, function (d) {
                                return d.unformattedTextValue;
                            });
                            instance.instanceAttributes.mesaurementsCADSR.push(new Measurements.CADSR(points, color, score, center, density, calcification, summary));
                        });
                    });
                }
            });
        });
    }
    CADSR.apply = apply;

    function processCADSR(cadsr, mappedFindings) {
        if (cadsr.cadFindings) {
            _.each(cadsr.cadFindings, function (findings) {
                if (findings.referencedImages && findings.referencedImages.length === 1) {
                    findings.cadResultSummary = cadsr.cadResultSummary;

                    if (!mappedFindings[findings.referencedImages[0].referencedSOPInstanceUID]) {
                        mappedFindings[findings.referencedImages[0].referencedSOPInstanceUID] = [];
                    }

                    mappedFindings[findings.referencedImages[0].referencedSOPInstanceUID].push(findings);
                }
            });
        }
    }
})(CADSR || (CADSR = {}));
/**
* Functions for creating popup windows
*/
var Popup;
(function (Popup) {
    /**
    * Create a popup window, returning a JQuery object which can be
    * used to populate it.
    */
    function createPopup() {
        var $body = $(document.body);

        var $popupScreen = $('<div>').addClass('popup-screen').appendTo($body);

        var $popup = $('<div>').addClass('popup').appendTo($popupScreen);

        $('<div>').addClass('close-icon').append($('<a>').attr({ href: '#' }).append($('<img>').attr({ src: '/static/images/affordances/close.png' })).click(function (e) {
            $popupScreen.remove();
            e.preventDefault();
        })).appendTo($popup);

        return $('<div>').addClass('popup-body').appendTo($popup);
    }
    Popup.createPopup = createPopup;
})(Popup || (Popup = {}));
///<reference path='../typings/underscore/underscore.d.ts' />
///<reference path='../typings/recorderjs/recorderjs.d.ts' />
///<reference path="Subject.ts" />

var HTML5AudioShim;
(function (HTML5AudioShim) {
    window.AudioContext = window.AudioContext || window.webkitAudioContext;
    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia;

    window.URL = window.URL || window.webkitURL;
})(HTML5AudioShim || (HTML5AudioShim = {}));

/**
* This module defines interfaces and classes for recording events to an event
* stream.
*/
var Recording;
(function (Recording) {
    Recording.MIME_TYPE = "application/x-recorded-viewer-session";

    var ScriptLink = (function () {
        function ScriptLink() {
        }
        return ScriptLink;
    })();
    Recording.ScriptLink = ScriptLink;

    

    /**
    * A recorder which ignores all events
    */
    var NullRecorder = (function () {
        function NullRecorder() {
        }
        NullRecorder.prototype.append = function (event) {
        };

        NullRecorder.prototype.getEvents = function () {
            return [];
        };

        NullRecorder.prototype.setZeroTimestamp = function (timestamp) {
        };
        return NullRecorder;
    })();
    Recording.NullRecorder = NullRecorder;

    /**
    * An implementation of Recorder which uses an array to store events
    */
    var ArrayScriptRecorder = (function () {
        function ArrayScriptRecorder() {
            this.events = [];
        }
        ArrayScriptRecorder.prototype.append = function (event) {
            var timestamp = new Date().getTime() - this.zeroTimestamp;
            this.events.push({ timestamp: timestamp, event: event });
        };

        ArrayScriptRecorder.prototype.appendWithTimestamp = function (event, timestamp) {
            this.events.push({ timestamp: timestamp, event: event });
        };

        ArrayScriptRecorder.prototype.getEvents = function () {
            return this.events.slice();
        };

        ArrayScriptRecorder.prototype.clear = function () {
            this.events.length = 0;
        };

        ArrayScriptRecorder.prototype.setZeroTimestamp = function (timestamp) {
            this.zeroTimestamp = timestamp;
        };
        return ArrayScriptRecorder;
    })();
    Recording.ArrayScriptRecorder = ArrayScriptRecorder;

    /**
    * Check compatibility
    */
    function isSupported() {
        return !!AudioContext;
    }
    Recording.isSupported = isSupported;

    /**
    * Read WAV data from a recorder object
    */
    function exportWaveAsBase64(audioRecorder) {
        return {
            subscribe: function (ob) {
                audioRecorder.exportWAV(function (blob) {
                    var reader = new FileReader();
                    reader.readAsDataURL(blob);
                    reader.onloadend = function () {
                        ob.next(reader.result);
                        ob.done();
                    };
                    reader.onerror = function () {
                        ob.fail(reader.error.toString());
                    };
                });

                return Observable.memptySubscription;
            }
        };
    }
    Recording.exportWaveAsBase64 = exportWaveAsBase64;

    /**
    * Start recording audio
    */
    function startAudioRecording(callback, onError) {
        try  {
            var context = new AudioContext();
        } catch (ex) {
            onError(ex);
        }

        navigator.getUserMedia({ audio: true }, function (stream) {
            var input = context.createMediaStreamSource(stream);

            var recorder = new Recorder(input, {
                workerPath: "resources/recorderWorker.js"
            });

            recorder.record();

            callback(recorder);
        }, onError);
    }
    Recording.startAudioRecording = startAudioRecording;

    /**
    * Play back a script
    */
    function play(script, runEvent, audio, reversePlayer, startTime) {
        if (typeof startTime === "undefined") { startTime = 0; }
        var events = _.sortBy(script, function (e) {
            return e.timestamp;
        });

        var listener = function () {
            var currentTime = audio.currentTime;
            var index = 0;

            while ((index < events.length) && (currentTime * 1000 >= events[index].timestamp)) {
                if (events[index].timestamp >= startTime * 1000) {
                    try  {
                        runEvent(events[index].event, events[index].timestamp);
                    } catch (ex) {
                        console.log("Error occurred during script playback: " + ex);
                    }
                }

                index++;
            }

            if (index > 0) {
                events = events.slice(index);
            }
        };

        if (reversePlayer.listener) {
            audio.removeEventListener("timeupdate", reversePlayer.listener);
        }

        reversePlayer.listener = listener;
        audio.addEventListener("timeupdate", listener);
    }
    Recording.play = play;
})(Recording || (Recording = {}));
var Compression;
(function (Compression) {
    var Base64 = (function () {
        function Base64() {
        }
        Base64.encode = function (bytes) {
            var i, len = bytes.length, base64 = "";

            for (i = 0; i < len; i += 3) {
                base64 += Base64.CHARS[bytes[i] >> 2];
                base64 += Base64.CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
                base64 += Base64.CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
                base64 += Base64.CHARS[bytes[i + 2] & 63];
            }

            if ((len % 3) === 2) {
                base64 = base64.substring(0, base64.length - 1) + "=";
            } else if (len % 3 === 1) {
                base64 = base64.substring(0, base64.length - 2) + "==";
            }

            return base64;
        };

        Base64.decode = function (base64) {
            var i;
            if (!Base64.LUT) {
                var lookup = new Uint8Array(256);
                for (i = 0; i < Base64.CHARS.length; i++) {
                    lookup[Base64.CHARS.charCodeAt(i)] = i;
                }
                Base64.LUT = lookup;
            }

            var bufferLength = base64.length * 0.75, len = base64.length, p = 0, encoded1, encoded2, encoded3, encoded4;

            if (base64[base64.length - 1] === "=") {
                bufferLength--;
                if (base64[base64.length - 2] === "=") {
                    bufferLength--;
                }
            }

            var arraybuffer = new ArrayBuffer(bufferLength), bytes = new Uint8Array(arraybuffer);

            for (i = 0; i < len; i += 4) {
                encoded1 = Base64.LUT[base64.charCodeAt(i)];
                encoded2 = Base64.LUT[base64.charCodeAt(i + 1)];
                encoded3 = Base64.LUT[base64.charCodeAt(i + 2)];
                encoded4 = Base64.LUT[base64.charCodeAt(i + 3)];

                bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
                bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
                bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
            }

            return new Uint8Array(arraybuffer);
        };
        Base64.CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
        return Base64;
    })();
    Compression.Base64 = Base64;

    var RLE = (function () {
        function RLE() {
        }
        // RLE implementation: https://github.com/sloosch/rle
        RLE.encodeBase64 = function (stream) {
            return Base64.encode(new Uint8Array(RLE.encode(stream).buffer));
        };

        RLE.decodeBase64 = function (stream) {
            return RLE.decode(new Uint32Array(Base64.decode(stream).buffer));
        };

        RLE.encode = function (stream) {
            var out = [0];
            var oidx = 0;
            var mcu = 0;
            var midx = 0;
            var symbol = false;
            for (var i = 0; i <= stream.length; i++) {
                if (i < stream.length && !!stream[i] === symbol) {
                    mcu++;
                } else {
                    for (var s = 0; s < 4; s++) {
                        var k = (mcu >> (s * 7)) & 0x7f;
                        out[oidx] |= k << (midx * 7 + 4);
                        if (++midx === 4) {
                            oidx++;
                            out[oidx] = 0;
                            midx = 0;
                        }

                        //overflow?
                        if (!(mcu > (1 << 7 * (s + 1)) - 1)) {
                            break;
                        } else {
                            out[oidx] |= 1 << midx;
                        }
                    }
                    mcu = 1;
                    symbol = !symbol;
                }
            }
            return new Uint32Array(out);
        };

        RLE.decode = function (stream) {
            var out = [];
            var mcu = 0;
            var symbol = true;
            var midx = 0;
            var oidx = 0;
            for (var i = 0; i < stream.length; i++) {
                for (var s = 0; s < 4; s++) {
                    var overflow = stream[i] & (1 << s);
                    if (!overflow) {
                        while (--mcu >= 0) {
                            out[oidx] = symbol;
                            oidx++;
                        }
                        mcu = 0;
                        symbol = !symbol;
                        midx = 0;
                    } else {
                        midx++;
                        if (midx === 4) {
                            throw new Error("Corrupted data.");
                        }
                    }
                    var k = (stream[i] >> (s * 7 + 4)) & 0x7f;
                    mcu |= k << (7 * midx);
                }
            }
            while (--mcu >= 0) {
                out[oidx] = symbol;
                oidx++;
            }
            return new Uint8Array(out);
        };
        return RLE;
    })();
    Compression.RLE = RLE;
})(Compression || (Compression = {}));
/**
* This module defines events which get recorded by the viewer application
*/
var Events;
(function (Events) {
    /**
    * Types of events which might be recorded
    */
    (function (EventType) {
        EventType[EventType["ImageTransformationChanged"] = 0] = "ImageTransformationChanged";
        EventType[EventType["WindowLevelChanged"] = 1] = "WindowLevelChanged";
        EventType[EventType["AnnotationAdded"] = 2] = "AnnotationAdded";
        EventType[EventType["MouseToolChanged"] = 3] = "MouseToolChanged";
        EventType[EventType["LayoutChanged"] = 4] = "LayoutChanged";
        EventType[EventType["SeriesChanged"] = 5] = "SeriesChanged";
        EventType[EventType["SelectedImageChanged"] = 6] = "SelectedImageChanged";
        EventType[EventType["MouseMove"] = 7] = "MouseMove";
        EventType[EventType["MouseDown"] = 8] = "MouseDown";
        EventType[EventType["InvertChanged"] = 9] = "InvertChanged";
        EventType[EventType["ToggleTextAnnotationsChanged"] = 10] = "ToggleTextAnnotationsChanged";
        EventType[EventType["ToggleMeasurementsChanged"] = 11] = "ToggleMeasurementsChanged";
        EventType[EventType["MagnifierPositionChanged"] = 12] = "MagnifierPositionChanged";
        EventType[EventType["ProbeToolChanged"] = 13] = "ProbeToolChanged";
        EventType[EventType["PlaneLocalizationChanged"] = 14] = "PlaneLocalizationChanged";
        EventType[EventType["EnhanceChanged"] = 15] = "EnhanceChanged";
        EventType[EventType["AnnotationDeleted"] = 16] = "AnnotationDeleted";
    })(Events.EventType || (Events.EventType = {}));
    var EventType = Events.EventType;

    

    
})(Events || (Events = {}));
///<reference path="Maybe.ts" />
var HangingProtocols;
(function (HangingProtocols) {
    /**
    * Return the first element of an array, or Nothing if the array is empty.
    */
    function head(arr) {
        if (arr.length > 0) {
            return Maybe.Just(arr[0]);
        }
        return Maybe.Nothing();
    }

    /**
    * Test if a string contains another
    */
    function contains(s, s1) {
        if (s) {
            return s.indexOf(s1) >= 0;
        } else {
            return false;
        }
    }

    /**
    * Compare one string to another
    */
    function compare(s, s1, filter) {
        if (filter == "equals") {
            return (s && (s == s1));
        } else if (filter == "not") {
            return !contains(s, s1);
        } else {
            return contains(s, s1);
        }
    }

    /**
    * Replicate a value N times in an array
    */
    function replicate(count, t) {
        var result = [];

        for (var i = 0; i < count; i++) {
            result[i] = t;
        }

        return result;
    }

    /**
    * Map a function over a list while keeping track of an accumulator
    */
    function mapAccumL(arr, acc, f) {
        var results = [];

        for (var i = 0; i < arr.length; i++) {
            var next = f(arr[i], acc);
            acc = next.acc;
            results.push(next.result);
        }

        return {
            acc: acc,
            results: results
        };
    }

    function modality(modality) {
        return {
            ctor: "HangingProtocols.ModalityFilter",
            value: modality
        };
    }
    HangingProtocols.modality = modality;

    function studyDescription(desc) {
        return {
            ctor: "HangingProtocols.StudyDescriptionFilter",
            value: desc
        };
    }
    HangingProtocols.studyDescription = studyDescription;

    function seriesDescription(desc) {
        return {
            ctor: "HangingProtocols.SeriesDescriptionFilter",
            stringFilter: desc
        };
    }
    HangingProtocols.seriesDescription = seriesDescription;

    function ImageTypeFilter(imageType) {
        return {
            ctor: "HangingProtocols.ImageTypeFilter",
            stringFilter: imageType
        };
    }
    HangingProtocols.ImageTypeFilter = ImageTypeFilter;

    function ViewPositionFilter(position) {
        return {
            ctor: "HangingProtocols.ViewPositionFilter",
            stringFilter: position
        };
    }
    HangingProtocols.ViewPositionFilter = ViewPositionFilter;

    function ImageLateralityFilter(laterality) {
        return {
            ctor: "HangingProtocols.ImageLateralityFilter",
            stringFilter: laterality
        };
    }
    HangingProtocols.ImageLateralityFilter = ImageLateralityFilter;

    function MultiframeFilter(isMultiframe) {
        return {
            ctor: "HangingProtocols.MultiframeFilter",
            boolFilter: isMultiframe
        };
    }
    HangingProtocols.MultiframeFilter = MultiframeFilter;

    function ultrasound(filter) {
        return {
            ctor: "HangingProtocols.USFilter",
            usFilter: filter
        };
    }
    HangingProtocols.ultrasound = ultrasound;

    function D(terms) {
        return {
            terms: terms
        };
    }
    HangingProtocols.D = D;

    function CNF(terms) {
        return {
            terms: terms
        };
    }
    HangingProtocols.CNF = CNF;

    HangingProtocols.defaultDisplayOptions = {
        flipped: false,
        rotation: 0,
        invert: false,
        showTextAnnotations: true,
        showMeasurements: true,
        windowLevel: Maybe.Nothing()
    };

    /**
    * Select studies for display sets from a collection of image sets based on the
    * image set filters associated to each display set.
    */
    function selectDisplaySets(displaySets, series) {
        var result = [];

        for (var i = 0; i < displaySets.length; i++) {
            var displaySet = displaySets[i];

            var displaySetResult = Maybe.fmap(head(series[displaySet.imageSetIndex]), function (series) {
                return {
                    series: series,
                    displayOptions: displaySet.displayOptions
                };
            });

            series[displaySet.imageSetIndex] = series[displaySet.imageSetIndex].slice(1);
            result.push(displaySetResult);
        }

        return {
            result: result,
            acc: series
        };
    }
    HangingProtocols.selectDisplaySets = selectDisplaySets;

    /**
    * A CNF term which always returns true
    */
    function cnfTrue() {
        return CNF([]);
    }
    HangingProtocols.cnfTrue = cnfTrue;

    /**
    * Create a default hanging protocol which hangs a study on a single screen
    */
    function defaultProtocol(rows, cols, frameRate) {
        return {
            name: "Default Protocol",
            version: HangingProtocols.CURRENT_HP_VERSION,
            studyFilters: cnfTrue(),
            imageSets: [{
                    name: "Default Image Set",
                    seriesFilters: cnfTrue()
                }],
            screens: [{
                    name: "Default Screen",
                    rows: rows,
                    cols: cols,
                    displaySets: replicate(rows * cols, {
                        name: "Default Display Set",
                        imageSetIndex: 0,
                        displayOptions: HangingProtocols.defaultDisplayOptions
                    })
                }],
            cineOptions: {
                defaultCineFrameRate: frameRate,
                ignoreFrameTimeTag: false,
                startCinePlaybackAutomatically: false
            }
        };
    }
    HangingProtocols.defaultProtocol = defaultProtocol;

    /**
    * Evaluate a CNF term
    */
    function cnf(apply, cnf, value) {
        // Return true if an empty array is passed (equivalent of cnfTrue) or there is a term match
        return _.all(cnf.terms, function (d) {
            return d.terms.length == 0 || _.any(d.terms, function (term) {
                return apply(term, value);
            });
        });
    }
    HangingProtocols.cnf = cnf;

    function cnfNonEmpty(apply, cnf, value) {
        return _.all(cnf.terms, function (d) {
            return _.any(d.terms, function (term) {
                return apply(term, value);
            });
        });
    }
    HangingProtocols.cnfNonEmpty = cnfNonEmpty;

    function matchesFilterType(filter, filterType) {
        return (filter.ctor === filterType);
    }
    HangingProtocols.matchesFilterType = matchesFilterType;

    function seriesFilterOther(filter) {
        return filter.terms && filter.terms.length && cnfNonEmpty(matchesFilterType, filter, "HangingProtocols.Other");
    }
    HangingProtocols.seriesFilterOther = seriesFilterOther;

    /**
    * Apply a single study filter to a study
    */
    function applyStudyFilter(filter, study) {
        if (filter.ctor === "HangingProtocols.ModalityFilter") {
            return _.contains(study.studyModalities, filter.value);
        } else if (filter.ctor === "HangingProtocols.StudyDescriptionFilter") {
            return contains(study.studyDescription, filter.value);
        } else {
            throw "Unknown study filter type";
        }
    }
    HangingProtocols.applyStudyFilter = applyStudyFilter;

    /**
    * Filter studies based on a CNF term of study filters
    */
    function filterStudy(filter, study) {
        return cnf(applyStudyFilter, filter, study);
    }
    HangingProtocols.filterStudy = filterStudy;

    /**
    * Apply a single series filter to a series
    */
    function applySeriesFilter(filter, series) {
        if (filter.ctor === "HangingProtocols.SeriesDescriptionFilter") {
            return compare(series.seriesDescription, filter.stringFilter, filter.filter);
        } else if (filter.ctor === "HangingProtocols.ImageTypeFilter") {
            return _.contains(series.seriesImageType, filter.stringFilter);
        } else if (filter.ctor === "HangingProtocols.ViewPositionFilter") {
            return compare(series.seriesViewPosition, filter.stringFilter, filter.filter);
        } else if (filter.ctor === "HangingProtocols.ImageLateralityFilter") {
            return compare(series.seriesImageLaterality, filter.stringFilter, filter.filter);
        } else if (filter.ctor === "HangingProtocols.MultiframeFilter") {
            return series.seriesMultiframe === filter.boolFilter;
        } else if (filter.ctor === "HangingProtocols.USFilter") {
            if (series.seriesImageType.length >= 4) {
                if (!isNaN(series.seriesImageType[4])) {
                    var code = parseInt(series.seriesImageType[4], 16);
                    var filterCode = filter.usFilter;
                    return (filterCode & code) === filterCode;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        } else if (filter.ctor === "HangingProtocols.Other") {
            if (series.seriesJSON) {
                var currentTags = series.seriesJSON.tags;
                var tags = filter.tag.split('->');
                var foundTag = Dicom.findTag(currentTags, tags);

                if (foundTag) {
                    return compare(foundTag.value, filter.stringFilter, filter.filter);
                }
            }

            return false;
        } else {
            throw "Unknown series filter type";
        }
    }
    HangingProtocols.applySeriesFilter = applySeriesFilter;

    /**
    * Filter series based on a CNF term of series filters
    */
    function filterSeries(filter, series) {
        return cnf(applySeriesFilter, filter, series);
    }
    HangingProtocols.filterSeries = filterSeries;

    /**
    * Apply a hanging protocol to an array of series, returning one HangingResult for each screen which
    * should be hanged.
    */
    function applyProtocol(hangingProtocol, series) {
        var imageSetSeries = _.map(hangingProtocol.imageSets, function (imageSet) {
            return _.filter(series, function (s) {
                return filterSeries(imageSet.seriesFilters, s);
            });
        });

        var results = mapAccumL(hangingProtocol.screens, imageSetSeries, function (screen, series) {
            return selectDisplaySets(screen.displaySets, series);
        });

        return {
            matches: results.results,
            rest: _.uniq(_.flatten(results.acc))
        };
    }
    HangingProtocols.applyProtocol = applyProtocol;
})(HangingProtocols || (HangingProtocols = {}));
/// <reference path='Geometry.ts' />
/**
* Helper methods for working with Canvas.
*
* TODO: remove hardcoded font names
*
* TODO: Remove the dependency on Models.Instance
*/
var Rendering;
(function (Rendering) {
    /**
    * Compute the height of text which fits the text to a maximum width
    */
    function fitTextToWidth(context, text, desiredWidth, maxHeight, unit) {
        if (typeof unit === "undefined") { unit = 'px'; }
        var lo = 0;
        var hi = maxHeight;

        while (hi - lo > 1) {
            var mid = (hi + lo) / 2;
            context.font = mid + unit + " FuturaTL-Book";
            var width = context.measureText(text).width;

            if (width > desiredWidth) {
                hi = mid;
            } else {
                lo = mid;
            }
        }

        return lo;
    }
    Rendering.fitTextToWidth = fitTextToWidth;

    function writeLineImpl(context, text, getLeft, top, height, color, unit) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        if (typeof unit === "undefined") { unit = 'px'; }
        if (text) {
            context.font = height + unit + " FuturaTL-Book";
            context.fillStyle = '#000000';
            var left = getLeft();
            context.fillText(text, left + 1, top + height + 1);
            context.fillStyle = color;
            context.fillText(text, left, top + height);
        }
    }

    function writeLineToFit(context, text, left, top, width, height, color) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        // height of font in px is equal to pt size in canvas
        var height = fitTextToWidth(context, text, width, height, 'pt');
        writeLineImpl(context, text, function () {
            return left;
        }, top - height, height, color, 'pt');
    }
    Rendering.writeLineToFit = writeLineToFit;

    /**
    * Write a line of text with a point
    */
    function writeLine(context, text, left, top, height, color) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        writeLineImpl(context, text, function () {
            return left;
        }, top, height, color);
    }
    Rendering.writeLine = writeLine;

    /**
    * Write a right-aligned line of text with a shadow
    */
    function writeLineRight(context, text, right, top, h) {
        writeLine(context, text, right - context.measureText(text).width, top, h);
    }
    Rendering.writeLineRight = writeLineRight;

    /**
    * Write a centered line of text with a shadow
    */
    function writeLineCentered(context, text, center, top, h, color) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        writeLineImpl(context, text, function () {
            return center - context.measureText(text).width / 2;
        }, top, h, color);
    }
    Rendering.writeLineCentered = writeLineCentered;

    /**
    * Write multiple lines of text with a point
    */
    function writeLines(context, lines, left, top, height, color) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        _.each(_.compact(lines), function (line) {
            writeLine(context, line, left, top, height, color);
            top += height;
        });
    }
    Rendering.writeLines = writeLines;

    /**
    * Write multiple lines of text, aligned to the top-left, skipping lines that do not fit.
    */
    function writeLinesDynamic(context, lines, left, top, height, totalHeight, color) {
        if (typeof color === "undefined") { color = '#ffffff'; }
        _.each(_.compact(lines), function (line) {
            if ((top + height) < totalHeight) {
                writeLine(context, line, left, top, height, color);
                top += height;
            }
        });
    }
    Rendering.writeLinesDynamic = writeLinesDynamic;

    /**
    * Write multiple lines of text with a shadow, aligned to the bottom-left.
    */
    function writeLinesBottomLeft(context, lines, left, bottom, height) {
        _.each(_.compact(lines).reverse(), function (line) {
            writeLine(context, line, left, bottom - height, height);
            bottom -= height;
        });
    }
    Rendering.writeLinesBottomLeft = writeLinesBottomLeft;

    /**
    * Write multiple lines of text, aligned to the bottom-left, skipping lines that do not fit.
    */
    function writeLinesBottomLeftDynamic(context, lines, left, bottom, height, totalHeight) {
        var linesAllowed = Math.min(lines.length, Math.floor(totalHeight / height));
        lines = lines.slice(0, linesAllowed);

        _.each(_.compact(lines).reverse(), function (line) {
            writeLine(context, line, left, bottom - height, height);
            bottom -= height;
        });
    }
    Rendering.writeLinesBottomLeftDynamic = writeLinesBottomLeftDynamic;

    /**
    * Write multiple lines of text with a point, aligned to the bottom-right.
    */
    function writeLinesBottomRight(context, lines, right, bottom, height) {
        _.each(_.compact(lines).reverse(), function (line) {
            writeLine(context, line, right - context.measureText(line).width, bottom - height, height);
            bottom -= height;
        });
    }
    Rendering.writeLinesBottomRight = writeLinesBottomRight;

    /**
    * Write multiple lines of text, aligned to the bottom-right, skipping lines that do not fit.
    */
    function writeLinesBottomRightDynamic(context, lines, right, bottom, height, totalHeight) {
        var linesAllowed = Math.min(lines.length, Math.floor(totalHeight / height));
        lines = lines.slice(0, linesAllowed);

        _.each(_.compact(lines).reverse(), function (line) {
            writeLine(context, line, right - context.measureText(line).width, bottom - height, height);
            bottom -= height;
        });
    }
    Rendering.writeLinesBottomRightDynamic = writeLinesBottomRightDynamic;

    /**
    * Write multiple lines of text with a point, aligned to the top-right.
    */
    function writeLinesTopRight(context, lines, right, top, height) {
        _.each(_.compact(lines), function (line) {
            writeLine(context, line, right - context.measureText(line).width, top, height);
            top += height;
        });
    }
    Rendering.writeLinesTopRight = writeLinesTopRight;

    /**
    * Write multiple lines of text, aligned to the top-right, skipping lines that do not fit.
    */
    function writeLinesTopRightDynamic(context, lines, right, top, height, totalHeight) {
        _.each(_.compact(lines), function (line) {
            if ((top + height) < totalHeight) {
                writeLine(context, line, right - context.measureText(line).width, top, height);
                top += height;
            }
        });
    }
    Rendering.writeLinesTopRightDynamic = writeLinesTopRightDynamic;

    /**
    * Write multiple lines of text with a point, aligned to the center.
    */
    function writeLinesCentered(context, lines, center, top, height) {
        _.each(_.compact(lines), function (line) {
            writeLineImpl(context, line, function () {
                return center - context.measureText(line).width / 2;
            }, top, height);
            top += height;
        });
    }
    Rendering.writeLinesCentered = writeLinesCentered;

    /**
    * Draw a line with a shadow, and a length annotation
    */
    function drawLine(context, line, style, lineWidth, directed) {
        if (typeof directed === "undefined") { directed = false; }
        context.lineWidth = lineWidth;

        context.save();

        context.shadowOffsetX = 1;
        context.shadowOffsetY = 1;
        context.shadowColor = '#000000';

        context.beginPath();
        context.strokeStyle = style;
        context.moveTo(line.start.x, line.start.y);
        context.lineTo(line.end.x, line.end.y);

        context.stroke();

        if (directed) {
            context.fillStyle = style;

            var angle = Math.PI / 6;

            var sin = Math.sin(angle);
            var cos = Math.cos(angle);

            var dx = line.start.x - line.end.x;
            var dy = line.start.y - line.end.y;

            var len = Math.sqrt(dx * dx + dy * dy);

            var x1 = line.end.x + (dx * cos - dy * sin) / len * 10.0;
            var y1 = line.end.y + (dx * sin + dy * cos) / len * 10.0;
            var x2 = line.end.x + (dy * sin + dx * cos) / len * 10.0;
            var y2 = line.end.y + (dy * cos - dx * sin) / len * 10.0;

            context.moveTo(line.end.x, line.end.y);
            context.lineTo(x1, y1);
            context.lineTo(x2, y2);
            context.closePath();

            context.fill();
        }

        context.restore();
    }
    Rendering.drawLine = drawLine;

    /**
    * Stroke a rectangle measurement
    */
    function drawRectangleMeasurement(context, p1, p2, style, lineWidth, filled, stippled) {
        context.lineWidth = lineWidth;

        if (stippled && context.setLineDash) {
            context.setLineDash([5, 3]);
        }

        context.beginPath();
        context.strokeStyle = context.fillStyle = '#000000';
        context.rect(p1.x + 1, p1.y + 1, p2.x - p1.x, p2.y - p1.y);
        filled ? context.fill() : context.stroke();

        context.beginPath();
        context.strokeStyle = context.fillStyle = style;
        context.rect(p1.x + 1, p1.y + 1, p2.x - p1.x, p2.y - p1.y);
        filled ? context.fill() : context.stroke();

        if (stippled && context.setLineDash) {
            context.setLineDash([]);
        }
    }
    Rendering.drawRectangleMeasurement = drawRectangleMeasurement;

    /**
    * Stroke an ellipse measurement
    */
    function drawEllipseMeasurement(context, p1, p2, style, lineWidth, detail, filled, shadow) {
        if (typeof detail === "undefined") { detail = 100; }
        if (typeof filled === "undefined") { filled = false; }
        if (typeof shadow === "undefined") { shadow = true; }
        var points = Geometry.pointsForEllipse(p1, p2, detail);

        drawPathMeasurement(context, points, style, lineWidth, filled, shadow);
    }
    Rendering.drawEllipseMeasurement = drawEllipseMeasurement;

    /**
    * Stroke an ellipse measurement
    */
    function drawCircle(context, c, r, style, lineWidth, filled) {
        if (typeof filled === "undefined") { filled = false; }
        context.save();

        context.shadowColor = "#000000";
        context.shadowOffsetX = context.shadowOffsetY = 1;

        context.lineWidth = lineWidth;

        context.beginPath();
        context.strokeStyle = context.fillStyle = style;
        context.arc(c.x, c.y, r, 0, Math.PI * 2);
        filled ? context.fill() : context.stroke();

        context.restore();
    }
    Rendering.drawCircle = drawCircle;

    function fillCircle(context, c, r, style) {
        context.save();
        context.beginPath();
        context.fillStyle = style;
        context.arc(c.x, c.y, r, 0, Math.PI * 2);
        context.fill();
        context.restore();
    }
    Rendering.fillCircle = fillCircle;

    /**
    * Stroke an ellipse measurement from axes
    */
    function drawEllipseMeasurementFromAxes(context, major, minor, style, lineWidth, detail, filled, shadow) {
        if (typeof detail === "undefined") { detail = 100; }
        if (typeof filled === "undefined") { filled = false; }
        if (typeof shadow === "undefined") { shadow = true; }
        var points = Geometry.pointsForEllipseFromAxes(major, minor, detail);

        drawPathMeasurement(context, points, style, lineWidth, filled, shadow);
    }
    Rendering.drawEllipseMeasurementFromAxes = drawEllipseMeasurementFromAxes;

    /**
    * Draw an interpolated path
    */
    function drawInterpolatedPath(context, data, style, lineWidth, detail, closed, filled, shadow) {
        if (typeof detail === "undefined") { detail = 100; }
        if (typeof closed === "undefined") { closed = false; }
        if (typeof filled === "undefined") { filled = false; }
        if (typeof shadow === "undefined") { shadow = true; }
        var points = Geometry.pointsForInterpolatedPath(data, detail, closed);

        drawPathMeasurement(context, points, style, lineWidth, filled, shadow);
    }
    Rendering.drawInterpolatedPath = drawInterpolatedPath;

    /**
    * Fill and stroke a rectangle
    */
    function drawRectangle(context, p, style, size) {
        context.beginPath();

        context.rect(p.x - size, p.y - size, size * 2, size * 2);
        context.fillStyle = style;
        context.fill();

        context.lineWidth = 1;
        context.strokeStyle = '#000000';
        context.stroke();
    }
    Rendering.drawRectangle = drawRectangle;

    function fillRectangle(context, p, style, size) {
        context.beginPath();
        context.rect(p.x - size, p.y - size, size * 2, size * 2);
        context.fillStyle = style;
        context.fill();
    }
    Rendering.fillRectangle = fillRectangle;

    /**
    * Stroke a rectangle
    */
    function drawRectangleBorder(context, x, y, w, h, color, stippled) {
        if (typeof stippled === "undefined") { stippled = false; }
        if (stippled && context.setLineDash) {
            context.setLineDash([5, 3]);
        }

        context.beginPath();
        context.rect(x, y, w, h);
        context.lineWidth = 1;
        context.strokeStyle = color;
        context.stroke();

        if (stippled && context.setLineDash) {
            context.setLineDash([]);
        }
    }
    Rendering.drawRectangleBorder = drawRectangleBorder;

    function fillRectangleBounds(context, x, y, w, h, color) {
        context.beginPath();
        context.rect(x, y, w, h);
        context.lineWidth = 1;
        context.fillStyle = color;
        context.fill();
    }
    Rendering.fillRectangleBounds = fillRectangleBounds;

    /**
    * Draw text at the center of a line
    */
    function drawTextOnLine(context, line, text) {
        var center = { x: (line.start.x + line.end.x) / 2 + 10, y: (line.start.y + line.end.y) / 2 };
        var lines = text.split(/\r?\n/);
        writeLinesCentered(context, lines, center.x, center.y, 12);
    }
    Rendering.drawTextOnLine = drawTextOnLine;

    /**
    * Draw a path
    */
    function drawPath(context, points, style, offsetX, offsetY, lineWidth, close) {
        if (points.length > 0) {
            context.lineWidth = lineWidth;
            context.strokeStyle = style;

            var start = points[0];

            context.beginPath();
            context.moveTo(start.x + offsetX, start.y + offsetY);

            for (var i = 1; i < points.length; i++) {
                var next = points[i];
                context.lineTo(next.x + offsetX, next.y + offsetY);
            }

            if (close) {
                var next = points[0];
                context.lineTo(next.x + offsetX, next.y + offsetY);
            }

            context.stroke();
        }
    }
    Rendering.drawPath = drawPath;

    /**
    * Fill a path
    */
    function fillPath(context, points, style, close) {
        if (points.length > 0) {
            context.fillStyle = style;

            var start = points[0];

            context.beginPath();
            context.moveTo(start.x, start.y);

            for (var i = 1; i < points.length; i++) {
                var next = points[i];
                context.lineTo(next.x, next.y);
            }

            if (close) {
                var next = points[0];
                context.lineTo(next.x, next.y);
            }

            context.fill();
        }
    }
    Rendering.fillPath = fillPath;

    /**
    * Draw a path with a shadow
    */
    function drawPathMeasurement(context, points, style, lineWidth, filled, shadow) {
        if (typeof shadow === "undefined") { shadow = true; }
        if (filled) {
            fillPath(context, points, style);
        } else {
            if (shadow) {
                drawPath(context, points, '#000000', 0.5, 0.5, lineWidth);
            }

            drawPath(context, points, style, 0, 0, lineWidth);
        }
    }
    Rendering.drawPathMeasurement = drawPathMeasurement;

    /**
    * Draw a cross at the specified point
    */
    function drawCross(context, color, point) {
        context.lineWidth = 1;

        context.beginPath();
        context.strokeStyle = '#000000';
        context.moveTo(point.x - 5 + 1, point.y + 1);
        context.lineTo(point.x + 5 + 1, point.y + 1);
        context.moveTo(point.x + 1, point.y - 5 + 1);
        context.lineTo(point.x + 1, point.y + 5 + 1);
        context.stroke();

        context.beginPath();
        context.strokeStyle = color;
        context.moveTo(point.x - 5, point.y);
        context.lineTo(point.x + 5, point.y);
        context.moveTo(point.x, point.y - 5);
        context.lineTo(point.x, point.y + 5);
        context.stroke();
    }
    Rendering.drawCross = drawCross;

    /**
    * Draw a crosshair, with open center.
    */
    function drawCrosshair(context, c, r, style, lineWidth) {
        var innerOffset = r - 7;
        var outerOffset = r;

        context.save();

        context.lineWidth = lineWidth;

        context.shadowOffsetX = 1;
        context.shadowOffsetY = 1;
        context.shadowColor = '#000000';

        context.beginPath();
        context.strokeStyle = style;

        context.moveTo(c.x - outerOffset, c.y);
        context.lineTo(c.x - innerOffset, c.y);

        context.moveTo(c.x + outerOffset, c.y);
        context.lineTo(c.x + innerOffset, c.y);

        context.moveTo(c.x, c.y - outerOffset);
        context.lineTo(c.x, c.y - innerOffset);

        context.moveTo(c.x, c.y + outerOffset);
        context.lineTo(c.x, c.y + innerOffset);

        context.stroke();

        context.restore();
    }
    Rendering.drawCrosshair = drawCrosshair;

    function drawPaintElement(context, c, r, style) {
        context.save();
        context.lineWidth = 2.0;
        context.beginPath();
        context.strokeStyle = context.fillStyle = style;
        context.arc(c.x, c.y, r, 0, Math.PI * 2);
        context.stroke();
        context.restore();
    }
    Rendering.drawPaintElement = drawPaintElement;
})(Rendering || (Rendering = {}));
var WebGL;
(function (WebGL) {
    /**
    * Create a WebGL rendering context
    */
    function createContext(canvas) {
        return canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
    }
    WebGL.createContext = createContext;

    /**
    * Initialize shaders for a rendering context
    */
    function initShaders(shaders, gl) {
        var fragmentShader = compileShader(gl, shaders.fragment, gl.FRAGMENT_SHADER);
        var vertexShader = compileShader(gl, shaders.vertex, gl.VERTEX_SHADER);

        if (fragmentShader && vertexShader) {
            var program = gl.createProgram();

            gl.attachShader(program, vertexShader);
            gl.attachShader(program, fragmentShader);

            gl.linkProgram(program);

            if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
                throw new Error("Unable to link shader program");
            }

            gl.useProgram(program);

            return program;
        }

        throw new Error("Shader was null in initShaders");
    }
    WebGL.initShaders = initShaders;

    /**
    * Compile a shader from source
    */
    function compileShader(gl, glsl, type) {
        var shader = gl.createShader(type);

        gl.shaderSource(shader, glsl);

        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.log("Unable to compile shader: " + gl.getShaderInfoLog(shader));
            throw new Error("Unable to compile shader");
        }

        return shader;
    }
    WebGL.compileShader = compileShader;

    /**
    * Create buffers and a texture object
    */
    function setupBuffersAndCreateTexture(gl, program) {
        var positionLocation = gl.getAttribLocation(program, "aPosition");

        var buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 1.0,
            1.0, 1.0,
            0.0, 0.0,
            0.0, 0.0,
            1.0, 1.0,
            1.0, 0.0
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(positionLocation);
        gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

        var texCoordLocation = gl.getAttribLocation(program, "aTexCoord");

        var texCoordBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
            0.0, 1.0,
            1.0, 1.0,
            0.0, 0.0,
            0.0, 0.0,
            1.0, 1.0,
            1.0, 0.0
        ]), gl.STATIC_DRAW);
        gl.enableVertexAttribArray(texCoordLocation);
        gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

        var texture = gl.createTexture();

        gl.bindTexture(gl.TEXTURE_2D, texture);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

        return texture;
    }
    WebGL.setupBuffersAndCreateTexture = setupBuffersAndCreateTexture;

    function createPaletteTexture(gl) {
        gl.activeTexture(gl.TEXTURE1);
        gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    }
    WebGL.createPaletteTexture = createPaletteTexture;
})(WebGL || (WebGL = {}));
/// <reference path='Subject.ts' />
/**
* Supporting classes for working with studies across multiple windows
*/
var MultiMonitor;
(function (MultiMonitor) {
    /**
    * Check if the Local Storage API is supported
    */
    function isLocalStorageSupported() {
        try  {
            return "localStorage" in window && window.localStorage !== null && window.localStorage !== undefined;
        } catch (e) {
            return false;
        }
    }
    MultiMonitor.isLocalStorageSupported = isLocalStorageSupported;

    /**
    * A Subject whose value is shared between windows with the same owner ID
    */
    var SharedSubject = (function () {
        function SharedSubject(key, owner, subject) {
            var _this = this;
            this.key = key;
            this.owner = owner;
            this.subject = subject;
            this.updates = subject.updates;

            if (MultiMonitor.isLocalStorageSupported()) {
                window.addEventListener("storage", function (e) {
                    return _this.handleStorageEvent(e);
                }, false);
            }
        }
        SharedSubject.prototype.handleStorageEvent = function (e) {
            try  {
                if (e.key === "viewerEvent") {
                    var value = JSON.parse(e.newValue);

                    if (value.key === this.key && value.owner === this.owner.value) {
                        this.subject.write(value.value);
                    }
                }
            } catch (ex) {
            }
        };

        SharedSubject.prototype.read = function () {
            return this.subject.read();
        };

        SharedSubject.prototype.write = function (t) {
            this.subject.write(t);

            if (MultiMonitor.isLocalStorageSupported()) {
                window.localStorage.setItem("viewerEvent", JSON.stringify({
                    owner: this.owner.value,
                    key: this.key,
                    value: t
                }));
            }
        };
        return SharedSubject;
    })();
    MultiMonitor.SharedSubject = SharedSubject;
})(MultiMonitor || (MultiMonitor = {}));
/**
* Helper functions for working with text annotation customizations
*/
var TextAnnotations;
(function (TextAnnotations) {
    function def(text, dflt) {
        return text ? text : dflt;
    }

    function maybe(t, f, dflt) {
        return t ? f(t) : dflt;
    }

    

    

    var Subtraction = (function () {
        function Subtraction() {
        }
        Subtraction.prototype.render = function (data) {
            if (data.series.subtraction) {
                var isMask = data.series.subtraction.isMask(data.instance, data.series.series);
                var loaded = data.series.subtraction.allMasksLoaded(data.series);
                var enabled = data.series.subtractionActive.read();
                var supports = data.series.supportsSubtraction();

                return enabled ? "{Subtraction}: {0}".replace("{Subtraction}", data.terminology.lookup(Terminology.Terms.Subtraction)).replace("{0}", isMask && supports ? "Mask" : (loaded && supports ? "Applied" : "None")) : "";
            } else {
                return null;
            }
        };
        return Subtraction;
    })();

    var CureMetrix = (function () {
        function CureMetrix() {
        }
        CureMetrix.prototype.render = function (data) {
            if (data.instance.instanceAttributes.mesaurementsCADSR.length) {
                return "CureMetrix";
            }

            return null;
        };
        return CureMetrix;
    })();

    var CADSRDensityFindings = (function () {
        function CADSRDensityFindings() {
        }
        CADSRDensityFindings.prototype.render = function (data) {
            if (data.instance.instanceAttributes.mesaurementsCADSR.length) {
                var found = _.filter(data.instance.instanceAttributes.mesaurementsCADSR, function (c) {
                    return c.density;
                });
                return "Density Findings: {0}".replace("{0}", found.length.toString());
            }

            return null;
        };
        return CADSRDensityFindings;
    })();

    var CADSRCalcificationFindings = (function () {
        function CADSRCalcificationFindings() {
        }
        CADSRCalcificationFindings.prototype.render = function (data) {
            if (data.instance.instanceAttributes.mesaurementsCADSR.length) {
                var found = _.filter(data.instance.instanceAttributes.mesaurementsCADSR, function (c) {
                    return c.calcification;
                });
                return "Calc Findings: {0}".replace("{0}", found.length.toString());
            }

            return null;
        };
        return CADSRCalcificationFindings;
    })();

    var PatientName = (function () {
        function PatientName() {
        }
        PatientName.prototype.render = function (data) {
            return "{Patient}: {0} ({1})".replace("{Patient}", data.terminology.lookup(Terminology.Terms.Patient)).replace("{0}", data.instance.studyAttributes.patientName).replace("{1}", def(data.instance.studyAttributes.patientSex, "-"));
        };
        return PatientName;
    })();

    var PatientDOB = (function () {
        function PatientDOB() {
        }
        PatientDOB.prototype.render = function (data) {
            return "{DOB}: {0}".replace("{DOB}", data.terminology.lookup(Terminology.Terms.DOB)).replace("{0}", maybe(data.instance.studyAttributes.patientBirthDate, function (d) {
                return d.toShortDateString(data.dateFormat);
            }, "-"));
        };
        return PatientDOB;
    })();

    var StudyDate = (function () {
        function StudyDate() {
        }
        StudyDate.prototype.render = function (data) {
            return "{Date}: {0} {1}".replace("{Date}", data.terminology.lookup(Terminology.Terms.Date)).replace("{0}", maybe(data.instance.studyAttributes.studyCreateDate, function (d) {
                return d.toShortDateString(data.dateFormat);
            }, "-")).replace("{1}", maybe(data.instance.studyAttributes.studyCreateTime, function (t) {
                return t.toShortTimeString();
            }, "-"));
        };
        return StudyDate;
    })();

    var AccessionNumber = (function () {
        function AccessionNumber() {
        }
        AccessionNumber.prototype.render = function (data) {
            return "{Acc}: {0}".replace("{Acc}", data.terminology.lookup(Terminology.Terms.AccessionNumberShort)).replace("{0}", data.instance.studyAttributes.accessionNumber.value);
        };
        return AccessionNumber;
    })();

    var PriorNumber = (function () {
        function PriorNumber() {
        }
        PriorNumber.prototype.render = function (data) {
            if (data.instance.studyAttributes.priorNumber !== undefined) {
                return "{0}, {1}".replace("{0}", data.terminology.lookup(data.instance.studyAttributes.isPrimary ? Terminology.Terms.Primary : Terminology.Terms.Related)).replace("{1}", data.instance.studyAttributes.priorNumber === 0 ? data.terminology.lookup(Terminology.Terms.Current) : data.terminology.lookup(Terminology.Terms.Prior) + " #" + data.instance.studyAttributes.priorNumber);
            }
            return null;
        };
        return PriorNumber;
    })();

    var ImageNumber = (function () {
        function ImageNumber() {
        }
        ImageNumber.prototype.render = function (data) {
            return "{Instance}: {0}/{1}".replace("{Instance}", data.terminology.lookup(Terminology.Terms.Instance)).replace("{0}", (data.instance.instanceAttributes.instanceNumber + 1).toString()).replace("{1}", data.instance.seriesAttributes.instanceCount.toString());
        };
        return ImageNumber;
    })();

    var ImageType = (function () {
        function ImageType() {
        }
        ImageType.prototype.render = function (data) {
            if (_.any(data.instance.instanceAttributes.imageType)) {
                return "{ImageType}: {0}".replace("{ImageType}", data.terminology.lookup(Terminology.Terms.ImageType)).replace("{0}", data.instance.instanceAttributes.imageType.join(","));
            }
            return null;
        };
        return ImageType;
    })();

    var MeasurementCreator = (function () {
        function MeasurementCreator() {
        }
        MeasurementCreator.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && selectedMeasurement.creator) {
                return "{Measurement}: {0}".replace("{Measurement}", data.terminology.lookup(Terminology.Terms.Measurement)).replace("{0}", selectedMeasurement.creator);
            }

            return null;
        };
        return MeasurementCreator;
    })();

    var PaintMeasurementArea = (function () {
        function PaintMeasurementArea() {
        }
        PaintMeasurementArea.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && !selectedMeasurement.active) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    var area = selectedMeasurement;

                    return "{Area}: {0}".replace("{Area}", data.terminology.lookup(Terminology.Terms.Area)).replace("{0}", area.areaText(data.instance, false));
                }
            }

            return null;
        };
        return PaintMeasurementArea;
    })();

    var PaintMeasurementLabel = (function () {
        function PaintMeasurementLabel() {
        }
        PaintMeasurementLabel.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && selectedMeasurement.label) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    return selectedMeasurement.label;
                }
            }

            return null;
        };
        return PaintMeasurementLabel;
    })();

    var PaintMeasurementMin = (function () {
        function PaintMeasurementMin() {
        }
        PaintMeasurementMin.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && data.stats && !selectedMeasurement.active) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    return "{Min}: {0}".replace("{Min}", data.terminology.lookup(Terminology.Terms.Min)).replace("{0}", data.stats.min.toFixed(2));
                }
            }

            return null;
        };
        return PaintMeasurementMin;
    })();

    var PaintMeasurementMax = (function () {
        function PaintMeasurementMax() {
        }
        PaintMeasurementMax.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && data.stats && !selectedMeasurement.active) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    return "{Max}: {1}".replace("{Max}", data.terminology.lookup(Terminology.Terms.Max)).replace("{1}", data.stats.max.toFixed(2));
                }
            }

            return null;
        };
        return PaintMeasurementMax;
    })();

    var PaintMeasurementMean = (function () {
        function PaintMeasurementMean() {
        }
        PaintMeasurementMean.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && data.stats && !selectedMeasurement.active) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    return "{Mean}: {0}".replace("{Mean}", data.terminology.lookup(Terminology.Terms.Mean)).replace("{0}", data.stats.mean.toFixed(2));
                }
            }

            return null;
        };
        return PaintMeasurementMean;
    })();

    var PaintMeasurementStDev = (function () {
        function PaintMeasurementStDev() {
        }
        PaintMeasurementStDev.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.Area) && data.stats && !selectedMeasurement.active) {
                if (_.find(data.instance.instanceAttributes.measurements, function (m) {
                    return m === selectedMeasurement;
                })) {
                    return "{SD}: {1}".replace("{SD}", data.terminology.lookup(Terminology.Terms.StandardDev)).replace("{1}", data.stats.stdev.toFixed(2));
                }
            }

            return null;
        };
        return PaintMeasurementStDev;
    })();

    var ProstateToolMeasurementLabel = (function () {
        function ProstateToolMeasurementLabel() {
        }
        ProstateToolMeasurementLabel.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.ProstateTool)) {
                return "Prostate Volume";
            }

            return null;
        };
        return ProstateToolMeasurementLabel;
    })();

    var ProstateToolMeasurement = (function () {
        function ProstateToolMeasurement(index) {
            this.index = index;
        }
        ProstateToolMeasurement.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.ProstateTool) && !selectedMeasurement.active) {
                var selectedProstateMeasurement = selectedMeasurement;
                var group = Measurements.ProstateTool.getGroup(selectedProstateMeasurement.groupId.value);

                if (group && (this.index < group.length)) {
                    var line = group[this.index];
                    var length = line.length(line.lastInstance, true);
                    var lengthText = (length / 10.0).toFixed(2) + " cm";

                    return "{Label}: {Value}".replace("{Label}", line.label).replace("{Value}", lengthText);
                }
            }

            return null;
        };
        return ProstateToolMeasurement;
    })();

    var ProstateToolGroupMeasurement = (function () {
        function ProstateToolGroupMeasurement() {
        }
        ProstateToolGroupMeasurement.prototype.render = function (data) {
            var selectedMeasurement = data.selectedMeasurement.read();

            if (selectedMeasurement && (selectedMeasurement instanceof Measurements.ProstateTool) && !selectedMeasurement.active) {
                var selectedProstateMeasurement = selectedMeasurement;
                var total = selectedProstateMeasurement.calculateVolume();
                if (!isNaN(total)) {
                    return "Volume: {Value} mL".replace("{Value}", total.toFixed(2));
                }
            }

            return null;
        };
        return ProstateToolGroupMeasurement;
    })();

    var ReferringPhysician = (function () {
        function ReferringPhysician() {
        }
        ReferringPhysician.prototype.render = function (data) {
            return "{RefPhys}: {0}".replace("{RefPhys}", data.terminology.lookup(Terminology.Terms.ReferringPhysician)).replace("{0}", data.instance.studyAttributes.referringPhysicianName);
        };
        return ReferringPhysician;
    })();

    var StudyDescription = (function () {
        function StudyDescription() {
        }
        StudyDescription.prototype.render = function (data) {
            return "{Study}: {0}".replace("{Study}", data.terminology.lookup(Terminology.Terms.Study)).replace("{0}", def(data.instance.studyAttributes.studyDescription, "-").truncate(25));
        };
        return StudyDescription;
    })();

    var SeriesDescription = (function () {
        function SeriesDescription() {
        }
        SeriesDescription.prototype.render = function (data) {
            return "{Series}: {0} ({1})".replace("{Series}", data.terminology.lookup(Terminology.Terms.Series)).replace("{0}", def(data.instance.instanceAttributes.seriesDescription, "-").truncate(25)).replace("{1}", data.instance.seriesAttributes.modality);
        };
        return SeriesDescription;
    })();

    var Zoom = (function () {
        function Zoom() {
        }
        Zoom.prototype.render = function (data) {
            var transform = data.transform.getImageTransformation(data.context.canvas.width, data.context.canvas.height, data.instance.instanceAttributes.columns, data.instance.instanceAttributes.rows);
            var imageScale = Math.sqrt(Vectors.determinant(transform));

            return "{Zoom}: {0}".replace("{Zoom}", data.terminology.lookup(Terminology.Terms.Zoom)).replace("{0}", (imageScale * 100).toFixed(2) + "%");
        };
        return Zoom;
    })();

    var Quality = (function () {
        function Quality() {
        }
        Quality.prototype.render = function (data) {
            var imageType = data.getImageType();

            return "{Quality}: {0}".replace("{Quality}", data.terminology.lookup(Terminology.Terms.Quality)).replace("{0}", imageType !== null ? data.terminology.lookup(Terminology.imageTypeToTerm(imageType)) : "");
        };
        return Quality;
    })();

    var WindowLevel = (function () {
        function WindowLevel() {
        }
        WindowLevel.prototype.render = function (data) {
            var windowLevel = data.windowLevel.read();

            return "{CenterWidth}: {0} ({1})".replace("{CenterWidth}", data.terminology.lookup(Terminology.Terms.CenterWidth)).replace("{0}", maybe(windowLevel, function (wl) {
                return "{0} +/- {1}".replace("{0}", wl.center.toFixed(2)).replace("{1}", (wl.width / 2.0).toFixed(2));
            }, data.terminology.lookup(Terminology.Terms.Auto))).replace("{1}", def(data.instance.instanceAttributes.voiLutFunction, "LINEAR"));
        };
        return WindowLevel;
    })();

    var RadiationMachineName = (function () {
        function RadiationMachineName() {
        }
        RadiationMachineName.prototype.render = function (data) {
            if (data.instance.instanceAttributes.radiationMachineName) {
                return "{RadiationMachineName}: {0}".replace("{RadiationMachineName}", data.terminology.lookup(Terminology.Terms.MachineName)).replace("{0}", def(data.instance.instanceAttributes.radiationMachineName, ""));
            }

            return null;
        };
        return RadiationMachineName;
    })();

    var KVP = (function () {
        function KVP() {
        }
        KVP.prototype.render = function (data) {
            if (data.instance.instanceAttributes.kvp) {
                return "{kVp}: {0}".replace("{kVp}", data.terminology.lookup(Terminology.Terms.kVp)).replace("{0}", maybe(data.instance.instanceAttributes.kvp, function (kvp) {
                    return kvp.toFixed(2);
                }, ""));
            }

            return null;
        };
        return KVP;
    })();

    var Exposure = (function () {
        function Exposure() {
        }
        Exposure.prototype.render = function (data) {
            if (data.instance.instanceAttributes.exposure) {
                return "{Exposure}: {0}".replace("{Exposure}", data.terminology.lookup(Terminology.Terms.Exposure)).replace("{0}", maybe(data.instance.instanceAttributes.exposure, function (exp) {
                    return "{0}{1}".replace("{0}", exp.toFixed(2)).replace("{1}", data.terminology.lookup(Terminology.Terms.mA));
                }, ""));
            }

            return null;
        };
        return Exposure;
    })();

    var SliceThickness = (function () {
        function SliceThickness() {
        }
        SliceThickness.prototype.render = function (data) {
            if (data.instance.instanceAttributes.sliceThickness) {
                return "{SliceThickness}: {0}".replace("{SliceThickness}", data.terminology.lookup(Terminology.Terms.SliceThickness)).replace("{0}", maybe(data.instance.instanceAttributes.sliceThickness, function (st) {
                    return "{0}{1}".replace("{0}", st.toFixed(2)).replace("{1}", data.terminology.lookup(Terminology.Terms.mm));
                }, ""));
            }

            return null;
        };
        return SliceThickness;
    })();

    var PixelSpacingMeaning = (function () {
        function PixelSpacingMeaning() {
        }
        PixelSpacingMeaning.prototype.render = function (data) {
            if (data.instance.instanceAttributes.pixelSpacingMeaning !== undefined) {
                var spacingTerm;

                switch (data.instance.instanceAttributes.pixelSpacingMeaning) {
                    case 0 /* PatientGeometry */:
                        spacingTerm = Terminology.Terms.PatientGeometry;
                        break;
                    case 1 /* AtImagingPlate */:
                        spacingTerm = Terminology.Terms.AtImagingPlate;
                        break;
                    default:
                        throw new Error("Unknown pixel spacing meaning");
                }

                return "{PixelSpacing}: {0}".replace("{PixelSpacing}", data.terminology.lookup(Terminology.Terms.PixelSpacing)).replace("{0}", data.terminology.lookup(spacingTerm));
            }

            return null;
        };
        return PixelSpacingMeaning;
    })();

    var CustomField = (function () {
        function CustomField() {
        }
        CustomField.prototype.render = function (data, setting) {
            var field = _.find(data.instance.studyAttributes.customfields, function (cf) {
                return cf.uuid == setting.option;
            });
            if (data.instance.studyAttributes.customfields && field != null) {
                return "{0}: {1}".replace("{0}", field.name).replace("{1}", field.value.replace(/(<([^>]+)>)/ig, ""));
            }

            return null;
        };
        return CustomField;
    })();

    var ImageLaterality = (function () {
        function ImageLaterality() {
        }
        ImageLaterality.prototype.render = function (data) {
            if (data.instance.instanceAttributes.imageLaterality) {
                return "{IL}: {0}".replace("{IL}", data.terminology.lookup(Terminology.Terms.ImageLaterality)).replace("{0}", data.instance.instanceAttributes.imageLaterality);
            }
            return null;
        };
        return ImageLaterality;
    })();

    var ViewPosition = (function () {
        function ViewPosition() {
        }
        ViewPosition.prototype.render = function (data) {
            if (data.instance.instanceAttributes.viewPosition) {
                return "{VP}: {0}".replace("{VP}", data.terminology.lookup(Terminology.Terms.ViewPosition)).replace("{0}", data.instance.instanceAttributes.viewPosition);
            }
            return null;
        };
        return ViewPosition;
    })();

    var StationName = (function () {
        function StationName() {
        }
        StationName.prototype.render = function (data) {
            if (data.instance.instanceAttributes.stationName) {
                return "{SN}: {0}".replace("{SN}", data.terminology.lookup(Terminology.Terms.StationName)).replace("{0}", data.instance.instanceAttributes.stationName);
            }
            return null;
        };
        return StationName;
    })();

    var OperatorsName = (function () {
        function OperatorsName() {
        }
        OperatorsName.prototype.render = function (data) {
            if (data.instance.instanceAttributes.operatorsName) {
                return "{ON}: {0}".replace("{ON}", data.terminology.lookup(Terminology.Terms.OperatorsName)).replace("{0}", data.instance.instanceAttributes.operatorsName);
            }
            return null;
        };
        return OperatorsName;
    })();

    var InstitutionName = (function () {
        function InstitutionName() {
        }
        InstitutionName.prototype.render = function (data) {
            if (data.instance.instanceAttributes.institutionName) {
                return "{IN}: {0}".replace("{IN}", data.terminology.lookup(Terminology.Terms.InstitutionName)).replace("{0}", data.instance.instanceAttributes.institutionName);
            }
            return null;
        };
        return InstitutionName;
    })();

    var InstitutionAddress = (function () {
        function InstitutionAddress() {
        }
        InstitutionAddress.prototype.render = function (data) {
            if (data.instance.instanceAttributes.institutionAddress) {
                return "{IA}: {0}".replace("{IA}", data.terminology.lookup(Terminology.Terms.InstitutionAddress)).replace("{0}", data.instance.instanceAttributes.institutionAddress);
            }
            return null;
        };
        return InstitutionAddress;
    })();

    var DetectorID = (function () {
        function DetectorID() {
        }
        DetectorID.prototype.render = function (data) {
            if (data.instance.instanceAttributes.detectorID) {
                return "{detID}: {0}".replace("{detID}", data.terminology.lookup(Terminology.Terms.DetectorID)).replace("{0}", data.instance.instanceAttributes.detectorID);
            }
            return null;
        };
        return DetectorID;
    })();

    var PatientMRN = (function () {
        function PatientMRN() {
        }
        PatientMRN.prototype.render = function (data) {
            return "{MRN}: {0}".replace("{MRN}", data.terminology.lookup(Terminology.Terms.PatientMRN)).replace("{0}", data.instance.studyAttributes.patientId.value);
        };
        return PatientMRN;
    })();

    var TagSeries = (function () {
        function TagSeries() {
        }
        TagSeries.prototype.render = function (data, setting) {
            var foundTag;

            if (data.instance.seriesAttributes.json && data.instance.seriesAttributes.json.tags && setting.option) {
                var currentTags = data.instance.seriesAttributes.json.tags;
                var tags = setting.option.replace(/\s/g, '').split('->');
                foundTag = Dicom.findTag(currentTags, tags);
            }

            if (foundTag) {
                return "{0}: {1}".replace("{0}", foundTag.name || setting.option).replace("{1}", foundTag.value);
            }
        };
        return TagSeries;
    })();

    var TagImage = (function () {
        function TagImage() {
        }
        TagImage.prototype.render = function (data, setting) {
            var foundTag;

            if (data.instance.instanceAttributes.json && data.instance.instanceAttributes.json.tags && setting.option) {
                var currentTags = data.instance.instanceAttributes.json.tags;
                var tags = setting.option.replace(/\s/g, '').split('->');
                foundTag = Dicom.findTag(currentTags, tags);
            }

            if (foundTag) {
                return "{0}: {1}".replace("{0}", foundTag.name || setting.option).replace("{1}", foundTag.value);
            }
        };
        return TagImage;
    })();

    /**
    * Cache of singleton objects
    */
    var textAnnotationInstances = {};

    /**
    * Create a TextAnnotation from its type
    */
    function createTextAnnotation(type) {
        if (!textAnnotationInstances[Classes.TextAnnotationType[type]]) {
            switch (type) {
                case 0 /* PatientName */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PatientName();
                    break;
                case 1 /* PatientDOB */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PatientDOB();
                    break;
                case 2 /* StudyDate */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new StudyDate();
                    break;
                case 18 /* AccessionNumber */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new AccessionNumber();
                    break;
                case 3 /* PriorNumber */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PriorNumber();
                    break;
                case 4 /* ImageNumber */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ImageNumber();
                    break;
                case 5 /* ImageType */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ImageType();
                    break;
                case 6 /* MeasurementCreator */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new MeasurementCreator();
                    break;
                case 39 /* PaintMeasurementLabel */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementLabel();
                    break;
                case 34 /* PaintMeasurementArea */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementArea();
                    break;
                case 35 /* PaintMeasurementMin */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementMin();
                    break;
                case 36 /* PaintMeasurementMax */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementMax();
                    break;
                case 37 /* PaintMeasurementMean */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementMean();
                    break;
                case 38 /* PaintMeasurementStDev */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PaintMeasurementStDev();
                    break;
                case 40 /* ProstateToolMeasurementLabel */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ProstateToolMeasurementLabel();
                    break;
                case 41 /* ProstateToolMeasurementLength */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ProstateToolMeasurement(0);
                    break;
                case 42 /* ProstateToolMeasurementWidth */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ProstateToolMeasurement(1);
                    break;
                case 43 /* ProstateToolMeasurementHeight */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ProstateToolMeasurement(2);
                    break;
                case 44 /* ProstateToolGroupMeasurement */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ProstateToolGroupMeasurement();
                    break;
                case 7 /* ReferringPhysician */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ReferringPhysician();
                    break;
                case 8 /* StudyDescription */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new StudyDescription();
                    break;
                case 9 /* SeriesDescription */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new SeriesDescription();
                    break;
                case 10 /* Zoom */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new Zoom();
                    break;
                case 11 /* Quality */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new Quality();
                    break;
                case 12 /* WindowLevel */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new WindowLevel();
                    break;
                case 13 /* RadiationMachineName */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new RadiationMachineName();
                    break;
                case 14 /* KVP */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new KVP();
                    break;
                case 15 /* Exposure */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new Exposure();
                    break;
                case 16 /* SliceThickness */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new SliceThickness();
                    break;
                case 17 /* PixelSpacingMeaning */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PixelSpacingMeaning();
                    break;
                case 19 /* CustomField */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new CustomField();
                    break;
                case 20 /* ImageLaterality */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ImageLaterality();
                    break;
                case 21 /* ViewPosition */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new ViewPosition();
                    break;
                case 22 /* StationName */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new StationName();
                    break;
                case 23 /* OperatorsName */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new OperatorsName();
                    break;
                case 24 /* InstitutionName */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new InstitutionName();
                    break;
                case 25 /* InstitutionAddress */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new InstitutionAddress();
                    break;
                case 26 /* DetectorID */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new DetectorID();
                    break;
                case 27 /* PatientMRN */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new PatientMRN();
                    break;
                case 28 /* TagSeries */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new TagSeries();
                    break;
                case 29 /* TagImage */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new TagImage();
                    break;
                case 30 /* Subtraction */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new Subtraction();
                    break;
                case 31 /* CureMetrix */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new CureMetrix();
                    break;
                case 32 /* CADSRDensityFindings */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new CADSRDensityFindings();
                    break;
                case 33 /* CADSRCalcificationFindings */:
                    textAnnotationInstances[Classes.TextAnnotationType[type]] = new CADSRCalcificationFindings();
                    break;
                default:
                    throw "Unknown text annotation type";
            }
        }

        return textAnnotationInstances[Classes.TextAnnotationType[type]];
    }
    TextAnnotations.createTextAnnotation = createTextAnnotation;

    /**
    * Get a list of annotation types to display depending on modality
    */
    function getTextAnnotations(modality, settings) {
        var modalitySettings;

        if (settings.modalities) {
            modalitySettings = _.find(settings.modalities, function (m) {
                return m.modality === modality;
            });
        }

        var annotations;

        if (modalitySettings && modalitySettings.textAnnotations !== undefined && modalitySettings.textAnnotations.length > 0) {
            annotations = _.map(modalitySettings.textAnnotations, function (o) {
                return {
                    type: Classes.TextAnnotationType[o.type],
                    position: Classes.TextAnnotationPosition[o.position],
                    option: o.option
                };
            });
        } else {
            annotations = TextAnnotations.defaults(modality);
        }

        if (settings.supportsSRAnnotations && (modality == "MG")) {
            annotations = annotations.concat(TextAnnotations.cureMetrixModule);
        }

        return annotations;
    }
    TextAnnotations.getTextAnnotations = getTextAnnotations;

    /**
    * Arrange text annotations by position
    */
    function arrangeAnnotations(annotations, data) {
        var topLeftText = [];
        var bottomLeftText = [];
        var bottomRightText = [];
        var topRightText = [];

        _.each(annotations, function (setting) {
            var annotation = TextAnnotations.createTextAnnotation(setting.type);
            var str = annotation.render(data, setting);

            if (str !== null) {
                switch (setting.position) {
                    case 0 /* TopLeft */:
                        topLeftText.push(str);
                        break;
                    case 1 /* BottomLeft */:
                        bottomLeftText.push(str);
                        break;
                    case 2 /* BottomRight */:
                        bottomRightText.push(str);
                        break;
                    case 3 /* TopRight */:
                        topRightText.push(str);
                        break;
                }
            }
        });

        return {
            topLeft: topLeftText,
            bottomLeft: bottomLeftText,
            bottomRight: bottomRightText,
            topRight: topRightText
        };
    }
    TextAnnotations.arrangeAnnotations = arrangeAnnotations;

    function usesCustomSeriesTag(modality, settings) {
        var annotations = TextAnnotations.getTextAnnotations(modality, settings);
        var found = _.find(annotations, function (item) {
            return item.type == 28 /* TagSeries */;
        });
        return !!found;
    }
    TextAnnotations.usesCustomSeriesTag = usesCustomSeriesTag;

    function usesCustomImageTag(modality, settings) {
        var annotations = TextAnnotations.getTextAnnotations(modality, settings);
        var found = _.find(annotations, function (item) {
            return item.type == 29 /* TagImage */;
        });
        return !!found;
    }
    TextAnnotations.usesCustomImageTag = usesCustomImageTag;
})(TextAnnotations || (TextAnnotations = {}));
/**
* This module defines tagged union types
*/
var Either;
(function (_Either) {
    

    /**
    * A value using the left constructor
    */
    function Left(a) {
        return {
            tag: true,
            left: a
        };
    }
    _Either.Left = Left;

    /**
    * A value using the right constructor
    */
    function Right(b) {
        return {
            tag: false,
            right: b
        };
    }
    _Either.Right = Right;

    /**
    * Deconstruct a tagged value
    */
    function either(e, left, right) {
        if (e.tag) {
            return left(e.left);
        } else {
            return right(e.right);
        }
    }
    _Either.either = either;

    /**
    * Test if an optional value is constructed using the left type
    */
    function isLeft(e) {
        return e.tag;
    }
    _Either.isLeft = isLeft;
})(Either || (Either = {}));
var Console;
(function (Console) {
    var ConsoleMock = (function () {
        function ConsoleMock() {
            this._log = [];
        }
        ConsoleMock.prototype.msIsIndependentlyComposed = function (element) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.clear = function () {
            this._log = [];
        };

        ConsoleMock.prototype.push = function (severity, message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 2); _i++) {
                optionalParams[_i] = arguments[_i + 2];
            }
            this._log.push({ severity: "info", message: message, optionalParams: optionalParams });
        };

        ConsoleMock.prototype.info = function (message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            this.push("info", message, optionalParams);
        };

        ConsoleMock.prototype.warn = function (message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            this.push("warn", message, optionalParams);
        };

        ConsoleMock.prototype.error = function (message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            this.push("error", message, optionalParams);
        };

        ConsoleMock.prototype.trace = function () {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.log = function (message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            this.push("log", message, optionalParams);
        };

        ConsoleMock.prototype.debug = function (message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            this.push("debug", message, optionalParams);
        };

        ConsoleMock.prototype.dir = function (value) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.dirxml = function (value) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 1); _i++) {
                optionalParams[_i] = arguments[_i + 1];
            }
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.assert = function (test, message) {
            var optionalParams = [];
            for (var _i = 0; _i < (arguments.length - 2); _i++) {
                optionalParams[_i] = arguments[_i + 2];
            }
            if (!test) {
                this.error(message, optionalParams);
            }
        };

        ConsoleMock.prototype.count = function (name) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.time = function (name) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.timeEnd = function () {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.group = function (name) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.groupCollapsed = function (name) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.groupEnd = function () {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.profile = function (reportName) {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.profileEnd = function () {
            throw new Error("Not implemented");
        };

        ConsoleMock.prototype.select = function (element) {
            throw new Error("Not implemented");
        };
        return ConsoleMock;
    })();

    if (typeof console === "undefined") {
        window.console = new ConsoleMock();
    }
})(Console || (Console = {}));
///<reference path="Maybe.ts" />
/**
* Hanging protocols determine which series to display on which parts of the screen, depending on the
* type of study.
*
* This module declares functions which help with selecting and applying hanging protocols.
*/
var HangingProtocols;
(function (HangingProtocols) {
    /**
    * Select the most appropriate hanging protocol
    */
    function select(study, settings) {
        var studyLike = {
            studyModalities: _.uniq(_.map(study.series, function (series) {
                return series.seriesAttributes.modality;
            })),
            studyDescription: study.studyAttributes.studyDescription
        };

        if (settings.protocols) {
            var matches = _.filter(settings.protocols, function (hp) {
                var version = hp.version || HangingProtocols.MIN_HP_VERSION;

                if (version < HangingProtocols.CURRENT_HP_VERSION) {
                    return false;
                }

                return HangingProtocols.filterStudy(hp.studyFilters, studyLike);
            });

            if (_.any(matches)) {
                return matches[0];
            }
        }

        var layout = layoutFromSize(settings.layout);

        var modality = study.series[0].seriesAttributes.modality;
        var cineSpeed = Cine.getDefaultCineSpeed(settings, modality);

        return HangingProtocols.defaultProtocol(layout.rows, layout.columns, cineSpeed);
    }
    HangingProtocols.select = select;

    function selectDefault(study, settings, layout) {
        if (!layout) {
            layout = layoutFromSize(settings.layout);
        }

        var modality = study.series[0].seriesAttributes.modality;
        var cineSpeed = Cine.getDefaultCineSpeed(settings, modality);
        return HangingProtocols.defaultProtocol(layout.rows, layout.columns, cineSpeed);
    }
    HangingProtocols.selectDefault = selectDefault;

    function needsJSON(hp) {
        var found = false;
        _.each(hp.imageSets, function (imageSet) {
            if (!found) {
                found = HangingProtocols.seriesFilterOther(imageSet.seriesFilters);
            }
        });

        return found;
    }
    HangingProtocols.needsJSON = needsJSON;

    /**
    * Apply a hanging protocol to a study to get a list of series to display
    */
    function apply(series, hp) {
        var seriesLikes = _.map(series, function (series) {
            var instanceAttributes = series.instances[0].instanceAttributes;
            var seriesAttributes = series.seriesAttributes;

            return {
                seriesModality: series.seriesAttributes.modality,
                seriesDescription: instanceAttributes.seriesDescription || seriesAttributes.description,
                seriesImageType: instanceAttributes.imageType,
                seriesViewPosition: instanceAttributes.viewPosition,
                seriesImageLaterality: instanceAttributes.imageLaterality,
                seriesMultiframe: Multiframe.isMultiframe(series),
                seriesJSON: instanceAttributes.json || series.seriesAttributes.json,
                series: series
            };
        });

        var maybes = HangingProtocols.applyProtocol(hp, seriesLikes);

        return {
            matches: _.map(maybes.matches, function (ms) {
                return _.map(ms, function (m) {
                    return Maybe.maybe(m, null, function (s) {
                        return {
                            series: s.series.series,
                            displayOptions: s.displayOptions
                        };
                    });
                });
            }),
            rest: _.map(maybes.rest, function (s) {
                return s.series;
            })
        };
    }
    HangingProtocols.apply = apply;

    /**
    * Convert a layout size, as specified in the settings object, to a layout object
    */
    function layoutFromSize(size) {
        switch (size ? size : 1) {
            case 2:
                return { rows: 1, columns: 2 };
            case 4:
                return { rows: 2, columns: 2 };
            case 6:
                return { rows: 2, columns: 3 };
            case 8:
                return { rows: 2, columns: 4 };
            case 9:
                return { rows: 3, columns: 3 };
            case 12:
                return { rows: 3, columns: 4 };
            case 16:
                return { rows: 4, columns: 4 };
            default:
                return { rows: 1, columns: 1 };
        }
    }
    HangingProtocols.layoutFromSize = layoutFromSize;
})(HangingProtocols || (HangingProtocols = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Routes.ts' />
///<reference path='../libs/Images.ts' />
///<reference path='../libs/Browser.ts' />
///<reference path='../filter/Filter.ts' />
///<reference path='../views/Series.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders a video
    */
    var CineLayer = (function () {
        function CineLayer(sessionId, cineActive, cineSpeed, transform, settings, terminology, requestRepaint, gotoFrame) {
            this.sessionId = sessionId;
            this.cineActive = cineActive;
            this.cineSpeed = cineSpeed;
            this.settings = settings;
            this.terminology = terminology;
            this.loadingMessage = new Subjects.ObservableValue(this.terminology.lookup(Terminology.Terms.VideoLoading));
            this.requestRepaint = requestRepaint;
            this.gotoFrame = gotoFrame;
            this.transform = transform;
        }
        CineLayer.prototype.createVideoElement = function (instance) {
            var _this = this;
            var $video;

            var posterUri = Routes.ImageData(this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version, new Classes.FrameNumber(0), 0 /* Thumbnail */, 8, false);

            var videoUri = Routes.Multiframe(this.sessionId, instance.studyAttributes.studyStorage, instance.studyAttributes.queryObject, instance.id, instance.instanceAttributes.version);

            if (Browser.isiOS()) {
                $video = $('<video loop autoplay controls>').addClass('video').attr({
                    src: videoUri,
                    type: "video/mp4",
                    poster: posterUri
                });

                $video.error(function (_) {
                    _this.setLoadingMessage(_this.terminology.lookup(Terminology.Terms.VideoLoadingError));
                });
            } else {
                $video = $('<video loop autoplay controls>').addClass('video').attr({
                    poster: posterUri
                });

                var $source = $('<source>').attr({
                    src: videoUri,
                    type: "video/mp4"
                }).appendTo($video);

                $source.on("error", function (_) {
                    _this.setLoadingMessage(_this.terminology.lookup(Terminology.Terms.VideoLoadingError));
                });
            }

            $video.on("loadedmetadata", function (_) {
                _this.setLoadingMessage("");
            });

            var video = $video.get(0);
            video.load();

            video.playbackRate = this.cineSpeed.read() / Cine.cineRenderRate;

            var saveCineSpeedToSettings = _.debounce(function () {
                Subjects.modify(_this.settings, function (settings) {
                    return Cine.saveCineSpeedToSettings(settings, instance.seriesAttributes.modality, _this.cineSpeed.read());
                });
            }, 500);

            Subjects.listen(this.cineSpeed, function (fps) {
                var rate = fps / Cine.cineRenderRate;
                video.playbackRate = rate;
            });

            if (this.cineActive.read()) {
                //Clear any existing timers first
                if (this.cineTimer) {
                    clearInterval(this.cineTimer);
                }

                // Render the cine every 1000/FPS milliseconds
                var FPS = (this.cineSpeed.read() >= 1) ? this.cineSpeed.read() : 1;
                this.cineTimer = setInterval(this.requestRepaint, (1000 / FPS));

                $video.one("canplay", function (_) {
                    _this.playVideo();
                });
            }

            Subjects.listen(this.cineActive, function (active) {
                if (active) {
                    if (_this.video.readyState >= 3) {
                        _this.playVideo();
                    } else {
                        $video.on("canplay.viewer", function (_) {
                            _this.playVideo();
                            $video.off("canplay.viewer");
                        });
                    }
                } else {
                    _this.video.pause();
                    clearInterval(_this.cineTimer);
                    var position = _this.video.currentTime / _this.video.duration;
                    var frame = Math.floor(position * instance.instanceAttributes.frameCount);
                    _this.gotoFrame(Math.min(instance.instanceAttributes.frameCount - 1, Math.max(0, frame)));
                }
            });

            return video;
        };

        CineLayer.prototype.setLoadingMessage = function (text) {
            this.loadingMessage.write(text);
            this.requestRepaint();
        };

        CineLayer.prototype.playVideo = function () {
            var _this = this;
            var playPromise = this.video.play();

            if (playPromise !== undefined) {
                playPromise.then(function (_) {
                    // Clear any existing timers first
                    if (_this.cineTimer) {
                        clearInterval(_this.cineTimer);
                    }

                    // Render the cine every 1000/FPS milliseconds
                    var FPS = (_this.cineSpeed.read() >= 1) ? _this.cineSpeed.read() : 1;
                    _this.cineTimer = setInterval(_this.requestRepaint, (1000 / FPS));
                }).catch(function (err) {
                    console.log(err.code, err.message, err.name);
                });
            }
        };

        CineLayer.prototype.getImageTransformation = function (dw, dh, sw, sh) {
            return Transform.transformToMatrix(this.transform.read(), dw, dh, sw, sh);
        };

        CineLayer.prototype.render = function (context, instanceUID, instance) {
            if (!this.video) {
                this.video = this.createVideoElement(instance);
            }

            var transform = this.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
            context.fillStyle = "black";
            context.fillRect(0, 0, context.canvas.width, context.canvas.height);

            context.save();
            context.transform(transform.entries[0], transform.entries[3], transform.entries[1], transform.entries[4], transform.entries[2], transform.entries[5]);
            context.drawImage(this.video, 0, 0);
            context.restore();

            if (this.loadingMessage.read()) {
                Rendering.writeLineCentered(context, this.loadingMessage.read(), context.canvas.width / 2.0, context.canvas.height / 2.0, 12.0);
            }
        };
        return CineLayer;
    })();
    Layers.CineLayer = CineLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../typings/underscore/underscore.d.ts' />
///<reference path='../libs/Touch.ts' />
///<reference path='../libs/Mouse.ts' />
///<reference path='../libs/Rendering.ts' />
///<reference path='../libs/Geometry.ts' />
var Layers;
(function (Layers) {
    Layers.DEFAULT_TEXT_SIZE = 12;

    /**
    * A Layer which renders a collection of measurements
    */
    var MeasurementLayer = (function () {
        function MeasurementLayer(visible, detailsVisible, createdByOthersVisible, renderer, measurementInProgress, selectedMouseTool, selectedMeasurement, transform, playbackMode, showStandardDev, showTextOnDirected, editingMeasurement, hideActiveMeasurementInfo, alwaysUseMillimeters, fontSize, mesaureVolume, smallAngleOnly, user) {
            /**
            * Default vector drawing line thickness
            * @type {number}
            */
            this.lineWidth = 2;
            /**
            * Font size
            */
            this.fontSize = Layers.DEFAULT_TEXT_SIZE;
            this.visible = visible;
            this.detailsVisible = detailsVisible;
            this.createdByOthersVisible = createdByOthersVisible;
            this.renderer = renderer;
            this.measurementInProgress = measurementInProgress;
            this.selectedMouseTool = selectedMouseTool;
            this.selectedMeasurement = selectedMeasurement;
            this.transform = transform;
            this.playbackMode = playbackMode;
            this.showStandardDev = showStandardDev;
            this.showTextOnDirected = showTextOnDirected;
            this.editingMeasurement = editingMeasurement;
            this.hideActiveMeasurementInfo = hideActiveMeasurementInfo;
            this.alwaysUseMillimeters = alwaysUseMillimeters;
            this.fontSize = fontSize;
            this.measureVolume = mesaureVolume;
            this.smallAngleOnly = smallAngleOnly;
            this.user = user;
        }
        /**
        * Render intersections of angle and line measurements
        */
        MeasurementLayer.prototype.renderIntersections = function (context, measurements, trans) {
            if (measurements) {
                var lines = measurements.filter(function (m) {
                    return m instanceof Measurements.LineMeasurement;
                });

                var angles = measurements.filter(function (m) {
                    return m instanceof Measurements.Angle;
                });

                var renderIntersection = function (p, angle) {
                    var pos = Transform.mapFromImage(p, trans);
                    var text = angle.toFixed(2) + "\u00b0 / " + (180 - angle).toFixed(2) + "\u00b0";

                    Rendering.writeLineCentered(context, text, pos.x, pos.y, 12);
                };

                _.each(lines, function (line) {
                    _.each(angles, function (angle) {
                        if (angle.points.length === 3) {
                            var a1 = { start: angle.points[0], end: angle.points[1] };
                            var a2 = { start: angle.points[1], end: angle.points[2] };

                            Maybe.fmap(Geometry.intersect(line.line, a1), function (p) {
                                renderIntersection(p, Geometry.angleBetween(line.line, a1));
                            });

                            Maybe.fmap(Geometry.intersect(line.line, a2), function (p) {
                                renderIntersection(p, Geometry.angleBetween(line.line, a2));
                            });
                        }
                    });
                });
            }
        };

        MeasurementLayer.prototype.render = function (context, instanceUID, instance, imageType, series) {
            var _this = this;
            if (this.visible.read() && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var measurements = instance.instanceAttributes.measurements;

                _.each(measurements, function (measurement) {
                    var color;

                    if (measurement.calibration) {
                        color = '#d200dd';
                    } else if (measurement.selected || (measurement.volume && measurement.volume.selected)) {
                        color = '#FF0000';
                    } else if (measurement.editable) {
                        color = '#FF8040';
                    } else {
                        color = '#91baed';
                    }

                    // Show full name of annotation creator based on setting
                    measurement.showCreator = _this.detailsVisible.read();

                    if (measurement instanceof Measurements.LineMeasurement) {
                        measurement.showTextOnDirected = _this.showTextOnDirected.read();
                    }

                    // Only show temporary measurements during playback
                    if ((!_this.playbackMode.read() || measurement.temporary) && (_this.createdByOthersVisible.read() || measurement.creatorId === _this.user.uuid)) {
                        measurement.render(context, _this.renderer, instanceUID, instance, series, trans, color, _this.lineWidth, (measurement.active && _this.hideActiveMeasurementInfo) || _this.hideMeasurementInfo || _this.alwaysHideInfo, _this.alwaysUseMillimeters, _this.fontSize, _this.showStandardDev.read(), _this.measureVolume.read(), _this.smallAngleOnly);
                    }
                });

                this.renderIntersections(context, instance.instanceAttributes.measurements, trans);

                _.each(measurements, function (measurement) {
                    if (!(measurement instanceof Measurements.Trace)) {
                        if (measurement.editable && measurement.selected && (measurement != _this.editingMeasurement.read()) && (_this.createdByOthersVisible.read() || measurement.creatorId === _this.user.uuid)) {
                            _.each(measurement.endpoints(), function (endpoint) {
                                Rendering.drawRectangle(context, Transform.mapFromImage(endpoint.location, trans), '#ffffff', 3);
                            });
                        }
                    }
                });

                var measurementInProgress = this.measurementInProgress.read();

                if (measurementInProgress != null) {
                    measurementInProgress.render(context, this.renderer, instanceUID, instance, series, trans, '#ffff00', this.lineWidth, this.hideActiveMeasurementInfo || this.hideMeasurementInfo, this.alwaysUseMillimeters, this.fontSize, this.showStandardDev.read(), this.measureVolume.read(), this.smallAngleOnly);
                }

                _.each(instance.instanceAttributes.mesaurementsCADSR, function (measurement) {
                    var color = '#91baed';

                    measurement.render(context, _this.renderer, instanceUID, instance, series, trans, color, _this.lineWidth, (measurement.active && _this.hideActiveMeasurementInfo) || _this.alwaysHideInfo, _this.alwaysUseMillimeters, _this.fontSize, _this.showStandardDev.read(), _this.measureVolume.read(), _this.smallAngleOnly);
                });
            }
        };
        return MeasurementLayer;
    })();
    Layers.MeasurementLayer = MeasurementLayer;
})(Layers || (Layers = {}));

var Measurements;
(function (Measurements) {
    

    

    /**
    * A measurement which measures the distance between two points with a line
    */
    var LineMeasurement = (function () {
        function LineMeasurement(start, end, directed) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            this.line = { start: start, end: end };
            this.directed = directed;
        }
        LineMeasurement.prototype.calibratedPixelSpacing = function () {
            return this.calibrationValue / Geometry.lineLength(this.line);
        };

        LineMeasurement.prototype.isNonEmpty = function () {
            return Geometry.lengthInf(this.line) > 1e-6;
        };

        LineMeasurement.prototype.distanceTo = function (imageCoords) {
            return Geometry.distanceToLine(this.line, imageCoords);
        };

        LineMeasurement.prototype.continueDrawing = function (imageCoords) {
            this.line.end = imageCoords;
        };

        LineMeasurement.prototype.startDrawing = function (imageCoords) {
        };

        LineMeasurement.prototype.stopDrawing = function (imageCoords) {
            return true;
        };

        LineMeasurement.prototype.endpoints = function () {
            var _this = this;
            return [
                {
                    location: this.line.start,
                    move: function (newLocation) {
                        _this.line.start = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.line = Geometry.copyLine(_this.originalLine);
                    }
                },
                {
                    location: this.line.end,
                    move: function (newLocation) {
                        _this.line.end = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.line = Geometry.copyLine(_this.originalLine);
                    }
                }];
        };

        LineMeasurement.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        LineMeasurement.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalLine = Geometry.copyLine(this.line);
        };

        LineMeasurement.prototype.continueMoving = function (dx, dy) {
            this.line.start.x = this.originalLine.start.x + dx;
            this.line.start.y = this.originalLine.start.y + dy;
            this.line.end.x = this.originalLine.end.x + dx;
            this.line.end.y = this.originalLine.end.y + dy;
        };

        LineMeasurement.prototype.length = function (instance, supportNonIsoPixels) {
            if (typeof supportNonIsoPixels === "undefined") { supportNonIsoPixels = false; }
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var length;

            if (this.pixelSpacingUser) {
                length = Geometry.lineLength(this.line) * this.pixelSpacingUser;
            } else if (pixelSpacing && pixelSpacing[0]) {
                if ((pixelSpacing.length === 2) && supportNonIsoPixels) {
                    length = Geometry.lineLength2(this.line, pixelSpacing[1], pixelSpacing[0]);
                } else {
                    length = Geometry.lineLength(this.line) * pixelSpacing[0];
                }
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                length = Geometry.lineLength2(this.line, this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
            } else if (this.calibration && this.calibrationValue) {
                length = this.calibrationValue;
            } else if (instance.instanceAttributes.calibration) {
                var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                length = Geometry.lineLength(this.line) * mmPerPixel;
            }

            return length;
        };

        LineMeasurement.prototype.lengthText = function (instance, alwaysMm) {
            var length = this.length(instance);

            if (length) {
                if ((length < 10.0) || alwaysMm) {
                    return length.toFixed(2) + " mm";
                } else {
                    return (length / 10.0).toFixed(2) + " cm";
                }
            }

            return null;
        };

        LineMeasurement.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            var transformed = Transform.mapFromImageL(this.line, trans);

            Rendering.drawLine(context, transformed, style, lineWidth, this.directed);

            if (!hideInfo && (!this.directed || (this.directed && (!this.hideMeasurement && this.showTextOnDirected)))) {
                var text = this.lengthText(instance, alwaysMm);

                if (!this.hideMeasurement && text) {
                    if (this.showCreator && this.creator && this.creator.length > 0) {
                        text = text + '\n' + this.creator;
                    }

                    var pos = Transform.mapFromImage(Geometry.findSouthPointFromLines([this.line], trans), trans);
                    Rendering.writeLinesCentered(context, [text], pos.x, pos.y + 2, fontSize);
                }

                if (this.label) {
                    var north = Transform.mapFromImage(Geometry.findNorthPointFromLines([this.line], trans), trans);
                    Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                }
            }
        };

        /**
        * Convert to an annotation
        */
        LineMeasurement.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[this.directed ? 12 /* Arrow */ : 5 /* Measure */],
                hideMeasurement: this.hideMeasurement,
                points: [this.line.start, this.line.end],
                calibration: this.calibration,
                calibrationValue: this.calibrationValue
            };
        };

        /**
        * Update data from a JSON object
        */
        LineMeasurement.prototype.edit = function (data) {
            this.line.start = data.points[0];
            this.line.end = data.points[1];
        };

        LineMeasurement.prototype.hasMoved = function () {
            return !Geometry.linesEqual(this.originalLine, this.line);
        };

        LineMeasurement.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text = this.lengthText(instance, false);

            if (text) {
                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [(this.line.start.x + this.line.end.x) / 2, (this.line.start.y + this.line.end.y) / 2],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        LineMeasurement.prototype.toGraphicObjects = function (instance) {
            return [
                {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [this.line.start.x, this.line.start.y, this.line.end.x, this.line.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }, {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 1,
                    graphicData: [this.line.end.x, this.line.end.y],
                    graphicType: "POINT",
                    graphicFilled: false
                }];
        };

        /**
        * Clone this measurement
        */
        LineMeasurement.prototype.clone = function () {
            return new LineMeasurement(Geometry.displacePoint(this.line.start, 10, 10), Geometry.displacePoint(this.line.end, 10, 10), this.directed);
        };

        LineMeasurement.prototype.statistics = function (instance, instanceUID, renderer, series) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var length;

            if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                length = Geometry.lineLength(this.line) * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                length = Geometry.lineLength2(this.line, this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
            }

            if (length) {
                return {
                    min: length,
                    max: length,
                    mean: NaN, stdev: NaN
                };
            }

            return null;
        };

        LineMeasurement.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return LineMeasurement;
    })();
    Measurements.LineMeasurement = LineMeasurement;

    var ProstateTool = (function (_super) {
        __extends(ProstateTool, _super);
        function ProstateTool(start, end, id) {
            _super.call(this, start, end, false);

            if (!ProstateTool.CURRENT_ID) {
                ProstateTool.reset();
            }

            this.groupId = id ? new Classes.AnnotationId(id) : ProstateTool.CURRENT_ID;
            this.label = ProstateTool.LABELS[ProstateTool.CURRENT_STEP];
            ProstateTool.add(this.groupId.value, this);
        }
        ProstateTool.add = function (id, annotation) {
            if (ProstateTool.GROUPS[id]) {
                ProstateTool.GROUPS[id].push(annotation);
            } else {
                ProstateTool.GROUPS[id] = [annotation];
            }
        };

        ProstateTool.getGroup = function (id) {
            return ProstateTool.GROUPS[id];
        };

        ProstateTool.reset = function () {
            ProstateTool.CURRENT_STEP = 0;
            ProstateTool.CURRENT_ID = Annotations.newRandomId();
        };

        ProstateTool.prototype.calculateVolume = function () {
            var group = ProstateTool.getGroup(this.groupId.value);
            var total = NaN;
            if (group && group.length == 3) {
                total = Math.PI / 6.0;
                var studyId;
                _.each(group, function (m) {
                    if (m.lastInstance) {
                        if (!studyId) {
                            studyId = m.lastInstance.studyAttributes.uuid.value;
                        }

                        if (studyId == m.lastInstance.studyAttributes.uuid.value) {
                            total = total * (m.length(m.lastInstance, true) / 10.0);
                        } else {
                            total = NaN;
                        }
                    } else {
                        total = NaN;
                    }
                });
            }

            return total;
        };

        ProstateTool.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            var transformed = Transform.mapFromImageL(this.line, trans);
            this.lastInstance = instance;

            Rendering.drawLine(context, transformed, style, lineWidth, this.directed);

            if (!hideInfo) {
                if (this.label) {
                    var north = Transform.mapFromImage(Geometry.findNorthPointFromLines([this.line], trans), trans);
                    Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                }
            }
        };

        ProstateTool.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[33 /* ProstateTool */],
                hideMeasurement: this.hideMeasurement,
                points: [this.line.start, this.line.end],
                calibration: this.calibration,
                calibrationValue: this.calibrationValue,
                groupId: this.groupId.value
            };
        };

        ProstateTool.prototype.getCurrentTermToken = function () {
            return ProstateTool.MESSAGES[ProstateTool.CURRENT_STEP];
        };

        ProstateTool.prototype.getCurrentStep = function () {
            return ProstateTool.CURRENT_STEP;
        };

        ProstateTool.prototype.getNumSteps = function () {
            return ProstateTool.NUM_STEPS;
        };

        ProstateTool.prototype.step = function () {
            ProstateTool.CURRENT_STEP += 1;
            return ProstateTool.CURRENT_STEP == ProstateTool.NUM_STEPS;
        };

        ProstateTool.prototype.reset = function () {
            ProstateTool.reset();
        };
        ProstateTool.GROUPS = {};
        ProstateTool.NUM_STEPS = 3;
        ProstateTool.MESSAGES = [
            new Classes.Term("webviewer:prostate-step-1", "Step 1: Make length measurement."),
            new Classes.Term("webviewer:prostate-step-2", "Step 2: Make width measurement."),
            new Classes.Term("webviewer:prostate-step-3", "Step 3: Make height measurement.")
        ];
        ProstateTool.LABELS = ["Length", "Width", "Height"];
        ProstateTool.CURRENT_STEP = 0;
        return ProstateTool;
    })(LineMeasurement);
    Measurements.ProstateTool = ProstateTool;

    /**
    * A measurement which measures the angle between three points
    */
    var Angle = (function () {
        function Angle(points) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            /**
            * First line has been drawn
            */
            this.firstLineIsComplete = false;
            this.hideMeasurement = false;
            this.points = points;
        }
        Angle.prototype.isNonEmpty = function () {
            if (this.points.length < 3) {
                return true;
            } else {
                return Geometry.distanceBetween(this.points[0], this.points[1]) > 1e-6 && Geometry.distanceBetween(this.points[1], this.points[2]) > 1e-6;
            }
        };

        Angle.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        Angle.prototype.distanceTo = function (imageCoords) {
            return Math.min(Geometry.distanceToLine({ start: this.points[0], end: this.points[1] }, imageCoords), Geometry.distanceToLine({ start: this.points[1], end: this.points[2] }, imageCoords));
        };

        Angle.prototype.continueDrawing = function (imageCoords) {
            this.points[this.points.length - 1] = imageCoords;
        };

        Angle.prototype.startDrawing = function (imageCoords) {
            if (this.firstLineIsComplete) {
                this.points[2] = imageCoords;
            }
        };

        Angle.prototype.stopDrawing = function (imageCoords) {
            if (this.firstLineIsComplete) {
                return true;
            } else {
                this.firstLineIsComplete = true;
                return false;
            }
        };

        Angle.prototype.endpoints = function () {
            var _this = this;
            return _.map(this.points, function (p, index, list) {
                return {
                    location: p,
                    move: function (newLocation) {
                        _this.points[index] = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.points[index] = Geometry.copyPoint(_this.originalPoints[index]);
                    }
                };
            });
        };

        Angle.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        Angle.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalPoints = _.map(this.points, function (p) {
                return Geometry.copyPoint(p);
            });
        };

        Angle.prototype.continueMoving = function (dx, dy) {
            var _this = this;
            _.each(this.points, function (p, index, list) {
                p.x = _this.originalPoints[index].x + dx;
                p.y = _this.originalPoints[index].y + dy;
            });
        };

        Angle.prototype.angleText = function () {
            if (this.points.length === 3) {
                var angle = Geometry.angleBetween({ start: this.points[0], end: this.points[1] }, { start: this.points[1], end: this.points[2] });

                return angle.toFixed(2) + "\u00b0 / " + (180 - angle).toFixed(2) + "\u00b0";
            }

            return "";
        };

        Angle.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            switch (this.points.length) {
                case 1:
                    Rendering.drawCross(context, style, Transform.mapFromImage(this.points[0], trans));
                    break;
                case 2:
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[0], end: this.points[1] }, trans), style, lineWidth);
                    break;
                case 3:
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[0], end: this.points[1] }, trans), style, lineWidth);
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[1], end: this.points[2] }, trans), style, lineWidth);

                    var text = this.angleText();
                    var pos = Transform.mapFromImage(Geometry.findSouthPoint(this.points, trans), trans);

                    if (!hideInfo) {
                        if (text) {
                            var lines = [text];
                            if (this.showCreator && this.creator && this.creator.length > 0) {
                                lines.push(this.creator);
                            }

                            Rendering.writeLinesCentered(context, lines, pos.x, pos.y + 2, fontSize);
                        }

                        if (this.label) {
                            var north = Transform.mapFromImage(Geometry.findNorthPoint(this.points, trans), trans);
                            Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                        }
                    }

                    break;
            }
        };

        /**
        * Convert to an annotation
        */
        Angle.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[13 /* Angle */],
                points: this.points
            };
        };

        /**
        * Update data from a JSON object
        */
        Angle.prototype.edit = function (data) {
            this.points = data.points;
        };

        Angle.prototype.hasMoved = function () {
            for (var i = 0; i < this.points.length; i++) {
                if (!Geometry.pointsEqual(this.points[i], this.originalPoints[i])) {
                    return true;
                }
            }
            return false;
        };

        Angle.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text = this.angleText();

            if (text) {
                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [this.points[1].x, this.points[1].y],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        Angle.prototype.toGraphicObjects = function (instance) {
            return [{
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 3,
                    graphicData: [
                        this.points[0].x, this.points[0].y,
                        this.points[1].x, this.points[1].y,
                        this.points[2].x, this.points[2].y
                    ],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }];
        };

        /**
        * Clone this measurement
        */
        Angle.prototype.clone = function () {
            return new Angle(_.map(this.points, function (p) {
                return Geometry.displacePoint(p, 10, 10);
            }));
        };

        Angle.prototype.statistics = function (instance, instanceUID, renderer, series) {
            return null;
        };

        Angle.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return Angle;
    })();
    Measurements.Angle = Angle;

    var FemoralHead = (function (_super) {
        __extends(FemoralHead, _super);
        function FemoralHead(points) {
            _super.call(this, points);

            if (this.points.length == 4) {
                this.circle = new Circle(this.points[0], this.points[3]);
            }
        }
        FemoralHead.prototype.circlePoints = function () {
            return [this.points[0], this.points[3]];
        };

        FemoralHead.prototype.isAngleNonEmpty = function () {
            if (this.points.length < 3) {
                return true;
            } else {
                return Geometry.distanceBetween(this.points[0], this.points[1]) > 1e-6 && Geometry.distanceBetween(this.points[0], this.points[2]) > 1e-6;
            }
        };

        FemoralHead.prototype.angleDistanceTo = function (imageCoords) {
            return Math.min(Geometry.distanceToLine({ start: this.points[0], end: this.points[1] }, imageCoords), Geometry.distanceToLine({ start: this.points[0], end: this.points[2] }, imageCoords));
        };

        FemoralHead.prototype.isNonEmpty = function () {
            return this.isAngleNonEmpty() && (!this.circle || this.circle.isNonEmpty());
        };

        FemoralHead.prototype.angleText = function () {
            if (this.points.length == 4) {
                var angle = Geometry.angleBetween({ start: this.points[0], end: this.points[1] }, { start: this.points[0], end: this.points[2] });

                return angle.toFixed(2) + "\u00b0";
            }

            return "";
        };

        FemoralHead.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            var text = [this.angleText()];

            switch (this.points.length) {
                case 1:
                    Rendering.drawCross(context, style, Transform.mapFromImage(this.points[0], trans));
                    break;
                case 2:
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[0], end: this.points[1] }, trans), style, lineWidth);
                    break;
                case 3:
                case 4:
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[0], end: this.points[1] }, trans), style, lineWidth);
                    Rendering.drawLine(context, Transform.mapFromImageL({ start: this.points[0], end: this.points[2] }, trans), style, lineWidth);
                    break;
            }

            if (this.circle) {
                context.setLineDash([5, 3]);
                this.circle.render(context, renderer, instanceUID, instance, series, trans, style, lineWidth, true, alwaysMm, fontSize, showStdDev);
                context.setLineDash([]);

                if (!hideInfo) {
                    if (this.showCreator && this.creator && this.creator.length > 0) {
                        text.push(this.creator);
                    }

                    var t1 = this.circle.points[0];
                    var t2 = this.circle.points[1];
                    var radius = Geometry.distanceBetween(t1, t2);
                    var south = Transform.mapFromImage(Geometry.findSouthPoint(_.flatten([
                        this.points,
                        [{ x: t1.x, y: t1.y + radius }]]), trans), trans);

                    Rendering.writeLinesCentered(context, text, south.x, south.y, fontSize);

                    if (this.label) {
                        var north = Transform.mapFromImage(Geometry.findNorthPoint(_.flatten([
                            this.points,
                            [{ x: t1.x, y: t1.y - radius }]]), trans), trans);
                        Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                    }
                }
            }
        };

        FemoralHead.prototype.distanceTo = function (imageCoords) {
            var circlePoints = this.circlePoints();
            var circleDistance = Math.min(Math.abs(this.signedDistanceTo(imageCoords)), Geometry.distanceBetween(imageCoords, circlePoints[0]));
            var angleDistance = this.angleDistanceTo(imageCoords);
            return Math.min(circleDistance, angleDistance);
        };

        FemoralHead.prototype.signedDistanceTo = function (imageCoords) {
            var circlePoints = this.circlePoints();
            var radius = Geometry.distanceBetween(circlePoints[0], circlePoints[1]);
            var toCenter = Geometry.distanceBetween(imageCoords, circlePoints[0]);
            return toCenter - radius;
        };

        FemoralHead.prototype.inside = function (imageCoords) {
            return (this.signedDistanceTo(imageCoords) < 0) || this.circle.inside(imageCoords);
        };

        FemoralHead.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[21 /* FemoralHead */],
                points: this.points,
                filled: false
            };
        };

        FemoralHead.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text = this.angleText();

            if (text) {
                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [this.points[0].x, this.points[0].y],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        FemoralHead.prototype.toGraphicObjects = function (instance) {
            var angleData = _super.prototype.toGraphicObjects.call(this, instance);
            var circleData = this.circle.toGraphicObjects(instance);
            return _.flatten([angleData, circleData]);
        };

        /**
        * Update data from a JSON object
        */
        FemoralHead.prototype.edit = function (data) {
            this.points = data.points;
        };

        FemoralHead.prototype.hasMoved = function () {
            for (var i = 0; i < this.points.length; i++) {
                if (!Geometry.pointsEqual(this.points[i], this.originalPoints[i])) {
                    return true;
                }
            }
            return false;
        };

        FemoralHead.prototype.continueDrawing = function (imageCoords) {
            this.points[this.points.length - 1] = imageCoords;
        };

        FemoralHead.prototype.startDrawing = function (imageCoords) {
            if (this.firstLineIsComplete) {
                this.points[2] = imageCoords;
            }
        };

        FemoralHead.prototype.stopDrawing = function (imageCoords) {
            if (this.firstLineIsComplete) {
                this.points[3] = Geometry.copyPoint(this.points[2]);
                this.circle = new Circle(this.points[0], this.points[3]);
                return true;
            } else {
                this.firstLineIsComplete = true;
                return false;
            }
        };

        FemoralHead.prototype.endpoints = function () {
            var _this = this;
            return _.map(this.points, function (p, index, list) {
                if (index == 0) {
                    return {
                        location: p,
                        move: function (newLocation) {
                            // don't resize circle when adjusting middle angle point
                            Geometry.setPoint(_this.points[3], Geometry.displacePointBy(_this.points[3], Geometry.pointDiff(_this.points[0], newLocation)));
                            Geometry.setPoint(_this.points[0], newLocation);
                        },
                        reset: function () {
                            Geometry.setPoint(_this.points[0], _this.originalPoints[0]);
                            Geometry.setPoint(_this.points[3], _this.originalPoints[3]);
                        }
                    };
                } else {
                    return {
                        location: p,
                        move: function (newLocation) {
                            Geometry.setPoint(_this.points[index], newLocation);
                        },
                        reset: function () {
                            Geometry.setPoint(_this.points[index], _this.originalPoints[index]);
                        }
                    };
                }
            }).reverse();
        };

        FemoralHead.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        FemoralHead.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalPoints = _.map(this.points, function (p) {
                return Geometry.copyPoint(p);
            });
        };

        FemoralHead.prototype.continueMoving = function (dx, dy) {
            for (var i = 0; i < this.points.length; i++) {
                this.points[i].x = this.originalPoints[i].x + dx;
                this.points[i].y = this.originalPoints[i].y + dy;
            }
        };

        FemoralHead.prototype.clone = function () {
            return new FemoralHead(_.map(this.points, function (p) {
                return Geometry.displacePoint(p, 10, 10);
            }));
        };

        FemoralHead.prototype.statistics = function (instance, instanceUID, renderer, series) {
            return null;
        };

        FemoralHead.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return FemoralHead;
    })(Angle);
    Measurements.FemoralHead = FemoralHead;

    /**
    * A measurement which measures the angle between two lines
    */
    var Cobb = (function () {
        function Cobb(l1, l2) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            this.hideMeasurement = false;
            this.l1 = l1;
            this.l2 = l2;
        }
        Cobb.prototype.isNonEmpty = function () {
            return Geometry.lengthInf(this.l1) > 1e-6 && (!this.l2 || Geometry.lengthInf(this.l2) > 1e-6);
        };

        Cobb.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        Cobb.prototype.distanceTo = function (imageCoords) {
            return Math.min(Geometry.distanceToLine(this.l1, imageCoords), Geometry.distanceToLine(this.l2, imageCoords), Geometry.distanceToLine(this.createMidline(), imageCoords));
        };

        Cobb.prototype.continueDrawing = function (imageCoords) {
            if (this.l2) {
                this.l2.end = imageCoords;
            } else {
                this.l1.end = imageCoords;
            }
        };

        Cobb.prototype.startDrawing = function (imageCoords) {
            if (this.firstLineIsComplete) {
                this.l2 = { start: imageCoords, end: imageCoords };
            }
        };

        Cobb.prototype.stopDrawing = function (imageCoords) {
            if (this.l2) {
                return true;
            } else {
                this.firstLineIsComplete = true;
                return false;
            }
        };

        Cobb.prototype.createMidline = function () {
            return {
                start: {
                    x: (this.l1.start.x + this.l1.end.x) / 2,
                    y: (this.l1.start.y + this.l1.end.y) / 2
                },
                end: {
                    x: (this.l2.start.x + this.l2.end.x) / 2,
                    y: (this.l2.start.y + this.l2.end.y) / 2
                }
            };
        };

        Cobb.prototype.endpoints = function () {
            var _this = this;
            return [
                {
                    location: this.l1.start,
                    move: function (newLocation) {
                        _this.l1.start = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.l1 = Geometry.copyLine(_this.originalLine1);
                    }
                },
                {
                    location: this.l1.end,
                    move: function (newLocation) {
                        _this.l1.end = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.l1 = Geometry.copyLine(_this.originalLine1);
                    }
                },
                {
                    location: this.l2.start,
                    move: function (newLocation) {
                        _this.l2.start = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.l2 = Geometry.copyLine(_this.originalLine2);
                    }
                },
                {
                    location: this.l2.end,
                    move: function (newLocation) {
                        _this.l2.end = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.l2 = Geometry.copyLine(_this.originalLine2);
                    }
                }];
        };

        Cobb.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);

            var dist1 = Geometry.distanceToLine(this.l1, imageCoords);
            var dist2 = Geometry.distanceToLine(this.l2, imageCoords);
            var distMid = Geometry.distanceToLine(this.createMidline(), imageCoords);

            if (distMid < dist1 && distMid < dist2) {
                this.movingMidLine = true;
                this.movingFirstLine = false;
            } else {
                this.movingFirstLine = (dist1 < dist2);
                this.movingMidLine = false;
            }
        };

        Cobb.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalLine1 = Geometry.copyLine(this.l1);
            this.originalLine2 = Geometry.copyLine(this.l2);
        };

        Cobb.prototype.continueMoving = function (dx, dy) {
            if (this.movingFirstLine || this.movingMidLine) {
                this.l1.start.x = this.originalLine1.start.x + dx;
                this.l1.start.y = this.originalLine1.start.y + dy;
                this.l1.end.x = this.originalLine1.end.x + dx;
                this.l1.end.y = this.originalLine1.end.y + dy;
            }

            if (!this.movingFirstLine || this.movingMidLine) {
                this.l2.start.x = this.originalLine2.start.x + dx;
                this.l2.start.y = this.originalLine2.start.y + dy;
                this.l2.end.x = this.originalLine2.end.x + dx;
                this.l2.end.y = this.originalLine2.end.y + dy;
            }
        };

        Cobb.prototype.angleText = function (smallAngleOnly) {
            var angle = Math.round(Geometry.angleBetween(this.l1, this.l2));
            var angle2 = 180 - angle;

            if (smallAngleOnly) {
                return Math.min(angle, angle2) + "\u00b0";
            } else {
                return angle + "\u00b0 / " + angle2 + "\u00b0";
            }
        };

        Cobb.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume, smallAngleOnly) {
            Rendering.drawLine(context, Transform.mapFromImageL(this.l1, trans), style, 2);

            if (this.l2) {
                var midline = this.createMidline();

                Rendering.drawLine(context, Transform.mapFromImageL(this.l2, trans), style, 2);
                Rendering.drawLine(context, Transform.mapFromImageL(midline, trans), '#800000', 2);

                var text = this.angleText(smallAngleOnly);

                var pos = Transform.mapFromImage(Geometry.findSouthPointFromLines([this.l1, this.l2], trans), trans);

                if (!hideInfo) {
                    if (text) {
                        var lines = [text];
                        if (this.showCreator && this.creator && this.creator.length > 0) {
                            lines.push(this.creator);
                        }

                        Rendering.writeLinesCentered(context, lines, pos.x, pos.y + 2, fontSize);
                    }

                    if (this.label) {
                        var north = Transform.mapFromImage(Geometry.findNorthPointFromLines([this.l1, this.l2], trans), trans);
                        Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                    }
                }
            }
        };

        /**
        * Convert to an annotation
        */
        Cobb.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[7 /* CobbAngle */],
                points: [this.l1.start, this.l1.end, this.l2.start, this.l2.end]
            };
        };

        /**
        * Update data from a JSON object
        */
        Cobb.prototype.edit = function (data) {
            this.l1.start = data.points[0];
            this.l1.end = data.points[1];
            this.l2.start = data.points[2];
            this.l2.end = data.points[3];
        };

        Cobb.prototype.hasMoved = function () {
            return !Geometry.linesEqual(this.originalLine1, this.l1) || !Geometry.linesEqual(this.originalLine2, this.l2);
        };

        Cobb.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text = this.angleText(false);

            if (text) {
                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [
                            (this.l1.start.x + this.l1.end.x + this.l2.start.x + this.l2.end.x) / 4 + 10,
                            (this.l1.start.y + this.l1.end.y + this.l2.start.y + this.l2.end.y) / 4
                        ],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        Cobb.prototype.toGraphicObjects = function (instance) {
            var midline = this.createMidline();

            return [
                {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [this.l1.start.x, this.l1.start.y, this.l1.end.x, this.l1.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }, {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [this.l2.start.x, this.l2.start.y, this.l2.end.x, this.l2.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }, {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [midline.start.x, midline.start.y, midline.end.x, midline.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }];
        };

        /**
        * Clone this measurement
        */
        Cobb.prototype.clone = function () {
            return new Cobb(Geometry.displaceLine(this.l1, 10, 10), Geometry.displaceLine(this.l2, 10, 10));
        };

        Cobb.prototype.statistics = function (instance, instanceUID, renderer, series) {
            return null;
        };

        Cobb.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return Cobb;
    })();
    Measurements.Cobb = Cobb;

    /**
    * A measurement which measures the length of two orthogonal axes.
    */
    var OrthogonalAxes = (function () {
        function OrthogonalAxes(points) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            this.hideMeasurement = false;
            this.l1 = { start: Geometry.copyPoint(points[0]), end: Geometry.copyPoint(points[1]) };
            this.l2 = { start: Geometry.copyPoint(points[2]), end: Geometry.copyPoint(points[3]) };
        }
        OrthogonalAxes.prototype.isNonEmpty = function () {
            return Geometry.lengthInf(this.l1) > 1e-6;
        };

        OrthogonalAxes.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        OrthogonalAxes.prototype.distanceTo = function (imageCoords) {
            return Math.min(Geometry.distanceToLine(this.l1, imageCoords), Geometry.distanceToLine(this.l2, imageCoords));
        };

        OrthogonalAxes.prototype.createOrthogonalLine = function (line, other) {
            var length = Geometry.lineLength(line) / 4.0;
            var midpoint = Geometry.midpoint(line);
            var orthogonal = Geometry.orthogonalLineSegment(line, midpoint, length, length);
            Geometry.setLine(other, orthogonal);
        };

        OrthogonalAxes.prototype.updateOrthogonalLine = function (line, other, original, opposite, grabbedPoint, otherOriginal) {
            var distance = Geometry.lineLength({ start: opposite, end: this.movingIntersection });
            var intersection = Geometry.interploatedPointAtDistance(Geometry.otherPoint(line, grabbedPoint), grabbedPoint, distance);
            var lengthStart = Geometry.lineLength({ start: this.movingIntersection, end: otherOriginal.start });
            var lengthEnd = Geometry.lineLength({ start: this.movingIntersection, end: otherOriginal.end });

            if (Geometry.onLeft(other, line.start) != Geometry.onLeft(other, line.end)) {
                if (line == this.l1) {
                    Geometry.setLine(other, Geometry.orthogonalLineSegment(line, intersection, lengthStart, lengthEnd));
                } else {
                    Geometry.setLine(other, Geometry.orthogonalLineSegment(line, intersection, lengthEnd, lengthStart), true);
                }
            } else {
                Geometry.setPoint(grabbedPoint, this.lastPoint);
            }
        };

        OrthogonalAxes.prototype.continueDrawing = function (imageCoords) {
            this.l1.end = imageCoords;
            this.createOrthogonalLine(this.l1, this.l2);
        };

        OrthogonalAxes.prototype.startDrawing = function (imageCoords) {
        };

        OrthogonalAxes.prototype.stopDrawing = function (imageCoords) {
            return true;
        };

        OrthogonalAxes.prototype.reset = function () {
            this.l1 = Geometry.copyLine(this.l1Original);
            this.l2 = Geometry.copyLine(this.l2Original);
        };

        OrthogonalAxes.prototype.intersection = function () {
            var intersect = Geometry.intersect(this.l1, this.l2);
            if (Maybe.hasValue(intersect)) {
                return intersect.value;
            }

            return null;
        };

        OrthogonalAxes.prototype.endpoints = function () {
            var _this = this;
            return [
                {
                    location: this.l1.start,
                    move: function (newLocation) {
                        _this.lastPoint = Geometry.copyPoint(_this.l1.start);
                        _this.l1.start = Geometry.copyPoint(newLocation);
                        _this.updateOrthogonalLine(_this.l1, _this.l2, _this.l1Original, _this.l1Original.end, _this.l1.start, _this.l2Original);
                    },
                    reset: function () {
                        _this.reset();
                    }
                },
                {
                    location: this.l1.end,
                    move: function (newLocation) {
                        _this.lastPoint = Geometry.copyPoint(_this.l1.end);
                        _this.l1.end = Geometry.copyPoint(newLocation);
                        _this.updateOrthogonalLine(_this.l1, _this.l2, _this.l1Original, _this.l1Original.start, _this.l1.end, _this.l2Original);
                    },
                    reset: function () {
                        _this.reset();
                    }
                },
                {
                    location: this.l2.start,
                    move: function (newLocation) {
                        _this.lastPoint = Geometry.copyPoint(_this.l2.start);
                        _this.l2.start = Geometry.copyPoint(newLocation);
                        _this.updateOrthogonalLine(_this.l2, _this.l1, _this.l2Original, _this.l2Original.end, _this.l2.start, _this.l1Original);
                    },
                    reset: function () {
                        _this.reset();
                    }
                },
                {
                    location: this.l2.end,
                    move: function (newLocation) {
                        _this.lastPoint = Geometry.copyPoint(_this.l2.end);
                        _this.l2.end = Geometry.copyPoint(newLocation);
                        _this.updateOrthogonalLine(_this.l2, _this.l1, _this.l2Original, _this.l2Original.start, _this.l2.end, _this.l1Original);
                    },
                    reset: function () {
                        _this.reset();
                    }
                },
                {
                    location: this.intersection(),
                    move: function (newLocation) {
                        var dx = newLocation.x - _this.movingIntersection.x;
                        var dy = newLocation.y - _this.movingIntersection.y;
                        var other, lineNew;

                        if (_this.movingIntersectionLine == _this.l1) {
                            lineNew = Geometry.displaceLine(_this.l1Original, dx, dy);
                            other = _this.l2;
                        } else if (_this.movingIntersectionLine == _this.l2) {
                            lineNew = Geometry.displaceLine(_this.l2Original, dx, dy);
                            other = _this.l1;
                        } else {
                            // determine along which line the mouse moved and displace the opposite line
                            var movingIntersectionLine = { start: newLocation, end: _this.movingIntersection };
                            var angle1 = Math.abs(90 - Geometry.angleBetween(movingIntersectionLine, _this.l1Original));
                            var angle2 = Math.abs(90 - Geometry.angleBetween(movingIntersectionLine, _this.l2Original));

                            if (angle1 < angle2) {
                                lineNew = Geometry.displaceLine(_this.l1Original, dx, dy);
                                other = _this.l2;
                                _this.movingIntersectionLine = _this.l1;
                            } else {
                                lineNew = Geometry.displaceLine(_this.l2Original, dx, dy);
                                other = _this.l1;
                                _this.movingIntersectionLine = _this.l2;
                            }
                        }

                        var intersect = Geometry.intersect(other, lineNew);
                        if (Maybe.hasValue(intersect)) {
                            Geometry.setLine(_this.movingIntersectionLine, lineNew);
                        }
                    },
                    reset: function () {
                        _this.reset();
                    }
                }];
        };

        OrthogonalAxes.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);

            if (Geometry.distanceToLine(this.l1, imageCoords) < Geometry.distanceToLine(this.l2, imageCoords)) {
                this.movingLine = this.l1;
            } else {
                this.movingLine = this.l2;
            }
        };

        OrthogonalAxes.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.l1Original = Geometry.copyLine(this.l1);
            this.l2Original = Geometry.copyLine(this.l2);
            this.movingIntersection = Geometry.copyPoint(this.intersection());
            this.movingIntersectionLine = null;
        };

        OrthogonalAxes.prototype.continueMoving = function (dx, dy) {
            this.l1.start.x = this.l1Original.start.x + dx;
            this.l1.start.y = this.l1Original.start.y + dy;
            this.l1.end.x = this.l1Original.end.x + dx;
            this.l1.end.y = this.l1Original.end.y + dy;

            this.l2.start.x = this.l2Original.start.x + dx;
            this.l2.start.y = this.l2Original.start.y + dy;
            this.l2.end.x = this.l2Original.end.x + dx;
            this.l2.end.y = this.l2Original.end.y + dy;
        };

        OrthogonalAxes.prototype.lengthText = function (instance, line, alwaysMm) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var length;

            if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                length = Geometry.lineLength(line) * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                length = Geometry.lineLength2(line, this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
            }

            if (length) {
                if ((length < 10.0) || alwaysMm) {
                    return length.toFixed(2) + " mm";
                } else {
                    return (length / 10.0).toFixed(2) + " cm";
                }
            }

            return null;
        };

        OrthogonalAxes.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            Rendering.drawLine(context, Transform.mapFromImageL(this.l1, trans), style, 2);

            if (this.l2) {
                Rendering.drawLine(context, Transform.mapFromImageL(this.l2, trans), style, 2);

                var pos = Transform.mapFromImage(Geometry.findSouthPointFromLines([this.l1, this.l2], trans), trans);

                if (!hideInfo) {
                    var text1 = this.lengthText(instance, this.l1, alwaysMm);
                    var text2 = this.lengthText(instance, this.l2, alwaysMm);

                    if (text1 && text2) {
                        var lines = [text1 + " / " + text2];
                        if (this.showCreator && this.creator && this.creator.length > 0) {
                            lines.push(this.creator);
                        }

                        Rendering.writeLinesCentered(context, lines, pos.x, pos.y + 2, fontSize);
                    }

                    if (this.label) {
                        var north = Transform.mapFromImage(Geometry.findNorthPointFromLines([this.l1, this.l2], trans), trans);
                        Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 8), fontSize + 4);
                    }
                }
            }
        };

        /**
        * Convert to an annotation
        */
        OrthogonalAxes.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[20 /* OrthoAxes */],
                points: [this.l1.start, this.l1.end, this.l2.start, this.l2.end]
            };
        };

        /**
        * Update data from a JSON object
        */
        OrthogonalAxes.prototype.edit = function (data) {
            this.l1.start = data.points[0];
            this.l1.end = data.points[1];
            this.l2.start = data.points[2];
            this.l2.end = data.points[3];
        };

        OrthogonalAxes.prototype.hasMoved = function () {
            return !Geometry.linesEqual(this.l1, this.l1Original) || !Geometry.linesEqual(this.l2, this.l2Original);
        };

        OrthogonalAxes.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text1 = this.lengthText(instance, this.l1, false);
            var text2 = this.lengthText(instance, this.l2, false);

            if (text1 && text2) {
                var text = text1 + " / " + text2;

                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [
                            (this.l1.start.x + this.l1.end.x + this.l2.start.x + this.l2.end.x) / 4 + 10,
                            (this.l1.start.y + this.l1.end.y + this.l2.start.y + this.l2.end.y) / 4
                        ],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        OrthogonalAxes.prototype.toGraphicObjects = function (instance) {
            return [
                {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [this.l1.start.x, this.l1.start.y, this.l1.end.x, this.l1.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }, {
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 2,
                    graphicData: [this.l2.start.x, this.l2.start.y, this.l2.end.x, this.l2.end.y],
                    graphicType: "POLYLINE",
                    graphicFilled: false
                }
            ];
        };

        /**
        * Clone this measurement
        */
        OrthogonalAxes.prototype.clone = function () {
            var l1 = Geometry.displaceLine(this.l1, 10, 10);
            var l2 = Geometry.displaceLine(this.l2, 10, 10);
            return new OrthogonalAxes([l1.start, l1.end, l2.start, l2.end]);
        };

        OrthogonalAxes.prototype.statistics = function (instance, instanceUID, renderer, series) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var length1, length2;

            if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                length1 = Geometry.lineLength(this.l1) * mmPerPixel;
                length2 = Geometry.lineLength(this.l2) * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                length1 = Geometry.lineLength2(this.l1, this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
                length2 = Geometry.lineLength2(this.l2, this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
            }

            if (length1 && length2) {
                return {
                    min: Math.min(length1, length2),
                    max: Math.max(length1, length2),
                    mean: NaN, stdev: NaN
                };
            }

            return null;
        };

        OrthogonalAxes.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return OrthogonalAxes;
    })();
    Measurements.OrthogonalAxes = OrthogonalAxes;

    /**
    * A measurement which draws a rectangle
    */
    var Rectangle = (function () {
        function Rectangle(p1, p2, filled) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            /**
            * If true, a shape will maintain a 1:1 aspect ratio when being resized.
            */
            this.maintainEqualAspectRatio = false;
            this.points = [p1, p2];
            this.filled = filled;
        }
        Rectangle.prototype.isNonEmpty = function () {
            return Geometry.lengthInf({ start: this.points[0], end: this.points[1] }) > 1e-6;
        };

        Rectangle.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        Rectangle.prototype.distanceTo = function (imageCoords) {
            if (this.filled && this.inside(imageCoords)) {
                return 0.0;
            }

            var lines = Geometry.rectangleToLines(this.points);

            var distance = _.reduce(lines, function (min, line) {
                return Math.min(min, Geometry.distanceToLine(line, imageCoords));
            }, Number.MAX_VALUE);

            if (isNaN(distance)) {
                return 0;
            }

            return distance;
        };

        Rectangle.prototype.inside = function (imageCoords) {
            var p1 = this.points[0];
            var p2 = this.points[1];

            return imageCoords.x >= Math.min(p1.x, p2.x) && imageCoords.x <= Math.max(p1.x, p2.x) && imageCoords.y >= Math.min(p1.y, p2.y) && imageCoords.y <= Math.max(p1.y, p2.y);
        };

        Rectangle.prototype.continueDrawing = function (imageCoords) {
            this.points[1] = imageCoords;
        };

        Rectangle.prototype.startDrawing = function (imageCoords) {
        };

        Rectangle.prototype.stopDrawing = function (imageCoords) {
            return true;
        };

        Rectangle.prototype.fixAspectRatio = function (movingPoint, oppositePoint) {
            var width = Math.abs(this.points[0].x - this.points[1].x);
            var height = Math.abs(this.points[0].y - this.points[1].y);

            if (width < height) {
                var yDiff = (movingPoint.y - oppositePoint.y) * (width / height);
                movingPoint.y = (oppositePoint.y + yDiff);
            } else {
                var xDiff = (movingPoint.x - oppositePoint.x) * (height / width);
                movingPoint.x = (oppositePoint.x + xDiff);
            }

            return movingPoint;
        };

        Rectangle.prototype.endpoints = function () {
            var _this = this;
            var p1 = this.points[0];
            var p2 = this.points[1];
            var p3 = { x: this.points[0].x, y: this.points[1].y };
            var p4 = { x: this.points[1].x, y: this.points[0].y };

            return [
                {
                    location: p1,
                    move: function (newLocation) {
                        _this.points[0] = Geometry.copyPoint(newLocation);

                        if (_this.maintainEqualAspectRatio) {
                            _this.fixAspectRatio(_this.points[0], _this.points[1]);
                        }
                    },
                    reset: function () {
                        _this.points[0] = Geometry.copyPoint(_this.originalPoints[0]);
                    }
                },
                {
                    location: p2,
                    move: function (newLocation) {
                        _this.points[1] = Geometry.copyPoint(newLocation);

                        if (_this.maintainEqualAspectRatio) {
                            _this.fixAspectRatio(_this.points[1], _this.points[0]);
                        }
                    },
                    reset: function () {
                        _this.points[1] = Geometry.copyPoint(_this.originalPoints[1]);
                    }
                },
                {
                    location: p3,
                    move: function (newLocation) {
                        _this.points[0].x = newLocation.x;
                        _this.points[1].y = newLocation.y;

                        if (_this.maintainEqualAspectRatio) {
                            var pt = _this.fixAspectRatio(newLocation, { x: _this.points[1].x, y: _this.points[0].y });
                            _this.points[0].x = pt.x;
                            _this.points[1].y = pt.y;
                        }
                    },
                    reset: function () {
                        _this.points[0].x = _this.originalPoints[0].x;
                        _this.points[1].y = _this.originalPoints[1].y;
                    }
                },
                {
                    location: p4,
                    move: function (newLocation) {
                        _this.points[1].x = newLocation.x;
                        _this.points[0].y = newLocation.y;

                        if (_this.maintainEqualAspectRatio) {
                            var pt = _this.fixAspectRatio(newLocation, { x: _this.points[0].x, y: _this.points[1].y });
                            _this.points[1].x = pt.x;
                            _this.points[0].y = pt.y;
                        }
                    },
                    reset: function () {
                        _this.points[1].x = _this.originalPoints[1].x;
                        _this.points[0].y = _this.originalPoints[0].y;
                    }
                }];
        };

        Rectangle.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        Rectangle.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalPoints = [
                Geometry.copyPoint(this.points[0]),
                Geometry.copyPoint(this.points[1])
            ];
        };

        Rectangle.prototype.continueMoving = function (dx, dy) {
            for (var i = 0; i < this.points.length; i++) {
                this.points[i].x = this.originalPoints[i].x + dx;
                this.points[i].y = this.originalPoints[i].y + dy;
            }

            if (this.volume) {
                _.each(this.volume.slices, function (s) {
                    s.measurement.volume = null;
                });
            }
        };

        Rectangle.prototype.statistics = function (instance, instanceUID, renderer) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                var width = instance.instanceAttributes.columns;
                var height = instance.instanceAttributes.rows;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                var min = Number.MAX_VALUE;
                var max = -Number.MAX_VALUE;
                var sum = 0.0;
                var mean = 0.0, M2 = 0.0, delta = 0.0, delta2 = 0.0;
                var count = 0.0;

                if (imageData) {
                    var minX = Math.floor(Math.min(this.points[0].x, this.points[1].x));
                    var minY = Math.floor(Math.min(this.points[0].y, this.points[1].y));
                    var maxX = Math.ceil(Math.max(this.points[0].x, this.points[1].x));
                    var maxY = Math.ceil(Math.max(this.points[0].y, this.points[1].y));

                    for (var i = minX; i < maxX; i++) {
                        for (var j = minY; j < maxY; j++) {
                            if (i > 0 && j > 0 && i < width && j < height && this.inside({ x: i, y: j })) {
                                var value = this.getPixelValue(i, j, width, height, imageData, instance);
                                count++;

                                // stdev calculated using Welford's one-pass method
                                delta = (value - mean);
                                mean += (delta / count);
                                delta2 = (value - mean);
                                M2 += (delta * delta2);

                                sum += value;
                                min = Math.min(value, min);
                                max = Math.max(value, max);
                            }
                        }
                    }

                    if (count === 0) {
                        min = max = 0.0;
                    }

                    var mean = sum / Math.max(1, count);

                    return {
                        min: min,
                        max: max,
                        mean: mean,
                        /*
                        Population (n) stdev used instead of sample (n-1) estimate.  The sample estimate is typically
                        used when it's infeasible to calculate the population variance.  This isn't the case here.  The
                        user is not using the ROI as a sample to estimate the population variance, so we should consider
                        the ROI itself as the entire population of voxels being considered.
                        */
                        stdev: (count > 0) ? Math.sqrt(M2 / count) : NaN
                    };
                }
            }

            return null;
        };

        Rectangle.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                var width = instance.instanceAttributes.columns;
                var height = instance.instanceAttributes.rows;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                var data = [];

                if (imageData) {
                    var minX = Math.floor(Math.min(this.points[0].x, this.points[1].x));
                    var minY = Math.floor(Math.min(this.points[0].y, this.points[1].y));
                    var maxX = Math.ceil(Math.max(this.points[0].x, this.points[1].x));
                    var maxY = Math.ceil(Math.max(this.points[0].y, this.points[1].y));

                    for (var i = minX; i < maxX; i++) {
                        for (var j = minY; j < maxY; j++) {
                            if (i > 0 && j > 0 && i < width && j < height && this.inside({ x: i, y: j })) {
                                var value = this.getPixelValue(i, j, width, height, imageData, instance);
                                data.push(value);
                            }
                        }
                    }

                    return data;
                }
            }

            return null;
        };

        Rectangle.prototype.statisticsVolume = function (instance, instanceUID, renderer, volume) {
            var width = instance.instanceAttributes.columns;
            var height = instance.instanceAttributes.rows;
            var min = Number.MAX_VALUE;
            var max = -Number.MAX_VALUE;
            var sum = 0.0;
            var mean = 0.0, M2 = 0.0, delta = 0.0, delta2 = 0.0;
            var count = 0.0;

            for (var ctr = 0; ctr < volume.length; ctr += 1) {
                var currentInstance = volume[ctr].instance;
                var currentMeasurement = volume[ctr].measurement;
                var currentInstanceId = currentInstance.id.value + ':' + currentInstance.frameNumber.value;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(currentInstanceId, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(currentInstanceId, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                if (imageData) {
                    var minX = Math.floor(Math.min(currentMeasurement.points[0].x, currentMeasurement.points[1].x));
                    var minY = Math.floor(Math.min(currentMeasurement.points[0].y, currentMeasurement.points[1].y));
                    var maxX = Math.ceil(Math.max(currentMeasurement.points[0].x, currentMeasurement.points[1].x));
                    var maxY = Math.ceil(Math.max(currentMeasurement.points[0].y, currentMeasurement.points[1].y));

                    for (var i = minX; i < maxX; i++) {
                        for (var j = minY; j < maxY; j++) {
                            if (i > 0 && j > 0 && i < width && j < height && currentMeasurement.inside({ x: i, y: j })) {
                                var value = currentMeasurement.getPixelValue(i, j, width, height, imageData, currentInstance);
                                count++;

                                // stdev calculated using Welford's one-pass method
                                delta = (value - mean);
                                mean += (delta / count);
                                delta2 = (value - mean);
                                M2 += (delta * delta2);

                                sum += value;
                                min = Math.min(value, min);
                                max = Math.max(value, max);
                            }
                        }
                    }
                }
            }

            if (count === 0) {
                min = max = 0.0;
            }

            var mean = sum / Math.max(1, count);

            return {
                min: min,
                max: max,
                mean: mean,
                /*
                Population (n) stdev used instead of sample (n-1) estimate.  The sample estimate is typically
                used when it's infeasible to calculate the population variance.  This isn't the case here.  The
                user is not using the ROI as a sample to estimate the population variance, so we should consider
                the ROI itself as the entire population of voxels being considered.
                */
                stdev: (count > 0) ? Math.sqrt(M2 / count) : NaN
            };
        };

        Rectangle.prototype.getPixelValue = function (i, j, w, h, data, instance) {
            var lowBits = data.data[(i + j * w) * 4];
            var highBits = data.data[(i + (j + h) * w) * 4];

            var raw = (highBits << 8) | lowBits;

            if (instance.instanceAttributes.signed) {
                raw = (raw & 0xffff) - 0x8000;
            }

            return raw * instance.instanceAttributes.rescaleSlope + instance.instanceAttributes.rescaleIntercept;
        };

        Rectangle.prototype.areaText = function (instance, alwaysMm) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var area = Number.NaN;

            if (this.pixelSpacingUser) {
                var mmPerPixel = this.pixelSpacingUser;
                area = Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            } else if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                area = Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                area = Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * this.ultrasoundPixelSpacing[0] * this.ultrasoundPixelSpacing[1];
            } else if (instance.instanceAttributes.calibration) {
                var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                area = Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            }

            if (!isNaN(area)) {
                var result;

                if ((area < 100.0) || alwaysMm) {
                    result = "Area: " + area.toFixed(2) + " mm\u00b2";
                } else {
                    result = "Area: " + (area / 100.0).toFixed(2) + " cm\u00b2";
                }

                return result;
            }

            return null;
        };

        Rectangle.prototype.volumeText = function (instance, alwaysMm, series, volume) {
            var pixelSpacingTag = instance.instanceAttributes.pixelSpacing;
            var sliceSpacing = Measurements.findSliceDistance(series, volume);
            var pixelSpacing;
            var voxelVolume;
            var numVoxels = 0;

            if (this.pixelSpacingUser) {
                pixelSpacing = this.pixelSpacingUser;
            } else if (pixelSpacingTag && pixelSpacingTag[0]) {
                pixelSpacing = pixelSpacingTag[0];
            } else if (instance.instanceAttributes.calibration) {
                pixelSpacing = instance.instanceAttributes.calibration.calibratedPixelSpacing();
            }

            voxelVolume = pixelSpacing * pixelSpacing * sliceSpacing;

            _.each(volume, function (s) {
                var rect = s.measurement;
                numVoxels += (Math.abs(rect.points[0].x - rect.points[1].x) * Math.abs(rect.points[0].y - rect.points[1].y));
            });

            var totalVolume = numVoxels * voxelVolume;

            if (totalVolume) {
                var result = [];
                ;

                if ((totalVolume < 1000.0) || alwaysMm) {
                    result.push("Volume: " + totalVolume.toFixed(2) + " mm\u00b3");
                } else {
                    result.push("Volume: " + (totalVolume / 1000.0).toFixed(2) + " cm\u00b3");
                }

                result.push("Voxel: " + pixelSpacing.toFixed(2) + " x " + pixelSpacing.toFixed(2) + " x " + sliceSpacing.toFixed(2) + " mm");

                return result;
            }

            return null;
        };

        Rectangle.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume) {
            var t1 = Transform.mapFromImage(this.points[0], trans);
            var t2 = Transform.mapFromImage(this.points[1], trans);
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;

            Rendering.drawRectangleMeasurement(context, t1, t2, style, lineWidth, this.filled);
            var text = [];

            if (!hideInfo) {
                var volume;
                var validGeometry = false;
                var volumeText;

                if (measureVolume && !this.volume) {
                    volume = Measurements.findVolume(instance, this, series);
                    if (volume.length <= 1 || !Measurements.volumeLoaded(volume, renderer)) {
                        volume = null;
                        measureVolume = false;
                    }
                }

                if (measureVolume) {
                    if (this.volume) {
                        volumeText = this.volume.sizeDescription;
                    } else {
                        volumeText = this.volumeText(instance, alwaysMm, series, volume);
                    }

                    if (volumeText) {
                        _.each(volumeText, function (s) {
                            return text.push(s);
                        });
                        validGeometry = true;
                    }
                }

                var areaText = this.areaText(instance, alwaysMm);
                if (areaText) {
                    text.push(areaText);
                    validGeometry = true;
                }

                if (pixelSpacing && pixelSpacing[0] && !this.hideMeasurement && validGeometry) {
                    var imageStatistics;
                    if (measureVolume) {
                        if (this.volume) {
                            imageStatistics = this.volume.stats;
                        } else {
                            imageStatistics = this.statisticsVolume(instance, instanceUID, renderer, volume);
                        }
                    } else {
                        imageStatistics = this.statistics(instance, instanceUID, renderer);
                    }

                    if (volume) {
                        var volObj = {
                            stats: imageStatistics,
                            sizeDescription: volumeText,
                            selected: this.selected,
                            slices: volume
                        };

                        _.each(volume, function (s) {
                            s.measurement.volume = volObj;
                        });

                        Measurements.updateVolumeLabel(volObj);
                    }

                    if (imageStatistics) {
                        text.push("Min: " + imageStatistics.min.toFixed(2));
                        text.push("Max: " + imageStatistics.max.toFixed(2));
                        text.push("Mean: " + imageStatistics.mean.toFixed(2));

                        if (showStdDev) {
                            text.push("SD: " + imageStatistics.stdev.toFixed(2));
                        }
                    }

                    if (this.showCreator && this.creator && this.creator.length > 0) {
                        text.push(this.creator);
                    }

                    Rendering.writeLines(context, text, Math.max(t1.x, t2.x), Math.max(t1.y, t2.y), fontSize);
                }

                if (this.label) {
                    Rendering.writeLineCentered(context, this.label, (t1.x + t2.x) / 2.0, Math.min(t1.y, t2.y) - (fontSize + 8), fontSize + 4);
                }
            }
        };

        /**
        * Convert to an annotation
        */
        Rectangle.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[6 /* Rectangle */],
                hideMeasurement: this.hideMeasurement,
                points: this.points,
                filled: this.filled
            };
        };

        /**
        * Update data from a JSON object
        */
        Rectangle.prototype.edit = function (data) {
            this.points = data.points;
        };

        Rectangle.prototype.hasMoved = function () {
            return !Geometry.pointsEqual(this.originalPoints[0], this.points[0]) || !Geometry.pointsEqual(this.originalPoints[1], this.points[1]);
        };

        Rectangle.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var areaText = this.areaText(instance, false);

            if (areaText) {
                var text = [areaText];
                var imageStatistics = this.statistics(instance, instanceUID, renderer);

                if (imageStatistics) {
                    text.push("Min: " + imageStatistics.min.toFixed(2));
                    text.push("Max: " + imageStatistics.max.toFixed(2));
                    text.push("Mean: " + imageStatistics.mean.toFixed(2));

                    if (showStdDev) {
                        text.push("SD: " + imageStatistics.stdev.toFixed(2));
                    }
                }

                var allText = text.join("\n");

                return [{
                        unformattedTextValue: allText,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [
                            Math.max(this.points[0].x, this.points[1].x),
                            Math.max(this.points[0].y, this.points[1].y)
                        ],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        Rectangle.prototype.toGraphicObjects = function (instance) {
            var minX = Math.min(this.points[0].x, this.points[1].x);
            var minY = Math.min(this.points[0].y, this.points[1].y);
            var maxX = Math.max(this.points[0].x, this.points[1].x);
            var maxY = Math.max(this.points[0].y, this.points[1].y);

            return [{
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 5,
                    graphicData: [
                        minX, minY,
                        maxX, minY,
                        maxX, maxY,
                        minX, maxY,
                        minX, minY
                    ],
                    graphicType: "POLYLINE",
                    graphicFilled: this.filled
                }];
        };

        /**
        * Clone this measurement
        */
        Rectangle.prototype.clone = function () {
            return new Rectangle(Geometry.displacePoint(this.points[0], 10, 10), Geometry.displacePoint(this.points[1], 10, 10), this.filled);
        };
        return Rectangle;
    })();
    Measurements.Rectangle = Rectangle;

    /**
    * A measurement which draws an ellipse
    */
    var Ellipse = (function (_super) {
        __extends(Ellipse, _super);
        function Ellipse(p1, p2, filled) {
            _super.call(this, p1, p2, filled);
        }
        Ellipse.prototype.areaText = function (instance, alwaysMm) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing || this.ultrasoundPixelSpacing;
            var area = Number.NaN;

            if (this.pixelSpacingUser) {
                var mmPerPixel = this.pixelSpacingUser;
                area = Math.PI / 4.0 * Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            } else if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                area = Math.PI / 4.0 * Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                area = Math.PI / 4.0 * Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * this.ultrasoundPixelSpacing[0] * this.ultrasoundPixelSpacing[1];
            } else if (instance.instanceAttributes.calibration) {
                var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                area = Math.PI / 4.0 * Math.abs(this.points[0].x - this.points[1].x) * Math.abs(this.points[0].y - this.points[1].y) * mmPerPixel * mmPerPixel;
            }

            if (!isNaN(area)) {
                var result;

                if ((area < 100.0) || alwaysMm) {
                    result = "Area: " + area.toFixed(2) + " mm\u00b2";
                } else {
                    result = "Area: " + (area / 100.0).toFixed(2) + " cm\u00b2";
                }

                return result;
            }

            return null;
        };

        Ellipse.prototype.volumeText = function (instance, alwaysMm, series, volume) {
            var pixelSpacingTag = instance.instanceAttributes.pixelSpacing;
            var sliceSpacing = Measurements.findSliceDistance(series, volume);
            var pixelSpacing;
            var voxelVolume;
            var numVoxels = 0;

            if (this.pixelSpacingUser) {
                pixelSpacing = this.pixelSpacingUser;
            } else if (pixelSpacingTag && pixelSpacingTag[0]) {
                pixelSpacing = pixelSpacingTag[0];
            } else if (instance.instanceAttributes.calibration) {
                pixelSpacing = instance.instanceAttributes.calibration.calibratedPixelSpacing();
            }

            voxelVolume = pixelSpacing * pixelSpacing * sliceSpacing;

            _.each(volume, function (s) {
                var rect = s.measurement;
                numVoxels += (Math.PI / 4.0 * Math.abs(rect.points[0].x - rect.points[1].x) * Math.abs(rect.points[0].y - rect.points[1].y));
            });

            var totalVolume = numVoxels * voxelVolume;

            if (totalVolume) {
                var result = [];

                if ((totalVolume < 1000.0) || alwaysMm) {
                    result.push("Volume: " + totalVolume.toFixed(2) + " mm\u00b3");
                } else {
                    result.push("Volume: " + (totalVolume / 1000.0).toFixed(2) + " cm\u00b3");
                }

                result.push("Voxel: " + pixelSpacing.toFixed(2) + " x " + pixelSpacing.toFixed(2) + " x " + sliceSpacing.toFixed(2) + " mm");

                return result;
            }

            return null;
        };

        Ellipse.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume) {
            var t1 = Transform.mapFromImage(this.points[0], trans);
            var t2 = Transform.mapFromImage(this.points[1], trans);
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;

            Rendering.drawEllipseMeasurement(context, t1, t2, style, lineWidth, 100, this.filled);

            // var text = this.areaText(renderer, instanceUID, instance, alwaysMm);
            var text = [];

            if (!hideInfo) {
                var volume;
                var volumeText;

                if (measureVolume && !this.volume) {
                    volume = Measurements.findVolume(instance, this, series);
                    if (volume.length <= 1 || !Measurements.volumeLoaded(volume, renderer)) {
                        volume = null;
                        measureVolume = false;
                    }
                }

                if (measureVolume) {
                    if (this.volume) {
                        volumeText = this.volume.sizeDescription;
                    } else {
                        volumeText = this.volumeText(instance, alwaysMm, series, volume);
                    }

                    if (volumeText) {
                        _.each(volumeText, function (s) {
                            return text.push(s);
                        });
                    }
                }

                var areaText = this.areaText(instance, alwaysMm);
                if (areaText) {
                    text.push(areaText);
                }

                if (pixelSpacing && pixelSpacing[0] && !this.hideMeasurement && text) {
                    var imageStatistics;
                    if (measureVolume) {
                        if (this.volume) {
                            imageStatistics = this.volume.stats;
                        } else {
                            imageStatistics = this.statisticsVolume(instance, instanceUID, renderer, volume);
                        }
                    } else {
                        imageStatistics = this.statistics(instance, instanceUID, renderer);
                    }

                    if (volume) {
                        var volObj = {
                            stats: imageStatistics,
                            sizeDescription: volumeText,
                            selected: this.selected,
                            slices: volume
                        };

                        _.each(volume, function (s) {
                            s.measurement.volume = volObj;
                        });

                        Measurements.updateVolumeLabel(volObj);
                    }

                    if (imageStatistics) {
                        text.push("Min: " + imageStatistics.min.toFixed(2));
                        text.push("Max: " + imageStatistics.max.toFixed(2));
                        text.push("Mean: " + imageStatistics.mean.toFixed(2));

                        if (showStdDev) {
                            text.push("SD: " + imageStatistics.stdev.toFixed(2));
                        }
                    }

                    if (this.showCreator && this.creator && this.creator.length > 0) {
                        text.push(this.creator);
                    }

                    Rendering.writeLines(context, text, Math.max(t1.x, t2.x), Math.max(t1.y, t2.y), fontSize);
                }

                if (this.label) {
                    Rendering.writeLineCentered(context, this.label, (t1.x + t2.x) / 2.0, Math.min(t1.y, t2.y) - (fontSize + 8), fontSize + 4);
                }
            }
        };

        Ellipse.prototype.distanceTo = function (imageCoords) {
            var points = Geometry.pointsForEllipse(this.points[0], this.points[1], 100);
            var lines = Geometry.pointsToLines(points);

            if (this.filled && this.inside(imageCoords)) {
                return 0.0;
            }

            return _.reduce(lines, function (min, line) {
                return Math.min(min, Geometry.distanceToLine(line, imageCoords));
            }, Number.MAX_VALUE);
        };

        Ellipse.prototype.inside = function (imageCoords) {
            var w = Math.abs(this.points[0].x - this.points[1].x);
            var h = Math.abs(this.points[0].y - this.points[1].y);

            var cx = (this.points[0].x + this.points[1].x) / 2;
            var cy = (this.points[0].y + this.points[1].y) / 2;

            var dx = (imageCoords.x - cx) / w;
            var dy = (imageCoords.y - cy) / h;

            return dx * dx + dy * dy <= 0.25;
        };

        /**
        * Convert to an annotation
        */
        Ellipse.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[8 /* Ellipse */],
                hideMeasurement: this.hideMeasurement,
                points: this.points,
                filled: this.filled
            };
        };

        Ellipse.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var areaText = this.areaText(instance, false);

            if (areaText) {
                var text = [areaText];
                var imageStatistics = this.statistics(instance, instanceUID, renderer);

                if (imageStatistics) {
                    text.push("Min: " + imageStatistics.min.toFixed(2));
                    text.push("Max: " + imageStatistics.max.toFixed(2));
                    text.push("Mean: " + imageStatistics.mean.toFixed(2));

                    if (showStdDev) {
                        text.push("SD: " + imageStatistics.stdev.toFixed(2));
                    }
                }

                var allText = text.join("\n");

                return [{
                        unformattedTextValue: allText,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [
                            Math.max(this.points[0].x, this.points[1].x),
                            Math.max(this.points[0].y, this.points[1].y)
                        ],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        Ellipse.prototype.toGraphicObjects = function (instance) {
            var width = Math.abs(this.points[0].x - this.points[1].x);
            var height = Math.abs(this.points[0].y - this.points[1].y);

            var minX = Math.min(this.points[0].x, this.points[1].x);
            var minY = Math.min(this.points[0].y, this.points[1].y);
            var maxX = Math.max(this.points[0].x, this.points[1].x);
            var maxY = Math.max(this.points[0].y, this.points[1].y);
            var midX = (this.points[0].x + this.points[1].x) / 2;
            var midY = (this.points[0].y + this.points[1].y) / 2;

            return [{
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 4,
                    graphicData: width > height ? [
                        minX, midY,
                        maxX, midY,
                        midX, minY,
                        midX, maxY
                    ] : [
                        midX, minY,
                        midX, maxY,
                        minX, midY,
                        maxX, midY
                    ],
                    graphicType: "ELLIPSE",
                    graphicFilled: this.filled
                }];
        };

        /**
        * Clone this measurement
        */
        Ellipse.prototype.clone = function () {
            return new Ellipse(Geometry.displacePoint(this.points[0], 10, 10), Geometry.displacePoint(this.points[1], 10, 10), this.filled);
        };
        return Ellipse;
    })(Rectangle);
    Measurements.Ellipse = Ellipse;

    /**
    * A measurement which draws a circle
    */
    var Circle = (function () {
        function Circle(p1, p2, filled) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            this.points = [p1, p2];
            this.filled = filled;
        }
        Circle.prototype.isNonEmpty = function () {
            return !Geometry.pointsEqual(this.points[0], this.points[1]);
        };

        Circle.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        Circle.prototype.areaText = function (renderer, instanceUID, instance, alwaysMm) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var radius = Number.NaN;

            if (this.pixelSpacingUser) {
                radius = Geometry.distanceBetween(this.points[0], this.points[1]) * this.pixelSpacingUser;
            } else if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                radius = Geometry.distanceBetween(this.points[0], this.points[1]) * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                radius = Geometry.distanceBetween2(this.points[0], this.points[1], this.ultrasoundPixelSpacing[0], this.ultrasoundPixelSpacing[1]);
            } else if (instance.instanceAttributes.calibration) {
                var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                radius = Geometry.distanceBetween(this.points[0], this.points[1]) * mmPerPixel;
            }

            if (!isNaN(radius)) {
                var result;

                if ((radius < 10.0) || alwaysMm) {
                    result = ["Radius: " + radius.toFixed(2) + " mm"];
                } else {
                    result = ["Radius: " + (radius / 10.0).toFixed(2) + " cm"];
                }

                return result;
            }

            return null;
        };

        Circle.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            var t1 = Transform.mapFromImage(this.points[0], trans);
            var t2 = Transform.mapFromImage(this.points[1], trans);

            var radius = Geometry.distanceBetween(t1, t2);

            Rendering.drawCircle(context, t1, radius, style, lineWidth, this.filled);

            Rendering.drawCross(context, style, t1);

            var text = this.areaText(renderer, instanceUID, instance, alwaysMm);

            if (!hideInfo) {
                if (!this.hideMeasurement && text) {
                    if (this.showCreator && this.creator && this.creator.length > 0) {
                        text.push(this.creator);
                    }

                    Rendering.writeLines(context, text, t2.x, t2.y, fontSize);
                }

                if (this.label) {
                    var radius = Geometry.distanceBetween(t1, t2);
                    Rendering.writeLineCentered(context, this.label, t1.x, t1.y - radius - (fontSize + 8), fontSize + 4);
                }
            }
        };

        Circle.prototype.distanceTo = function (imageCoords) {
            return Math.min(Math.abs(this.signedDistanceTo(imageCoords)), Geometry.distanceBetween(imageCoords, this.points[0]));
        };

        Circle.prototype.signedDistanceTo = function (imageCoords) {
            var radius = Geometry.distanceBetween(this.points[0], this.points[1]);

            var toCenter = Geometry.distanceBetween(imageCoords, this.points[0]);

            return toCenter - radius;
        };

        Circle.prototype.inside = function (imageCoords) {
            return this.signedDistanceTo(imageCoords) < 0;
        };

        /**
        * Convert to an annotation
        */
        Circle.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[16 /* Circle */],
                hideMeasurement: this.hideMeasurement,
                points: this.points,
                filled: this.filled
            };
        };

        Circle.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var text = this.areaText(renderer, instanceUID, instance, false).join("\n");

            if (text) {
                return [{
                        unformattedTextValue: text,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [this.points[1].x, this.points[1].y],
                        anchorPointVisibility: true
                    }];
            }

            return [];
        };

        Circle.prototype.toGraphicObjects = function (instance) {
            var radius = Geometry.distanceBetween(this.points[0], this.points[1]);
            var center = this.points[0];

            var minX = center.x - radius;
            var minY = center.y - radius;
            var maxX = center.x + radius;
            var maxY = center.y + radius;

            return [{
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: 4,
                    graphicData: [
                        minX, center.y,
                        maxX, center.y,
                        center.x, minY,
                        center.x, maxY
                    ],
                    graphicType: "ELLIPSE",
                    graphicFilled: this.filled
                }];
        };

        /**
        * Update data from a JSON object
        */
        Circle.prototype.edit = function (data) {
            this.points = data.points;
        };

        Circle.prototype.hasMoved = function () {
            return !Geometry.pointsEqual(this.originalPoints[0], this.points[0]) || !Geometry.pointsEqual(this.originalPoints[1], this.points[1]);
        };

        Circle.prototype.continueDrawing = function (imageCoords) {
            this.points[1] = imageCoords;
        };

        Circle.prototype.startDrawing = function (imageCoords) {
        };

        Circle.prototype.stopDrawing = function (imageCoords) {
            return true;
        };

        Circle.prototype.endpoints = function () {
            var _this = this;
            return [{
                    location: this.points[1],
                    move: function (newLocation) {
                        _this.points[1] = Geometry.copyPoint(newLocation);
                    },
                    reset: function () {
                        _this.points[1] = Geometry.copyPoint(_this.originalPoints[1]);
                    }
                }];
        };

        Circle.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        Circle.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalPoints = [
                Geometry.copyPoint(this.points[0]),
                Geometry.copyPoint(this.points[1])
            ];
        };

        Circle.prototype.continueMoving = function (dx, dy) {
            for (var i = 0; i < this.points.length; i++) {
                this.points[i].x = this.originalPoints[i].x + dx;
                this.points[i].y = this.originalPoints[i].y + dy;
            }
        };

        /**
        * Clone this measurement
        */
        Circle.prototype.clone = function () {
            return new Circle(Geometry.displacePoint(this.points[0], 10, 10), Geometry.displacePoint(this.points[1], 10, 10), this.filled);
        };

        Circle.prototype.statistics = function (instance, instanceUID, renderer, series) {
            return null;
        };

        Circle.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };
        return Circle;
    })();
    Measurements.Circle = Circle;

    var Text = (function (_super) {
        __extends(Text, _super);
        function Text(p1, p2, text) {
            _super.call(this, p1, p2, false);
            this.text = text;
        }
        Text.prototype.edit = function (data) {
            this.text = data.text;
            this.propagation = data.propagation;
        };

        Text.prototype.distanceTo = function (imageCoords) {
            if (Geometry.pointsEqual(this.points[0], this.points[1])) {
                return Math.min(Math.abs(this.points[0].x - imageCoords.x), Math.abs(this.points[0].y - imageCoords.y));
            } else {
                if (!Geometry.pointsEqual(this.points[0], this.points[1]) && this.inside(imageCoords)) {
                    return 0.0;
                }

                var lines = Geometry.rectangleToLines(this.points);

                var distance = _.reduce(lines, function (min, line) {
                    return Math.min(min, Geometry.distanceToLine(line, imageCoords));
                }, Number.MAX_VALUE);
                if (isNaN(distance)) {
                    return 0;
                }

                return distance;
            }
        };

        Text.prototype.areaText = function (instance) {
            return null;
        };

        Text.prototype.statistics = function (instance, instanceUID, renderer) {
            return null;
        };

        Text.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            if (Geometry.pointsEqual(this.points[0], this.points[1])) {
                var screenCoords = Transform.mapFromImage(this.points[0], trans);

                Rendering.writeLine(context, this.text, screenCoords.x, screenCoords.y, 12, style);
                if (this.showCreator && this.creator && this.creator.length > 0) {
                    Rendering.writeLine(context, this.creator, screenCoords.x, screenCoords.y + 12, 12);
                }
            } else {
                var t1 = Transform.mapFromImage(this.points[0], trans);
                var t2 = Transform.mapFromImage(this.points[1], trans);
                var top = t1.y > t2.y ? t1.y : t2.y;
                var left = t1.x < t2.x ? t1.x : t2.x;

                if (this.selected || !this.text) {
                    Rendering.drawRectangleMeasurement(context, t1, t2, style, lineWidth, this.filled, true);
                }

                Rendering.writeLineToFit(context, this.text, left, top, Math.abs(t2.x - t1.x), Math.abs(t2.y - t1.y), style);

                if (this.showCreator && this.creator && this.creator.length > 0) {
                    Rendering.writeLine(context, this.creator, Math.min(t1.x, t2.x), Math.max(t1.y, t2.y) + 2, 12);
                }
            }
        };

        Text.prototype.createAtPoint = function (text, trans) {
            // draw text annotation box in default size
            var width = Math.round(15 / Transform.getScaleX(trans));
            var height = Math.round(8 / Transform.getScaleY(trans));
            this.points[0].x -= width;
            this.points[1].x += width;
            this.points[0].y -= height;
            this.points[1].y += height;
            this.text = text;
        };

        /**
        * Convert to an annotation
        */
        Text.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[10 /* Text */],
                points: this.points,
                text: this.text
            };
        };

        Text.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            return [{
                    unformattedTextValue: this.text,
                    anchorPointAnnotationUnits: "PIXEL",
                    anchorPoint: [
                        (this.points[0].x + this.points[1].x) / 2.0,
                        (this.points[0].y + this.points[1].y) / 2.0
                    ],
                    anchorPointVisibility: true
                }];

            return [];
        };
        return Text;
    })(Rectangle);
    Measurements.Text = Text;

    var Stamp = (function (_super) {
        __extends(Stamp, _super);
        function Stamp(p1, p2, text) {
            _super.call(this, p1, p2, text);
        }
        Stamp.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev) {
            if (Geometry.pointsEqual(this.points[0], this.points[1])) {
                var screenCoords = Transform.mapFromImage(this.points[0], trans);
                Rendering.writeLine(context, this.text, screenCoords.x, screenCoords.y, 12, style);
            } else {
                var t1 = Transform.mapFromImage(this.points[0], trans);
                var t2 = Transform.mapFromImage(this.points[1], trans);
                var top = t1.y > t2.y ? t1.y : t2.y;
                var left = t1.x < t2.x ? t1.x : t2.x;

                if (this.selected || !this.text) {
                    Rendering.drawRectangleMeasurement(context, t1, t2, style, lineWidth, this.filled, true);
                }

                Rendering.writeLineToFit(context, this.text, left, top, Math.abs(t2.x - t1.x), Math.abs(t2.y - t1.y), style);
            }
        };

        Stamp.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[31 /* Stamp */],
                points: this.points,
                text: this.text
            };
        };
        return Stamp;
    })(Text);
    Measurements.Stamp = Stamp;

    var DropCircle = (function (_super) {
        __extends(DropCircle, _super);
        function DropCircle(p1, p2, filled) {
            _super.call(this, p1, p2, filled);
            this.maintainEqualAspectRatio = true;
        }
        DropCircle.prototype.distanceTo = function (imageCoords) {
            var points = Geometry.pointsForEllipse(this.points[0], this.points[1], 100);
            var lines = Geometry.pointsToLines(points);

            if (this.inside(imageCoords)) {
                return 0.0;
            }

            return _.reduce(lines, function (min, line) {
                return Math.min(min, Geometry.distanceToLine(line, imageCoords));
            }, Number.MAX_VALUE);
        };

        DropCircle.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[18 /* DropCircle */],
                hideMeasurement: this.hideMeasurement,
                points: this.points,
                filled: this.filled
            };
        };
        return DropCircle;
    })(Ellipse);
    Measurements.DropCircle = DropCircle;

    var DropSquare = (function (_super) {
        __extends(DropSquare, _super);
        function DropSquare(p1, p2, filled) {
            _super.call(this, p1, p2, filled);
            this.maintainEqualAspectRatio = true;
        }
        DropSquare.prototype.distanceTo = function (imageCoords) {
            if (this.inside(imageCoords)) {
                return 0.0;
            }

            var lines = Geometry.rectangleToLines(this.points);

            var distance = _.reduce(lines, function (min, line) {
                return Math.min(min, Geometry.distanceToLine(line, imageCoords));
            }, Number.MAX_VALUE);

            if (isNaN(distance)) {
                return 0;
            }

            return distance;
        };

        DropSquare.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[19 /* DropSquare */],
                hideMeasurement: this.hideMeasurement,
                points: this.points,
                filled: this.filled
            };
        };
        return DropSquare;
    })(Rectangle);
    Measurements.DropSquare = DropSquare;

    /**
    * An annotation consisting of a simple, closed, non-self-intersecting polygon.
    */
    var Polygon = (function () {
        function Polygon(points, closed, valid, filled) {
            /**
            * True if this measurement is filled
            */
            this.filled = false;
            /**
            * True if this is a temporary measurement, made while recording or playing a script
            */
            this.temporary = false;
            /**
            * True if this polygon is non-self-intersecting
            */
            this.valid = true;
            /**
            * The point index at which the polygon became invalid
            */
            this.invalidIndex = 0;
            /**
            * The last point to be edited
            */
            this.lastEdited = null;
            /**
            * The bounding box of the polygon
            */
            this.bounds = null;
            /**
            * True if this polygon is closed
            */
            this.closed = false;
            this.hideMeasurement = false;
            this.points = points;
            this.closed = closed;
            this.filled = filled;
            this.valid = valid;

            if (closed) {
                this.bounds = Geometry.findBoundingBox(this.points);
            }
        }
        Polygon.prototype.isNonEmpty = function () {
            if (this.closed) {
                return Geometry.polygonArea(this.points) > 1e-6;
            } else {
                return true;
            }
        };

        Polygon.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        // checks for self-intersection in O(N) time, assuming only last edited point needs to be checked
        Polygon.prototype.updateValidity = function () {
            if (this.valid && (this.lastEdited != null)) {
                if (this.points.length > 2) {
                    if (this.closed) {
                        var index = _.indexOf(this.points, this.lastEdited);

                        if (index != -1) {
                            this.bounds = Geometry.findBoundingBox(this.points);

                            var nextIndex = (index == (this.points.length - 1) ? 0 : index + 1);
                            var prevIndex = (index == 0 ? (this.points.length - 1) : index - 1);
                            var line1 = { start: this.lastEdited, end: this.points[nextIndex] };
                            var line2 = { start: this.lastEdited, end: this.points[prevIndex] };

                            for (var ctr = 0; ctr < this.points.length; ctr += 1) {
                                var nextIndex = (ctr == (this.points.length - 1) ? 0 : ctr + 1);
                                var nextLine = { start: this.points[ctr], end: this.points[nextIndex] };

                                if (!Geometry.hasCommonPoint(line1, nextLine)) {
                                    var intersect = Geometry.intersect(line1, nextLine);
                                    if (Maybe.hasValue(intersect)) {
                                        this.valid = false;
                                        break;
                                    }
                                }

                                if (!Geometry.hasCommonPoint(line2, nextLine)) {
                                    var intersect = Geometry.intersect(line2, nextLine);
                                    if (Maybe.hasValue(intersect)) {
                                        this.valid = false;
                                        break;
                                    }
                                }
                            }
                        }
                    } else {
                        var line = {
                            start: this.points[this.points.length - 2],
                            end: this.points[this.points.length - 1]
                        };

                        for (var ctr = 0; ctr < this.points.length - 1; ctr += 1) {
                            var nextLine = { start: this.points[ctr], end: this.points[ctr + 1] };

                            if (!Geometry.hasCommonPoint(line, nextLine)) {
                                var intersect = Geometry.intersect(line, nextLine);
                                if (Maybe.hasValue(intersect)) {
                                    this.invalidIndex = this.points.length - 1;
                                    this.valid = false;
                                    break;
                                }
                            }
                        }
                    }
                }

                this.lastEdited = null;
            }
        };

        Polygon.prototype.inside = function (imageCoords) {
            if (this.closed) {
                if (Geometry.insideRectangle(this.bounds, imageCoords)) {
                    return Geometry.insidePolygon(this.points, imageCoords);
                } else {
                    return false;
                }
            }

            return false;
        };

        Polygon.prototype.distanceTo = function (imageCoords) {
            var _this = this;
            if (this.filled && this.inside(imageCoords)) {
                return 0.0;
            }

            var dists = _.map(this.points, function (p, index, list) {
                var nextIndex = (index == (_this.points.length - 1) ? 0 : index + 1);
                return Geometry.distanceToLine({ start: _this.points[index], end: _this.points[nextIndex] }, imageCoords);
            });
            return _.min(dists);
        };

        Polygon.prototype.continueDrawing = function (imageCoords) {
            this.points[this.points.length - 1] = imageCoords;
        };

        Polygon.prototype.startDrawing = function (imageCoords, trans) {
            if (this.points.length == 2) {
                if (Geometry.pointsEqual(this.points[0], this.points[1])) {
                    this.points.pop();
                    this.lastEdited = this.points[0];
                }
            }

            if (!Geometry.pointsEqual(this.points[this.points.length - 1], imageCoords)) {
                this.points.push(imageCoords);
                this.lastEdited = this.points[this.points.length - 1];
            }
        };

        Polygon.prototype.stopDrawing = function (imageCoords, trans) {
            if (this.points.length > 3) {
                if (Geometry.distanceBetween(this.points[0], imageCoords) < 5.0) {
                    this.points.pop();
                    this.closed = true;
                    this.lastEdited = this.points[this.points.length - 1];
                    this.updateValidity();
                    this.bounds = Geometry.findBoundingBox(this.points);
                    return true;
                }
            }

            this.lastEdited = imageCoords;
            this.updateValidity();

            return false;
        };

        Polygon.prototype.endpoints = function () {
            var _this = this;
            return _.map(this.points, function (p, index, list) {
                return {
                    location: p,
                    move: function (newLocation) {
                        _this.lastEdited = p;
                        _this.valid = true;
                        Geometry.setPoint(p, newLocation);
                    },
                    reset: function () {
                        _this.lastEdited = p;
                        _this.valid = true;
                        Geometry.setPoint(p, _this.originalPoints[index]);
                    }
                };
            });
        };

        Polygon.prototype.startMoving = function (imageCoords) {
            this.saveOriginalEndpoints(imageCoords);
        };

        Polygon.prototype.saveOriginalEndpoints = function (imageCoords) {
            this.originalPoints = _.map(this.points, function (p) {
                return Geometry.copyPoint(p);
            });
        };

        Polygon.prototype.continueMoving = function (dx, dy) {
            var _this = this;
            _.each(this.points, function (p, index, list) {
                p.x = _this.originalPoints[index].x + dx;
                p.y = _this.originalPoints[index].y + dy;
            });

            if (this.volume) {
                _.each(this.volume.slices, function (s) {
                    s.measurement.volume = null;
                });
            }

            this.bounds = Geometry.findBoundingBox(this.points);
        };

        Polygon.prototype.areaText = function (instance, alwaysMm) {
            if (this.points.length > 2) {
                var pixelSpacing = instance.instanceAttributes.pixelSpacing;
                var area = Number.NaN;

                if (this.pixelSpacingUser) {
                    var mmPerPixel = this.pixelSpacingUser;
                    area = Geometry.polygonArea(this.points) * mmPerPixel * mmPerPixel;
                } else if (pixelSpacing && pixelSpacing[0]) {
                    var mmPerPixel = pixelSpacing[0];
                    area = Geometry.polygonArea(this.points) * mmPerPixel * mmPerPixel;
                } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                    area = Geometry.polygonArea(this.points) * this.ultrasoundPixelSpacing[0] * this.ultrasoundPixelSpacing[1];
                } else if (instance.instanceAttributes.calibration) {
                    var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                    area = Geometry.polygonArea(this.points) * mmPerPixel * mmPerPixel;
                }

                if (!isNaN(area)) {
                    var result;

                    if ((area < 100.0) || alwaysMm) {
                        result = "Area: " + area.toFixed(2) + " mm\u00b2";
                    } else {
                        result = "Area: " + (area / 100.0).toFixed(2) + " cm\u00b2";
                    }

                    return result;
                }
            }

            return "";
        };

        Polygon.prototype.volumeText = function (instance, alwaysMm, series, volume) {
            if (this.points.length > 2) {
                var pixelSpacingTag = instance.instanceAttributes.pixelSpacing;
                var sliceSpacing = Measurements.findSliceDistance(series, volume);
                var pixelSpacing;
                var voxelVolume;
                var numVoxels = 0;

                if (this.pixelSpacingUser) {
                    pixelSpacing = this.pixelSpacingUser;
                } else if (pixelSpacingTag && pixelSpacingTag[0]) {
                    pixelSpacing = pixelSpacingTag[0];
                } else if (instance.instanceAttributes.calibration) {
                    pixelSpacing = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                }

                voxelVolume = pixelSpacing * pixelSpacing * sliceSpacing;

                _.each(volume, function (s) {
                    numVoxels += Geometry.polygonArea(s.measurement.points);
                });

                var totalVolume = numVoxels * voxelVolume;

                if (totalVolume) {
                    var result = [];

                    if ((totalVolume < 1000.0) || alwaysMm) {
                        result.push("Volume: " + totalVolume.toFixed(2) + " mm\u00b3");
                    } else {
                        result.push("Volume: " + (totalVolume / 1000.0).toFixed(2) + " cm\u00b3");
                    }

                    result.push("Voxel: " + pixelSpacing.toFixed(2) + " x " + pixelSpacing.toFixed(2) + " x " + sliceSpacing.toFixed(2) + " mm");

                    return result;
                }
            }

            return null;
        };

        Polygon.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume) {
            if (this.points.length > 1) {
                var transformedPoints = _.map(this.points, function (p) {
                    return Transform.mapFromImage(p, trans);
                });

                if (this.filled) {
                    Rendering.fillPath(context, transformedPoints, style, this.closed);
                } else {
                    Rendering.drawPath(context, transformedPoints, style, 0, 0, lineWidth, this.closed);
                }

                this.updateValidity();

                if (this.closed && this.valid) {
                    var text = [];

                    if (!hideInfo) {
                        var volume;
                        var validGeometry = false;
                        var volumeText;

                        if (measureVolume && !this.volume) {
                            volume = Measurements.findVolume(instance, this, series);
                            if (volume.length <= 1 || !Measurements.volumeLoaded(volume, renderer)) {
                                volume = null;
                                measureVolume = false;
                            }
                        }

                        if (measureVolume) {
                            if (this.volume) {
                                volumeText = this.volume.sizeDescription;
                            } else {
                                volumeText = this.volumeText(instance, alwaysMm, series, volume);
                            }

                            if (volumeText) {
                                _.each(volumeText, function (s) {
                                    return text.push(s);
                                });
                                validGeometry = true;
                            }
                        }

                        var areaText = this.areaText(instance, alwaysMm);
                        if (areaText) {
                            text.push(areaText);
                            validGeometry = true;
                        }

                        if (validGeometry) {
                            var imageStatistics;
                            if (measureVolume) {
                                if (this.volume) {
                                    imageStatistics = this.volume.stats;
                                } else {
                                    imageStatistics = this.statisticsVolume(instance, instanceUID, renderer, volume);
                                }
                            } else {
                                imageStatistics = this.statistics(instance, instanceUID, renderer);
                            }

                            if (volume) {
                                var volObj = {
                                    stats: imageStatistics,
                                    sizeDescription: volumeText,
                                    selected: this.selected,
                                    slices: volume
                                };

                                _.each(volume, function (s) {
                                    s.measurement.volume = volObj;
                                });

                                Measurements.updateVolumeLabel(volObj);
                            }

                            if (imageStatistics) {
                                text.push("Min: " + imageStatistics.min.toFixed(2));
                                text.push("Max: " + imageStatistics.max.toFixed(2));
                                text.push("Mean: " + imageStatistics.mean.toFixed(2));

                                if (showStdDev) {
                                    text.push("SD: " + imageStatistics.stdev.toFixed(2));
                                }
                            }
                        }

                        if (this.showCreator && this.creator && this.creator.length > 0) {
                            text.push(this.creator);
                        }

                        var south = Transform.mapFromImage(Geometry.findSouthPoint(this.points, trans), trans);

                        Rendering.writeLinesCentered(context, text, south.x, south.y + 3, fontSize);

                        if (this.label) {
                            var north = Transform.mapFromImage(Geometry.findNorthPoint(this.points, trans), trans);
                            Rendering.writeLineCentered(context, this.label, north.x, north.y - (fontSize + 10), fontSize + 4);
                        }
                    }
                }
            }
        };

        /**
        * Convert to an annotation
        */
        Polygon.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[28 /* Polygon */],
                points: this.points,
                closed: this.closed,
                filled: this.filled,
                valid: this.valid
            };
        };

        /**
        * Update data from a JSON object
        */
        Polygon.prototype.edit = function (data) {
            this.points = data.points;
        };

        Polygon.prototype.hasMoved = function () {
            for (var i = 0; i < this.points.length; i++) {
                if (!Geometry.pointsEqual(this.points[i], this.originalPoints[i])) {
                    return true;
                }
            }
            return false;
        };

        Polygon.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            var areaText = this.areaText(instance, false);
            var textObj = [];

            if (areaText) {
                var text = [areaText];
                var imageStatistics = this.statistics(instance, instanceUID, renderer);

                if (imageStatistics) {
                    text.push("Min: " + imageStatistics.min.toFixed(2));
                    text.push("Max: " + imageStatistics.max.toFixed(2));
                    text.push("Mean: " + imageStatistics.mean.toFixed(2));

                    if (showStdDev) {
                        text.push("SD: " + imageStatistics.stdev.toFixed(2));
                    }
                }

                var allText = text.join("\n");

                var south = Geometry.findSouthPoint(this.points);
                textObj.push({
                    unformattedTextValue: allText,
                    anchorPointAnnotationUnits: "PIXEL",
                    anchorPoint: [south.x, south.y],
                    anchorPointVisibility: true
                });

                if (this.label) {
                    var north = Geometry.findNorthPoint(this.points);
                    textObj.push({
                        unformattedTextValue: this.label,
                        anchorPointAnnotationUnits: "PIXEL",
                        anchorPoint: [north.x, north.y],
                        anchorPointVisibility: true
                    });
                }
            }

            if (textObj.length) {
                return textObj;
            }

            return [];
        };

        Polygon.prototype.toGraphicObjects = function (instance) {
            var allPoints = _.flatten(_.map(this.points, function (p, index, list) {
                return [p.x, p.y];
            }));

            if (this.closed) {
                allPoints.push(this.points[0].x);
                allPoints.push(this.points[0].y);
            }

            return [{
                    graphicAnnotationUnits: "PIXEL",
                    numberOfGraphicPoints: allPoints.length / 2,
                    graphicData: allPoints,
                    graphicType: "POLYLINE",
                    graphicFilled: this.filled
                }];
        };

        /**
        * Clone this measurement
        */
        Polygon.prototype.clone = function () {
            return new Polygon(_.map(this.points, function (p) {
                return Geometry.displacePoint(p, 10, 10);
            }), this.closed, this.valid);
        };

        Polygon.prototype.statistics = function (instance, instanceUID, renderer) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                var width = instance.instanceAttributes.columns;
                var height = instance.instanceAttributes.rows;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                var min = Number.MAX_VALUE;
                var max = -Number.MAX_VALUE;
                var sum = 0.0;
                var mean = 0.0, M2 = 0.0, delta = 0.0, delta2 = 0.0;
                var count = 0.0;

                if (imageData) {
                    var minX = Math.floor(Math.min(this.bounds[0].x, this.bounds[1].x));
                    var minY = Math.floor(Math.min(this.bounds[0].y, this.bounds[1].y));
                    var maxX = Math.ceil(Math.max(this.bounds[0].x, this.bounds[1].x));
                    var maxY = Math.ceil(Math.max(this.bounds[0].y, this.bounds[1].y));

                    for (var i = minX; i < maxX; i++) {
                        for (var j = minY; j < maxY; j++) {
                            if (i > 0 && j > 0 && i < width && j < height && this.inside({ x: i, y: j })) {
                                var value = this.getPixelValue(i, j, width, height, imageData, instance);
                                count++;

                                // stdev calculated using Welford's one-pass method
                                delta = (value - mean);
                                mean += (delta / count);
                                delta2 = (value - mean);
                                M2 += (delta * delta2);

                                sum += value;
                                min = Math.min(value, min);
                                max = Math.max(value, max);
                            }
                        }
                    }

                    if (count === 0) {
                        min = max = 0.0;
                    }

                    var mean = sum / Math.max(1, count);

                    return {
                        min: min,
                        max: max,
                        mean: mean,
                        /*
                        Population (n) stdev used instead of sample (n-1) estimate.  The sample estimate is typically
                        used when it's infeasible to calculate the population variance.  This isn't the case here.  The
                        user is not using the ROI as a sample to estimate the population variance, so we should consider
                        the ROI itself as the entire population of voxels being considered.
                        */
                        stdev: (count > 0) ? Math.sqrt(M2 / count) : NaN
                    };
                }
            }

            return null;
        };

        Polygon.prototype.statisticsVolume = function (instance, instanceUID, renderer, volume) {
            var width = instance.instanceAttributes.columns;
            var height = instance.instanceAttributes.rows;
            var min = Number.MAX_VALUE;
            var max = -Number.MAX_VALUE;
            var sum = 0.0;
            var mean = 0.0, M2 = 0.0, delta = 0.0, delta2 = 0.0;
            var count = 0.0;

            for (var ctr = 0; ctr < volume.length; ctr += 1) {
                var currentInstance = volume[ctr].instance;
                var currentMeasurement = volume[ctr].measurement;
                var currentInstanceId = currentInstance.id.value + ':' + currentInstance.frameNumber.value;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(currentInstanceId, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(currentInstanceId, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                if (imageData) {
                    var minX = Math.floor(Math.min(currentMeasurement.bounds[0].x, currentMeasurement.bounds[1].x));
                    var minY = Math.floor(Math.min(currentMeasurement.bounds[0].y, currentMeasurement.bounds[1].y));
                    var maxX = Math.ceil(Math.max(currentMeasurement.bounds[0].x, currentMeasurement.bounds[1].x));
                    var maxY = Math.ceil(Math.max(currentMeasurement.bounds[0].y, currentMeasurement.bounds[1].y));

                    for (var i = minX; i < maxX; i++) {
                        for (var j = minY; j < maxY; j++) {
                            if (i > 0 && j > 0 && i < width && j < height && currentMeasurement.inside({ x: i, y: j })) {
                                var value = currentMeasurement.getPixelValue(i, j, width, height, imageData, currentInstance);
                                count++;

                                // stdev calculated using Welford's one-pass method
                                delta = (value - mean);
                                mean += (delta / count);
                                delta2 = (value - mean);
                                M2 += (delta * delta2);

                                sum += value;
                                min = Math.min(value, min);
                                max = Math.max(value, max);
                            }
                        }
                    }
                }
            }

            if (count === 0) {
                min = max = 0.0;
            }

            var mean = sum / Math.max(1, count);

            return {
                min: min,
                max: max,
                mean: mean,
                /*
                Population (n) stdev used instead of sample (n-1) estimate.  The sample estimate is typically
                used when it's infeasible to calculate the population variance.  This isn't the case here.  The
                user is not using the ROI as a sample to estimate the population variance, so we should consider
                the ROI itself as the entire population of voxels being considered.
                */
                stdev: (count > 0) ? Math.sqrt(M2 / count) : NaN
            };
        };

        Polygon.prototype.getPixelValue = function (i, j, w, h, data, instance) {
            var lowBits = data.data[(i + j * w) * 4];
            var highBits = data.data[(i + (j + h) * w) * 4];

            var raw = (highBits << 8) | lowBits;

            if (instance.instanceAttributes.signed) {
                raw = (raw & 0xffff) - 0x8000;
            }

            return raw * instance.instanceAttributes.rescaleSlope + instance.instanceAttributes.rescaleIntercept;
        };

        Polygon.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            // this is only needed in very special cases, intentionally leaving this unimplemented for now
            return null;
        };
        return Polygon;
    })();
    Measurements.Polygon = Polygon;

    var CADSR = (function (_super) {
        __extends(CADSR, _super);
        function CADSR(points, color, score, center, density, calcification, summary) {
            _super.call(this, points, true, true, false);
            this.color = color;
            this.score = score;
            this.center = center;
            this.density = density;
            this.calcification = calcification;
            this.summary = summary;
        }
        // find ROIs where text is overlapping
        CADSR.findOverlappingROILabels = function (instance, trans, start, fontSize, context) {
            var loc = start.getTextLocation(trans);
            var width = context.measureText(start.getText()).width;
            var clashing = [];

            _.each(instance.instanceAttributes.mesaurementsCADSR, function (cad) {
                var cadLoc = cad.getTextLocation(trans);
                if ((Math.abs(loc.x - cadLoc.x) < width) && (Math.abs(loc.y - cadLoc.y) < fontSize)) {
                    clashing.push(cad);
                }
            });

            if (clashing.length > 1) {
                return clashing;
            }

            return [];
        };

        CADSR.prototype.fromHex = function (hex) {
            hex = hex.replace("#", "");
            var result = /^([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
            return result ? {
                r: parseInt(result[1], 16),
                g: parseInt(result[2], 16),
                b: parseInt(result[3], 16)
            } : null;
        };

        CADSR.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume) {
            if (this.points.length > 1) {
                var transformedPoints = _.map(this.points, function (p) {
                    return Transform.mapFromImage(p, trans);
                });

                var collapsed = Math.sqrt(Math.abs(Vectors.determinant(trans))) < 0.333;
                var rgb = this.fromHex(this.color);
                style = "rgba(" + rgb.r + "," + rgb.g + "," + rgb.b + ",0.65)";
                var loc = this.getTextLocation(trans);
                var gap = 3;
                var textHeight = fontSize + 2;
                context.font = fontSize + "px" + " FuturaTL-Book";

                if (collapsed && (this.density || this.calcification)) {
                    var centerTrans = Transform.mapFromImage(this.center, trans);
                    if (this.density) {
                        Rendering.fillCircle(context, centerTrans, 5, style);
                    } else {
                        Rendering.fillRectangle(context, centerTrans, style, 5);
                    }
                } else {
                    Rendering.drawPath(context, transformedPoints, style, 0, 0, 4, true);
                }

                var clashing = CADSR.findOverlappingROILabels(instance, trans, this, fontSize, context);
                if (clashing.length) {
                    // draw stacked annotation text labels if overlapping
                    loc = clashing[0].getTextLocation(trans);
                    _.each(clashing, function (cad, index) {
                        Rendering.writeLines(context, [cad.getText(true)], loc.x, loc.y + (index * textHeight + gap) + gap, textHeight);
                    });
                } else {
                    Rendering.writeLines(context, [this.getText()], loc.x, loc.y + gap, textHeight);
                }
            }
        };

        CADSR.prototype.getText = function (describe) {
            if (typeof describe === "undefined") { describe = false; }
            var des = "";

            if (describe) {
                if (this.density) {
                    des = " (Density)";
                } else {
                    des = " (Calc) ";
                }
            }

            return "neuScore = " + this.score + des;
        };

        CADSR.prototype.getTextLocation = function (trans) {
            var south;
            var west;
            var collapsed = Math.sqrt(Math.abs(Vectors.determinant(trans))) < 0.333;

            if (collapsed && (this.density || this.calcification)) {
                var centerTrans = Transform.mapFromImage(this.center, trans);
                west = south = centerTrans;
            } else {
                south = Transform.mapFromImage(Geometry.findSouthPoint(this.points, trans), trans);
                west = Transform.mapFromImage(Geometry.findWestPoint(this.points, trans), trans);
            }

            return { x: west.x, y: south.y + 3 };
        };
        return CADSR;
    })(Polygon);
    Measurements.CADSR = CADSR;

    var Trace = (function (_super) {
        __extends(Trace, _super);
        function Trace() {
            _super.apply(this, arguments);
            this.ready = false;
        }
        Trace.prototype.startDrawing = function (imageCoords, trans) {
            var scale = trans ? trans.entries[0] : 1.0;

            if (this.lastPoint) {
                var scaledThreshold = Trace.THRESHOLD_DISTANCE / scale;
                var currentDist = Geometry.distanceBetween(this.lastPoint, imageCoords);

                if (currentDist > scaledThreshold) {
                    _super.prototype.startDrawing.call(this, imageCoords, trans);

                    if (!this.ready) {
                        var scaledTarget = Trace.TARGET_DISTANCE / scale;
                        this.ready = (Geometry.distanceBetween(this.points[0], imageCoords) > scaledTarget);
                    }

                    this.lastPoint = imageCoords;
                }
            } else {
                this.lastPoint = imageCoords;
            }
        };

        Trace.prototype.stopDrawing = function (imageCoords, trans) {
            if (this.points.length > 3) {
                var dist = Geometry.distanceBetween(this.points[0], imageCoords);
                var scale = trans ? trans.entries[0] : 1.0;
                var scaledTarget = Trace.TARGET_DISTANCE / scale;

                if (this.ready && (dist < scaledTarget)) {
                    this.closed = true;
                    this.lastEdited = this.points[this.points.length - 1];
                    this.updateValidity();
                    this.bounds = Geometry.findBoundingBox(this.points);
                    return true;
                }
            }

            return false;
        };

        Trace.prototype.distanceTo = function (imageCoords) {
            var _this = this;
            if (this.filled && this.inside(imageCoords)) {
                return 0.0;
            }

            var dists = _.map(this.points, function (p, index, list) {
                var nextIndex = (index == (_this.points.length - 1) ? 0 : index + 1);
                return Geometry.distanceToLine({ start: _this.points[index], end: _this.points[nextIndex] }, imageCoords);
            });
            return _.min(dists);
        };

        Trace.prototype.toAnnotationData = function () {
            return {
                type: Classes.MouseTool[29 /* Trace */],
                points: this.points,
                closed: true,
                filled: this.filled,
                valid: this.valid
            };
        };

        Trace.prototype.clone = function () {
            return new Trace(_.map(this.points, function (p) {
                return Geometry.displacePoint(p, 10, 10);
            }), true, this.valid);
        };
        Trace.TARGET_DISTANCE = 5.0;
        Trace.THRESHOLD_DISTANCE = 1.0;
        return Trace;
    })(Polygon);
    Measurements.Trace = Trace;

    var Area = (function () {
        function Area(points, width, height, cursorSize, color, data, area, filled) {
            if (typeof color === "undefined") { color = 0; }
            if (typeof filled === "undefined") { filled = false; }
            this.points = points;
            this.width = width;
            this.height = height;
            this.cursorSize = cursorSize;
            this.changed = true;
            this.needsStats = true;
            this.color = color;
            this.filled = filled;

            if (data) {
                this.area = Compression.RLE.decodeBase64(data);
                this.updatePathMarkers(points, false);
                this.findBounds();
            } else if (area) {
                this.area = area;
                this.updateArea();
            } else {
                this.area = new Uint8Array(this.width * this.height);
                this.bounds = [
                    { x: this.width - 1, y: this.height - 1 }, { x: 0, y: 0 }
                ];
                this.fillElement(points[0]);
            }
        }
        Area.prototype.updateArea = function (area) {
            if (area) {
                this.area = area;
            }

            this.changed = true;
            this.needsStats = true;
            this.updatePathMarkers([{ x: 0, y: 0 }, { x: this.width - 1, y: this.height - 1 }], false);
            this.findBounds();
        };

        Area.prototype.isNonEmpty = function () {
            return !!_.find(this.area, function (d) {
                return d !== 0;
            });
        };

        Area.prototype.calibratedPixelSpacing = function () {
            return Number.NaN;
        };

        Area.prototype.distanceTo = function (imageCoords) {
            var x = Math.round(imageCoords.x);
            var y = Math.round(imageCoords.y);
            var index = (y * this.width) + x;

            if (this.area[index] & Area.PATH_MARKER) {
                return 0;
            }

            return Number.MAX_VALUE;
        };

        Area.prototype.startDrawing = function (imageCoords) {
            this.erase = (this.isNonEmpty() && this.distanceTo(imageCoords) !== 0);
        };

        Area.prototype.continueDrawing = function (imageCoords) {
            if (this.lastPoint) {
                this.fillElementPath(this.lastPoint, imageCoords);
            } else {
                this.fillElement(imageCoords);
            }

            this.lastPoint = Geometry.copyPoint(imageCoords);
            this.changed = true;
            this.needsStats = false;
        };

        Area.prototype.stopDrawing = function (imageCoords) {
            this.fillElementPath(this.lastPoint, imageCoords);
            this.lastPoint = null;
            this.changed = true;
            this.needsStats = true;
            this.findBounds();
            return true;
        };

        Area.prototype.endpoints = function () {
            return null;
        };

        Area.prototype.startMoving = function (imageCoords) {
            // do nothing
        };

        Area.prototype.continueMoving = function (dx, dy) {
            // do nothing
        };

        Area.prototype.render = function (context, renderer, instanceUID, instance, series, trans, style, lineWidth, hideInfo, alwaysMm, fontSize, showStdDev, measureVolume) {
            style = Annotations.COLORS[this.color];

            if (this.changed) {
                this.lines = this.findPath();
                this.changed = false;
            }

            if (this.needsStats) {
                var stats = this.statistics(instance, instanceUID, renderer);

                if (stats) {
                    this.lastStats = stats;
                    this.needsStats = false;
                }
            }

            context.lineWidth = 1.0;
            context.strokeStyle = style;
            context.beginPath();

            _.each(this.lines, function (l) {
                var lt = Transform.mapFromImageL(l, trans);
                context.moveTo(lt.start.x, lt.start.y);
                context.lineTo(lt.end.x, lt.end.y);
            });

            context.stroke();

            if (this.filled && !this.active) {
                var rgb = this.hexToRgb(style);
                if (!this.fillData) {
                    var fillCanvas = Images.createCanvas(this.width, this.height);
                    var fillContext = fillCanvas.getContext("2d");
                    this.fillData = fillContext.getImageData(0, 0, this.width, this.height);
                }

                for (var ctrY = 0; ctrY < this.height; ctrY += 1) {
                    for (var ctrX = 0; ctrX < this.width; ctrX += 1) {
                        var index = ((ctrY * this.width) + ctrX);
                        var rgbIndex = index * 4;
                        if (this.area[index]) {
                            this.fillData.data[rgbIndex] = rgb[0];
                            this.fillData.data[rgbIndex + 1] = rgb[1];
                            this.fillData.data[rgbIndex + 2] = rgb[2];
                            this.fillData.data[rgbIndex + 3] = Area.FILL_ALPHA;
                        } else {
                            this.fillData.data[rgbIndex] = 0;
                            this.fillData.data[rgbIndex + 1] = 0;
                            this.fillData.data[rgbIndex + 2] = 0;
                            this.fillData.data[rgbIndex + 3] = 0;
                        }
                    }
                }

                context.save();
                context.transform(trans.entries[0], trans.entries[3], trans.entries[1], trans.entries[4], trans.entries[2], trans.entries[5]);
                Images.drawImageData(context, this.fillData);
                context.restore();
            }

            if (this.selected && !this.active) {
                var boundsP1 = Transform.mapFromImage(this.bounds[0], trans);
                var boundsP2 = Transform.mapFromImage({ x: this.bounds[1].x + 1, y: this.bounds[1].y + 1 }, trans);

                Rendering.drawRectangleBorder(context, boundsP1.x, boundsP1.y, boundsP2.x - boundsP1.x, boundsP2.y - boundsP1.y, "#fff", true);
            }
        };

        Area.prototype.toAnnotationData = function () {
            return {
                area: Compression.RLE.encodeBase64(this.area),
                color: this.color,
                cursorSize: this.cursorSize,
                filled: this.filled,
                height: this.height,
                points: [{ x: 0, y: 0 }, { x: this.width - 1, y: this.height - 1 }],
                type: Classes.MouseTool[32 /* Area */],
                width: this.width
            };
        };

        Area.prototype.edit = function (data) {
            // do nothing
        };

        Area.prototype.saveOriginalEndpoints = function (imageCoords) {
            // do nothing
        };

        Area.prototype.hasMoved = function () {
            return false;
        };

        Area.prototype.toTextObjects = function (renderer, instanceUID, instance, showStdDev) {
            return null;
        };

        Area.prototype.toGraphicObjects = function (instance) {
            return null;
        };

        Area.prototype.clone = function () {
            return new Area(this.points, this.width, this.height, this.cursorSize, this.color, Compression.RLE.encodeBase64(this.area));
        };

        Area.prototype.statistics = function (instance, instanceUID, renderer) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                var width = instance.instanceAttributes.columns;
                var height = instance.instanceAttributes.rows;

                var imageData = renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                var min = Number.MAX_VALUE;
                var max = -Number.MAX_VALUE;
                var sum = 0.0;
                var mean = 0.0, M2 = 0.0, delta = 0.0, delta2 = 0.0;
                var count = 0.0;

                if (imageData) {
                    var minX = Math.floor(Math.min(this.bounds[0].x, this.bounds[1].x));
                    var minY = Math.floor(Math.min(this.bounds[0].y, this.bounds[1].y));
                    var maxX = Math.ceil(Math.max(this.bounds[0].x, this.bounds[1].x));
                    var maxY = Math.ceil(Math.max(this.bounds[0].y, this.bounds[1].y));

                    for (var i = minX; i <= maxX; i++) {
                        for (var j = minY; j <= maxY; j++) {
                            var index = (j * this.width) + i;
                            if (i > 0 && j > 0 && i < width && j < height && (this.area[index] & Area.PATH_MARKER)) {
                                var value = this.getPixelValue(i, j, width, height, imageData, instance);
                                count++;

                                // stdev calculated using Welford's one-pass method
                                delta = (value - mean);
                                mean += (delta / count);
                                delta2 = (value - mean);
                                M2 += (delta * delta2);

                                sum += value;
                                min = Math.min(value, min);
                                max = Math.max(value, max);
                            }
                        }
                    }

                    if (count === 0) {
                        min = max = 0.0;
                    }

                    var mean = sum / Math.max(1, count);

                    return {
                        min: min,
                        max: max,
                        mean: mean,
                        /*
                        Population (n) stdev used instead of sample (n-1) estimate.  The sample estimate is typically
                        used when it's infeasible to calculate the population variance.  This isn't the case here.  The
                        user is not using the ROI as a sample to estimate the population variance, so we should consider
                        the ROI itself as the entire population of voxels being considered.
                        */
                        stdev: (count > 0) ? Math.sqrt(M2 / count) : NaN
                    };
                }
            }

            return null;
        };

        Area.prototype.getPixelValue = function (i, j, w, h, data, instance) {
            var lowBits = data.data[(i + j * w) * 4];
            var highBits = data.data[(i + (j + h) * w) * 4];

            var raw = (highBits << 8) | lowBits;

            if (instance.instanceAttributes.signed) {
                raw = (raw & 0xffff) - 0x8000;
            }

            return raw * instance.instanceAttributes.rescaleSlope + instance.instanceAttributes.rescaleIntercept;
        };

        Area.prototype.getAllPixelValues = function (instance, instanceUID, renderer) {
            return null;
        };

        Area.prototype.areaText = function (instance, alwaysMm) {
            var pixelSpacing = instance.instanceAttributes.pixelSpacing;
            var area = Number.NaN;

            if (this.pixelSpacingUser) {
                var mmPerPixel = this.pixelSpacingUser;
                area = this.findCount() * mmPerPixel * mmPerPixel;
            } else if (pixelSpacing && pixelSpacing[0]) {
                var mmPerPixel = pixelSpacing[0];
                area = this.findCount() * mmPerPixel * mmPerPixel;
            } else if (this.ultrasoundPixelSpacing && (this.ultrasoundPixelSpacing.length == 2)) {
                area = this.findCount() * this.ultrasoundPixelSpacing[0] * this.ultrasoundPixelSpacing[1];
            } else if (instance.instanceAttributes.calibration) {
                var mmPerPixel = instance.instanceAttributes.calibration.calibratedPixelSpacing();
                area = this.findCount() * mmPerPixel * mmPerPixel;
            }

            if (!isNaN(area)) {
                var result;

                if ((area < 100.0) || alwaysMm) {
                    result = area.toFixed(2) + " mm\u00b2";
                } else {
                    result = (area / 100.0).toFixed(2) + " cm\u00b2";
                }

                return result;
            }
        };

        Area.prototype.hexToRgb = function (hexStr) {
            var hex = parseInt(hexStr.substring(1), 16);
            var r = (hex >> 16) & 255;
            var g = (hex >> 8) & 255;
            var b = hex & 255;
            return [r, g, b];
        };

        Area.prototype.findCount = function () {
            var count = 0;
            for (var ctrY = this.bounds[0].y; ctrY <= this.bounds[1].y; ctrY += 1) {
                for (var ctrX = this.bounds[0].x; ctrX <= this.bounds[1].x; ctrX += 1) {
                    var index = (ctrY * this.width) + ctrX;
                    if (this.area[index] & Area.PATH_MARKER) {
                        count++;
                    }
                }
            }

            return count;
        };

        // get bounds of paint element
        Area.prototype.findPaintElementArea = function (imageCoords) {
            var center = { x: Math.round(imageCoords.x) + 0.5, y: Math.round(imageCoords.y) + 0.5 };

            return [
                {
                    x: Math.max(0, Math.floor(center.x - this.cursorSize - 1)),
                    y: Math.max(0, Math.floor(center.y - this.cursorSize - 1))
                },
                {
                    x: Math.min(this.width, Math.ceil(center.x + this.cursorSize + 1)),
                    y: Math.min(this.height, Math.ceil(center.y + this.cursorSize + 1))
                }
            ];
        };

        // fill within the paint element at the specified location
        Area.prototype.fillElement = function (imageCoords, update) {
            if (typeof update === "undefined") { update = true; }
            var center = { x: Math.round(imageCoords.x) + 0.5, y: Math.round(imageCoords.y) + 0.5 };
            var fillValue = this.erase ? 0 : Area.PATH_MARKER;

            var paintArea = this.findPaintElementArea(imageCoords);
            for (var ctrY = paintArea[0].y; ctrY <= paintArea[1].y; ctrY += 1) {
                for (var ctrX = paintArea[0].x; ctrX <= paintArea[1].x; ctrX += 1) {
                    if (Geometry.distanceBetween(center, { x: ctrX, y: ctrY }) < this.cursorSize) {
                        var index = (ctrY * this.width) + ctrX;
                        this.area[index] = fillValue;
                    }
                }
            }

            if (update) {
                this.updatePathMarkers(paintArea);
            }
        };

        // fill within paint element between two points
        Area.prototype.fillElementPath = function (start, end) {
            var pathShape = Geometry.createRectanglePath(start, end, this.cursorSize * 2);
            var startArea = this.findPaintElementArea(start);
            var endArea = this.findPaintElementArea(end);
            var paintArea = [
                {
                    x: Math.min(startArea[0].x, endArea[0].x),
                    y: Math.min(startArea[0].y, endArea[0].y)
                },
                {
                    x: Math.max(startArea[1].x, endArea[1].x),
                    y: Math.max(startArea[1].y, endArea[1].y)
                }
            ];

            var fillValue = this.erase ? 0 : Area.PATH_MARKER;

            for (var ctrY = paintArea[0].y; ctrY <= paintArea[1].y; ctrY += 1) {
                for (var ctrX = paintArea[0].x; ctrX <= paintArea[1].x; ctrX += 1) {
                    if (Geometry.insidePolygon(pathShape, { x: ctrX, y: ctrY })) {
                        var index = (ctrY * this.width) + ctrX;
                        this.area[index] = fillValue;
                    }
                }
            }

            this.fillElement(start, false);
            this.fillElement(end, false);

            this.updatePathMarkers(paintArea);
        };

        Area.prototype.findBounds = function () {
            var minX = Number.MAX_VALUE;
            var minY = Number.MAX_VALUE;
            var maxX = Number.MIN_VALUE;
            var maxY = Number.MIN_VALUE;
            var found = false;

            for (var ctrY = 0; ctrY < this.height; ctrY += 1) {
                for (var ctrX = 0; ctrX < this.width; ctrX += 1) {
                    var index = (ctrY * this.width) + ctrX;
                    if (this.area[index] & Area.PATH_MARKER) {
                        if (!found) {
                            found = true;
                            minX = maxX = ctrX;
                            minY = maxY = ctrY;
                        } else {
                            if (ctrX < minX) {
                                minX = ctrX;
                            } else if (ctrX > maxX) {
                                maxX = ctrX;
                            }

                            if (ctrY < minY) {
                                minY = ctrY;
                            } else if (ctrY > maxY) {
                                maxY = ctrY;
                            }
                        }
                    }
                }
            }

            if (found) {
                this.bounds = [
                    { x: minX, y: minY },
                    { x: maxX, y: maxY }
                ];
            } else {
                this.bounds = [
                    { x: 0, y: 0 },
                    { x: 0, y: 0 }
                ];
            }
        };

        // update the path flags
        Area.prototype.updatePathMarkers = function (area, updateBounds) {
            if (typeof updateBounds === "undefined") { updateBounds = true; }
            for (var ctrY = area[0].y; ctrY < area[1].y; ctrY += 1) {
                for (var ctrX = area[0].x; ctrX < area[1].x; ctrX += 1) {
                    var index = (ctrY * this.width) + ctrX;
                    var indexUp = ((ctrY - 1) * this.width) + ctrX;
                    var indexDown = ((ctrY + 1) * this.width) + ctrX;
                    var indexLeft = (ctrY * this.width) + ctrX - 1;
                    var indexRight = (ctrY * this.width) + ctrX + 1;

                    if ((this.area[index] & Area.PATH_MARKER) && (ctrY >= 0) && !(this.area[indexUp] & Area.PATH_MARKER)) {
                        this.area[index] |= Area.PATH_TOP;
                    } else {
                        this.area[index] &= ~Area.PATH_TOP;
                    }

                    if ((this.area[index] & Area.PATH_MARKER) && (ctrY < this.height) && !(this.area[indexDown] & Area.PATH_MARKER)) {
                        this.area[index] |= Area.PATH_BOTTOM;
                    } else {
                        this.area[index] &= ~Area.PATH_BOTTOM;
                    }

                    if ((this.area[index] & Area.PATH_MARKER) && (ctrX >= 0) && !(this.area[indexLeft] & Area.PATH_MARKER)) {
                        this.area[index] |= Area.PATH_LEFT;
                    } else {
                        this.area[index] &= ~Area.PATH_LEFT;
                    }

                    if ((this.area[index] & Area.PATH_MARKER) && (ctrX < this.width) && !(this.area[indexRight] & Area.PATH_MARKER)) {
                        this.area[index] |= Area.PATH_RIGHT;
                    } else {
                        this.area[index] &= ~Area.PATH_RIGHT;
                    }
                }
            }

            // temporarily update bounds
            if (updateBounds) {
                this.bounds[0].x = Math.min(this.bounds[0].x, area[0].x);
                this.bounds[0].y = Math.min(this.bounds[0].y, area[0].y);
                this.bounds[1].x = Math.max(this.bounds[1].x, area[1].x);
                this.bounds[1].y = Math.max(this.bounds[1].y, area[1].y);
            }
        };

        // create the path as (*unordered*) line segments
        Area.prototype.findPath = function () {
            var lines = [];

            for (var ctrY = this.bounds[0].y; ctrY <= this.bounds[1].y; ctrY += 1) {
                for (var ctrX = this.bounds[0].x; ctrX <= this.bounds[1].x; ctrX += 1) {
                    var index = (ctrY * this.width) + ctrX;

                    if (this.area[index] & Area.PATH_TOP) {
                        lines.push({ start: { x: ctrX, y: ctrY }, end: { x: ctrX + 1, y: ctrY } });
                    }

                    if (this.area[index] & Area.PATH_BOTTOM) {
                        lines.push({ start: { x: ctrX, y: ctrY + 1 }, end: { x: ctrX + 1, y: ctrY + 1 } });
                    }

                    if (this.area[index] & Area.PATH_LEFT) {
                        lines.push({ start: { x: ctrX, y: ctrY }, end: { x: ctrX, y: ctrY + 1 } });
                    }

                    if (this.area[index] & Area.PATH_RIGHT) {
                        lines.push({ start: { x: ctrX + 1, y: ctrY }, end: { x: ctrX + 1, y: ctrY + 1 } });
                    }
                }
            }

            return lines;
        };
        Area.PATH_TOP = 2;
        Area.PATH_BOTTOM = 4;
        Area.PATH_LEFT = 8;
        Area.PATH_RIGHT = 16;
        Area.PATH_MARKER = 1;
        Area.ELEMENT_RADIUS_RATIO = 0.025;
        Area.ELEMENT_RADIUS_MIN = 3;
        Area.FILL_ALPHA = 100;
        return Area;
    })();
    Measurements.Area = Area;

    /**
    * Create a new measurement with the specified type
    */
    function createMeasurement(tool, imageCoords, terminology, hideMeasurement, calibration, instance, color, drop_shape_width) {
        var result;

        var half_width = 5;
        if (Classes.MouseTools.isDropMeasurementTool(tool)) {
            if (instance) {
                var full_width = (drop_shape_width > 0) ? drop_shape_width : 10;
                var pixelSpacing = instance.instanceAttributes.pixelSpacing;
                if (pixelSpacing && pixelSpacing[0]) {
                    half_width = (full_width / pixelSpacing[0]) / 2.0;
                }
            }
        }

        switch (tool) {
            case 5 /* Measure */:
            case 25 /* LineAnnotate */:
            case 30 /* CalibrateLine */:
                result = new LineMeasurement(imageCoords, imageCoords, false);
                break;
            case 33 /* ProstateTool */:
                result = new ProstateTool(imageCoords, imageCoords);
                break;
            case 12 /* Arrow */:
            case 22 /* ArrowAnnotate */:
                result = new LineMeasurement(imageCoords, imageCoords, true);
                break;
            case 13 /* Angle */:
                result = new Angle([imageCoords, imageCoords]);
                break;
            case 7 /* CobbAngle */:
                result = new Cobb({ start: imageCoords, end: imageCoords }, null);
                break;
            case 6 /* Rectangle */:
            case 26 /* RectangleAnnotate */:
                result = new Rectangle(imageCoords, imageCoords, false);
                break;
            case 8 /* Ellipse */:
            case 24 /* EllipseAnnotate */:
                result = new Ellipse(imageCoords, imageCoords, false);
                break;
            case 16 /* Circle */:
                result = new Circle(imageCoords, imageCoords, false);
                break;
            case 10 /* Text */:
                result = new Text(imageCoords, imageCoords, "");
                break;
            case 31 /* Stamp */:
                result = new Stamp(imageCoords, imageCoords);
                break;
            case 18 /* DropCircle */:
            case 23 /* CircleAnnotate */:
                result = new DropCircle({ x: imageCoords.x - half_width, y: imageCoords.y - half_width }, { x: imageCoords.x + half_width, y: imageCoords.y + half_width }, false);
                break;
            case 19 /* DropSquare */:
            case 27 /* SquareAnnotate */:
                result = new DropSquare({ x: imageCoords.x - half_width, y: imageCoords.y - half_width }, { x: imageCoords.x + half_width, y: imageCoords.y + half_width }, false);
                break;
            case 20 /* OrthoAxes */:
                result = new OrthogonalAxes([imageCoords, imageCoords, imageCoords, imageCoords]);
                break;
            case 21 /* FemoralHead */:
                result = new FemoralHead([imageCoords, imageCoords]);
                break;
            case 28 /* Polygon */:
                result = new Polygon([imageCoords, imageCoords], false, true, false);
                break;
            case 29 /* Trace */:
                result = new Trace([imageCoords], false, true, false);
                break;
            case 32 /* Area */:
                var length = Math.max(instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                var cursorSize = Math.max(Area.ELEMENT_RADIUS_MIN, Math.round(length * Area.ELEMENT_RADIUS_RATIO));
                result = new Area([imageCoords], instance.instanceAttributes.columns, instance.instanceAttributes.rows, cursorSize, color);
                break;
            default:
                throw new Classes.InvalidArgumentException("Invalid mouse tool selection");
        }

        if (result) {
            result.editable = true;
            result.hideMeasurement = hideMeasurement;
            result.calibration = calibration;

            if (result.calibration) {
                instance.instanceAttributes.calibration = result;
            }
        }

        return result;
    }
    Measurements.createMeasurement = createMeasurement;

    /**
    * Select the measurement closest to the specified point
    */
    function select(tool, measurements, color, imageCoords, epsilon, isDropTool) {
        var closestEndpoint = null;
        var closestEndpointDistance = Number.MAX_VALUE;

        var closestMeasurement = null;
        var closestDistance = Number.MAX_VALUE;

        _.each(measurements, function (measurement) {
            if (!(measurement instanceof Trace) && !(measurement instanceof Area)) {
                _.each(measurement.endpoints(), function (endpoint) {
                    var endpointDistance = Geometry.distanceBetween(endpoint.location, imageCoords);

                    if (endpointDistance < closestEndpointDistance) {
                        closestEndpointDistance = endpointDistance;
                        closestEndpoint = { endpoint: endpoint, measurement: measurement };
                    }
                });
            }

            var distance = measurement.distanceTo(imageCoords);

            if (measurement instanceof Area) {
                if (color !== measurement.color) {
                    distance = Number.MAX_VALUE;
                }
            }

            if (distance < closestDistance) {
                closestDistance = distance;
                closestMeasurement = measurement;
            }
        });

        var haveMeasurement = closestMeasurement !== null && closestDistance < epsilon;
        var haveEndpoint = closestEndpoint !== null && ((closestEndpointDistance < epsilon) && (isDropTool ? (closestEndpointDistance < epsilon / 2.5) : true));

        if (!haveMeasurement && Classes.MouseTools.isPaintTool(tool)) {
            var found = _.find(measurements, function (m) {
                return (m instanceof Measurements.Area) && (color === m.color);
            });
            if (found) {
                haveMeasurement = true;
                closestMeasurement = found;
            }
        }

        if (haveEndpoint && closestEndpoint.measurement.editable) {
            closestEndpoint.measurement.selected = true;
            if (closestEndpoint.measurement.volume) {
                closestEndpoint.measurement.volume.selected = true;
            }
            return { endpoint: closestEndpoint.endpoint, measurement: closestEndpoint.measurement };
        } else if (haveMeasurement) {
            closestMeasurement.selected = true;
            if (closestMeasurement.volume) {
                closestMeasurement.volume.selected = true;
            }
            closestMeasurement.startMoving(imageCoords);
            return { measurement: closestMeasurement };
        } else {
            if (tool == 30 /* CalibrateLine */) {
                var calibrationMeasurement;
                _.each(measurements, function (measurement) {
                    if (!calibrationMeasurement && measurement.calibration) {
                        calibrationMeasurement = measurement;
                    }
                });

                if (calibrationMeasurement) {
                    calibrationMeasurement.selected = true;
                    return { endpoint: calibrationMeasurement.endpoints()[0], measurement: calibrationMeasurement };
                }
            }

            return {};
        }
    }
    Measurements.select = select;

    function findSliceDistance(series, slices) {
        var spacing;
        _.each(slices, function (s) {
            if (s.measurement.sliceSpacingUser && !spacing) {
                spacing = s.measurement.sliceSpacingUser;
            }
        });

        if (spacing) {
            return spacing;
        }

        // basic validation checks, is this volumetric data?
        if (slices.length <= 1) {
            return Number.NaN;
        }

        var firstOrientation = null;
        var valid = true;

        _.each(slices, function (s) {
            var orientation = s.instance.instanceAttributes.imageOrientationPatient;
            var position = s.instance.instanceAttributes.imagePositionPatient;

            if (valid && orientation && position && orientation.length === 6 && position.length === 3) {
                if (!firstOrientation) {
                    firstOrientation = s.instance.instanceAttributes.imageOrientationPatient;
                } else {
                    var nextOrientation = s.instance.instanceAttributes.imageOrientationPatient;
                    valid = (valid && Math.abs(firstOrientation[0] - nextOrientation[0]) < 1e-6 && Math.abs(firstOrientation[1] - nextOrientation[1]) < 1e-6 && Math.abs(firstOrientation[2] - nextOrientation[2]) < 1e-6 && Math.abs(firstOrientation[3] - nextOrientation[3]) < 1e-6 && Math.abs(firstOrientation[4] - nextOrientation[4]) < 1e-6 && Math.abs(firstOrientation[5] - nextOrientation[5]) < 1e-6);
                }
            } else {
                valid = false;
            }
        });

        if (!valid) {
            return Number.NaN;
        }

        // calculate distance difference between two adjacent slices along slice normal
        var normal = Vectors.toArray(SeriesGeometry.normalPlaneForInstance(slices[0].instance.instanceAttributes).n);
        var first = slices[0].instance.instanceAttributes.imagePositionPatient;
        var second = slices[1].instance.instanceAttributes.imagePositionPatient;
        var firstDistance = 0;
        var secondDistance = 0;

        for (var i = 0; i < 3; i++) {
            firstDistance += normal[i] * first[i];
            secondDistance += normal[i] * second[i];
        }

        return Math.abs(firstDistance - secondDistance);
    }
    Measurements.findSliceDistance = findSliceDistance;

    function updateVolumeLabel(volume) {
        var label = "";

        _.each(volume.slices, function (s) {
            if (!label && s.measurement.label) {
                label = s.measurement.label;
            }
        });

        _.each(volume.slices, function (s) {
            s.measurement.label = label;
        });
    }
    Measurements.updateVolumeLabel = updateVolumeLabel;

    function volumeLoaded(slices, renderer) {
        var loadedCount = 0;

        _.each(slices, function (s) {
            var nextInstanceId = s.instance.id.value + ':' + s.instance.frameNumber.value;
            var imageData = renderer.visit({
                visitCanvasRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(nextInstanceId, 2 /* Diagnostic */);

                    if (imageElement) {
                        return Images.getImageData(imageElement.imageElement);
                    }

                    return null;
                },
                visitWebGLRenderer: function (renderer) {
                    var imageElement = renderer.imageElements.get(nextInstanceId, 2 /* Diagnostic */);

                    if (imageElement) {
                        return Images.getImageData(imageElement.imageElement);
                    }

                    return null;
                }
            }, null);

            if (imageData) {
                loadedCount += 1;
            }
        });

        return loadedCount === slices.length;
    }
    Measurements.volumeLoaded = volumeLoaded;

    /**
    * Find consecutive measurement annotations that form a volume.
    * @param instance
    * @param measurement
    * @param series
    */
    function findVolume(instance, measurement, series) {
        var volume = [];

        if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
            volume.push({ instance: instance, measurement: measurement });
            var startBox = Geometry.findBoundingBox(measurement.points);
            var startIndex = _.indexOf(series.instances, instance);
            var box = startBox;

            // look for intersecting measurements on current instance; no volume, if found
            var nextMeasurements = instance.instanceAttributes.measurements;
            if (nextMeasurements) {
                var intersecting = [];
                for (var ctrM = 0; ctrM < nextMeasurements.length; ctrM += 1) {
                    var nextMeasurement = nextMeasurements[ctrM];
                    if (nextMeasurement !== measurement) {
                        if (nextMeasurement.toAnnotationData().type == measurement.toAnnotationData().type) {
                            var nextBox = Geometry.findBoundingBox(nextMeasurement.points);
                            if (Geometry.boundBoxesIntersect(box, nextBox)) {
                                intersecting.push(nextMeasurement);
                                if (nextMeasurement.volume) {
                                    _.each(nextMeasurement.volume.slices, function (s) {
                                        s.measurement.volume = null;
                                    });
                                }
                            }
                        }
                    }
                }

                if (intersecting.length > 0) {
                    return volume;
                }
            }

            for (var ctr = startIndex + 1; ctr < series.instances.length; ctr += 1) {
                var nextInstance = series.instances[ctr];
                var startCount = volume.length;
                if (WindowLevelPresets.shouldUse16BitWindowLevel(nextInstance)) {
                    var nextMeasurements = nextInstance.instanceAttributes.measurements;
                    if (nextMeasurements) {
                        var intersecting = [];
                        for (var ctrM = 0; ctrM < nextMeasurements.length; ctrM += 1) {
                            var nextMeasurement = nextMeasurements[ctrM];
                            if (nextMeasurement.toAnnotationData().type == measurement.toAnnotationData().type) {
                                var nextBox = Geometry.findBoundingBox(nextMeasurement.points);
                                if (Geometry.boundBoxesIntersect(box, nextBox)) {
                                    intersecting.push(nextMeasurement);
                                }
                            }
                        }

                        if (intersecting.length === 1) {
                            volume.push({ instance: nextInstance, measurement: intersecting[0] });
                            box = Geometry.findBoundingBox(intersecting[0].points);
                        }
                    }
                }

                if (startCount === volume.length) {
                    break;
                }
            }

            // look for contiguous intersecting measurements below; add to volume, if found
            box = startBox;
            for (var ctr = startIndex - 1; ctr >= 0; ctr -= 1) {
                var nextInstance = series.instances[ctr];
                var startCount = volume.length;

                if (WindowLevelPresets.shouldUse16BitWindowLevel(nextInstance)) {
                    var nextMeasurements = nextInstance.instanceAttributes.measurements;
                    if (nextMeasurements) {
                        var intersecting = [];
                        for (var ctrM = 0; ctrM < nextMeasurements.length; ctrM += 1) {
                            var nextMeasurement = nextMeasurements[ctrM];
                            if (nextMeasurement.toAnnotationData().type == measurement.toAnnotationData().type) {
                                var nextBox = Geometry.findBoundingBox(nextMeasurement.points);
                                if (Geometry.boundBoxesIntersect(box, nextBox)) {
                                    intersecting.push(nextMeasurement);
                                }
                            }
                        }

                        if (intersecting.length === 1) {
                            volume.push({ instance: nextInstance, measurement: intersecting[0] });
                            box = Geometry.findBoundingBox(intersecting[0].points);
                        }
                    }
                }

                if (startCount === volume.length) {
                    break;
                }
            }
        }

        return volume;
    }
    Measurements.findVolume = findVolume;
})(Measurements || (Measurements = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders a one-pixel border
    */
    var Border = (function () {
        function Border(selectedSubject, linkedSubject) {
            this.selectedSubject = selectedSubject;
            this.linkedSubject = linkedSubject;
        }
        Border.prototype.render = function (context, instanceUID, instance) {
            if (this.selectedSubject.read()) {
                Rendering.drawRectangleBorder(context, 1.5, 1.5, context.canvas.width - 3, context.canvas.height - 3, '#00ffff');
            } else if (this.linkedSubject.read()) {
                Rendering.drawRectangleBorder(context, 1.5, 1.5, context.canvas.width - 3, context.canvas.height - 3, '#ffff00');
            } else {
                Rendering.drawRectangleBorder(context, 1.5, 1.5, context.canvas.width - 3, context.canvas.height - 3, '#ffffff');
            }
        };
        return Border;
    })();
    Layers.Border = Border;
})(Layers || (Layers = {}));
///<reference path='../libs/Subject.ts' />
var Layers;
(function (Layers) {
    (function (Cursor) {
        Cursor[Cursor["Blank"] = 0] = "Blank";
        Cursor[Cursor["Crosshair"] = 1] = "Crosshair";
        Cursor[Cursor["Paint"] = 2] = "Paint";
    })(Layers.Cursor || (Layers.Cursor = {}));
    var Cursor = Layers.Cursor;

    /**
    * A layer which renders image attributes as text
    */
    var CursorLayer = (function () {
        function CursorLayer(visible, transformManager, color) {
            var _this = this;
            this.cursor = 0 /* Blank */;
            this.mousePosition = { x: 0, y: 0 };
            this.mousePositionImage = { x: 0, y: 0 };
            this.visible = visible;
            this.transformManager = transformManager;
            this.color = color;

            Subjects.listen(visible, function (value) {
                _this.clear();
            });
        }
        CursorLayer.prototype.clear = function (context) {
            context = context || this.lastUsedContext;

            if (context) {
                context.clearRect(0, 0, context.canvas.width, context.canvas.height);
            }
        };

        CursorLayer.prototype.render = function (context, instanceUID, instance) {
            if (this.visible.read() && (this.cursor != 0 /* Blank */)) {
                this.lastUsedContext = context;
                this.clear();

                if (this.cursor === 1 /* Crosshair */ || !instance) {
                    Rendering.drawCrosshair(context, this.mousePosition, 12, "#FFF", 2);
                } else if (this.cursor === 2 /* Paint */) {
                    var trans = this.transformManager.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                    var scale = trans.entries[0];
                    var length = Math.max(instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                    var cursorSize = Math.max(Measurements.Area.ELEMENT_RADIUS_MIN, Math.round(length * Measurements.Area.ELEMENT_RADIUS_RATIO));
                    Rendering.drawPaintElement(context, { x: this.mousePosition.x + scale, y: this.mousePosition.y + scale }, cursorSize * scale - scale, Annotations.COLORS[this.color.read()]);
                }
            }
        };
        return CursorLayer;
    })();
    Layers.CursorLayer = CursorLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders an error message
    */
    var ErrorLayer = (function () {
        function ErrorLayer(terminology) {
            this.terminology = terminology;
        }
        ErrorLayer.prototype.render = function (context, instanceUID, instance, imageType) {
            if (instance.instanceAttributes.mostRecentHiResLoadFailed) {
                context.save();

                context.shadowColor = "#808080";

                context.font = "100px FontAwesome";
                context.fillStyle = "#ffffff";
                context.fillText("\uf071", context.canvas.width / 2 - 50, context.canvas.height / 2 + 30);

                Rendering.writeLineCentered(context, this.terminology.lookup(Terminology.Terms.ImageLoadingErrorLine1), context.canvas.width / 2, context.canvas.height / 2 + 40, 14);
                Rendering.writeLineCentered(context, this.terminology.lookup(Terminology.Terms.ImageLoadingErrorLine2), context.canvas.width / 2, context.canvas.height / 2 + 55, 14);

                context.restore();
            }
        };
        return ErrorLayer;
    })();
    Layers.ErrorLayer = ErrorLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders a progress bar for the percent of images loaded
    */
    var ProgressBarLayer = (function () {
        function ProgressBarLayer() {
        }
        ProgressBarLayer.prototype.render = function (context, instanceUID, instance) {
            // Progress bar for images
            var imageDataLoaded = instance.seriesAttributes.imageDataLoaded.read();

            if (imageDataLoaded < instance.seriesAttributes.instanceCount) {
                var fraction = imageDataLoaded / instance.seriesAttributes.instanceCount;

                Rendering.drawPath(context, [{ x: 5, y: context.canvas.height - 5 }, { x: 5 + (context.canvas.width - 10) * fraction, y: context.canvas.height - 5 }], "#00ffff", 0, 0, 2);
            }
        };
        return ProgressBarLayer;
    })();
    Layers.ProgressBarLayer = ProgressBarLayer;
})(Layers || (Layers = {}));
var Layers;
(function (Layers) {
    /**
    * A layer which renders presentation state data
    */
    var PresentationStateLayer = (function () {
        function PresentationStateLayer(visible, gspsInfoVisible, hiddenGSPSLayers, highlightedGraphic, transform) {
            this.visible = visible;
            this.gspsInfoVisible = gspsInfoVisible;
            this.highlightedGraphic = highlightedGraphic;
            this.hiddenGSPSLayers = hiddenGSPSLayers;
            this.transform = transform;
        }
        PresentationStateLayer.prototype.toScreenCoords = function (points, trans) {
            var result = [];

            for (var i = 0; i < points.length; i += 2) {
                var imageCoords = {
                    x: points[i],
                    y: points[i + 1]
                };

                result.push(Transform.mapFromImage(imageCoords, trans));
            }

            return result;
        };

        PresentationStateLayer.prototype.renderGraphicObject = function (context, instance, graphicObject, trans) {
            if (graphicObject.graphicAnnotationUnits === "PIXEL") {
                var data = this.toScreenCoords(graphicObject.graphicData, trans);

                var color = PresentationStateLayer.GRAPHIC_COLOR;
                var highlighted = this.highlightedGraphic.read();

                if (this.gspsInfoVisible.read() && highlighted && (highlighted.graphicObject === graphicObject)) {
                    color = PresentationStateLayer.GRAPHIC_HIGHLIGHT_COLOR;
                }

                switch (graphicObject.graphicType) {
                    case "POINT":
                    case "MULTIPOINT":
                        _.each(data, function (point) {
                            Rendering.drawCross(context, color, point);
                        });
                        break;
                    case "POLYLINE":
                        Rendering.drawPathMeasurement(context, data, color, 1.5, graphicObject.graphicFilled, false);
                        break;
                    case "INTERPOLATED":
                        var closed = Geometry.pointsEqual(data[0], data[data.length - 1]);

                        Rendering.drawInterpolatedPath(context, data, color, 1.5, 10, closed, graphicObject.graphicFilled, false);
                        break;
                    case "CIRCLE":
                        if (data.length == 2) {
                            var center = data[0];
                            var radius = Geometry.distanceBetween(data[1], center);

                            var x1 = center.x - radius;
                            var y1 = center.y - radius;
                            var x2 = center.x + radius;
                            var y2 = center.y + radius;

                            Rendering.drawEllipseMeasurement(context, { x: x1, y: y1 }, { x: x2, y: y2 }, color, 1.5, 100, graphicObject.graphicFilled, false);
                        }
                        break;
                    case "ELLIPSE":
                        if (data.length == 4) {
                            Rendering.drawEllipseMeasurementFromAxes(context, { start: data[0], end: data[1] }, { start: data[2], end: data[3] }, color, 1.5, 100, graphicObject.graphicFilled, false);
                        }
                        break;
                }
            }
        };

        PresentationStateLayer.prototype.renderTextObject = function (context, instance, textObject, trans) {
            if (textObject.anchorPoint) {
                if (textObject.anchorPointAnnotationUnits === "PIXEL") {
                    var points = this.toScreenCoords(textObject.anchorPoint, trans);

                    if (points.length > 0) {
                        if (textObject.anchorPointVisibility) {
                            Rendering.drawCross(context, PresentationStateLayer.TEXT_COLOR, points[0]);
                        }
                        if (textObject.unformattedTextValue) {
                            Rendering.writeLines(context, textObject.unformattedTextValue.split("\n"), points[0].x, points[0].y, 12, PresentationStateLayer.TEXT_COLOR);
                        }
                    }
                }
            } else if (textObject.boundingBoxTopLeftHandCorner && textObject.boundingBoxBottomRightHandCorner) {
                if (textObject.boundingBoxAnnotationUnits === "PIXEL") {
                    var topLeft = this.toScreenCoords(textObject.boundingBoxTopLeftHandCorner, trans);
                    var bottomRight = this.toScreenCoords(textObject.boundingBoxBottomRightHandCorner, trans);

                    Rendering.fillRectangleBounds(context, Math.min(bottomRight[0].x, topLeft[0].x), Math.min(bottomRight[0].y, topLeft[0].y), Math.abs(bottomRight[0].x - topLeft[0].x), Math.abs(bottomRight[0].y - topLeft[0].y), "rgba(50, 50, 50, 0.25)");

                    if (topLeft.length > 0 && bottomRight.length > 0) {
                        Rendering.writeLines(context, textObject.unformattedTextValue.split("\n"), topLeft[0].x, topLeft[0].y, 12, PresentationStateLayer.TEXT_COLOR);
                    }
                }
            }
        };

        PresentationStateLayer.prototype.renderGraphicAnnotation = function (context, instance, graphicAnnotation, trans) {
            var _this = this;
            var highlighted = this.highlightedGraphic.read();

            if (!this.gspsInfoVisible.read() || (highlighted && (highlighted.graphic === graphicAnnotation))) {
                _.each(graphicAnnotation.graphicObjectSequence, function (graphicObject) {
                    _this.renderGraphicObject(context, instance, graphicObject, trans);
                });

                _.each(graphicAnnotation.textObjectSequence, function (textObject) {
                    _this.renderTextObject(context, instance, textObject, trans);
                });
            }
        };

        PresentationStateLayer.prototype.renderPresentationState = function (context, instance, presentationState, trans) {
            var _this = this;
            _.each(presentationState.graphicAnnotationModule.graphicAnnotationSequence, function (graphicAnnotation) {
                if ((graphicAnnotation.referencedImageSequence.length == 0) || (_.any(graphicAnnotation.referencedImageSequence, function (referencedInstance) {
                    return referencedInstance.referencedSOPInstanceUID === instance.id.value && Math.max(0, referencedInstance.referencedFrameNumber - 1) === instance.frameNumber.value;
                }))) {
                    _this.renderGraphicAnnotation(context, instance, graphicAnnotation, trans);
                }
            });
        };

        PresentationStateLayer.prototype.render = function (context, instanceUID, instance) {
            var _this = this;
            if (this.visible.read() && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var hiddenLayers = this.hiddenGSPSLayers.read();

                _.each(instance.instanceAttributes.presentationStates, function (presentationState) {
                    if (!_.contains(hiddenLayers, presentationState)) {
                        _this.renderPresentationState(context, instance, presentationState, trans);
                    }
                });
            }
        };
        PresentationStateLayer.GRAPHIC_COLOR = '#74b1ff';
        PresentationStateLayer.GRAPHIC_HIGHLIGHT_COLOR = '#ffbb43';
        PresentationStateLayer.TEXT_COLOR = '#ffffff';
        return PresentationStateLayer;
    })();
    Layers.PresentationStateLayer = PresentationStateLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders reference lines, i.e. the intersection of the instance plane with another instance plane
    */
    var ReferenceLines = (function () {
        function ReferenceLines(selectedInstance, visible, transform) {
            this.selectedInstance = selectedInstance;
            this.visible = visible;
            this.transform = transform;
        }
        ReferenceLines.prototype.render = function (context, instanceUID, instance, imageType) {
            if (this.visible.read() && instance.instanceAttributes.imageOrientationPatient && instance.instanceAttributes.imagePositionPatient && instance.instanceAttributes.pixelSpacing && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var selectedInstance = this.selectedInstance.read();

                if (selectedInstance != null && selectedInstance.studyUid.value === instance.studyAttributes.queryObject.studyUid.value && selectedInstance.seriesUid.value !== instance.seriesAttributes.seriesUid.value && selectedInstance.imageOrientationPatient && selectedInstance.imagePositionPatient && selectedInstance.pixelSpacing) {
                    if (SeriesGeometry.areOrthogonal(instance, selectedInstance)) {
                        this.drawIntersection(context, instance, selectedInstance);
                    }
                }
            }
        };

        /**
        * Draw the intersection between two instances
        */
        ReferenceLines.prototype.drawIntersection = function (context, instance, selectedInstance) {
            var intersection = ReferenceLines.getIntersection(instance, selectedInstance);

            if (intersection != null) {
                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var p1 = Transform.mapFromImage(intersection[0], trans);
                var p2 = Transform.mapFromImage(intersection[1], trans);

                context.lineWidth = 2;

                context.beginPath();
                context.strokeStyle = '#000000';
                context.moveTo(p1.x + 1, p1.y + 1);
                context.lineTo(p2.x + 1, p2.y + 1);
                context.stroke();

                context.beginPath();
                context.strokeStyle = '#ffff00';
                context.moveTo(p1.x, p1.y);
                context.lineTo(p2.x, p2.y);
                context.stroke();
            }
        };

        /**
        * Compute the intersection between two instances. This is defined as the set of points
        * at the intersection of the bounds of the target instance with the plane of the source instance.
        */
        ReferenceLines.getIntersection = function (source, target) {
            var sourcePlane = SeriesGeometry.planeForInstance(source);
            var targetBounds = SeriesGeometry.boundsForInstance(target);

            var intersections = [];

            for (var i = 0; i < targetBounds.length; i++) {
                var bound = targetBounds[i];
                var distance = Vectors.intersectionDistance(sourcePlane, bound);

                if (distance >= 0 && distance <= 1) {
                    var intersection = Vectors.add(bound.o, Vectors.scale(bound.v, distance));
                    var coords = Vectors.coords(sourcePlane, intersection);
                    intersections.push({
                        x: coords[0] / source.instanceAttributes.pixelSpacing[0],
                        y: coords[1] / source.instanceAttributes.pixelSpacing[0]
                    });
                }
            }

            if (intersections.length == 2) {
                return intersections;
            } else {
                return null;
            }
        };
        return ReferenceLines;
    })();
    Layers.ReferenceLines = ReferenceLines;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders the probe tool
    */
    var ProbeLayer = (function () {
        function ProbeLayer(renderer, location, selectedTool, transform) {
            this.renderer = renderer;
            this.location = location;
            this.selectedTool = selectedTool;
            this.transform = transform;
        }
        ProbeLayer.prototype.render = function (context, instanceUID, instance) {
            var imageCoords = this.location.read();

            if (imageCoords && WindowLevelPresets.shouldUse16BitWindowLevel(instance) && this.selectedTool.read() === 11 /* Probe */ && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var imageData = this.renderer.visit({
                    visitCanvasRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    },
                    visitWebGLRenderer: function (renderer) {
                        var imageElement = renderer.imageElements.get(instanceUID, 2 /* Diagnostic */);

                        if (imageElement) {
                            return Images.getImageData(imageElement.imageElement);
                        }

                        return null;
                    }
                }, null);

                var cols = instance.instanceAttributes.columns;
                var rows = instance.instanceAttributes.rows;

                imageCoords = {
                    x: Math.max(0, Math.min(cols, Math.round(imageCoords.x))),
                    y: Math.max(0, Math.min(rows, Math.round(imageCoords.y)))
                };

                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                var screenCoords = Transform.mapFromImage(imageCoords, trans);

                Rendering.drawCross(context, '#00ff00', screenCoords);

                if (imageData) {
                    var lowBits = imageData.data[(imageCoords.x + imageCoords.y * cols) * 4];
                    var highBits = imageData.data[(imageCoords.x + (imageCoords.y + rows) * cols) * 4];

                    var raw = (highBits << 8) | lowBits;

                    if (instance.instanceAttributes.signed) {
                        raw = (raw & 0xffff) - 0x8000;
                    }

                    var rescaled = raw * instance.instanceAttributes.rescaleSlope + instance.instanceAttributes.rescaleIntercept;

                    Rendering.writeLine(context, rescaled.toFixed(2), screenCoords.x, screenCoords.y, 12);
                }
            }
        };
        return ProbeLayer;
    })();
    Layers.ProbeLayer = ProbeLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders the magnifier
    */
    var MagnifierLayer = (function () {
        function MagnifierLayer(location, selectedTool, transform, prepareImage) {
            this.location = location;
            this.selectedTool = selectedTool;
            this.transform = transform;
            this.prepareImage = prepareImage;
        }
        MagnifierLayer.prototype.render = function (context, instanceUID, instance) {
            var imageCoords = this.location.read();

            if (imageCoords && this.selectedTool.read() === 15 /* Magnify */ && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var prepared = this.prepareImage.getAndPrepareImageData(context, instance);

                if (prepared && prepared.imageData) {
                    var magSize = Math.max(150, Math.min(context.canvas.width, context.canvas.height) / 2);

                    var transform = this.transform.read();

                    var matrix = Transform.transformToMatrix(transform, context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                    var screenCoords = Vectors.multiplyM(matrix, imageCoords);

                    context.save();

                    context.rect(screenCoords.x - magSize / 2, screenCoords.y - magSize / 2, magSize, magSize);
                    context.clip();

                    context.fillStyle = "#000000";
                    context.fillRect(0, 0, context.canvas.width, context.canvas.height);

                    var zoomTransform = Transform.zoomRotateAround(instance, screenCoords, transform, transform.scale * 2, transform.rotation, context.canvas.width, context.canvas.height);

                    var zoomMatrix = Transform.transformToMatrix(zoomTransform, context.canvas.width, context.canvas.height, prepared.width, prepared.height);

                    context.transform(zoomMatrix.entries[0], zoomMatrix.entries[3], zoomMatrix.entries[1], zoomMatrix.entries[4], zoomMatrix.entries[2], zoomMatrix.entries[5]);
                    Images.drawImageData(context, prepared.imageData);

                    context.restore();

                    context.save();

                    context.lineWidth = 1;
                    context.strokeStyle = "#FFFFFF";

                    context.strokeRect(screenCoords.x - magSize / 2, screenCoords.y - magSize / 2, magSize, magSize);

                    context.restore();
                }
            }
        };
        return MagnifierLayer;
    })();
    Layers.MagnifierLayer = MagnifierLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders a zoom/rotate overlay
    */
    var FreeRotateLayer = (function () {
        function FreeRotateLayer(center, selectedTool, transform) {
            this.center = center;
            this.selectedTool = selectedTool;
            this.transform = transform;
        }
        FreeRotateLayer.prototype.render = function (context, instanceUID, instance) {
            var center = this.center.read();

            if (center && this.selectedTool.read() === 14 /* FreeRotate */) {
                Rendering.drawCross(context, "#808080", center);
            }
        };
        return FreeRotateLayer;
    })();
    Layers.FreeRotateLayer = FreeRotateLayer;
})(Layers || (Layers = {}));
var Layers;
(function (Layers) {
    /**
    * A Layer which renders ultrasound regions
    */
    var Ultrasound = (function () {
        function Ultrasound(transform, visible) {
            this.transform = transform;
            this.visible = visible;
        }
        Ultrasound.prototype.render = function (context, instanceUID, instance) {
            if (Dicom.Ultrasound.isUltrasound(instance) && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed) && instance.instanceAttributes.json && this.visible.read()) {
                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var us = new Dicom.Ultrasound(instance.instanceAttributes.json);
                var numRegions = us.getNumRegions();

                for (var ctr = 0; ctr < numRegions; ctr += 1) {
                    var region = us.getRegion(ctr);

                    if (region.isDrawable()) {
                        var style = region.isAnnotatable() ? "#c8d1ff" : "#ffbac4";
                        var t1 = Transform.mapFromImage(region.bounds.min, trans);
                        var t2 = Transform.mapFromImage(region.bounds.max, trans);
                        Rendering.drawRectangleMeasurement(context, t1, t2, style, 2, false, true);
                    }
                }
            }
        };
        return Ultrasound;
    })();
    Layers.Ultrasound = Ultrasound;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Routes.ts' />
///<reference path='../libs/Images.ts' />
///<reference path='../libs/Browser.ts' />
///<reference path='../filter/Filter.ts' />
///<reference path='../views/Series.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders a video
    */
    var ConditionalLayer = (function () {
        function ConditionalLayer(active, layer) {
            this.active = active;
            this.layer = layer;
        }
        ConditionalLayer.prototype.render = function (context, instanceUID, instance, imageType, series) {
            if (this.active.read()) {
                this.layer.render(context, instanceUID, instance, imageType, series);
            }
        };
        return ConditionalLayer;
    })();
    Layers.ConditionalLayer = ConditionalLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Strings.ts' />
///<reference path='../libs/Rendering.ts' />
///<reference path='../libs/Terminology.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders the overlay raster
    */
    var OverlayLayer = (function () {
        function OverlayLayer(visible, overlayData, transform, supportROIOverlay) {
            this.visible = visible;
            this.overlayData = overlayData;
            this.transform = transform;
            this.supportROIOverlay = supportROIOverlay;
        }
        OverlayLayer.prototype.render = function (context, instanceUID, instance) {
            if (this.visible.read()) {
                var width = context.canvas.width;
                var height = context.canvas.height;

                var transform = this.transform.getImageTransformation(width, height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                for (var overlayPlane = 0; overlayPlane < Dicom.Overlay.MAX; overlayPlane += 1) {
                    if (instance.instanceAttributes.containsOverlayDataPlane[overlayPlane]) {
                        var imageData = this.overlayData.get(instanceUID + '-' + overlayPlane, null);
                        if (imageData) {
                            context.save();
                            context.transform(transform.entries[0], transform.entries[3], transform.entries[1], transform.entries[4], transform.entries[2], transform.entries[5]);

                            var roiOverlay = this.supportROIOverlay && (overlayPlane > 0);

                            if (roiOverlay) {
                                context.shadowOffsetX = 1;
                                context.shadowOffsetY = 1;
                                context.shadowColor = "#000000";
                            }

                            Images.drawImageData(context, imageData, roiOverlay);
                            context.restore();
                        }
                    }
                }
            }
        };
        return OverlayLayer;
    })();
    Layers.OverlayLayer = OverlayLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Strings.ts' />
///<reference path='../libs/Rendering.ts' />
///<reference path='../libs/Terminology.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A layer which renders image attributes as text
    */
    var Attributes = (function () {
        function Attributes(windowLevel, getImageType, visible, terminology, selectedMeasurement, settings, transform, series) {
            this.windowLevel = windowLevel;
            this.getImageType = getImageType;
            this.visible = visible;
            this.terminology = terminology;
            this.selectedMeasurement = selectedMeasurement;
            this.settings = settings;
            this.transform = transform;
            this.series = series;
        }
        Attributes.prototype.render = function (context, instanceUID, instance) {
            if (this.visible.read()) {
                var width = context.canvas.width;
                var height = context.canvas.height;
                var maxHeight = Math.max(12.0, Math.min(Math.min(width, height) / 20.0, 16.0));
                var maxDrawingAreaHeight = height / 2.5;

                var annotations = TextAnnotations.getTextAnnotations(instance.seriesAttributes.modality, this.settings.read());
                var dateFormat = this.settings.read().dateFormat;

                var data = {
                    transform: this.transform,
                    getImageType: this.getImageType,
                    windowLevel: this.windowLevel,
                    instance: instance,
                    terminology: this.terminology,
                    selectedMeasurement: this.selectedMeasurement,
                    context: context,
                    dateFormat: dateFormat,
                    series: this.series
                };

                var measurement = this.selectedMeasurement.read();
                if (measurement instanceof Measurements.Area) {
                    var area = measurement;
                    data.stats = area.lastStats;
                }

                var arranged = TextAnnotations.arrangeAnnotations(annotations, data);

                var allText = _.flatten([arranged.topLeft, arranged.bottomLeft, arranged.bottomRight, arranged.topRight]);
                var fontHeight = _.min(_.map(allText, function (text) {
                    return Rendering.fitTextToWidth(context, text, width / 2.5, maxHeight);
                }));

                var ctr = 0;

                while (fontHeight < 11) {
                    var longest = _.max(allText, function (text) {
                        return text.length;
                    });
                    var truncate = function (str, index, list) {
                        if ((str.length == longest.length) && (str.length > 5)) {
                            list[index] = str.substr(0, str.length - 5) + "...";
                        }
                    };

                    _.each(arranged.topLeft, truncate);
                    _.each(arranged.topRight, truncate);
                    _.each(arranged.bottomLeft, truncate);
                    _.each(arranged.bottomRight, truncate);

                    allText = _.flatten([arranged.topLeft, arranged.bottomLeft, arranged.bottomRight, arranged.topRight]);
                    var fontHeightNew = _.min(_.map(allText, function (text) {
                        return Rendering.fitTextToWidth(context, text, width / 2.5, maxHeight);
                    }));

                    if (fontHeight == fontHeightNew) {
                        if (ctr > 2) {
                            break;
                        }

                        ctr++;
                    } else {
                        fontHeight = fontHeightNew;
                        ctr = 0;
                    }
                }

                // handles if canvas height is not large enough to maintain minimum font size (drop lines)
                Rendering.writeLinesDynamic(context, arranged.topLeft, 5, 5, fontHeight, maxDrawingAreaHeight);
                Rendering.writeLinesBottomLeftDynamic(context, arranged.bottomLeft, 5, height - 10, fontHeight, maxDrawingAreaHeight);
                Rendering.writeLinesBottomRightDynamic(context, arranged.bottomRight, width - 5, height - 10, fontHeight, maxDrawingAreaHeight);
                Rendering.writeLinesTopRightDynamic(context, arranged.topRight, width - 5, 5, fontHeight, maxDrawingAreaHeight);
            }
        };
        return Attributes;
    })();
    Layers.Attributes = Attributes;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Rendering.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A letter which indicates patient orientation
    */
    (function (Letter) {
        Letter[Letter["A"] = 0] = "A";
        Letter[Letter["P"] = 1] = "P";
        Letter[Letter["L"] = 2] = "L";
        Letter[Letter["R"] = 3] = "R";
        Letter[Letter["I"] = 4] = "I";
        Letter[Letter["S"] = 5] = "S";
    })(Layers.Letter || (Layers.Letter = {}));
    var Letter = Layers.Letter;

    /**
    * Components of a 3-vector
    */
    (function (Component) {
        Component[Component["X"] = 0] = "X";
        Component[Component["Y"] = 1] = "Y";
        Component[Component["Z"] = 2] = "Z";
    })(Layers.Component || (Layers.Component = {}));
    var Component = Layers.Component;

    

    /**
    * A Layer which renders patient orientation annotations
    */
    var Orientation = (function () {
        function Orientation(visible, transform) {
            this.transform = transform;
            this.visible = visible;
        }
        Orientation.prototype.render = function (context, instanceUID, instance) {
            if (this.visible.read()) {
                var directionCosines = this.calculateDirectionCosines(context, instance);

                if (directionCosines) {
                    Orientation.renderAnnotations(context, directionCosines[0], directionCosines[1]);
                }
            }
        };

        Orientation.prototype.calculateDirectionCosines = function (context, instance) {
            var inv = this.transform.getInverseImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

            var imageOrientationPatient = instance.instanceAttributes.imageOrientationPatient;

            if (imageOrientationPatient && imageOrientationPatient.length === 6) {
                // The image orientation patient vector determines a matrix which
                // maps image coordinates to patient coordinates.
                var imageToPatient = {
                    entries: [
                        imageOrientationPatient[0], imageOrientationPatient[3], 0, 0,
                        imageOrientationPatient[1], imageOrientationPatient[4], 0, 0,
                        imageOrientationPatient[2], imageOrientationPatient[5], 1, 0,
                        0, 0, 0, 1
                    ]
                };

                // The inv matrix screen coordinates to image coordinates.
                // We want to know what up, down, left and right in the screen coordinates
                // correspond to in patient coordinates, so we compose the two matrices.
                // In fact, we just multiply the columns of inv by imageToPatient, since
                // we just want to know what happens to the axes anyway.
                var row = Vectors.multiply3(imageToPatient, {
                    x: inv.entries[0],
                    y: inv.entries[3],
                    z: 0
                });

                var col = Vectors.multiply3(imageToPatient, {
                    x: inv.entries[1],
                    y: inv.entries[4],
                    z: 0
                });

                return [row, col];
            }

            return null;
        };

        /**
        * Render orientation annotations for an instance
        */
        Orientation.renderAnnotations = function (context, row, col) {
            var width = context.canvas.width;
            var height = context.canvas.height;

            var fontHeight = Math.max(10.0, Math.min(Math.min(width, height) / 20.0, 24.0));
            var letters = Orientation.getImageOrientation(row, col);

            Rendering.writeLineCentered(context, Letter[letters.top], width / 2, 5, fontHeight);
            Rendering.writeLine(context, Letter[letters.left], 5, height / 2 - fontHeight / 2, fontHeight);
            Rendering.writeLineRight(context, Letter[letters.right], width - 5, height / 2 - fontHeight / 2, fontHeight);
            Rendering.writeLineCentered(context, Letter[letters.bottom], width / 2, height - 5 - fontHeight, fontHeight);
        };

        /**
        * Identify the largest component of a vector
        */
        Orientation.largestAbsoluteComponent = function (v) {
            var x = Math.abs(v.x);
            var y = Math.abs(v.y);
            var z = Math.abs(v.z);

            if (x > y && x > z) {
                return { component: 0 /* X */, value: v.x };
            } else if (y > x && y > z) {
                return { component: 1 /* Y */, value: v.y };
            } else {
                return { component: 2 /* Z */, value: v.z };
            }
        };

        /**
        * Convert a subset of the image orientation patient vector to a pair of orientation letters
        *
        * @param {from} The index at which the vector begins inside the image orientation patient vector
        */
        Orientation.lettersFromVector = function (v) {
            var largestAbsoluteComponent = Orientation.largestAbsoluteComponent(v);

            var letters;

            switch (largestAbsoluteComponent.component) {
                case 0 /* X */:
                    letters = [3 /* R */, 2 /* L */];
                    break;
                case 1 /* Y */:
                    letters = [0 /* A */, 1 /* P */];
                    break;
                case 2 /* Z */:
                    letters = [4 /* I */, 5 /* S */];
                    break;
            }

            if (largestAbsoluteComponent.value < 0) {
                return [letters[1], letters[0]];
            } else {
                return letters;
            }
        };

        /**
        * Convert the image orientation patient vector to a full set of orientation letters
        */
        Orientation.getImageOrientation = function (row, col) {
            var leftRight = Orientation.lettersFromVector(row);
            var topBottom = Orientation.lettersFromVector(col);

            return {
                left: leftRight[0],
                right: leftRight[1],
                top: topBottom[0],
                bottom: topBottom[1]
            };
        };
        return Orientation;
    })();
    Layers.Orientation = Orientation;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders the magnifier
    */
    var RulerLayer = (function () {
        function RulerLayer(transform, visible) {
            this.transform = transform;
            this.visible = visible;
        }
        RulerLayer.prototype.render = function (context, instanceUID, instance) {
            if (instance.instanceAttributes.pixelSpacing && instance.instanceAttributes.pixelSpacing.length > 0 && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed) && context.canvas.height > 200 && this.visible.read()) {
                context.save();

                context.shadowOffsetX = 1;
                context.shadowOffsetY = 1;
                context.shadowColor = "#000000";

                context.lineWidth = 1;
                context.strokeStyle = "#FFFFFF";

                var transform = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);
                var zoomFactor = Math.sqrt(Math.abs(Vectors.determinant(transform)));

                var mmPerPixel = instance.instanceAttributes.pixelSpacing[0] / zoomFactor;
                var pixelsPerCm = 10.0 / mmPerPixel;

                var width = context.canvas.width / 3;
                var height = 10;

                var divisionWidth = pixelsPerCm;

                if (divisionWidth > width / 2) {
                    divisionWidth /= 10;
                }

                var x0 = context.canvas.width / 3;
                var x1 = context.canvas.width * 2 / 3;
                var y = 50;

                context.beginPath();

                context.moveTo(x0, y - height / 2);
                context.lineTo(x0, y + height / 2);
                context.moveTo(x1, y - height / 2);
                context.lineTo(x1, y + height / 2);
                context.moveTo(x0, y);
                context.lineTo(x1, y);

                for (var x = x0; x < x1; x += divisionWidth) {
                    context.moveTo(x, y - height / 3);
                    context.lineTo(x, y + height / 3);
                }

                context.stroke();

                context.restore();
            }
        };
        return RulerLayer;
    })();
    Layers.RulerLayer = RulerLayer;
})(Layers || (Layers = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../models/Study.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../libs/Vector.ts' />
///<reference path='../libs/SeriesGeometry.ts' />
///<reference path='../classes/Types.ts' />
var Layers;
(function (Layers) {
    /**
    * A Layer which renders the plane localization cursor
    */
    var PlaneLocalizationLayer = (function () {
        function PlaneLocalizationLayer(planeLocalizationCursor, selectedTool, transform) {
            this.planeLocalizationCursor = planeLocalizationCursor;
            this.selectedTool = selectedTool;
            this.transform = transform;
        }
        PlaneLocalizationLayer.prototype.render = function (context, instanceUID, instance) {
            var cursor = this.planeLocalizationCursor.read();

            if (cursor && cursor.studyUid.value === instance.studyAttributes.queryObject.studyUid.value && SeriesGeometry.hasGeometricMetadata(instance) && this.selectedTool.read() === 9 /* Localization */ && !(instance.instanceAttributes.mostRecentThumbnailLoadFailed && instance.instanceAttributes.mostRecentHiResLoadFailed)) {
                var patientCoords = cursor.coords;
                var imageCoords = SeriesGeometry.mapFromPatient(patientCoords, instance);

                var trans = this.transform.getImageTransformation(context.canvas.width, context.canvas.height, instance.instanceAttributes.columns, instance.instanceAttributes.rows);

                var screenCoords = Transform.mapFromImage(imageCoords, trans);

                Rendering.drawCross(context, '#ff0000', screenCoords);
            }
        };
        return PlaneLocalizationLayer;
    })();
    Layers.PlaneLocalizationLayer = PlaneLocalizationLayer;
})(Layers || (Layers = {}));
///<reference path='Filter.ts' />
var Filters;
(function (Filters) {
    /**
    * A Filter which applies a color lookup table to an image.
    */
    var Colors = (function () {
        function Colors(colorTable) {
            this.colorTable = colorTable;
        }
        Colors.prototype.apply = function (context, instance, imageData) {
            var lut = this.colorTable.read();

            if (lut) {
                var colorData = context.createImageData(imageData.width, imageData.height);
                var palette;

                if (this.lastColorTable && (this.lastColorTable == lut)) {
                    palette = this.lastPalette;
                } else {
                    this.lastColorTable = lut;
                    this.lastPalette = palette = ColorTablePresets.createPalette(lut);
                }

                var origData = imageData.data;
                var newData = colorData.data;

                var monochrome = instance.instanceAttributes.photometricInterpretation.indexOf("MONOCHROME") >= 0;

                for (var i = 0; i < origData.length;) {
                    var val = monochrome ? origData[i] * 4 : ~~(0.2126 * origData[i] + 0.7152 * origData[i + 1] + 0.0722 * origData[i + 2]) * 4;
                    newData[i] = palette[val];
                    i++;
                    newData[i] = palette[val + 1];
                    i++;
                    newData[i] = palette[val + 2];
                    i++;
                    newData[i] = 255;
                    i++;
                }

                return colorData;
            }

            return imageData;
        };
        return Colors;
    })();
    Filters.Colors = Colors;
})(Filters || (Filters = {}));
///<reference path='../classes/Types.ts' />
///<reference path='Filter.ts' />
var Filters;
(function (Filters) {
    /**
    * A Filter which isolates a window of color values, aka. Window Level, aka. brightness and contrast.
    *
    * Works with 8-bit input images
    */
    var WindowLevel = (function () {
        function WindowLevel(windowLevel, windowLevelOriginal, subtractionEnabled) {
            this.windowLevel = windowLevel;
            this.windowLevelOriginal = windowLevelOriginal;
            this.lastWindowLevel = { center: 0, width: 0 };
            this.lastInstanceId = null;
            this.lastImageWidth = null;
            this.lastImageHeight = null;
            this.lastImageData = null;
            this.lastInstanceFrameNumber = null;
            this.subtractionEnabled = subtractionEnabled;
        }
        WindowLevel.prototype.apply = function (context, instance, imageData) {
            if (WindowLevelPresets.shouldUse16BitWindowLevel(instance)) {
                var sigmoid = instance.instanceAttributes.voiLutFunction === "SIGMOID";

                return this.apply16BitFilter(context, instance, imageData, sigmoid);
            } else {
                return this.apply8BitFilter(context, instance, imageData);
            }
        };

        /**
        * 8-bit window level filter
        */
        WindowLevel.prototype.apply8BitFilter = function (context, instance, imageData) {
            var windowLevel = this.windowLevel.read();

            if (windowLevel && (windowLevel.center !== 128.0 || windowLevel.width !== 256.0)) {
                var low = (windowLevel.center - windowLevel.width / 2);

                // var high = (windowLevel.center + windowLevel.width / 2);
                var newImageData = context.createImageData(imageData.width, imageData.height);
                var newData = newImageData.data;

                var lookup = new Array(256);

                for (var i = 0; i < 256; i++) {
                    lookup[i] = null;
                }

                var width = (windowLevel.width);

                var data = imageData.data;

                var lookupValue = function (raw) {
                    var newValue = lookup[raw];

                    if (newValue == null) {
                        newValue = ((raw - low) * 256.0) / width;

                        if (newValue < 0) {
                            newValue = 0;
                        }

                        if (newValue > 255) {
                            newValue = 255;
                        }

                        lookup[raw] = newValue;
                    }

                    return newValue;
                };

                for (var i = 0; i < data.length;) {
                    newData[i] = lookupValue(data[i]);
                    i++;
                    newData[i] = lookupValue(data[i]);
                    i++;
                    newData[i] = lookupValue(data[i]);
                    i++;
                    newData[i] = 255;
                    i++;
                }

                return newImageData;
            }

            return imageData;
        };

        /**
        * Approximate a 16-bit window level filter
        */
        WindowLevel.prototype.apply16BitFilter = function (context, instance, imageData, sigmoid) {
            var windowLevel = this.windowLevel.read();

            if (this.lastWindowLevel != null && this.lastInstanceId != null && this.lastInstanceFrameNumber != null && this.lastImageWidth != null && windowLevel.center == this.lastWindowLevel.center && windowLevel.width == this.lastWindowLevel.width && instance.id.value == this.lastInstanceId.value && imageData.width == this.lastImageWidth && imageData.height == this.lastImageHeight && instance.frameNumber.value == this.lastInstanceFrameNumber.value) {
                return this.lastImageData;
            }

            var signed = this.subtractionEnabled.read() || instance.instanceAttributes.signed || false;
            var typeRange = (1 << instance.instanceAttributes.bitsStored);
            var typeSignedMax = (1 << instance.instanceAttributes.bitsStored - 1) - 1;
            var slope = instance.instanceAttributes.rescaleSlope || 1;
            var intercept = instance.instanceAttributes.rescaleIntercept || 0;

            if (WindowLevelPresets.instanceNeedsWindowLevel(instance)) {
                instance.instanceAttributes.windowLevelDetection = true;
                windowLevel = WindowLevelPresets.findWindowLevels(instance, imageData);
                instance.instanceAttributes.windowCenter = [windowLevel.center];
                instance.instanceAttributes.windowWidth = [windowLevel.width];
                this.windowLevelOriginal.write(windowLevel);
            }

            if (instance.instanceAttributes.presentationLUTShape === "INVERSE" || instance.instanceAttributes.photometricInterpretation === "MONOCHROME1") {
                windowLevel = {
                    center: (signed ? 0 : (1 << instance.instanceAttributes.bitsStored)) * slope - windowLevel.center,
                    width: windowLevel.width
                };
            }

            var newImageData = context.createImageData(imageData.width, imageData.height / 2);
            var newData = newImageData.data;

            var fullImageSize = imageData.width * imageData.height / 2 * 4;

            var bits = (signed) ? 16 : instance.instanceAttributes.bitsStored + 1;
            bits = (bits > 16) ? 16 : bits;
            var lookupSize = Math.pow(2, bits);

            var lookup = new Array(lookupSize);

            var low = (windowLevel.center - windowLevel.width / 2);

            // var high = (windowLevel.center + windowLevel.width / 2);
            var width = (windowLevel.width);

            var data = imageData.data;

            for (var i = 0; i < lookupSize;) {
                var newValue = 0;
                var index = i;
                if (signed) {
                    index = (index & 0xffff) - 0x8000;
                }
                var rescaled = index * slope + intercept;

                if (sigmoid) {
                    newValue = 256 / (1 + Math.exp(-4 * (rescaled - windowLevel.center) / windowLevel.width));
                } else {
                    newValue = ((rescaled - low) * 256.0) / width;
                }

                newValue = (newValue < 0) ? 0 : newValue;
                newValue = (newValue > 255) ? 255 : newValue;

                lookup[index] = newValue;

                i++;
            }

            var l = data.length / 2;

            for (var i = 0; i < l;) {
                var raw = (data[i + fullImageSize] << 8) | data[i];

                if (signed) {
                    raw = (raw & 0xffff) - 0x8000;

                    if (raw > typeSignedMax) {
                        raw -= typeRange;
                    }
                }

                var value = lookup[raw];

                newData[i] = value;
                newData[i + 1] = value;
                newData[i + 2] = value;
                newData[i + 3] = 255;

                i = i + 4;
            }

            windowLevel = this.windowLevel.read();
            this.lastWindowLevel.center = windowLevel.center;
            this.lastWindowLevel.width = windowLevel.width;
            this.lastInstanceId = new Classes.InstanceUid(instance.id.value);
            this.lastImageWidth = imageData.width;
            this.lastImageHeight = imageData.height;
            this.lastImageData = newImageData;
            this.lastInstanceFrameNumber = new Classes.FrameNumber(instance.frameNumber.value);

            return newImageData;
        };
        return WindowLevel;
    })();
    Filters.WindowLevel = WindowLevel;
})(Filters || (Filters = {}));
///<reference path='Filter.ts' />
var Filters;
(function (Filters) {
    /**
    * XA Subtraction (DSA) filter.
    */
    var Subtraction = (function () {
        function Subtraction(series, enabled) {
            this.series = series;
            this.enabled = enabled;
        }
        Subtraction.prototype.apply = function (context, instance, imageData) {
            var newImageData = imageData;
            var enabled = this.enabled.read();

            if (enabled) {
                var xa = this.series.subtraction;
                var allMasksLoaded = true;
                var maskInstanceIndices = [0];

                if (xa) {
                    allMasksLoaded = xa.allMasksLoaded(this.series);
                    maskInstanceIndices = xa.getMaskIndices();
                }

                var frameIndex = _.indexOf(this.series.series.instances, instance);
                var subtractionKey = this.series.series.instances[0].id + "-" + this.series.getImageType(instance) + "-" + frameIndex + "-" + JSON.stringify(maskInstanceIndices);

                if (allMasksLoaded) {
                    if (this.lastSubtractionKey == subtractionKey) {
                        newImageData = this.lastSubtractionData;
                    } else {
                        if (instance.instanceAttributes.bitsStored > 8) {
                            newImageData = this.subtract(context, imageData, maskInstanceIndices);
                        } else {
                            newImageData = this.subtract8bit(context, imageData, maskInstanceIndices);
                        }

                        this.lastSubtractionKey = subtractionKey;
                        this.lastSubtractionData = newImageData;
                    }
                } else {
                    this.lastSubtractionKey = null;
                    this.lastSubtractionData = null;
                }
            } else {
                this.lastSubtractionKey = null;
                this.lastSubtractionData = null;
            }

            return newImageData;
        };

        Subtraction.prototype.getMaskData = function (index) {
            var imageData = null;

            var maskInstance = this.series.series.instances[index];
            var imageType = this.series.getImageType(maskInstance);
            var instanceKey = this.series.getInstanceKey(index);
            var imageElement = this.series.renderer.imageElements.get(instanceKey, imageType);

            if (imageElement) {
                imageData = Images.getImageData(imageElement.imageElement);
            }

            return imageData;
        };

        Subtraction.prototype.averageMasks = function (context, imageData, indices) {
            var _this = this;
            if (indices.length == 1) {
                return this.getMaskData(indices[0]);
            } else {
                var newImageData = context.createImageData(imageData.width, imageData.height);
                var allMaskData = _.map(indices, function (index) {
                    return _this.getMaskData(index);
                });

                var l = imageData.data.length / 2;
                var fullImageSize = imageData.width * imageData.height / 2 * 4;
                var numMasks = allMaskData.length;

                for (var i = 0; i < l;) {
                    var total = 0;

                    for (var ctr = 0; ctr < numMasks; ctr += 1) {
                        total += (allMaskData[ctr].data[i + fullImageSize] << 8) | allMaskData[ctr].data[i];
                    }

                    total = Math.round(total / numMasks);
                    var low = total & 0xff;
                    var high = (total >> 8) & 0xff;

                    newImageData.data[i] = low;
                    newImageData.data[i + 1] = low;
                    newImageData.data[i + 2] = low;
                    newImageData.data[i + 3] = 255;

                    newImageData.data[i + fullImageSize] = high;
                    newImageData.data[i + fullImageSize + 1] = high;
                    newImageData.data[i + fullImageSize + 2] = high;
                    newImageData.data[i + fullImageSize + 3] = 255;

                    i = i + 4;
                }

                return newImageData;
            }
        };

        Subtraction.prototype.subtract = function (context, imageData, maskInstanceIndices) {
            var maskData = this.averageMasks(context, imageData, maskInstanceIndices);

            var maskImageData = maskData.data;
            var frameImageData = imageData.data;
            var newImageData = context.createImageData(imageData.width, imageData.height);

            if (maskImageData.length === frameImageData.length) {
                var l = maskImageData.length / 2;
                var fullImageSize = imageData.width * imageData.height / 2 * 4;

                var min = Number.MAX_VALUE;
                var max = -Number.MAX_VALUE;

                for (var i = 0; i < l;) {
                    var frameRaw = (frameImageData[i + fullImageSize] << 8) | frameImageData[i];
                    var maskRaw = (maskImageData[i + fullImageSize] << 8) | maskImageData[i];

                    var subtracted = frameRaw - maskRaw;
                    min = Math.min(subtracted, min);
                    max = Math.max(subtracted, max);

                    subtracted = (subtracted & 0xffff) + 0x8000;
                    var low = subtracted & 0xff;
                    var high = (subtracted >> 8) & 0xff;

                    newImageData.data[i] = low;
                    newImageData.data[i + 1] = low;
                    newImageData.data[i + 2] = low;
                    newImageData.data[i + 3] = 255;

                    newImageData.data[i + fullImageSize] = high;
                    newImageData.data[i + fullImageSize + 1] = high;
                    newImageData.data[i + fullImageSize + 2] = high;
                    newImageData.data[i + fullImageSize + 3] = 255;

                    i = i + 4;
                }

                this.series.subtraction.windowLevel = {
                    center: 0,
                    width: this.series.subtraction.windowLevel ? Math.max(max - min, this.series.subtraction.windowLevel.width) : 256
                };
            }

            return newImageData;
        };

        Subtraction.prototype.averageMasks8bit = function (context, imageData, indices) {
            var _this = this;
            if (indices.length == 1) {
                return this.getMaskData(indices[0]);
            } else {
                var newImageData = context.createImageData(imageData.width, imageData.height);
                var allMaskData = _.map(indices, function (index) {
                    return _this.getMaskData(index);
                });

                var l = imageData.data.length;
                var numMasks = allMaskData.length;

                for (var i = 0; i < l;) {
                    var total = 0;

                    for (var ctr = 0; ctr < numMasks; ctr += 1) {
                        total += allMaskData[ctr].data[i];
                    }

                    total = Math.round(total / numMasks);
                    total = total & 0xff;

                    newImageData.data[i] = total;
                    newImageData.data[i + 1] = total;
                    newImageData.data[i + 2] = total;
                    newImageData.data[i + 3] = 255;

                    i = i + 4;
                }

                return newImageData;
            }
        };

        Subtraction.prototype.subtract8bit = function (context, imageData, maskInstanceIndices) {
            var maskData = this.averageMasks8bit(context, imageData, maskInstanceIndices);

            var maskImageData = maskData.data;
            var frameImageData = imageData.data;
            var newImageData = context.createImageData(imageData.width, imageData.height);

            if (maskImageData.length === frameImageData.length) {
                var l = maskImageData.length;

                var min = Number.MAX_VALUE;
                var max = -Number.MAX_VALUE;

                for (var i = 0; i < l;) {
                    var frameRaw = frameImageData[i] & 0xFF;
                    var maskRaw = maskImageData[i] & 0xFF;

                    var subtracted = frameRaw - maskRaw;
                    subtracted = Math.floor((subtracted + 256.0) / 2.0);

                    min = Math.min(subtracted, min);
                    max = Math.max(subtracted, max);

                    newImageData.data[i] = subtracted;
                    newImageData.data[i + 1] = subtracted;
                    newImageData.data[i + 2] = subtracted;
                    newImageData.data[i + 3] = 255;

                    i = i + 4;
                }

                this.series.subtraction.windowLevel = {
                    center: (max + min) / 2,
                    width: this.series.subtraction.windowLevel ? (max - min) : 256
                };
            }

            return newImageData;
        };
        return Subtraction;
    })();
    Filters.Subtraction = Subtraction;
})(Filters || (Filters = {}));
///<reference path='Filter.ts' />
var Filters;
(function (Filters) {
    /**
    * A Filter which inverts its 8-bit input
    */
    var Invert = (function () {
        function Invert(enabled) {
            this.enabled = enabled;
        }
        Invert.prototype.apply = function (context, instance, imageData) {
            var enabled = this.enabled.read();

            if (enabled) {
                var inverted = context.createImageData(imageData.width, imageData.height);

                var oldData = imageData.data;
                var newData = inverted.data;

                for (var i = 0; i < imageData.data.length;) {
                    newData[i] = 255 ^ oldData[i];
                    i++;
                    newData[i] = 255 ^ oldData[i];
                    i++;
                    newData[i] = 255 ^ oldData[i];
                    i++;
                    newData[i] = 255;
                    i++;
                }

                return inverted;
            }

            return imageData;
        };
        return Invert;
    })();
    Filters.Invert = Invert;
})(Filters || (Filters = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Array.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../models/Study.ts' />
var Views;
(function (Views) {
    /**
    * Contains all of the subviews required for the report page
    */
    (function (Report) {
        /**
        * The Report view
        */
        var Application = (function () {
            function Application(el, studyStorage, queryObject, sessionId, terminology) {
                this.study = null;
                this.el = el;
                this.studyStorage = studyStorage;
                this.queryObject = queryObject;
                this.sessionId = sessionId;
                this.terminology = terminology;
            }
            /**
            * Load the study from storage and call the render method
            */
            Application.prototype.load = function () {
                var _this = this;
                var reportId = new Classes.InstanceUid(Query.findParameter(window.location, "uuid"));
                var version = new Classes.ImageVersion(Query.findParameter(window.location, "version"));

                var loadReport = V3Storage.GetStructuredReport(this.sessionId, this.studyStorage, this.queryObject, reportId, version);

                return Observable.map(Observable.invoke(loadReport, function (nodes) {
                    _this.render(_this.el, nodes);
                }), function (_) {
                    return null;
                });
            };

            /**
            * Calculate the depth of a structured report node
            */
            Application.prototype.nodeDepth = function (node) {
                var _this = this;
                if (node.children) {
                    return 1 + _.max(_.map(node.children, function (node) {
                        return _this.nodeDepth(node);
                    }));
                }

                return 1;
            };

            /**
            * Layout the screen
            */
            Application.prototype.render = function (el, nodes) {
                var _this = this;
                var $report = $('<div>').addClass('report');

                $report.append($('<h1>').text(this.terminology.lookup(Terminology.Terms.StructuredReport)));
                $report.append($('<hr>'));

                var rootDepth = _.max(_.map(nodes, function (node) {
                    return _this.nodeDepth(node);
                }));

                var $table = $('<table>').addClass('table table-striped table-bordered').appendTo($report);

                var $tbody = $('<tbody>').appendTo($table);

                var $tr = $('<tr>').appendTo($tbody);

                var renderer = new Views.Report.TableRenderer($tbody, $tr, rootDepth);

                _.each(nodes, function (node, index, list) {
                    return renderer.render(node, index);
                });

                var $footer = $('<footer>').appendTo($report);
                this.renderFootnotes($footer, renderer.footnotes);

                el.replaceWith($report);
            };

            /**
            * Render a list of footnotes
            */
            Application.prototype.renderFootnotes = function (el, footnotes) {
                _.each(footnotes, function (note, index, list) {
                    var $p = $('<p>').appendTo(el);
                    $p.append($('<a>').attr("name", "footnote-" + index));
                    $p.append($('<sup>').text('#' + index));
                    $p.append($('<pre>').text(note));
                });
            };
            return Application;
        })();
        Report.Application = Application;

        var TableRenderer = (function () {
            function TableRenderer($tbody, $tr, depth) {
                this.row = 0;
                this.footnotes = [];
                this.curDepth = 0;
                this.$tbody = $tbody;
                this.$tr = $tr;
                this.depth = depth;
            }
            TableRenderer.prototype.render = function (node, index) {
                var _this = this;
                // Create a new row if this isn't the first child of the parent node
                if (index > 0) {
                    this.$tr = $('<tr>').appendTo(this.$tbody);
                    this.row++;
                }

                // Save the current row, we'll need it later to calculate the row span
                var oldRow = this.row;

                var $td = $('<td>').append($('<strong>').text(node.key)).appendTo(this.$tr);

                if (node.children) {
                    this.curDepth++;
                    _.each(node.children, function (node, index, list) {
                        return _this.render(node, index);
                    });
                    this.curDepth--;
                } else if (node.value) {
                    var text = node.value.join(" ");

                    var $td = $('<td>').attr("colspan", this.depth - this.curDepth).appendTo(this.$tr);

                    if (text.length > 100) {
                        var nextNote = this.footnotes.length;
                        this.footnotes.push(text);
                        $td.append($('<a>').attr("href", "#footnote-" + nextNote).append($('<sup>').text('#' + nextNote)));
                    } else {
                        $td.append($('<pre>').text(text));
                    }
                }

                var newRow = this.row;
                $td.attr("rowspan", newRow - oldRow + 1);
            };
            return TableRenderer;
        })();
        Report.TableRenderer = TableRenderer;
    })(Views.Report || (Views.Report = {}));
    var Report = Views.Report;
})(Views || (Views = {}));
///<reference path="../typings/jquery/jquery.d.ts" />
///<reference path="../typings/underscore/underscore.d.ts" />
///<reference path="../classes/Types.ts" />
///<reference path="../libs/Observable.ts" />
///<reference path="../libs/V3Storage.ts" />
///<reference path="../libs/Subject.ts" />
///<reference path="../libs/Touch.ts" />
///<reference path="../libs/Cine.ts" />
///<reference path="../models/Study.ts" />

var Views;
(function (Views) {
    /**
    * A view which shows a his.terminology of a multiframe image
    */
    var Video = (function () {
        function Video(sessionId, el, settings, instance, terminology, isMultiFrame) {
            if (typeof isMultiFrame === "undefined") { isMultiFrame = true; }
            this.sessionId = sessionId;
            this.el = el;
            this.settings = settings;
            this.instance = instance;
            this.terminology = terminology;
            this.cineSpeed = new Subjects.ObservableValue(Cine.getDefaultCineSpeed(settings.read(), this.instance.seriesAttributes.modality));
            this.isMultiFrame = isMultiFrame;
        }
        Video.prototype.render = function () {
            var _this = this;
            var $toolbar = $('<div>').addClass('toolbar');
            var toolbar = new Views.MenuBar($toolbar);

            toolbar.addToolbarButton({
                title: this.terminology.lookup(Terminology.Terms.Faster),
                tooltip: this.terminology.lookup(Terminology.Terms.FasterTooltip),
                icon: "w_cine_faster",
                click: function () {
                    return _this.increaseCineSpeed();
                }
            });

            toolbar.addToolbarButton({
                title: this.terminology.lookup(Terminology.Terms.Slower),
                tooltip: this.terminology.lookup(Terminology.Terms.SlowerTooltip),
                icon: "w_cine_slower",
                click: function () {
                    return _this.decreaseCineSpeed();
                }
            });

            var $videoPanel = $('<div>').addClass('video-panel');

            var $textPanel = $('<div>').addClass('video-image-info').appendTo($videoPanel);

            $textPanel.append($('<div>').text(this.terminology.lookup(Terminology.Terms.Patient) + ": " + this.instance.studyAttributes.patientName));
            $textPanel.append($('<div>').text(this.terminology.lookup(Terminology.Terms.Study) + ": " + this.instance.studyAttributes.studyDescription));
            $textPanel.append($('<div>').text(this.terminology.lookup(Terminology.Terms.Series) + ": " + this.instance.instanceAttributes.seriesDescription));
            $textPanel.append($('<div>').append(this.$fpsText = $('<span>').text(this.cineSpeed.read().toString())).append(this.terminology.lookup(Terminology.Terms.FPS)));

            var $loadingText = $('<span>').text(this.terminology.lookup(Terminology.Terms.VideoLoading));

            $textPanel.append($('<div>').append($loadingText));

            var posterUri = Routes.ImageData(this.sessionId, this.instance.studyAttributes.studyStorage, this.instance.studyAttributes.queryObject, this.instance.id, this.instance.instanceAttributes.version, new Classes.FrameNumber(0), 0 /* Thumbnail */, 8, false);

            var multiFrameUri = Routes.Multiframe(this.sessionId, this.instance.studyAttributes.studyStorage, this.instance.studyAttributes.queryObject, this.instance.id, this.instance.instanceAttributes.version);

            var videoUri = Routes.VideoData(this.sessionId, this.instance.studyAttributes.studyStorage, this.instance.studyAttributes.queryObject, this.instance.id, this.instance.instanceAttributes.version, false, true);

            var srcUri = (this.isMultiFrame == true) ? multiFrameUri : videoUri;
            var videoId = 'vid_' + Math.floor((Math.random() * 100000) + 1);
            var videoOptions = {
                playbackRates: [0.3, 0.7, 1.0, 1.5, 2.0, 5.0],
                inactivityTimeout: 0,
                muted: true
            };

            // Don't include a poster for mp4 videos
            posterUri = (this.isMultiFrame == true) ? posterUri : null;

            if (navigator.userAgent.indexOf('iPad') >= 0 || navigator.userAgent.indexOf('iPhone') >= 0) {
                var $video = $('<video loop autoplay controls>').addClass('video').attr({
                    src: srcUri,
                    type: "video/mp4",
                    poster: posterUri
                }).appendTo($videoPanel);

                $video.error(function (_) {
                    $loadingText.text(_this.terminology.lookup(Terminology.Terms.VideoLoadingError));
                });
            } else {
                var $video = $('<video loop autoplay controls preload="auto">').attr('id', videoId).addClass('video');

                if (this.isMultiFrame == false) {
                    $video.addClass('video-js vjs-static-controls');
                }

                $video.attr({ poster: posterUri }).appendTo($videoPanel);

                var $source = $('<source>').attr({
                    src: srcUri,
                    type: "video/mp4"
                }).appendTo($video);

                $source.on("error", function (_) {
                    $loadingText.text(_this.terminology.lookup(Terminology.Terms.VideoLoadingError));
                });
            }

            $video.on("loadedmetadata", function (_) {
                $loadingText.hide();
            });

            this.video = $video.get(0);

            this.el.empty();
            this.el.append($toolbar);
            this.el.append($videoPanel);

            if (this.isMultiFrame == false) {
                videojs(videoId, videoOptions).ready(function () {
                    this.playbackRate(1.0);

                    $('.vjs-loading-spinner').css('opacity', '0.0');
                });
            }

            this.video.playbackRate = this.cineSpeed.read() / Cine.cineRenderRate;

            var saveCineSpeedToSettings = _.debounce(function () {
                Subjects.modify(_this.settings, function (settings) {
                    return Cine.saveCineSpeedToSettings(settings, _this.instance.seriesAttributes.modality, _this.cineSpeed.read());
                });
            }, 500);

            Subjects.listen(this.cineSpeed, function (fps) {
                var rate = fps / Cine.cineRenderRate;
                _this.video.playbackRate = rate;

                _this.$fpsText.text(fps.toString());

                saveCineSpeedToSettings();
            });
        };

        Video.prototype.increaseCineSpeed = function () {
            Subjects.modify(this.cineSpeed, function (s) {
                return Math.min(60, ++s);
            });
        };

        Video.prototype.decreaseCineSpeed = function () {
            Subjects.modify(this.cineSpeed, function (s) {
                return Math.max(1, --s);
            });
        };
        return Video;
    })();
    Views.Video = Video;
})(Views || (Views = {}));
var Views;
(function (Views) {
    /**
    * View available recordings
    */
    var RecordingsInfo = (function () {
        function RecordingsInfo(el, sessionId, permissions, terminology, listenForKeyboardInput) {
            this.el = el;
            this.sessionId = sessionId;
            this.terminology = terminology;
            this.permissions = permissions;
            this.terminology = terminology;
            this.listenForKeyboardInput = listenForKeyboardInput;
        }
        RecordingsInfo.prototype.playRecording = function (attachment, queryObject) {
            var uri = "/viewer/#script/" + queryObject.storageNamespace.value + "/" + queryObject.studyUid.value + "/" + attachment.phiNamespace.value + "?sid=" + this.sessionId.value;
            uri += "&attachmentId=" + attachment.id.value;
            uri += "&attachmentVersion=" + attachment.version.value;
            uri += "&attachmentPhiNs=" + attachment.phiNamespace.value;
            window.open(uri);
        };

        RecordingsInfo.prototype.deleteRecording = function (attachment, study, studyStorage, queryObject) {
            var _this = this;
            var message = this.terminology.lookup(Terminology.Terms.DeleteRecordingWarning);

            if (window.confirm(message)) {
                var deleteAttachment = V3Storage.deleteAttachment(this.sessionId, studyStorage, queryObject, attachment);
                $('.overlay').addClass('application-loading');

                Observable._finally(deleteAttachment, function () {
                    $('.overlay').removeClass('application-loading');
                }).subscribe({
                    done: function () {
                        study.attachments = _.filter(study.attachments, function (att) {
                            return att != attachment;
                        });
                        _this.render(study, studyStorage, queryObject);
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.ErrorDeletingRecording));
                    }
                });
            }
        };

        RecordingsInfo.prototype.render = function (study, studyStorage, queryObject) {
            var _this = this;
            var recordingInfo = this;
            var linkId = null;
            var currentRecording = null;
            var recordings = [];

            var $recordings = $('<div>');
            $recordings.append($('<h2>').text(this.terminology.lookup(Terminology.Terms.Recordings)));

            if (study.attachments && study.attachments.length) {
                _.each(study.attachments, function (attachment) {
                    if (attachment.mime == Recording.MIME_TYPE) {
                        recordings.push(attachment);
                    }
                });
            }

            if (recordings.length > 0) {
                currentRecording = recordings[0];

                // recordings list
                var $recordingsSelect = $('<select class="recordingsSelect" name="recordingsSelect" />');
                _.each(recordings, function (recording) {
                    var filename = recording.filename;
                    if (!filename) {
                        filename = "(Untitled)";
                    }

                    filename = filename.replace(".json", "");
                    $("<option />", { value: recording.id.value, text: filename }).appendTo($recordingsSelect);
                });

                $recordingsSelect.on('change', function () {
                    currentRecording = recordings[$recordingsSelect.find(":selected").index()];
                });

                $recordings.append($recordingsSelect);

                // play recordings
                var $playRecording = $('<p>').appendTo($recordings);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.PlayRecording)).click(function (e) {
                    _this.playRecording(currentRecording, queryObject);
                    e.preventDefault();
                }).appendTo($playRecording);

                // delete recording
                var $deleteRecording = $('<p>').appendTo($recordings);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.DeleteRecording)).click(function (e) {
                    _this.deleteRecording(currentRecording, study, studyStorage, queryObject);
                    e.preventDefault();
                }).appendTo($deleteRecording);

                // share recordings
                var $shareRecordingLink = $('<a>');
                var $shareRecording = $('<p>').append($shareRecordingLink);

                if (!LocalViewer.isLocalViewer() && (this.permissions.link_edit !== 0) && (this.permissions.link_view !== 0)) {
                    $shareRecordingLink.attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.ShareRecordingLink));

                    var $shareRecordingForm = $('<div>');
                    var $shareRecordingFormEmail = $('<input>').attr({ type: 'text' }).attr({ id: 'meeting-form-email' }).attr({ placeholder: 'Email Address' }).css({
                        "color": "black",
                        "font-size": "11px",
                        "width": "100%",
                        "margin": "4px 0"
                    });
                    $shareRecordingForm.append($shareRecordingFormEmail);

                    var clearForm = function () {
                        $shareRecordingForm.hide();
                        recordingInfo.listenForKeyboardInput.write(true);
                    };

                    var $shareRecordingFormSuccess = $('<p>').text(this.terminology.lookup(Terminology.Terms.SentRecordingLink));

                    var $shareRecordingFormCancel = $('<a>').attr({ href: '#' }).addClass('blue-button').css({ "width": "49%", "display": "inline-block", "float": "left" }).text("Cancel").click(function (e) {
                        clearForm();
                    });

                    var $shareRecordingFormSend = $('<a>').attr({ href: '#' }).addClass('blue-button').css({ "width": "49%", "display": "inline-block", "float": "right" }).text("Send").click(function (e) {
                        var email = $("#meeting-form-email").val();
                        if (email) {
                            var $overlay = $('.overlay');
                            $overlay.addClass('application-loading');

                            var ob;
                            if (linkId) {
                                ob = Observable._finally(Services.ShareRecordingLink(_this.sessionId, linkId, new Classes.EmailAddress(email)), function () {
                                    $overlay.removeClass('application-loading');
                                });
                            } else {
                                ob = Observable._finally(Services.CreateScriptLink(_this.sessionId, queryObject, new Classes.StudyUUID(studyStorage.uuid), new Classes.AttachmentID(currentRecording.id.value), new Classes.AttachmentVersion(currentRecording.version.value), new Classes.AttachmentPhiNamespace(currentRecording.phiNamespace.value), new Classes.EmailAddress(email)), function () {
                                    $overlay.removeClass('application-loading');
                                });
                            }

                            ob.subscribe({
                                done: function () {
                                },
                                next: function (link) {
                                    linkId = (linkId || new Classes.RecordingLinkId(link.uuid));
                                    clearForm();
                                    $shareRecording.after($shareRecordingFormSuccess);
                                },
                                fail: function (err) {
                                    window.alert(_this.terminology.lookup(Terminology.Terms.ErrorSharingRecording));
                                    Services.AuditLog("Unable to create study link: " + err, "RecordingInfo", _this.sessionId);
                                }
                            });
                        }
                    });

                    $shareRecordingForm.append($shareRecordingFormCancel).append($shareRecordingFormSend);
                    $shareRecordingForm.append($('<div>').css({ "clear": "both" }));
                    $shareRecordingForm.hide();
                    $shareRecording.append($shareRecordingForm);

                    var $shareInfo = $('<div>');

                    $shareRecordingFormEmail.focus(function () {
                        recordingInfo.listenForKeyboardInput.write(false);
                    }).blur(function () {
                        recordingInfo.listenForKeyboardInput.write(true);
                    });

                    $shareRecordingLink.click(function (e) {
                        $shareRecordingForm.toggle();

                        if ($shareRecordingForm.is(":visible")) {
                            $shareRecordingFormSuccess.remove();
                            $shareRecordingFormEmail.val("");
                            $shareRecordingFormEmail.focus();
                        } else {
                            recordingInfo.listenForKeyboardInput.write(true);
                        }

                        e.preventDefault();
                    });

                    $recordings.append($shareRecording);
                    $recordings.append($shareInfo);
                }
            } else {
                $recordings.append($('<h2>').css({
                    "text-align": "center"
                }).append($('<em>').text(this.terminology.lookup(Terminology.Terms.NoRecordingsFound))));
            }

            this.el.empty();
            this.el.append($recordings);
        };
        return RecordingsInfo;
    })();
    Views.RecordingsInfo = RecordingsInfo;
})(Views || (Views = {}));
var Views;
(function (Views) {
    /**
    * View available GSPS
    */
    var GSPSInfo = (function () {
        function GSPSInfo(el, sessionId, terminology, application) {
            this.allGSPS = [];
            this.el = el;
            this.sessionId = sessionId;
            this.terminology = terminology;
            this.application = application;
            this.series = this.findSeries();
        }
        GSPSInfo.prototype.findAllGSPS = function (instance) {
            var _this = this;
            var gsps = [];

            if (instance) {
                _.each(instance.instanceAttributes.presentationStates, function (ps) {
                    _this.findPresentationStates(instance, ps, gsps);
                });
            }

            return gsps;
        };

        GSPSInfo.prototype.findSeries = function () {
            var series = this.application.selectedSeriesContainer.read();

            if (series) {
                return series;
            }

            return null;
        };

        GSPSInfo.prototype.findAnnotations = function (instance, graphicAnnotation, gsps) {
            _.each(graphicAnnotation.graphicObjectSequence, function (graphicObject) {
                if (graphicObject.graphicType !== "POINT") {
                    gsps.push({
                        graphic: graphicAnnotation,
                        graphicObject: graphicObject,
                        name: graphicObject.graphicType + " " + (gsps.length + 1)
                    });
                }
            });

            return gsps;
        };

        GSPSInfo.prototype.findPresentationStates = function (instance, presentationState, gsps) {
            var _this = this;
            _.each(presentationState.graphicAnnotationModule.graphicAnnotationSequence, function (graphicAnnotation) {
                if ((graphicAnnotation.referencedImageSequence.length == 0) || (_.any(graphicAnnotation.referencedImageSequence, function (referencedInstance) {
                    return referencedInstance.referencedSOPInstanceUID === instance.id.value && Math.max(0, referencedInstance.referencedFrameNumber - 1) === instance.frameNumber.value;
                }))) {
                    return _this.findAnnotations(instance, graphicAnnotation, gsps);
                }
            });

            return [];
        };

        GSPSInfo.prototype.updateForInstance = function () {
            this.series = this.findSeries();
            this.allGSPS = [];

            if (this.series) {
                this.application.highlightedGraphic.write(null);

                var instance = this.series.currentInstance().read();
                if (instance) {
                    this.allGSPS = this.findAllGSPS(instance);

                    if (this.allGSPS.length) {
                        this.application.highlightedGraphic.write(this.allGSPS[0]);
                    }
                }
            }

            this.series.renderAll();
        };

        GSPSInfo.prototype.updateSelected = function ($gspsSelect, index) {
            this.application.highlightedGraphic.write(this.allGSPS[index]);
            $gspsSelect.find("option").each(function () {
                if ($(this).prop('value') == index) {
                    $(this).prop('selected', true);
                } else {
                    $(this).prop('selected', false);
                }
            });

            this.series.renderAll();
        };

        GSPSInfo.prototype.nextGSPS = function () {
            if (this.allGSPS.length) {
                var highlightedGraphic = this.application.highlightedGraphic.read();
                var index = _.indexOf(this.allGSPS, highlightedGraphic);
                index += 1;
                index %= this.allGSPS.length;
                this.updateSelected(this.$gspsSelect, index);
            }
        };

        GSPSInfo.prototype.previousGSPS = function () {
            if (this.allGSPS.length) {
                var highlightedGraphic = this.application.highlightedGraphic.read();
                var index = _.indexOf(this.allGSPS, highlightedGraphic);
                index -= 1;
                if (index < 0) {
                    index = this.allGSPS.length - 1;
                }
                this.updateSelected(this.$gspsSelect, index);
            }
        };

        GSPSInfo.prototype.render = function () {
            var _this = this;
            var gspsTerm = this.terminology.lookup(Terminology.Terms.GSPS);
            var $gsps = $('<div>');
            $gsps.append($('<h2>').text(gspsTerm));

            this.updateForInstance();

            if (this.allGSPS.length && this.series) {
                // gsps list
                var $gspsSelect = $('<select id="gspsSelect" class="recordingsSelect" name="recordingsSelect" />');
                _.each(this.allGSPS, function (gsps, i) {
                    $("<option />", { value: i, text: gsps.name }).appendTo($gspsSelect);
                });

                this.$gspsSelect = $gspsSelect;
                $gspsSelect.on('change', function () {
                    _this.application.highlightedGraphic.write(_this.allGSPS[$gspsSelect.find(":selected").index()]);
                    _this.series.renderAll();
                });

                $gsps.append($gspsSelect);
                $gsps.append($('<div>').css({ textAlign: "center", paddingBottom: "20px" }).text("(" + this.allGSPS.length + " found)"));

                // next gsps
                var $nextGSPS = $('<p>').appendTo($gsps);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.Next)).click(function (e) {
                    _this.nextGSPS();
                    e.preventDefault();
                }).appendTo($nextGSPS);

                // previous gsps
                var $prevGSPS = $('<p>').appendTo($gsps);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.Previous)).click(function (e) {
                    _this.previousGSPS();
                    e.preventDefault();
                }).appendTo($prevGSPS);
            } else {
                $gsps.append($('<h2>').css({
                    "text-align": "center"
                }).append($('<em>').text(this.terminology.lookup(Terminology.Terms.NoGSPSFound))));
            }

            this.el.empty();
            this.el.append($gsps);

            if (this.series) {
                this.series.renderAll();
            }
        };
        return GSPSInfo;
    })();
    Views.GSPSInfo = GSPSInfo;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Array.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../models/Study.ts' />
var Views;
(function (Views) {
    /**
    * Views required by the MPR sub-application
    */
    (function (MPR) {
        var IMAGE_SIZE = 512;

        var MAX_TILE_X = 8;
        var MAX_TILE_Y = 8;
        var MAX_SLICES = MAX_TILE_X * MAX_TILE_Y * 16;

        

        /**
        * The 3D Rendering View
        */
        var Application = (function () {
            function Application(el, studyStorage, queryObject, sessionId, terminology, settings, accountSettings, permissions) {
                this.series = null;
                // The bounds of the patient geometry
                this.patientBounds = null;
                // The focal point, where the three views intersect
                this.focus = { x: 0.5, y: 0.5, z: 0.5 };
                this.zoom = 1.0;
                // Zoom factor for the 2D views
                this.zoom2d = 1.0;
                // The current window level
                this.windowLevel = { center: 500, width: 1500 };
                // Volume rotation
                this.volumeRotation = Vectors.identity3();
                // The hardness of the volume texels
                this.hardness = 0.5;
                // Occlusion plane position
                this.occlusionDistance = 0.0;
                // The selected mouse tool
                this.mouseTool = new Subjects.ObservableValue(9 /* Localization */);
                // True if MIP is active
                this.mipActive = new Subjects.ObservableValue(false);
                this.el = el;
                this.studyStorage = studyStorage;
                this.queryObject = queryObject;
                this.sessionId = sessionId;
                this.terminology = terminology;
                this.settings = settings;
                this.accountSettings = accountSettings;
                this.permissions = permissions;
            }
            /**
            * Load the study from storage and call the render method
            */
            Application.prototype.load = function () {
                var _this = this;
                var loadShaders = Observable.invoke(Observable.zip(AJAX.ajaxGet("shaders/fragment.glsl", "text"), AJAX.ajaxGet("shaders/vertex.glsl", "text"), function (fragment, vertex) {
                    return [fragment, vertex];
                }), function (shaders) {
                    _this.initRendering(_this.el, shaders);
                });

                var loadStudy = Observable.invoke(Observable.bind2(Study.loadStudy(this.sessionId, this.studyStorage, this.queryObject, false, this.permissions), function (study) {
                    return _this.loadSeries(study);
                }, function (study, _) {
                    return study;
                }), function (series) {
                    $('#grid').show();
                    $('.toolbar').show();

                    _this.setupEventHandlers();

                    _this.render();
                });

                if (this.accountSettings.viewer_enable_mpr !== undefined && this.accountSettings.viewer_enable_mpr < 1) {
                    window.alert("Multiplanar Reconstruction is not enabled for this account. Please contact Ambra support for assistance.");
                    return Observable.ret({});
                }

                return Observable.bind2(loadShaders, function (_) {
                    return loadStudy;
                }, function (_, study) {
                    return study;
                });
            };

            /**
            * Compile a shader
            */
            Application.prototype.compileShader = function (glsl, type) {
                var shader = this.gl.createShader(type);

                this.gl.shaderSource(shader, glsl);

                this.gl.compileShader(shader);

                if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
                    console.error("Unable to compile shader: " + this.gl.getShaderInfoLog(shader));
                    throw new Error("Unable to compile shader");
                }

                return shader;
            };

            /**
            * Set up keyboard and mouse bindings
            */
            Application.prototype.setupEventHandlers = function () {
                var _this = this;
                $(window).resize(function (_) {
                    _this.render();
                });

                var mouseDown = false;
                var originalMousePosition;

                $('#canvasFrame').on("mousedown", function (e) {
                    originalMousePosition = Mouse.relativeTo(e, $("#canvasFrame"));
                    _this.originalWindowLevel = _this.windowLevel;
                    _this.originalVolumeRotation = { entries: _this.volumeRotation.entries.slice() };
                    _this.originalZoom = _this.zoom;
                    _this.originalZoom2d = _this.zoom2d;

                    $('#canvasFrame').css({ cursor: 'crosshair' });
                    mouseDown = true;
                }).on("mouseup mouseout", function (_) {
                    $('#canvasFrame').css({ cursor: 'default' });
                    mouseDown = false;
                }).on("mousemove", function (e) {
                    if (mouseDown) {
                        _this.handleMouseDrag(originalMousePosition, Mouse.relativeTo(e, $("#canvasFrame")));
                    }
                });

                $('#canvasFrame').mousewheel(function (e, delta, dx, dy) {
                    isNaN(delta) || _this.handleWheel(Mouse.relativeTo(e, $("#canvasFrame")), delta);
                    e.preventDefault();
                });

                $(document).keydown(function (e) {
                    _this.handleKeyPress(e);
                });

                this.mipActive.updates.subscribe({
                    done: function () {
                    },
                    fail: function (_) {
                    },
                    next: function (_) {
                        return _this.render();
                    }
                });
            };

            /**
            * Create the WebGLProgram
            */
            Application.prototype.createProgram = function (shaders) {
                var fragmentShader = this.compileShader(shaders[0], this.gl.FRAGMENT_SHADER);
                var vertexShader = this.compileShader(shaders[1], this.gl.VERTEX_SHADER);

                if (fragmentShader && vertexShader) {
                    var program = this.gl.createProgram();

                    this.gl.attachShader(program, vertexShader);
                    this.gl.attachShader(program, fragmentShader);

                    this.gl.linkProgram(program);

                    if (!this.gl.getProgramParameter(program, this.gl.LINK_STATUS)) {
                        throw new Error("Unable to link shader program");
                    }

                    this.gl.useProgram(program);

                    return program;
                }
            };

            /**
            * Set up vertex and texture coordinate buffers
            */
            Application.prototype.setupBuffers = function (program) {
                var positionLocation = this.gl.getAttribLocation(program, "aPosition");

                var buffer = this.gl.createBuffer();
                this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
                this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
                    -1.0, 1.0,
                    1.0, 1.0,
                    -1.0, -1.0,
                    -1.0, -1.0,
                    1.0, 1.0,
                    1.0, -1.0
                ]), this.gl.STATIC_DRAW);
                this.gl.enableVertexAttribArray(positionLocation);
                this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0);

                var texCoordLocation = this.gl.getAttribLocation(program, "aTexCoord");

                var texCoordBuffer = this.gl.createBuffer();
                this.gl.bindBuffer(this.gl.ARRAY_BUFFER, texCoordBuffer);
                this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
                    0.0, 0.0,
                    1.0, 0.0,
                    0.0, 1.0,
                    0.0, 1.0,
                    1.0, 0.0,
                    1.0, 1.0
                ]), this.gl.STATIC_DRAW);
                this.gl.enableVertexAttribArray(texCoordLocation);
                this.gl.vertexAttribPointer(texCoordLocation, 2, this.gl.FLOAT, false, 0, 0);
            };

            /**
            * Layout the screen, and hook up any event handlers
            */
            Application.prototype.initRendering = function (el, shaders) {
                var _this = this;
                var $toolbar = $('<div>').addClass('toolbar').appendTo(el);

                this.initToolbar($toolbar);

                var $canvasFrame = $('<div>').attr('id', 'canvasFrame').appendTo(el);

                this.canvas = document.createElement("canvas");
                this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");

                this.program = this.createProgram(shaders);

                this.setupBuffers(this.program);

                $canvasFrame.append($(this.canvas));

                var $grid = $('<div>').attr('id', 'grid');

                this.canvas2d = _.map([1, 2, 3, 4], function (i) {
                    var canvas = Images.createCanvas(0, 0);
                    var $canvas = $(canvas).css({ width: "100%", height: "100%" });
                    var $border = $('<div>').addClass('border').append($canvas);
                    var $gridElement = $('<div>').attr('id', 'grid' + i).append($border);

                    $grid.append($gridElement);

                    return canvas;
                });

                $canvasFrame.append($grid);

                var $sliders = $('<div>').attr('id', 'sliders').appendTo($('#grid4 .border'));

                var $hardnessDiv = $('<div>').append($('<i>').addClass('fa fa-adjust')).appendTo($sliders);

                var $hardnessSlider = $('<input>').attr({
                    type: 'range',
                    min: '1',
                    max: '50',
                    value: '25'
                }).addClass('hardness-slider').appendTo($hardnessDiv);

                $hardnessSlider.on('input', function (e) {
                    var value = $(e.target).val();
                    _this.hardness = value / 50.0;
                    _this.render();
                }).on('mousemove', function (e) {
                    e.stopPropagation();
                });

                var $occlusionDiv = $('<div>').append($('<i>').addClass('fa fa-eraser')).appendTo($sliders);

                var $occlusionSlider = $('<input>').attr({
                    type: 'range',
                    min: '0',
                    max: '100',
                    value: '0'
                }).appendTo($occlusionDiv);

                $occlusionSlider.on('input', function (e) {
                    var value = $(e.target).val();
                    _this.occlusionDistance = value / 100.0 * 0.866;
                    _this.render();
                }).on('mousemove', function (e) {
                    e.stopPropagation();
                });
            };

            /**
            * Get the window level preset toolbar buttons
            */
            Application.prototype.getWindowLevelPresetButtons = function () {
                var _this = this;
                var modalitySettings = _.find(this.settings.modalities, function (m) {
                    return m.modality === "CT";
                });

                var presets;

                if (modalitySettings && modalitySettings.presets !== undefined && modalitySettings.presets.length > 0) {
                    presets = modalitySettings.presets;
                } else {
                    presets = WindowLevelPresets.defaults("CT", this.terminology);
                }

                return _.map(_.take(presets, 10), function (preset, index) {
                    return {
                        title: preset.name.substr(0, 8),
                        tooltip: preset.name,
                        icon: "w_window",
                        action: Either.Left(function () {
                            _this.applyWindowLevelPreset(index);
                        })
                    };
                });
            };

            /**
            * Save a window level preset
            */
            Application.prototype.saveWindowLevelPreset = function () {
                var presetName = window.prompt(this.terminology.lookup(Terminology.Terms.EnterPresetName), this.terminology.lookup(Terminology.Terms.Untitled));

                if (presetName) {
                    var modalitySettings = _.find(this.settings.modalities, function (m) {
                        return m.modality === "CT";
                    });

                    if (!modalitySettings) {
                        this.settings.modalities.push(modalitySettings = { modality: "CT" });
                    }

                    if (!modalitySettings.presets) {
                        modalitySettings.presets = WindowLevelPresets.defaults("CT", this.terminology);
                    }

                    if (modalitySettings.presets.length >= 9) {
                        var presetNumberString = window.prompt(this.terminology.lookup(Terminology.Terms.SelectPresetToReplace), "1");

                        var presetNumber;

                        if (!presetNumberString || presetNumberString.length != 1 || !_.contains(['1', '2', '3', '4', '5', '6', '7', '8', '9'], presetNumberString[0])) {
                            presetNumber = 1;
                        } else {
                            presetNumber = parseInt(presetNumberString);
                        }

                        modalitySettings.presets[presetNumber - 1] = {
                            name: presetName,
                            windowLevel: this.windowLevel,
                            opacity: this.hardness
                        };
                    } else {
                        modalitySettings.presets.push({
                            name: presetName,
                            windowLevel: this.windowLevel,
                            opacity: this.hardness
                        });
                    }

                    this.initToolbar($('.toolbar').empty());
                    this.saveSettings();
                }
            };

            /**
            * Save settings to services
            */
            Application.prototype.saveSettings = function () {
                Services.putSettings(this.sessionId, this.settings).subscribe({
                    done: function () {
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        console.error("Unable to save settings to services: " + err);
                    }
                });
            };

            /**
            * Set up the toolbar
            */
            Application.prototype.initToolbar = function ($toolbar) {
                var _this = this;
                var toolbar = new Views.MenuBar($toolbar);

                Views.AbstractToolbars.addMouseToolButton(toolbar, {
                    title: this.terminology.lookup(Terminology.Terms.Rotate),
                    tooltip: this.terminology.lookup(Terminology.Terms.RotateTooltip),
                    icon: "w_rotate",
                    tool: 17 /* MPRRotate */,
                    selectedTool: this.mouseTool,
                    selectedTool2: this.mouseTool
                });

                Views.AbstractToolbars.addMouseToolButton(toolbar, {
                    title: this.terminology.lookup(Terminology.Terms.Zoom),
                    tooltip: this.terminology.lookup(Terminology.Terms.ZoomTooltip),
                    icon: "w_zoom",
                    tool: 2 /* Zoom */,
                    selectedTool: this.mouseTool,
                    selectedTool2: this.mouseTool
                });

                Views.AbstractToolbars.addDropDownButton(toolbar, {
                    title: this.terminology.lookup(Terminology.Terms.WindowLevel),
                    tooltip: this.terminology.lookup(Terminology.Terms.WindowLevelTooltip),
                    icon: "w_window"
                }, [
                    {
                        title: this.terminology.lookup(Terminology.Terms.SelectWindowLevel),
                        tooltip: this.terminology.lookup(Terminology.Terms.SelectWindowLevelTooltip),
                        icon: "w_pan",
                        action: Either.Right({
                            tool: 3 /* Window */,
                            selectedTool: this.mouseTool,
                            selectedTool2: this.mouseTool
                        })
                    }, {
                        title: this.terminology.lookup(Terminology.Terms.SavePreset),
                        tooltip: this.terminology.lookup(Terminology.Terms.SavePresetTooltip),
                        icon: "w_save",
                        action: Either.Left(function () {
                            _this.saveWindowLevelPreset();
                        })
                    }].concat(this.getWindowLevelPresetButtons()));

                Views.AbstractToolbars.addMouseToolButton(toolbar, {
                    title: this.terminology.lookup(Terminology.Terms.PlaneLocalization),
                    tooltip: this.terminology.lookup(Terminology.Terms.PlaneLocalizationTooltip),
                    icon: "w_3d",
                    tool: 9 /* Localization */,
                    selectedTool: this.mouseTool,
                    selectedTool2: this.mouseTool
                });

                toolbar.addToggleButton({
                    title: this.terminology.lookup(Terminology.Terms.MIP),
                    tooltip: this.terminology.lookup(Terminology.Terms.MIPTooltip),
                    icon: "w_reflines",
                    selected: this.mipActive
                });
            };

            /**
            * Load the images for a series and create textures
            */
            Application.prototype.loadSeries = function (study) {
                var _this = this;
                var seriesUid = Query.findParameter(window.location, "seriesUid");

                var series = _.find(study.series, function (s) {
                    return s.seriesAttributes.seriesUid.value === seriesUid;
                });

                if (!series) {
                    throw new Error("Series not found");
                }

                this.series = series;

                var numTextures = Math.min(16, series.instances.length / MAX_TILE_X / MAX_TILE_Y);
                var availableTextures = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS);

                if (numTextures > availableTextures) {
                    throw new Error("Not enough available texture units.");
                }

                var textureSize = this.gl.getParameter(this.gl.MAX_TEXTURE_SIZE);

                if (textureSize < IMAGE_SIZE * Math.max(MAX_TILE_X, MAX_TILE_Y)) {
                    throw new Error("Max texture size is too small.");
                }

                var ctxs = _.map(_.range(0, numTextures), function (_) {
                    var canvas = document.createElement("canvas");
                    canvas.width = IMAGE_SIZE * MAX_TILE_X;
                    canvas.height = IMAGE_SIZE * MAX_TILE_Y * 2;
                    return canvas.getContext('2d');
                });

                var loadImageAttributes = Observable.sequenceA(_.map(_.take(series.instances, MAX_SLICES), function (inst) {
                    return Study.loadImageAttributes(_this.sessionId, inst);
                }));

                var imagesAndAttributes = Observable.bind(loadImageAttributes, function (_1) {
                    if (_this.validateSeriesMetadata()) {
                        _this.patientBounds = _this.computePatientBounds();

                        var slices = _.sortBy(_.take(series.instances, MAX_SLICES), function (slice) {
                            return -slice.instanceAttributes.imagePositionPatient[2];
                        });

                        return Observable.sequenceA(_.map(slices, function (inst, index) {
                            var image = new Image();
                            var uri = Routes.ImageData(_this.sessionId, _this.studyStorage, _this.queryObject, inst.id, inst.instanceAttributes.version, new Classes.FrameNumber(0), 2 /* Diagnostic */, 16, false);

                            var loadImage = Observable.invoke(Images.loadImageOnly(image, uri), function (_) {
                                var ctx = ctxs[Math.floor(index / (MAX_TILE_X * MAX_TILE_Y))];
                                var index1 = index % (MAX_TILE_X * MAX_TILE_Y);

                                ctx.drawImage(image, 0, 0, image.width, image.height, (index1 % MAX_TILE_X) * IMAGE_SIZE, ((index1 / MAX_TILE_X) | 0) * IMAGE_SIZE * 2, IMAGE_SIZE, IMAGE_SIZE * 2); // Destination height
                            });

                            return loadImage;
                        }));
                    } else {
                        window.alert("This series cannot be used for multiplanar reconstruction. Please select an axial series with evenly-spaced slices.");
                    }
                });

                return Observable.forget(Observable.invoke(imagesAndAttributes, function (is) {
                    var gl = _this.gl;

                    var textures = [
                        gl.TEXTURE0,
                        gl.TEXTURE1,
                        gl.TEXTURE2,
                        gl.TEXTURE3,
                        gl.TEXTURE4,
                        gl.TEXTURE5,
                        gl.TEXTURE6,
                        gl.TEXTURE7,
                        gl.TEXTURE8,
                        gl.TEXTURE9,
                        gl.TEXTURE10,
                        gl.TEXTURE11,
                        gl.TEXTURE12,
                        gl.TEXTURE13,
                        gl.TEXTURE14,
                        gl.TEXTURE15
                    ];

                    _.each(ctxs, function (ctx, index, list) {
                        var texture = gl.createTexture();

                        gl.activeTexture(textures[index]);
                        gl.bindTexture(gl.TEXTURE_2D, texture);

                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

                        gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, gl.LUMINANCE, gl.UNSIGNED_BYTE, ctx.canvas);

                        var texLoc = gl.getUniformLocation(_this.program, "uImage" + (index + 1));
                        gl.uniform1i(texLoc, index);
                    });
                }));
            };

            /**
            * Ensure the following:
            *
            * - The series is an axial series (all instances parallel to the xy-plane)
            * - Instances are evenly spaced and sorted
            * - Images are square and patient spacing is isometric
            */
            Application.prototype.validateSeriesMetadata = function () {
                var firstOrientation = this.series.instances[0].instanceAttributes.imageOrientationPatient;

                if (!(Math.abs(firstOrientation[0]) > Math.abs(firstOrientation[1]) && Math.abs(firstOrientation[0]) > Math.abs(firstOrientation[2]) && Math.abs(firstOrientation[4]) > Math.abs(firstOrientation[3]) && Math.abs(firstOrientation[4]) > Math.abs(firstOrientation[5]))) {
                    console.error("Series is not axial or metadata tags are missing.");
                    return false;
                }

                var allAxial = _.all(_.take(this.series.instances, MAX_SLICES), function (inst) {
                    var imagePositionPatient = inst.instanceAttributes.imagePositionPatient;
                    var imageOrientationPatient = inst.instanceAttributes.imageOrientationPatient;

                    if (!(imagePositionPatient !== null && imagePositionPatient.length === 3 && imageOrientationPatient !== null && imageOrientationPatient.length === 6)) {
                        return false;
                    }

                    return Math.abs(firstOrientation[0] - imageOrientationPatient[0]) < 1e-6 && Math.abs(firstOrientation[1] - imageOrientationPatient[1]) < 1e-6 && Math.abs(firstOrientation[2] - imageOrientationPatient[2]) < 1e-6 && Math.abs(firstOrientation[3] - imageOrientationPatient[3]) < 1e-6 && Math.abs(firstOrientation[4] - imageOrientationPatient[4]) < 1e-6 && Math.abs(firstOrientation[5] - imageOrientationPatient[5]) < 1e-6;
                });

                if (!allAxial) {
                    console.error("Series is not axial or metadata tags are missing.");
                    return false;
                }

                var zs = _.map(_.take(this.series.instances, MAX_SLICES), function (inst) {
                    var imagePositionPatient = inst.instanceAttributes.imagePositionPatient;
                    return imagePositionPatient[2];
                });

                var diffs = function (xs) {
                    return _.foldl(xs, function (prev, curr, index, list) {
                        return Maybe.maybe(prev.x, { dxs: [], x: Maybe.Just(curr) }, function (x) {
                            return { dxs: prev.dxs.concat([curr - x]), x: Maybe.Just(curr) };
                        });
                    }, { dxs: [], x: Maybe.Nothing() }).dxs;
                };

                var minZ = _.min(zs), maxZ = _.max(zs);

                // A sequence is linear if its double-differences are constantly zero
                var ddzs = diffs(diffs(_.map(zs, function (z) {
                    return z / (maxZ - minZ);
                })));

                var evenlySpaced = _.all(ddzs, function (d) {
                    return Math.abs(d) < 1e-4;
                });

                if (!evenlySpaced) {
                    console.error("Slices are not evenly spaced.");
                    return false;
                }

                var isometric = _.all(_.take(this.series.instances, MAX_SLICES), function (inst) {
                    var attr = inst.instanceAttributes;
                    var pixelSpacing = attr.pixelSpacing;

                    return pixelSpacing !== null && pixelSpacing.length === 2 && pixelSpacing[0] === pixelSpacing[1] && attr.rows === attr.columns;
                });

                if (!isometric) {
                    console.error("Images are either not square or not isometric.");
                    return false;
                }

                return true;
            };

            /**
            * Compute the bounds of the volume in patient coordinates
            */
            Application.prototype.computePatientBounds = function () {
                var lastIndex = Math.min(this.series.instances.length, MAX_SLICES) - 1;

                var i1 = this.series.instances[0].instanceAttributes;
                var i2 = this.series.instances[lastIndex].instanceAttributes;

                var vs1 = SeriesGeometry.boundingVerticesForInstance(i1);
                var vs2 = SeriesGeometry.boundingVerticesForInstance(i2);

                return {
                    mx: Math.min(vs1.tl.x, vs1.br.x),
                    Mx: Math.max(vs1.tl.x, vs1.br.x),
                    My: Math.max(vs1.tl.y, vs1.br.y),
                    my: Math.min(vs1.tl.y, vs1.br.y),
                    mz: Math.min(vs1.tl.z, vs2.tl.z),
                    Mz: Math.max(vs1.tl.z, vs2.tl.z)
                };
            };

            /**
            * Calculate appropriate scaling factors based on aspect ratio and
            * volume dimensions
            */
            Application.prototype.aspectScale = function (aspect, w, h) {
                var volumeAspect = h / w;

                var ax, ay;

                if (aspect > volumeAspect) {
                    return {
                        sx: 1.0,
                        sy: aspect / volumeAspect
                    };
                } else {
                    return {
                        sx: volumeAspect / aspect,
                        sy: 1.0
                    };
                }
            };

            /**
            * Draw reference lines on the overlay canvas
            */
            Application.prototype.drawReferenceLines = function (canvas, trans, mipH, mipV, dw, dh, hColor, vColor) {
                var ctx = canvas.getContext('2d');

                var x = this.focus.x;
                var y = this.focus.y;
                var z = this.focus.z;

                ctx.save();

                if (this.mipActive.read()) {
                    ctx.save();

                    ctx.setLineDash([5]);

                    var p1 = Vectors.multiply3(trans, Vectors.add(this.focus, mipV));
                    var p2 = Vectors.multiply3(trans, Vectors.sub(this.focus, mipV));

                    ctx.strokeStyle = vColor;
                    ctx.beginPath();
                    ctx.moveTo(p1.x * dw, 0);
                    ctx.lineTo(p1.x * dw, dh);
                    ctx.moveTo(p2.x * dw, 0);
                    ctx.lineTo(p2.x * dw, dh);
                    ctx.stroke();

                    var p3 = Vectors.multiply3(trans, Vectors.add(this.focus, mipH));
                    var p4 = Vectors.multiply3(trans, Vectors.add(this.focus, mipH));

                    ctx.strokeStyle = hColor;
                    ctx.beginPath();
                    ctx.moveTo(0, p3.y * dh);
                    ctx.lineTo(dw, p3.y * dh);
                    ctx.moveTo(0, p4.y * dh);
                    ctx.lineTo(dw, p4.y * dh);
                    ctx.stroke();

                    ctx.restore();
                }

                var p1 = Vectors.multiply3(trans, this.focus);

                ctx.strokeStyle = vColor;
                ctx.beginPath();
                ctx.moveTo(p1.x * dw, 0);
                ctx.lineTo(p1.x * dw, dh);
                ctx.stroke();

                ctx.strokeStyle = hColor;
                ctx.beginPath();
                ctx.moveTo(0, p1.y * dh);
                ctx.lineTo(dw, p1.y * dh);
                ctx.stroke();

                ctx.restore();
            };

            /**
            * Render annotations for the current series
            */
            Application.prototype.renderAnnotations = function (canvas) {
                canvas.width = $(canvas).width();
                canvas.height = $(canvas).height();

                var width = canvas.width;
                var height = canvas.height;

                var context = canvas.getContext('2d');

                var maxHeight = Math.min(Math.min(width, height) / 20.0, 16.0);

                var annotations = TextAnnotations.getTextAnnotations(this.series.seriesAttributes.modality, this.settings);
                var dateFormat = this.settings.dateFormat;

                var data = {
                    transform: {
                        getImageTransformation: function (dw, dh, sw, sh) {
                            return Vectors.identity();
                        },
                        getInverseImageTransformation: function (dw, dh, sw, sh) {
                            return Vectors.identity();
                        },
                        mapToImage: function (p, dw, dh, sw, sh) {
                            return p;
                        }
                    },
                    getImageType: function () {
                        return 2 /* Diagnostic */;
                    },
                    windowLevel: Subjects.ret(this.windowLevel),
                    instance: this.series.instances[0],
                    terminology: this.terminology,
                    selectedMeasurement: Subjects.ret(null),
                    context: context,
                    dateFormat: dateFormat
                };

                var arranged = TextAnnotations.arrangeAnnotations(annotations, data);

                var allText = _.flatten([arranged.topLeft, arranged.bottomLeft, arranged.bottomRight]);
                var fontHeight = _.min(_.map(allText, function (text) {
                    return Rendering.fitTextToWidth(context, text, width / 2.5, maxHeight);
                }));

                context.save();

                Rendering.writeLines(context, arranged.topLeft, 5, 5, fontHeight);
                Rendering.writeLinesBottomLeft(context, arranged.bottomLeft, 5, height - 10, fontHeight);
                Rendering.writeLinesBottomRight(context, arranged.bottomRight, width - 5, height - 10, fontHeight);
                Rendering.writeLinesTopRight(context, arranged.topRight, width - 5, 5, fontHeight);

                context.restore();
            };

            /**
            * Render orientation annotations
            */
            Application.prototype.renderOrientation = function (canvas, imageOrientationPatient) {
                var context = canvas.getContext('2d');

                context.save();

                Layers.Orientation.renderAnnotations(context, {
                    x: imageOrientationPatient[0],
                    y: imageOrientationPatient[1],
                    z: imageOrientationPatient[2]
                }, {
                    x: imageOrientationPatient[3],
                    y: imageOrientationPatient[4],
                    z: imageOrientationPatient[5]
                });

                context.restore();
            };

            /**
            * Render the next frame
            */
            Application.prototype.render = function () {
                var gl = this.gl;

                gl.clearColor(0, 0, 0, 1);
                gl.clear(gl.COLOR_BUFFER_BIT);

                var width = this.canvas.width = $('#canvasFrame').width();
                var height = this.canvas.height = $('#canvasFrame').height();

                gl.viewport(0, 0, width, height);

                var aspect = height / width;

                var bounds = this.patientBounds;

                var pTrans = gl.getUniformLocation(this.program, "pTrans");
                var tTrans = gl.getUniformLocation(this.program, "tTrans");

                var uSlices = gl.getUniformLocation(this.program, "uSlices");
                gl.uniform1f(uSlices, Math.min(this.series.instances.length, MAX_SLICES));

                var instance = this.series.instances[0];

                // Window level parameters
                var centerLocation = gl.getUniformLocation(this.program, "uCenter");
                gl.uniform1f(centerLocation, this.windowLevel.center / 65536);

                var widthLocation = gl.getUniformLocation(this.program, "uWidth");
                gl.uniform1f(widthLocation, this.windowLevel.width / 65536);

                var signedLocation = gl.getUniformLocation(this.program, "uSigned");
                gl.uniform1i(signedLocation, instance.instanceAttributes.signed ? 1 : 0);

                var sigmoidLocation = gl.getUniformLocation(this.program, "uSigmoid");
                gl.uniform1i(sigmoidLocation, instance.instanceAttributes.voiLutFunction === "SIGMOID" ? 1 : 0);

                var slopeLocation = gl.getUniformLocation(this.program, "uSlope");
                gl.uniform1f(slopeLocation, instance.instanceAttributes.rescaleSlope || 1);

                var interceptLocation = gl.getUniformLocation(this.program, "uIntercept");
                gl.uniform1f(interceptLocation, (instance.instanceAttributes.rescaleIntercept || 0) / 65536);

                var volumeLocation = gl.getUniformLocation(this.program, "uVolume");
                gl.uniform1i(volumeLocation, 0);

                var hardnessLocation = gl.getUniformLocation(this.program, "uHardness");
                gl.uniform1f(hardnessLocation, this.hardness * 50.0);

                var occlusionLocation = gl.getUniformLocation(this.program, "uOcclude");
                gl.uniform1f(occlusionLocation, this.occlusionDistance);

                // MIP Parameters
                var mipLocation = gl.getUniformLocation(this.program, "uMIP");
                gl.uniform1i(mipLocation, this.mipActive.read() ? 1 : 0);

                var mipDepthLocation = gl.getUniformLocation(this.program, "uMIPDepth");

                var maxBounds = Math.max(this.patientBounds.Mx - this.patientBounds.mx, this.patientBounds.My - this.patientBounds.my, this.patientBounds.Mz - this.patientBounds.mz);

                var mipX = maxBounds / (this.patientBounds.Mx - this.patientBounds.mx) * 0.05;
                var mipY = maxBounds / (this.patientBounds.My - this.patientBounds.my) * 0.05;
                var mipZ = maxBounds / (this.patientBounds.Mz - this.patientBounds.mz) * 0.05;

                // Axial
                var as = this.aspectScale(aspect, bounds.Mx - bounds.mx, bounds.My - bounds.my);

                gl.uniformMatrix3fv(pTrans, false, [
                    1 / 2, 0.0, 0.0,
                    0.0, 1 / 2, 0.0,
                    -1 / 2, 1 / 2, 1.0]);

                gl.uniformMatrix4fv(tTrans, false, Vectors.transpose3(this.createTextureMatrix(as, Vectors.identity3(), this.focus.z)));

                gl.uniform1f(mipDepthLocation, mipZ);

                gl.drawArrays(gl.TRIANGLES, 0, 6);

                // Axial reference lines
                var axial2d = this.canvas2d[0];

                this.renderAnnotations(axial2d);

                var orientation = this.series.instances[0].instanceAttributes.imageOrientationPatient;
                var plane = SeriesGeometry.normalPlaneForInstance(this.series.instances[0].instanceAttributes);

                this.renderOrientation(axial2d, orientation);

                this.drawReferenceLines(axial2d, this.invTextureMatrix(as, Vectors.identity3()), { x: 0.0, y: mipY, z: 0.0 }, { x: mipX, y: 0.0, z: 0.0 }, width / 2, height / 2, "red", "yellow");

                // Sagittal
                var ss = this.aspectScale(aspect, bounds.My - bounds.my, bounds.Mz - bounds.mz);

                gl.uniformMatrix3fv(pTrans, false, [
                    1 / 2, 0.0, 0.0,
                    0.0, 1 / 2, 0.0,
                    1 / 2, 1 / 2, 1.0]);

                gl.uniformMatrix4fv(tTrans, false, Vectors.transpose3(this.createTextureMatrix(ss, this.sagittal(), this.focus.x)));

                gl.uniform1f(mipDepthLocation, mipX);

                gl.drawArrays(gl.TRIANGLES, 0, 6);

                // Sagittal reference lines
                var sagittal2d = this.canvas2d[1];

                this.renderAnnotations(sagittal2d);

                this.renderOrientation(sagittal2d, MPR.Application.sagittalOrientation(orientation, plane));

                this.drawReferenceLines(sagittal2d, this.invTextureMatrix(ss, this.invSagittal()), { x: 0.0, y: 0.0, z: mipZ }, { x: 0.0, y: mipY, z: 0.0 }, width / 2, height / 2, "cyan", "red");

                // Coronal
                var cs = this.aspectScale(aspect, bounds.Mx - bounds.mx, bounds.Mz - bounds.mz);

                gl.uniformMatrix3fv(pTrans, false, [
                    1 / 2, 0.0, 0.0,
                    0.0, 1 / 2, 0.0,
                    -1 / 2, -1 / 2, 1.0]);

                gl.uniformMatrix4fv(tTrans, false, Vectors.transpose3(this.createTextureMatrix(cs, this.coronal(), this.focus.y)));

                gl.uniform1f(mipDepthLocation, mipY);

                gl.drawArrays(gl.TRIANGLES, 0, 6);

                // Coronal reference lines
                var coronal2d = this.canvas2d[2];

                this.renderAnnotations(coronal2d);

                this.renderOrientation(coronal2d, MPR.Application.coronalOrientation(orientation, plane));

                this.drawReferenceLines(coronal2d, this.invTextureMatrix(cs, this.coronal()), { x: 0.0, y: 0.0, z: mipZ }, { x: mipX, y: 0.0, z: 0.0 }, width / 2, height / 2, "cyan", "yellow");

                // Volume
                gl.uniform1i(volumeLocation, 1);

                gl.uniformMatrix3fv(pTrans, false, [
                    1 / 2, 0.0, 0.0,
                    0.0, 1 / 2, 0.0,
                    1 / 2, -1 / 2, 1.0]);

                var viewportAspect = this.aspectScale(aspect, 1.0, 1.0);

                gl.uniformMatrix4fv(tTrans, false, this.createVolumeMatrix(viewportAspect));

                gl.uniform1f(mipDepthLocation, mipY);

                gl.drawArrays(gl.TRIANGLES, 0, 6);
            };

            Application.prototype.volumeAspect = function () {
                var w = this.patientBounds.Mx - this.patientBounds.mx;
                var h = this.patientBounds.My - this.patientBounds.my;
                var d = this.patientBounds.Mz - this.patientBounds.mz;

                if (w >= h && w >= d) {
                    return Vectors.scale3(1.0, w / h, w / d);
                } else if (h >= w && h >= d) {
                    return Vectors.scale3(h / w, 1.0, h / d);
                } else {
                    return Vectors.scale3(d / w, d / h, 1.0);
                }
            };

            Application.prototype.aspectMatrix = function (as) {
                return Vectors.compose3(Vectors.translate3((1 - as.sx) / 2, (1 - as.sy) / 2, 0), Vectors.scale3(as.sx, as.sy, 1));
            };

            Application.prototype.invAspectMatrix = function (as) {
                return Vectors.compose3(Vectors.scale3(1 / as.sx, 1 / as.sy, 1), Vectors.translate3((as.sx - 1) / 2, (as.sy - 1) / 2, 0));
            };

            Application.prototype.sagittal = function () {
                return {
                    entries: [
                        0, 0, 1, 0,
                        1, 0, 0, 0,
                        0, 1, 0, 0,
                        0, 0, 0, 1
                    ]
                };
            };

            Application.prototype.invSagittal = function () {
                return {
                    entries: [
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        1, 0, 0, 0,
                        0, 0, 0, 1
                    ]
                };
            };

            Application.prototype.coronal = function () {
                return {
                    entries: [
                        1, 0, 0, 0,
                        0, 0, 1, 0,
                        0, 1, 0, 0,
                        0, 0, 0, 1
                    ]
                };
            };

            Application.prototype.createTextureMatrix = function (as, rotation, depth) {
                var zoom = 1.0 / this.zoom2d;

                return [
                    rotation,
                    Vectors.translate3(0.5, 0.5, depth),
                    Vectors.scale3(zoom, zoom, zoom),
                    Vectors.translate3(-0.5, -0.5, 0.0),
                    this.aspectMatrix(as)
                ].foldl1(Vectors.compose3);
            };

            Application.prototype.invTextureMatrix = function (as, rotation) {
                var zoom = this.zoom2d;

                return [
                    this.invAspectMatrix(as),
                    Vectors.translate3(0.5, 0.5, 0.0),
                    Vectors.scale3(zoom, zoom, zoom),
                    Vectors.translate3(-0.5, -0.5, 0.0),
                    rotation
                ].foldl1(Vectors.compose3);
            };

            Application.prototype.createVolumeMatrix = function (as) {
                var zoom = 1.0 / this.zoom;

                var m = Vectors.transpose3([
                    Vectors.translate3(this.focus.x, this.focus.y, this.focus.z),
                    this.volumeAspect(),
                    this.volumeRotation,
                    Vectors.scale3(zoom, zoom, 1.0),
                    Vectors.translate3(-0.5, -0.5, 0.0),
                    this.aspectMatrix(as)
                ].foldl1(Vectors.compose3));

                return m;
            };

            /**
            * Handle a key press
            */
            Application.prototype.handleKeyPress = function (e) {
                if (e.keyCode >= 97 && e.keyCode <= 105) {
                    var index = e.keyCode - 97;
                    this.applyWindowLevelPreset(index);
                    e.preventDefault();
                } else if (e.keyCode >= 49 && e.keyCode <= 57) {
                    var index = e.keyCode - 49;
                    this.applyWindowLevelPreset(index);
                    e.preventDefault();
                } else {
                    switch (e.keyCode) {
                        case 87:
                            this.mouseTool.write(3 /* Window */);
                            break;
                        case 90:
                            this.mouseTool.write(2 /* Zoom */);
                            break;
                        case 82:
                            this.mouseTool.write(17 /* MPRRotate */);
                            break;
                        case 77:
                            Subjects.modify(this.mipActive, function (b) {
                                return !b;
                            });
                            break;
                        case 76:
                            this.mouseTool.write(9 /* Localization */);
                            break;
                    }
                }
            };

            Application.sagittalOrientation = function (orientation, plane) {
                return _.drop(orientation, 3).concat(plane.n.z > 0 ? [
                    plane.n.x,
                    plane.n.y,
                    plane.n.z
                ] : [
                    -plane.n.x,
                    -plane.n.y,
                    -plane.n.z
                ]);
            };

            Application.coronalOrientation = function (orientation, plane) {
                return _.take(orientation, 3).concat(plane.n.z > 0 ? [
                    plane.n.x,
                    plane.n.y,
                    plane.n.z
                ] : [
                    -plane.n.x,
                    -plane.n.y,
                    -plane.n.z
                ]);
            };

            /**
            * Handle a mouse drag event
            */
            Application.prototype.handleMouseDrag = function (old, pos) {
                switch (this.mouseTool.read()) {
                    case 17 /* MPRRotate */:
                        this.applyRotation(old, pos);
                        break;
                    case 2 /* Zoom */:
                        this.applyZoom(old, pos);
                        break;
                    case 9 /* Localization */:
                        this.localize(old, pos);
                        break;
                    case 3 /* Window */:
                        this.applyWindowLevel(old, pos);
                        break;
                }
            };

            /**
            * Update the volume rotation
            */
            Application.prototype.applyRotation = function (old, pos) {
                var dx = old.x - pos.x;
                var dy = old.y - pos.y;

                var m = { entries: this.originalVolumeRotation.entries.slice() };

                if (Math.abs(dy) > 5) {
                    m = Vectors.compose3(m, Vectors.rotateX(dy / 40.0));
                }

                if (Math.abs(dx) > 5) {
                    m = Vectors.compose3(m, Vectors.rotateY(dx / 40.0));
                }

                this.volumeRotation = m;

                this.render();
            };

            /**
            * Update the volume zoom
            */
            Application.prototype.applyZoom = function (old, pos) {
                var dy = old.y - pos.y;

                var w = $('#canvasFrame').width() / 2;
                var h = $('#canvasFrame').height() / 2;

                if (pos.x > w && pos.y > h) {
                    // Volume view, modify volume zoom
                    if (Math.abs(dy) > 5) {
                        this.zoom = Math.min(20.0, Math.max(0.5, this.originalZoom * Math.exp(dy / 40.0)));
                    } else {
                        this.zoom = this.originalZoom;
                    }
                } else {
                    // 2D view, modify 2D zoom
                    if (Math.abs(dy) > 5) {
                        this.zoom2d = Math.min(20.0, Math.max(0.5, this.originalZoom2d * Math.exp(dy / 40.0)));
                    } else {
                        this.zoom2d = this.originalZoom2d;
                    }
                }

                this.render();
            };

            /**
            * Update the window level settings based on mouse movement
            */
            Application.prototype.applyWindowLevel = function (old, pos) {
                var dx = old.x - pos.x;
                var dy = old.y - pos.y;

                if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
                    var sensitivity = Math.min(Math.max(1.0, this.originalWindowLevel.center / 1000.0), 10.0);

                    this.windowLevel = {
                        center: Math.round(this.originalWindowLevel.center + sensitivity * dy),
                        width: Math.round(Math.max(1.0, this.originalWindowLevel.width * Math.exp(dx / 200.0)))
                    };

                    this.render();
                }
            };

            /**
            * Plane localization
            */
            Application.prototype.localize = function (old, pos) {
                var dx = old.x - pos.x;
                var dy = old.y - pos.y;

                var w = $('#canvasFrame').width() / 2;
                var h = $('#canvasFrame').height() / 2;

                var bounds = this.patientBounds;

                var aspect = h / w;

                if (pos.x < w && pos.y < h) {
                    // Axial, modify x and y
                    var as = this.aspectScale(aspect, bounds.Mx - bounds.mx, bounds.My - bounds.my);

                    var trans = this.createTextureMatrix(as, Vectors.identity3(), 0.0);
                    var p = Vectors.multiply3(trans, { x: pos.x / w, y: pos.y / h, z: 0 });

                    this.focus.x = p.x;
                    this.focus.y = p.y;
                } else if (pos.y < h) {
                    // Sagittal, modify y and z
                    var as = this.aspectScale(aspect, bounds.My - bounds.my, bounds.Mz - bounds.mz);

                    var trans = this.createTextureMatrix(as, this.sagittal(), 0.0);
                    var p = Vectors.multiply3(trans, { x: (pos.x - w) / w, y: pos.y / h, z: 0 });

                    this.focus.y = p.y;
                    this.focus.z = p.z;
                } else if (pos.x < w) {
                    // Coronal, modify x and z
                    var as = this.aspectScale(aspect, bounds.Mx - bounds.mx, bounds.Mz - bounds.mz);

                    var trans = this.createTextureMatrix(as, this.coronal(), 0.0);
                    var p = Vectors.multiply3(trans, { x: pos.x / w, y: (pos.y - h) / h, z: 0 });

                    this.focus.x = p.x;
                    this.focus.z = p.z;
                }

                this.render();
            };

            /**
            * Apply a window level preset
            */
            Application.prototype.applyWindowLevelPreset = function (index) {
                var modality = _.find(this.settings.modalities, function (m) {
                    return m.modality === "CT";
                });

                var presets;

                if (modality && modality.presets) {
                    presets = modality.presets;
                } else {
                    presets = WindowLevelPresets.defaults("CT", this.terminology);
                }

                if (index < presets.length) {
                    this.windowLevel = presets[index].windowLevel;

                    if (presets[index].opacity !== undefined) {
                        this.hardness = presets[index].opacity;
                        $('.hardness-slider').val(((this.hardness * 50.0) | 0).toString());
                    }

                    this.render();
                }
            };

            /**
            * Handle a wheel event
            */
            Application.prototype.handleWheel = function (pos, delta) {
                var w = $('#canvasFrame').width();
                var h = $('#canvasFrame').height();

                var clamp = function (x) {
                    return Math.min(Math.max(x, 0.0), 1.0);
                };

                if (pos.x * 2 < w && pos.y * 2 < h) {
                    // Axial, modify z
                    this.focus.z = clamp(this.focus.z + delta / 50.0);
                } else if (pos.x * 2 >= w && pos.y * 2 < h) {
                    // Sagittal, modify x
                    this.focus.x = clamp(this.focus.x + delta / 50.0);
                } else if (pos.x * 2 < w) {
                    // Coronal, modify y
                    this.focus.y = clamp(this.focus.y + delta / 50.0);
                }

                this.render();
            };
            return Application;
        })();
        MPR.Application = Application;
    })(Views.MPR || (Views.MPR = {}));
    var MPR = Views.MPR;
})(Views || (Views = {}));
var Views;
(function (Views) {
    /**
    * View available attachments
    */
    var AttachmentInfo = (function () {
        function AttachmentInfo(el, sessionId, permissions, terminology, listenForKeyboardInput) {
            this.el = el;
            this.sessionId = sessionId;
            this.terminology = terminology;
            this.permissions = permissions;
            this.terminology = terminology;
            this.listenForKeyboardInput = listenForKeyboardInput;
        }
        AttachmentInfo.prototype.openAttachment = function (attachment, studyStorage, queryObject) {
            var uri = Routes.Attachment(this.sessionId, studyStorage, queryObject, attachment.id, attachment.phiNamespace, attachment.version);
            window.open(uri);
        };

        AttachmentInfo.prototype.deleteAttachment = function (attachment, study, studyStorage, queryObject) {
            var _this = this;
            var message = this.terminology.lookup(Terminology.Terms.DeleteReportWarning);

            if (window.confirm(message)) {
                var deleteAttachment = V3Storage.deleteAttachment(this.sessionId, studyStorage, queryObject, attachment);
                $('.overlay').addClass('application-loading');

                Observable._finally(deleteAttachment, function () {
                    $('.overlay').removeClass('application-loading');
                }).subscribe({
                    done: function () {
                        study.attachments = _.filter(study.attachments, function (att) {
                            return att != attachment;
                        });
                        _this.render(study, studyStorage, queryObject);
                    },
                    next: function (_) {
                    },
                    fail: function (err) {
                        window.alert(_this.terminology.lookup(Terminology.Terms.ErrorDeletingReport));
                    }
                });
            }
        };

        AttachmentInfo.prototype.render = function (study, studyStorage, queryObject) {
            var _this = this;
            var currentAttachment = null;
            var attachments = [];

            var $attachments = $('<div>');
            $attachments.append($('<h2>').text(this.terminology.lookup(Terminology.Terms.AttachedReports)));

            if (study.attachments && study.attachments.length) {
                _.each(study.attachments, function (attachment) {
                    if (attachment.mime != Recording.MIME_TYPE) {
                        attachments.push(attachment);
                    }
                });
            }

            if (attachments.length > 0) {
                currentAttachment = attachments[0];

                // attachments list
                var $attachmentsSelect = $('<select class="recordingsSelect" name="recordingsSelect" />');
                _.each(attachments, function (attachment) {
                    var filename = attachment.filename;
                    if (!filename) {
                        filename = "(Untitled)";
                    }

                    filename = filename.replace(".json", "");
                    $("<option />", { value: attachment.id.value, text: filename }).appendTo($attachmentsSelect);
                });

                $attachmentsSelect.on('change', function () {
                    currentAttachment = attachments[$attachmentsSelect.find(":selected").index()];
                });

                $attachments.append($attachmentsSelect);

                // play attachments
                var $playAttachment = $('<p>').appendTo($attachments);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.OpenReport)).click(function (e) {
                    _this.openAttachment(currentAttachment, studyStorage, queryObject);
                    e.preventDefault();
                }).appendTo($playAttachment);

                // delete attachment
                var $deleteAttachment = $('<p>').appendTo($attachments);
                $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.DeleteReport)).click(function (e) {
                    _this.deleteAttachment(currentAttachment, study, studyStorage, queryObject);
                    e.preventDefault();
                }).appendTo($deleteAttachment);
            } else {
                $attachments.append($('<h2>').css({
                    "text-align": "center"
                }).append($('<em>').text(this.terminology.lookup(Terminology.Terms.NoReportsFound))));
            }

            this.el.empty();
            this.el.append($attachments);
        };
        return AttachmentInfo;
    })();
    Views.AttachmentInfo = AttachmentInfo;
})(Views || (Views = {}));
///<reference path='../typings/jquery/jquery.d.ts' />
///<reference path='../classes/Types.ts' />
///<reference path='../libs/Observable.ts' />
///<reference path='../libs/Query.ts' />
///<reference path='../libs/Date.ts' />
///<reference path='../libs/Number.ts' />
///<reference path='../libs/Services.ts' />
///<reference path='../libs/V3Storage.ts' />
///<reference path='../libs/Study.ts' />
///<reference path='../libs/Array.ts' />
///<reference path='../libs/Subject.ts' />
///<reference path='../models/StudySchema.ts' />
///<reference path='../models/Study.ts' />
var Views;
(function (Views) {
    /**
    * Contains all of the subviews required for the report page
    */
    (function (Mobile) {
        /**
        * The Report view
        */
        var Application = (function () {
            function Application(el, studyStorage, queryObject, sessionId, terminology, dateFormat, settings, permissions) {
                this.study = null;
                this.selectedInstance = new Subjects.ObservableValue({ series: 0, image: 0 });
                this.transform = new Subjects.ObservableValue({
                    flipped: false,
                    offsetX: 0,
                    offsetY: 0,
                    rotation: 0,
                    scale: 1.0
                });
                this.$images = [];
                this.annotations = [];
                this.el = el;
                this.studyStorage = studyStorage;
                this.queryObject = queryObject;
                this.sessionId = sessionId;
                this.terminology = terminology;
                this.dateFormat = dateFormat;
                this.settings = settings;
                this.permissions = permissions;
            }
            /**
            * Load the study from storage and call the render method
            */
            Application.prototype.load = function () {
                var _this = this;
                var loadStudy = Study.loadStudy(this.sessionId, this.studyStorage, this.queryObject, true, this.permissions);

                return Observable.invoke(loadStudy, function (study) {
                    _this.study = study;
                    _this.render(_this.el, study);
                });
            };

            /**
            * Layout the screen, and hook up any event handlers
            */
            Application.prototype.render = function (el, study) {
                var _this = this;
                var $mobile = $('<div>').addClass('mobileFrame');

                _.each(study.series, function (series, index, list) {
                    var $series = [];

                    _.each(series.instances, function (instance, index, list) {
                        var uri = Routes.ImageData(_this.sessionId, _this.studyStorage, _this.queryObject, instance.id, instance.instanceAttributes.version, instance.frameNumber, 2 /* Diagnostic */, 8, false, true);

                        var $image = $('<div>').addClass('imageFrame').css('background-image', 'url(' + uri + ')').hide().appendTo($mobile);

                        $series[index] = $image;
                    });

                    _this.$images[index] = $series;
                });

                var $annotations = $('<div>').addClass('annotations');

                this.annotations.push($('<div>').appendTo($annotations));
                this.annotations.push($('<div>').appendTo($annotations));
                this.annotations.push($('<div>').appendTo($annotations));

                $annotations.appendTo($mobile);

                this.setupEventHandlers($mobile.get(0));

                this.renderCurrentImage();

                el.replaceWith($mobile);

                // Set up arrow overlays
                _.each([
                    'next-series',
                    'prev-series',
                    'next-image',
                    'prev-image'
                ], function (clazz) {
                    $('<div>').addClass('arrow-overlay').addClass(clazz).hide().appendTo($(document.body));
                });
            };

            Application.prototype.setupEventHandlers = function (el) {
                var _this = this;
                var hammer = new Hammer.Manager(el);

                hammer.on('panstart rotatestart pinchstart', function (_) {
                    _this.originalTransform = _this.transform.read();
                });

                hammer.add(new Hammer.Pan());

                hammer.add(new Hammer.Pinch({
                    enable: true,
                    threshold: 0.1
                }));

                hammer.add(new Hammer.Rotate({
                    enable: true,
                    threshold: 10
                }));

                hammer.get('pinch').recognizeWith('rotate');
                hammer.get('rotate').requireFailure('pinch');

                hammer.add(new Hammer.Tap({
                    event: 'doubletap',
                    taps: 2
                }));
                hammer.add(new Hammer.Tap({
                    event: 'singletap',
                    taps: 1
                }));

                hammer.get('doubletap').recognizeWith('singletap');
                hammer.get('singletap').requireFailure('doubletap');

                hammer.on('pan', function (e) {
                    return _this.pan(e);
                });
                hammer.on('pinch', function (e) {
                    return _this.pinch(e);
                });
                hammer.on('rotate', function (e) {
                    return _this.rotate(e);
                });

                hammer.on('singletap', function (e) {
                    var $frame = $('.mobileFrame');

                    var w = $frame.width();
                    var h = $frame.height();

                    var x = e.center.x;
                    var y = e.center.y;

                    var b1 = x * h > y * w;
                    var b2 = (w - x) * h > y * w;

                    if (b1) {
                        if (b2) {
                            _this.loadPrevImage();
                        } else {
                            _this.loadNextSeries();
                        }
                    } else {
                        if (b2) {
                            _this.loadPrevSeries();
                        } else {
                            _this.loadNextImage();
                        }
                    }
                });

                hammer.on('doubletap', function (_) {
                    return _this.toggleAnnotations();
                });

                this.selectedInstance.updates.subscribe({
                    next: function (_) {
                        return _this.renderCurrentImage();
                    },
                    done: function () {
                    },
                    fail: function (_) {
                    }
                });

                this.transform.updates.subscribe({
                    next: function (tx) {
                        return _this.applyTransform(tx);
                    },
                    done: function () {
                    },
                    fail: function (_) {
                    }
                });
            };

            Application.prototype.pan = function (e) {
                var _this = this;
                Subjects.modify(this.transform, function (tx) {
                    return {
                        offsetX: _this.originalTransform.offsetX + e.deltaX,
                        offsetY: _this.originalTransform.offsetY + e.deltaY,
                        flipped: tx.flipped,
                        rotation: tx.rotation,
                        scale: tx.scale
                    };
                });
            };

            Application.prototype.pinch = function (e) {
                var _this = this;
                Subjects.modify(this.transform, function (tx) {
                    return {
                        offsetX: tx.offsetX,
                        offsetY: tx.offsetY,
                        flipped: tx.flipped,
                        rotation: tx.rotation,
                        scale: _this.originalTransform.scale * e.scale
                    };
                });
            };

            Application.prototype.rotate = function (e) {
                var _this = this;
                Subjects.modify(this.transform, function (tx) {
                    return {
                        offsetX: tx.offsetX,
                        offsetY: tx.offsetY,
                        flipped: tx.flipped,
                        rotation: _this.originalTransform.rotation + e.rotation / 90,
                        scale: tx.scale
                    };
                });
            };

            Application.prototype.loadNextSeries = function () {
                var _this = this;
                this.arrow('next-series');

                Subjects.modify(this.selectedInstance, function (i) {
                    var len = _this.study.series.length;

                    return {
                        series: (i.series + 1) % len,
                        image: 0
                    };
                });

                this.resetTransform();
            };

            Application.prototype.loadPrevSeries = function () {
                var _this = this;
                this.arrow('prev-series');

                Subjects.modify(this.selectedInstance, function (i) {
                    var len = _this.study.series.length;

                    return {
                        series: (i.series + len - 1) % len,
                        image: 0
                    };
                });

                this.resetTransform();
            };

            Application.prototype.loadNextImage = function () {
                var _this = this;
                this.arrow('next-image');

                Subjects.modify(this.selectedInstance, function (i) {
                    var len = _this.study.series[i.series].instances.length;

                    return {
                        series: i.series,
                        image: (i.image + len - 1) % len
                    };
                });
            };

            Application.prototype.loadPrevImage = function () {
                var _this = this;
                this.arrow('prev-image');

                Subjects.modify(this.selectedInstance, function (i) {
                    var len = _this.study.series[i.series].instances.length;

                    return {
                        series: i.series,
                        image: (i.image + 1) % len
                    };
                });
            };

            Application.prototype.arrow = function (clazz) {
                if (this.settings && !this.settings.disableMobileArrows) {
                    var $arrow = $('.' + clazz);

                    $arrow.show();
                    $arrow.fadeOut(800);
                }
            };

            Application.prototype.renderCurrentImage = function () {
                var selectedInstance = this.selectedInstance.read();

                var series = this.study.series[selectedInstance.series];
                var instance = series.instances[selectedInstance.image];

                $('.imageFrame').hide();
                this.$images[selectedInstance.series][selectedInstance.image].show();

                if (instance.studyAttributes.patientBirthDate) {
                    this.annotations[0].text(instance.studyAttributes.patientName + " (" + instance.studyAttributes.patientBirthDate.toShortDateString(this.dateFormat) + ")");
                } else {
                    this.annotations[0].text(instance.studyAttributes.patientName);
                }

                this.annotations[1].text(instance.instanceAttributes.seriesDescription + " (" + instance.seriesAttributes.modality + ")");
                this.annotations[2].text(this.terminology.lookup(Terminology.Terms.Instance) + " #" + (instance.instanceAttributes.instanceIndex + 1).toString() + "/" + series.instances.length);
            };

            Application.prototype.resetTransform = function () {
                this.transform.write({
                    flipped: false,
                    offsetX: 0,
                    offsetY: 0,
                    rotation: 0,
                    scale: 1.0
                });
            };

            Application.prototype.applyTransform = function (tx) {
                var selectedInstance = this.selectedInstance.read();

                _.each(this.$images[selectedInstance.series], function (el) {
                    return el.css("transform", "translate({0}px,{1}px) scale({2},{2}) rotate({3}deg)".replace(/\{0\}/g, tx.offsetX.toString()).replace(/\{1\}/g, tx.offsetY.toString()).replace(/\{2\}/g, tx.scale.toString()).replace(/\{3\}/g, (tx.rotation * 90).toString()));
                });
            };

            Application.prototype.toggleAnnotations = function () {
                _.each(this.annotations, function (ann) {
                    ann.toggle();
                });
            };
            return Application;
        })();
        Mobile.Application = Application;

        $(function () {
            $('#help-icon').click(function (_) {
                $('#help-modal').modal();
            });
        });
    })(Views.Mobile || (Views.Mobile = {}));
    var Mobile = Views.Mobile;
})(Views || (Views = {}));
var Views;
(function (Views) {
    /**
    * View which displays meeting information
    */
    var MeetingInfo = (function () {
        function MeetingInfo(el, sessionId, permissions, terminology, presenter, endMeeting, leaveMeeting, changePresenter, listenForKeyboardInput) {
            this.el = el;
            this.sessionId = sessionId;
            this.permissions = permissions;
            this.terminology = terminology;
            this.presenter = presenter;
            this.endMeeting = endMeeting;
            this.leaveMeeting = leaveMeeting;
            this.changePresenter = changePresenter;
            this.listenForKeyboardInput = listenForKeyboardInput;
        }
        /**
        * Reload the roster and re-render
        */
        MeetingInfo.prototype.updateRoster = function (sessionId, meetingId) {
            var _this = this;
            Services.MeetingRoster(sessionId, meetingId).subscribe({
                done: function () {
                },
                fail: function (err) {
                    Services.AuditLog("Unable to read meeting roster: " + err, "MeetingInfo", _this.sessionId);
                },
                next: function (roster) {
                    _this.renderAttendees(meetingId, roster.users);
                }
            });
        };

        /**
        * Render just the attendee list
        */
        MeetingInfo.prototype.renderAttendees = function (meetingId, attendees) {
            var _this = this;
            this.$attendees.empty();

            var cleanName = function (name) {
                if (name && (name.indexOf("Anonymous") != -1)) {
                    var parts = name.split("-");
                    if (parts.length == 3) {
                        return parts[2].trim();
                    }
                }

                return name;
            };

            _.each(attendees, function (attendee) {
                var $attendee = $('<li>').addClass('attendee');

                if (attendee.presenter) {
                    $attendee.append($('<i>').addClass('fa fa-desktop'));
                } else {
                    if (_this.presenter.read()) {
                        $attendee.append($('<a>').append($('<i>').addClass('fa fa-user')).attr({
                            href: '#',
                            title: _this.terminology.lookup(Terminology.Terms.MakePresenter)
                        }).click(function (e) {
                            _this.changePresenter(meetingId, new Classes.UserUUID(attendee.uuid));
                            e.preventDefault();
                        }));
                    } else {
                        $attendee.append($('<i>').addClass('fa fa-user'));
                    }
                }

                $attendee.append($('<span>').text(cleanName(attendee.name)));

                _this.$attendees.append($attendee);
            });
        };

        /**
        * Create necessary elements, add them to the DOM and hook up event handlers
        */
        MeetingInfo.prototype.render = function (queryObject, studyUuid, meetingId, name) {
            var _this = this;
            var meetingInfo = this;
            var linkId = null;

            var $meeting = $('<div>');

            // Meeting Title
            $meeting.append($('<h2>').append($('<em>').text(name)));

            // Attendee UI
            $meeting.append($('<h2>').text(this.terminology.lookup(Terminology.Terms.MeetingAttendees)));

            this.$attendees = $('<ul>').addClass('attendees').appendTo($meeting);

            this.updateRoster(this.sessionId, meetingId);

            // Leave Meeting button
            var $leaveMeeting = $('<p>').appendTo($meeting);

            var $leaveMeetingLink = $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.LeaveMeeting)).click(function (e) {
                _this.leaveMeeting(meetingId);
                e.preventDefault();
            }).appendTo($leaveMeeting);

            $leaveMeeting.toggle(!this.presenter.read());

            if (this.presenterListener) {
                this.presenterListener.cancel();
                this.presenterListener = null;
            }

            Subjects.listen(this.presenter, function (presenter) {
                $leaveMeeting.toggle(!presenter);
            });

            // End Meeting button
            var $endMeeting = $('<p>').appendTo($meeting);

            var $endMeetingLink = $('<a>').attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.EndMeeting)).click(function (e) {
                _this.endMeeting(meetingId);
                e.preventDefault();
            }).appendTo($endMeeting);

            $endMeeting.toggle(this.presenter.read());

            this.presenterListener = Subjects.listen(this.presenter, function (presenter) {
                $endMeeting.toggle(presenter);
                _this.updateRoster(_this.sessionId, meetingId);
            });

            var $shareMeetingLink = $('<a>');
            var $shareMeeting = $('<p>').append($shareMeetingLink);

            // Share Meeting UI
            if (!LocalViewer.isLocalViewer() && (this.permissions.link_edit !== 0) && (this.permissions.link_view !== 0)) {
                $shareMeetingLink.attr({ href: '#' }).addClass('blue-button').text(this.terminology.lookup(Terminology.Terms.ShareMeetingLink));

                var $shareMeetingForm = $('<div>');
                var $shareMeetingFormEmail = $('<input>').attr({ type: 'text' }).attr({ id: 'meeting-form-email' }).attr({ placeholder: 'Email Address' }).css({ "color": "black", "font-size": "11px", "width": "100%", "margin": "4px 0" });
                $shareMeetingForm.append($shareMeetingFormEmail);

                var clearForm = function () {
                    $shareMeetingForm.hide();
                    meetingInfo.listenForKeyboardInput.write(true);
                };

                var $shareMeetingFormSuccess = $('<p>').text(this.terminology.lookup(Terminology.Terms.SentMeetingLink));

                var $shareMeetingFormCancel = $('<a>').attr({ href: '#' }).addClass('blue-button').css({ "width": "49%", "display": "inline-block", "float": "left" }).text("Cancel").click(function (e) {
                    clearForm();
                });

                var $shareMeetingFormSend = $('<a>').attr({ href: '#' }).addClass('blue-button').css({ "width": "49%", "display": "inline-block", "float": "right" }).text("Send").click(function (e) {
                    var email = $("#meeting-form-email").val();
                    if (email) {
                        var $overlay = $('.overlay');

                        $overlay.addClass('application-loading');

                        var ob;
                        if (linkId) {
                            ob = Observable._finally(Services.ShareMeetingLink(_this.sessionId, linkId, new Classes.EmailAddress(email)), function () {
                                $overlay.removeClass('application-loading');
                            });
                        } else {
                            ob = Observable._finally(Services.CreateMeetingLink(_this.sessionId, meetingId, queryObject, studyUuid, new Classes.EmailAddress(email)), function () {
                                $overlay.removeClass('application-loading');
                            });
                        }

                        ob.subscribe({
                            done: function () {
                            },
                            next: function (link) {
                                linkId = (linkId || new Classes.MeetingLinkId(link.uuid));
                                clearForm();
                                $shareMeeting.after($shareMeetingFormSuccess);
                            },
                            fail: function (err) {
                                window.alert(_this.terminology.lookup(Terminology.Terms.ErrorSharingMeeting));
                                Services.AuditLog("Unable to create study link: " + err, "MeetingInfo", _this.sessionId);
                            }
                        });
                    }
                });

                $shareMeetingForm.append($shareMeetingFormCancel).append($shareMeetingFormSend);
                $shareMeetingForm.append($('<div>').css({ "clear": "both" }));
                $shareMeetingForm.hide();

                $shareMeeting.append($shareMeetingForm);

                var $shareInfo = $('<div>');

                $shareMeetingFormEmail.focus(function () {
                    meetingInfo.listenForKeyboardInput.write(false);
                }).blur(function () {
                    meetingInfo.listenForKeyboardInput.write(true);
                });

                $shareMeetingLink.click(function (e) {
                    $shareMeetingForm.toggle();

                    if ($shareMeetingForm.is(":visible")) {
                        $shareMeetingFormSuccess.remove();
                        $shareMeetingFormEmail.val("");
                        $shareMeetingFormEmail.focus();
                    } else {
                        meetingInfo.listenForKeyboardInput.write(true);
                    }

                    e.preventDefault();
                });

                $meeting.append($shareMeeting);
                $meeting.append($shareInfo);
            }

            this.el.empty();
            this.el.append($meeting);
        };
        return MeetingInfo;
    })();
    Views.MeetingInfo = MeetingInfo;
})(Views || (Views = {}));
