UNPKG

ng-cornerstone

Version:

[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.git

1 lines 178 kB
{"version":3,"file":"ng-cornerstone.mjs","sources":["../../../projects/ng-cornerstone/src/lib/core/config/types.ts","../../../projects/ng-cornerstone/src/lib/core/util/convert.ts","../../../projects/ng-cornerstone/src/lib/core/provider/ptScalingMetaDataProvider.ts","../../../projects/ng-cornerstone/src/lib/core/init/initProviders.ts","../../../projects/ng-cornerstone/src/lib/core/init/initVolumeLoader.js","../../../projects/ng-cornerstone/src/lib/core/init/cornerstone.service.ts","../../../projects/ng-cornerstone/src/lib/core/provider/getPixelSpacingInformation.ts","../../../projects/ng-cornerstone/src/lib/core/provider/getPTImageIdInstanceMetadata.ts","../../../projects/ng-cornerstone/src/lib/core/provider/image-id.service.ts","../../../projects/ng-cornerstone/src/lib/core/provider/setCtTransferFunctionForVolumeActor.ts","../../../projects/ng-cornerstone/src/lib/core/provider/setStackForViewports.ts","../../../projects/ng-cornerstone/src/lib/core/config/layout.ts","../../../projects/ng-cornerstone/src/lib/components/icon/icon.component.ts","../../../projects/ng-cornerstone/src/lib/components/button/button.component.ts","../../../projects/ng-cornerstone/src/lib/components/button/button.component.html","../../../projects/ng-cornerstone/src/lib/components/button/button-group.component.ts","../../../projects/ng-cornerstone/src/lib/components/icon/icon.service.ts","../../../projects/ng-cornerstone/src/lib/components/icon/icon.module.ts","../../../projects/ng-cornerstone/src/lib/components/button/button.module.ts","../../../projects/ng-cornerstone/src/lib/tool/tool.types.ts","../../../projects/ng-cornerstone/src/lib/tool/tool.config.ts","../../../projects/ng-cornerstone/src/lib/tool/tool-bar.component.ts","../../../projects/ng-cornerstone/src/lib/tool/tool-bar.component.html","../../../projects/ng-cornerstone/src/lib/tool/tool.module.ts","../../../projects/ng-cornerstone/src/lib/viewport/base-viewport.component.ts","../../../projects/ng-cornerstone/src/lib/viewport/viewport.component.ts","../../../projects/ng-cornerstone/src/lib/viewport/stack-viewport.component.ts","../../../projects/ng-cornerstone/src/lib/viewport/volume3d-viewport.component.ts","../../../projects/ng-cornerstone/src/lib/viewport/orthographic-viewport.component.ts","../../../projects/ng-cornerstone/src/lib/viewport/viewport.module.ts","../../../projects/ng-cornerstone/src/lib/viewer/viewer.component.ts","../../../projects/ng-cornerstone/src/lib/viewer/viewer.component.html","../../../projects/ng-cornerstone/src/lib/viewer/viewer.module.ts","../../../projects/ng-cornerstone/src/ng-cornerstone.ts"],"sourcesContent":["import { Enums as csCoreEnum } from '@cornerstonejs/core';\nimport { Enums as csToolEnum } from '@cornerstonejs/tools';\n\nexport type SafeAny = any;\nexport type FunctionProp<T> = (...args: SafeAny[]) => T;\n\nexport enum RequestSchema {\n wadoRs = 'wadors:',\n wadoUri = 'wadouri:',\n nifti = 'nifti:',\n}\n\nexport enum VolumeLoaderSchema {\n stream = 'cornerstoneStreamingImageVolume',\n dynamicStream = 'cornerstoneStreamingDynamicImageVolume',\n nifti = 'nifti',\n}\n\nexport interface ImageInfo {\n imageIds?: string[]; // Auto-filled property, used for stack viewport\n volumeId?: string; // Auto-filled property, used for volume3d viewport\n studyInstanceUID?: string;\n seriesInstanceUID?: string;\n sopInstanceUIDs?: string[];\n urlRoot: string;\n schema: RequestSchema;\n viewportType: csCoreEnum.ViewportType;\n segmentType?: csToolEnum.SegmentationRepresentations;\n}\n\nexport function imageInfoToUniqueId(imageInfo: ImageInfo): string {\n if (imageInfo) {\n if (imageInfo.schema === RequestSchema.wadoRs) {\n return RequestSchema.wadoRs + imageInfo?.studyInstanceUID + imageInfo?.seriesInstanceUID;\n } else if (imageInfo.schema === RequestSchema.nifti) {\n return RequestSchema.nifti + imageInfo.urlRoot;\n } else {\n console.error('Unsupported request schema');\n return '';\n }\n }\n return '';\n}\n","/**\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE\n */\n\nimport { _isNumberValue, coerceBooleanProperty, coerceCssPixelValue } from '@angular/cdk/coercion';\nimport { FunctionProp, SafeAny } from '../config/types';\n\nexport function toBoolean(value: boolean | string): boolean {\n return coerceBooleanProperty(value);\n}\n\nexport function toNumber(value: number | string): number;\nexport function toNumber<D>(value: number | string, fallback: D): number | D;\nexport function toNumber(value: number | string, fallbackValue: number = 0): number {\n return _isNumberValue(value) ? Number(value) : fallbackValue;\n}\n\nexport function toCssPixel(value: number | string): string {\n return coerceCssPixelValue(value);\n}\n\n// eslint-disable no-invalid-this\n\n/**\n * Get the function-property type's value\n */\nexport function valueFunctionProp<T>(prop: FunctionProp<T> | T, ...args: SafeAny[]): T {\n return typeof prop === 'function' ? (prop as FunctionProp<T>)(...args) : prop;\n}\n\nfunction propDecoratorFactory<T, D>(name: string, fallback: (v: T) => D): (target: SafeAny, propName: string) => void {\n function propDecorator(\n target: SafeAny,\n propName: string,\n originalDescriptor?: TypedPropertyDescriptor<SafeAny>,\n ): SafeAny {\n const privatePropName = `$$__zorroPropDecorator__${propName}`;\n\n if (Object.prototype.hasOwnProperty.call(target, privatePropName)) {\n console.warn(`The prop \"${privatePropName}\" is already exist, it will be overrided by ${name} decorator.`);\n }\n\n Object.defineProperty(target, privatePropName, {\n configurable: true,\n writable: true,\n });\n\n return {\n get(): string {\n return originalDescriptor && originalDescriptor.get\n ? originalDescriptor.get.bind(this)()\n : this[privatePropName];\n },\n set(value: T): void {\n if (originalDescriptor && originalDescriptor.set) {\n originalDescriptor.set.bind(this)(fallback(value));\n }\n this[privatePropName] = fallback(value);\n },\n };\n }\n\n return propDecorator;\n}\n\n/**\n * Input decorator that handle a prop to do get/set automatically with toBoolean\n *\n * Why not using @InputBoolean alone without @Input? AOT needs @Input to be visible\n *\n * @howToUse\n * ```\n * @Input() @InputBoolean() visible: boolean = false;\n *\n * // Act as below:\n * // @Input()\n * // get visible() { return this.__visible; }\n * // set visible(value) { this.__visible = value; }\n * // __visible = false;\n * ```\n */\nexport function InputBoolean(): SafeAny {\n return propDecoratorFactory('InputBoolean', toBoolean);\n}\n\nexport function InputCssPixel(): SafeAny {\n return propDecoratorFactory('InputCssPixel', toCssPixel);\n}\n\nexport function InputNumber(fallbackValue?: SafeAny): SafeAny {\n return propDecoratorFactory('InputNumber', (value: string | number) => toNumber(value, fallbackValue));\n}\n","import { utilities as csUtils } from '@cornerstonejs/core';\n\nconst scalingPerImageId = {};\n\nfunction addInstance(imageId, scalingMetaData) {\n const imageURI = csUtils.imageIdToURI(imageId);\n scalingPerImageId[imageURI] = scalingMetaData;\n}\n\nfunction get(type, imageId) {\n if (type === 'scalingModule') {\n const imageURI = csUtils.imageIdToURI(imageId);\n return scalingPerImageId[imageURI];\n }\n}\n\nexport default { addInstance, get };\n","import * as cornerstone from '@cornerstonejs/core';\nimport ptScalingMetaDataProvider from '../provider/ptScalingMetaDataProvider';\n\nconst { calibratedPixelSpacingMetadataProvider } = cornerstone.utilities;\n\nexport default function initProviders() {\n cornerstone.metaData.addProvider(ptScalingMetaDataProvider.get.bind(ptScalingMetaDataProvider), 10000);\n cornerstone.metaData.addProvider(\n calibratedPixelSpacingMetadataProvider.get.bind(calibratedPixelSpacingMetadataProvider),\n 11000,\n );\n}\n","import { cornerstoneNiftiImageLoader } from '@cornerstonejs/nifti-volume-loader';\nimport { VolumeLoaderSchema } from '../config/types';\nimport {\n imageLoader,\n volumeLoader,\n cornerstoneStreamingImageVolumeLoader,\n cornerstoneStreamingDynamicImageVolumeLoader,\n} from '@cornerstonejs/core';\n\nexport default function initVolumeLoader() {\n volumeLoader.registerUnknownVolumeLoader(cornerstoneStreamingImageVolumeLoader);\n volumeLoader.registerVolumeLoader(VolumeLoaderSchema.stream, cornerstoneStreamingImageVolumeLoader);\n volumeLoader.registerVolumeLoader(VolumeLoaderSchema.dynamicStream, cornerstoneStreamingDynamicImageVolumeLoader);\n imageLoader.registerImageLoader(VolumeLoaderSchema.nifti, cornerstoneNiftiImageLoader);\n}\n","import initProviders from './initProviders';\nimport initVolumeLoader from './initVolumeLoader';\nimport {\n cache,\n eventTarget,\n imageLoader,\n init as csRenderInit,\n metaData,\n RenderingEngine,\n Types as csCoreTypes,\n} from '@cornerstonejs/core';\nimport * as polySeg from '@cornerstonejs/polymorphic-segmentation';\nimport { Injectable, OnDestroy } from '@angular/core';\nimport { init as csToolInit, Types as csToolTypes, destroy } from '@cornerstonejs/tools';\nimport { Subject } from 'rxjs';\nimport cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class CornerstoneService implements OnDestroy {\n private renderingEngineId = 'RENDERING_ENGINE_ID';\n private renderingEngine!: RenderingEngine;\n private toolGroup!: csToolTypes.IToolGroup;\n private initialized = false;\n\n constructor() {}\n\n async init() {\n try {\n if (this.initialized) {\n return;\n }\n\n initProviders();\n cornerstoneDICOMImageLoader.init();\n initVolumeLoader();\n await Promise.all([\n csRenderInit(),\n csToolInit({\n addons: {\n polySeg: polySeg as any,\n },\n }),\n ]);\n\n this.renderingEngine = new RenderingEngine(this.renderingEngineId);\n\n this.initialized = true;\n console.debug('CornerstoneService initialized');\n } catch (error) {\n console.error('Failed to initialize CornerstoneService:', error);\n throw error;\n }\n }\n\n private checkInitialized() {\n if (!this.initialized) {\n throw new Error('CornerstoneService not initialized');\n }\n }\n\n setToolGroup(toolGroup: csToolTypes.IToolGroup) {\n this.toolGroup = toolGroup;\n }\n\n getRenderingEngine() {\n this.checkInitialized();\n return this.renderingEngine;\n }\n\n getRenderingEngineId() {\n return this.renderingEngineId;\n }\n\n getToolGroup() {\n return this.toolGroup;\n }\n\n ngOnDestroy(): void {\n this.renderingEngine.destroy();\n eventTarget.reset();\n cache.purgeCache();\n destroy();\n metaData.removeAllProviders();\n imageLoader.unregisterAllImageLoaders();\n console.debug('cs service destroyed');\n }\n}\n","// See https://github.com/OHIF/Viewers/blob/94a9067fe3d291d30e25a1bda5913511388edea2/platform/core/src/utils/metadataProvider/getPixelSpacingInformation.js\n\nexport default function getPixelSpacingInformation(instance) {\n // See http://gdcm.sourceforge.net/wiki/index.php/Imager_Pixel_Spacing\n\n // TODO: Add Ultrasound region spacing\n // TODO: Add manual calibration\n\n // TODO: Use ENUMS from dcmjs\n const projectionRadiographSOPClassUIDs = [\n '1.2.840.10008.5.1.4.1.1.1', //\tCR Image Storage\n '1.2.840.10008.5.1.4.1.1.1.1', //\tDigital X-Ray Image Storage – for Presentation\n '1.2.840.10008.5.1.4.1.1.1.1.1', //\tDigital X-Ray Image Storage – for Processing\n '1.2.840.10008.5.1.4.1.1.1.2', //\tDigital Mammography X-Ray Image Storage – for Presentation\n '1.2.840.10008.5.1.4.1.1.1.2.1', //\tDigital Mammography X-Ray Image Storage – for Processing\n '1.2.840.10008.5.1.4.1.1.1.3', //\tDigital Intra – oral X-Ray Image Storage – for Presentation\n '1.2.840.10008.5.1.4.1.1.1.3.1', //\tDigital Intra – oral X-Ray Image Storage – for Processing\n '1.2.840.10008.5.1.4.1.1.12.1', //\tX-Ray Angiographic Image Storage\n '1.2.840.10008.5.1.4.1.1.12.1.1', //\tEnhanced XA Image Storage\n '1.2.840.10008.5.1.4.1.1.12.2', //\tX-Ray Radiofluoroscopic Image Storage\n '1.2.840.10008.5.1.4.1.1.12.2.1', //\tEnhanced XRF Image Storage\n '1.2.840.10008.5.1.4.1.1.12.3', // X-Ray Angiographic Bi-plane Image Storage\tRetired\n ];\n\n const {\n PixelSpacing,\n ImagerPixelSpacing,\n SOPClassUID,\n PixelSpacingCalibrationType,\n PixelSpacingCalibrationDescription,\n EstimatedRadiographicMagnificationFactor,\n SequenceOfUltrasoundRegions,\n } = instance;\n\n const isProjection = projectionRadiographSOPClassUIDs.includes(SOPClassUID);\n\n const TYPES = {\n NOT_APPLICABLE: 'NOT_APPLICABLE',\n UNKNOWN: 'UNKNOWN',\n CALIBRATED: 'CALIBRATED',\n DETECTOR: 'DETECTOR',\n };\n\n if (!isProjection) {\n return PixelSpacing;\n }\n\n if (isProjection && !ImagerPixelSpacing) {\n // If only Pixel Spacing is present, and this is a projection radiograph,\n // PixelSpacing should be used, but the user should be informed that\n // what it means is unknown\n return {\n PixelSpacing,\n type: TYPES.UNKNOWN,\n isProjection,\n };\n } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing === ImagerPixelSpacing) {\n // If Imager Pixel Spacing and Pixel Spacing are present and they have the same values,\n // then the user should be informed that the measurements are at the detector plane\n return {\n PixelSpacing,\n type: TYPES.DETECTOR,\n isProjection,\n };\n } else if (PixelSpacing && ImagerPixelSpacing && PixelSpacing !== ImagerPixelSpacing) {\n // If Imager Pixel Spacing and Pixel Spacing are present and they have different values,\n // then the user should be informed that these are \"calibrated\"\n // (in some unknown manner if Pixel Spacing Calibration Type and/or\n // Pixel Spacing Calibration Description are absent)\n return {\n PixelSpacing,\n type: TYPES.CALIBRATED,\n isProjection,\n PixelSpacingCalibrationType,\n PixelSpacingCalibrationDescription,\n };\n } else if (!PixelSpacing && ImagerPixelSpacing) {\n let CorrectedImagerPixelSpacing = ImagerPixelSpacing;\n if (EstimatedRadiographicMagnificationFactor) {\n // Note that in IHE Mammo profile compliant displays, the value of Imager Pixel Spacing is required to be corrected by\n // Estimated Radiographic Magnification Factor and the user informed of that.\n // TODO: should this correction be done before all of this logic?\n CorrectedImagerPixelSpacing = ImagerPixelSpacing.map(\n (pixelSpacing) => pixelSpacing / EstimatedRadiographicMagnificationFactor,\n );\n } else {\n console.warn('EstimatedRadiographicMagnificationFactor was not present. Unable to correct ImagerPixelSpacing.');\n }\n\n return {\n PixelSpacing: CorrectedImagerPixelSpacing,\n isProjection,\n };\n } else if (SequenceOfUltrasoundRegions && typeof SequenceOfUltrasoundRegions === 'object') {\n const { PhysicalDeltaX, PhysicalDeltaY } = SequenceOfUltrasoundRegions;\n const USPixelSpacing = [PhysicalDeltaX * 10, PhysicalDeltaY * 10];\n\n return {\n PixelSpacing: USPixelSpacing,\n };\n } else if (\n SequenceOfUltrasoundRegions &&\n Array.isArray(SequenceOfUltrasoundRegions) &&\n SequenceOfUltrasoundRegions.length > 1\n ) {\n console.warn(\n 'Sequence of Ultrasound Regions > one entry. This is not yet implemented, all measurements will be shown in pixels.',\n );\n } else if (!isProjection && !ImagerPixelSpacing) {\n // If only Pixel Spacing is present, and this is not a projection radiograph,\n // we can stop here\n return {\n PixelSpacing,\n type: TYPES.NOT_APPLICABLE,\n isProjection,\n };\n }\n\n console.warn('Unknown combination of PixelSpacing and ImagerPixelSpacing identified. Unable to determine spacing.');\n}\n","import { metaData } from '@cornerstonejs/core';\nimport type { InstanceMetadata } from '@cornerstonejs/calculate-suv';\n\nexport default function getPTImageIdInstanceMetadata(imageId: string): InstanceMetadata {\n const petSequenceModule = metaData.get('petIsotopeModule', imageId);\n const generalSeriesModule = metaData.get('generalSeriesModule', imageId);\n const patientStudyModule = metaData.get('patientStudyModule', imageId);\n\n const ptSeriesModule = metaData.get('petSeriesModule', imageId);\n const ptImageModule = metaData.get('petImageModule', imageId);\n\n if (!petSequenceModule) {\n throw new Error('petSequenceModule metadata is required');\n }\n\n const radiopharmaceuticalInfo = petSequenceModule.radiopharmaceuticalInfo;\n\n const { seriesDate, seriesTime, acquisitionDate, acquisitionTime } = generalSeriesModule;\n const { patientWeight } = patientStudyModule;\n const { correctedImage, units, decayCorrection } = ptSeriesModule;\n\n if (\n seriesDate === undefined ||\n seriesTime === undefined ||\n patientWeight === undefined ||\n acquisitionDate === undefined ||\n acquisitionTime === undefined ||\n correctedImage === undefined ||\n units === undefined ||\n decayCorrection === undefined ||\n radiopharmaceuticalInfo.radionuclideTotalDose === undefined ||\n radiopharmaceuticalInfo.radionuclideHalfLife === undefined ||\n (radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === undefined &&\n seriesDate === undefined &&\n radiopharmaceuticalInfo.radiopharmaceuticalStartTime === undefined)\n //\n ) {\n throw new Error('required metadata are missing');\n }\n\n const instanceMetadata: InstanceMetadata = {\n CorrectedImage: correctedImage,\n Units: units,\n RadionuclideHalfLife: radiopharmaceuticalInfo.radionuclideHalfLife,\n RadionuclideTotalDose: radiopharmaceuticalInfo.radionuclideTotalDose,\n DecayCorrection: decayCorrection,\n PatientWeight: patientWeight,\n SeriesDate: seriesDate,\n SeriesTime: seriesTime,\n AcquisitionDate: acquisitionDate,\n AcquisitionTime: acquisitionTime,\n };\n\n if (\n radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&\n radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&\n typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime === 'string'\n ) {\n instanceMetadata.RadiopharmaceuticalStartDateTime = radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime;\n }\n\n if (\n radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime &&\n radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== undefined &&\n typeof radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime !== 'string'\n ) {\n const dateString = convertInterfaceDateToString(radiopharmaceuticalInfo.radiopharmaceuticalStartDateTime);\n instanceMetadata.RadiopharmaceuticalStartDateTime = dateString;\n }\n\n if (\n instanceMetadata.AcquisitionDate &&\n instanceMetadata.AcquisitionDate !== undefined &&\n typeof instanceMetadata.AcquisitionDate !== 'string'\n ) {\n const dateString = convertInterfaceDateToString(instanceMetadata.AcquisitionDate);\n instanceMetadata.AcquisitionDate = dateString;\n }\n\n if (\n instanceMetadata.SeriesDate &&\n instanceMetadata.SeriesDate !== undefined &&\n typeof instanceMetadata.SeriesDate !== 'string'\n ) {\n const dateString = convertInterfaceDateToString(instanceMetadata.SeriesDate);\n instanceMetadata.SeriesDate = dateString;\n }\n\n if (\n radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&\n radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&\n typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime === 'string'\n ) {\n instanceMetadata.RadiopharmaceuticalStartTime = radiopharmaceuticalInfo.radiopharmaceuticalStartTime;\n }\n\n if (\n radiopharmaceuticalInfo.radiopharmaceuticalStartTime &&\n radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== undefined &&\n typeof radiopharmaceuticalInfo.radiopharmaceuticalStartTime !== 'string'\n ) {\n const timeString = convertInterfaceTimeToString(radiopharmaceuticalInfo.radiopharmaceuticalStartTime);\n instanceMetadata.RadiopharmaceuticalStartTime = timeString;\n }\n\n if (\n instanceMetadata.AcquisitionTime &&\n instanceMetadata.AcquisitionTime !== undefined &&\n typeof instanceMetadata.AcquisitionTime !== 'string'\n ) {\n const timeString = convertInterfaceTimeToString(instanceMetadata.AcquisitionTime);\n instanceMetadata.AcquisitionTime = timeString;\n }\n\n if (\n instanceMetadata.SeriesTime &&\n instanceMetadata.SeriesTime !== undefined &&\n typeof instanceMetadata.SeriesTime !== 'string'\n ) {\n const timeString = convertInterfaceTimeToString(instanceMetadata.SeriesTime);\n instanceMetadata.SeriesTime = timeString;\n }\n\n if (ptImageModule.frameReferenceTime && ptImageModule.frameReferenceTime !== undefined) {\n instanceMetadata.FrameReferenceTime = ptImageModule.frameReferenceTime;\n }\n\n if (ptImageModule.actualFrameDuration && ptImageModule.actualFrameDuration !== undefined) {\n instanceMetadata.ActualFrameDuration = ptImageModule.actualFrameDuration;\n }\n\n if (patientStudyModule.patientSex && patientStudyModule.patientSex !== undefined) {\n instanceMetadata.PatientSex = patientStudyModule.patientSex;\n }\n\n if (patientStudyModule.patientSize && patientStudyModule.patientSize !== undefined) {\n instanceMetadata.PatientSize = patientStudyModule.patientSize;\n }\n\n // Todo: add private tags\n // if (\n // dicomMetaData['70531000'] ||\n // dicomMetaData['70531000'] !== undefined ||\n // dicomMetaData['70531009'] ||\n // dicomMetaData['70531009'] !== undefined\n // ) {\n // const philipsPETPrivateGroup: PhilipsPETPrivateGroup = {\n // SUVScaleFactor: dicomMetaData['70531000'],\n // ActivityConcentrationScaleFactor: dicomMetaData['70531009'],\n // };\n // instanceMetadata.PhilipsPETPrivateGroup = philipsPETPrivateGroup;\n // }\n\n // if (dicomMetaData['0009100d'] && dicomMetaData['0009100d'] !== undefined) {\n // instanceMetadata.GEPrivatePostInjectionDateTime = dicomMetaData['0009100d'];\n // }\n\n return instanceMetadata;\n}\n\nfunction convertInterfaceTimeToString(time): string {\n const hours = `${time.hours || '00'}`.padStart(2, '0');\n const minutes = `${time.minutes || '00'}`.padStart(2, '0');\n const seconds = `${time.seconds || '00'}`.padStart(2, '0');\n\n const fractionalSeconds = `${time.fractionalSeconds || '000000'}`.padEnd(6, '0');\n\n const timeString = `${hours}${minutes}${seconds}.${fractionalSeconds}`;\n return timeString;\n}\n\nfunction convertInterfaceDateToString(date): string {\n const month = `${date.month}`.padStart(2, '0');\n const day = `${date.day}`.padStart(2, '0');\n const dateString = `${date.year}${month}${day}`;\n return dateString;\n}\n\nexport { getPTImageIdInstanceMetadata };\n","import { Injectable } from '@angular/core';\nimport { metaData, utilities } from '@cornerstonejs/core';\nimport { api } from 'dicomweb-client';\nimport cornerstoneDICOMImageLoader from '@cornerstonejs/dicom-image-loader';\nimport dcmjs from 'dcmjs';\nimport getPixelSpacingInformation from './getPixelSpacingInformation';\nimport { calculateSUVScalingFactors, InstanceMetadata } from '@cornerstonejs/calculate-suv';\nimport { ImageInfo, RequestSchema } from '../config/types';\nimport { getPTImageIdInstanceMetadata } from './getPTImageIdInstanceMetadata';\nimport ptScalingMetaDataProvider from './ptScalingMetaDataProvider';\n\nconst { DicomMetaDictionary } = dcmjs.data;\n@Injectable({\n providedIn: 'root',\n})\nexport class ImageIdService {\n constructor() {}\n\n async wadoRsCreateImageIdsAndCacheMetaData(imageInfo: ImageInfo): Promise<string[]> {\n const MODALITY = '00080060';\n const SOP_INSTANCE_UID = '00080018';\n const { urlRoot, studyInstanceUID, seriesInstanceUID } = imageInfo;\n const client = new api.DICOMwebClient({ url: urlRoot });\n const instances = await client.retrieveSeriesMetadata({\n studyInstanceUID,\n seriesInstanceUID,\n });\n const modality = instances[0][MODALITY].Value[0];\n let sopInstanceUIDSet;\n if (imageInfo?.sopInstanceUIDs && imageInfo?.sopInstanceUIDs.length >= 0) {\n sopInstanceUIDSet = new Set<string>(imageInfo?.sopInstanceUIDs);\n } else {\n const instanceUIDs = instances.map((instanceMetaData) => {\n return instanceMetaData[SOP_INSTANCE_UID].Value[0];\n });\n sopInstanceUIDSet = new Set<string>(instanceUIDs);\n imageInfo.sopInstanceUIDs = instanceUIDs;\n }\n let imageIds: string[] = [];\n instances.forEach((instanceMetaData) => {\n const sopInstanceUID = instanceMetaData[SOP_INSTANCE_UID].Value[0];\n if (sopInstanceUIDSet.has(sopInstanceUID)) {\n const imageId = ImageIdService.wadoRsCreateImageIds({\n ...imageInfo,\n sopInstanceUIDs: [sopInstanceUID],\n });\n imageIds.push(...imageId); // only one image id\n cornerstoneDICOMImageLoader.wadors.metaDataManager.add(imageId[0], instanceMetaData);\n }\n });\n imageIds = this.convertMultiframeImageIds(imageIds);\n\n this.pixelSpacingProvider(imageIds);\n\n if (modality === 'PT') {\n this.ptProvider(imageIds);\n }\n return imageIds;\n }\n\n static createImageIds(imageInfo: ImageInfo) {\n if (imageInfo?.schema === RequestSchema.wadoRs) {\n return ImageIdService.wadoRsCreateImageIds(imageInfo);\n } else if (imageInfo?.schema === RequestSchema.wadoUri) {\n return ImageIdService.wadoURICreateImageIds(imageInfo);\n }\n return [];\n }\n\n static wadoURICreateImageIds(imageInfo: ImageInfo): string[] {\n if (!imageInfo.sopInstanceUIDs || imageInfo.sopInstanceUIDs.length === 0) {\n console.error('Invalid image info', imageInfo);\n return [];\n }\n const { urlRoot, studyInstanceUID, seriesInstanceUID, sopInstanceUIDs } = imageInfo;\n const wadoURIRoot = `${RequestSchema.wadoUri}${urlRoot}?requestType=WADO&studyUID=${studyInstanceUID}&seriesUID=${seriesInstanceUID}&contentType=application%2Fdicom`;\n return sopInstanceUIDs!.map((uid) => {\n return `${wadoURIRoot}&objectUID=${uid}`;\n });\n }\n\n static wadoRsCreateImageIds(imageInfo: ImageInfo): string[] {\n if (!imageInfo.sopInstanceUIDs || imageInfo.sopInstanceUIDs.length === 0) {\n console.error('Invalid image info', imageInfo);\n return [];\n }\n const { urlRoot, studyInstanceUID, seriesInstanceUID, sopInstanceUIDs } = imageInfo;\n const wadoURIRoot = `${RequestSchema.wadoRs}${urlRoot}/studies/${studyInstanceUID}/series/${seriesInstanceUID}`;\n return sopInstanceUIDs!.map((uid) => {\n return `${wadoURIRoot}/instances/${uid}/frames/1`;\n });\n }\n\n convertMultiframeImageIds(imageIds: string[]): string[] {\n const newImageIds: string[] = [];\n imageIds.forEach((imageId) => {\n const { imageIdFrameless } = this.getFrameInformation(imageId);\n const instanceMetaData = metaData.get('multiframeModule', imageId);\n if (instanceMetaData && instanceMetaData.NumberOfFrames && instanceMetaData.NumberOfFrames > 1) {\n const NumberOfFrames = instanceMetaData.NumberOfFrames;\n for (let i = 0; i < NumberOfFrames; i++) {\n const newImageId = imageIdFrameless + (i + 1);\n newImageIds.push(newImageId);\n }\n } else newImageIds.push(imageId);\n });\n return newImageIds;\n }\n\n getFrameInformation(imageId): {\n frameIndex: number;\n imageIdFrameless: string;\n } {\n if (imageId.includes('wadors:')) {\n const frameIndex = imageId.indexOf('/frames/');\n const imageIdFrameless = frameIndex > 0 ? imageId.slice(0, frameIndex + 8) : imageId;\n return {\n frameIndex,\n imageIdFrameless,\n };\n } else {\n const frameIndex = imageId.indexOf('&frame=');\n let imageIdFrameless = frameIndex > 0 ? imageId.slice(0, frameIndex + 7) : imageId;\n if (!imageIdFrameless.includes('&frame=')) {\n imageIdFrameless = imageIdFrameless + '&frame=';\n }\n return {\n frameIndex,\n imageIdFrameless,\n };\n }\n }\n\n pixelSpacingProvider(imageIds: string[]): void {\n imageIds.forEach((imageId) => {\n let instanceMetaData = cornerstoneDICOMImageLoader.wadors.metaDataManager.get(imageId);\n\n // It was using JSON.parse(JSON.stringify(...)) before but it is 8x slower\n instanceMetaData = this.removeInvalidTags(instanceMetaData);\n\n if (instanceMetaData) {\n // Add calibrated pixel spacing\n const metadata = DicomMetaDictionary.naturalizeDataset(instanceMetaData);\n const pixelSpacing = getPixelSpacingInformation(metadata);\n\n if (pixelSpacing) {\n utilities.calibratedPixelSpacingMetadataProvider.add(\n imageId,\n pixelSpacing.map((s) => parseFloat(s)),\n );\n }\n }\n });\n }\n ptProvider(imageIds: string[]): void {\n const instanceMetadataArray: InstanceMetadata[] = [];\n imageIds.forEach((imageId) => {\n const instanceMetadata = getPTImageIdInstanceMetadata(imageId);\n\n // TODO: Temporary fix because static-wado is producing a string, not an array of values\n // (or maybe dcmjs isn't parsing it correctly?)\n // It's showing up like 'DECY\\\\ATTN\\\\SCAT\\\\DTIM\\\\RAN\\\\RADL\\\\DCAL\\\\SLSENS\\\\NORM'\n // but calculate-suv expects ['DECY', 'ATTN', ...]\n if (typeof instanceMetadata.CorrectedImage === 'string') {\n instanceMetadata.CorrectedImage = (instanceMetadata.CorrectedImage as string).split('\\\\');\n }\n\n if (instanceMetadata) {\n instanceMetadataArray.push(instanceMetadata);\n }\n });\n if (instanceMetadataArray.length) {\n const suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray);\n instanceMetadataArray.forEach((instanceMetadata, index) => {\n ptScalingMetaDataProvider.addInstance(imageIds[index], suvScalingFactors[index]);\n });\n }\n }\n\n removeInvalidTags(srcMetadata) {\n // Object.create(null) make it ~9% faster\n const dstMetadata = Object.create(null);\n const tagIds = Object.keys(srcMetadata);\n let tagValue;\n\n tagIds.forEach((tagId) => {\n tagValue = srcMetadata[tagId];\n\n if (tagValue !== undefined && tagValue !== null) {\n dstMetadata[tagId] = tagValue;\n }\n });\n\n return dstMetadata;\n }\n}\n","const windowWidth = 400;\nconst windowCenter = 40;\n\nconst lower = windowCenter - windowWidth / 2.0;\nconst upper = windowCenter + windowWidth / 2.0;\n\nconst ctVoiRange = { lower, upper };\n\nfunction setCtTransferFunctionForVolumeActor({ volumeActor }) {\n volumeActor.getProperty().getRGBTransferFunction(0).setMappingRange(lower, upper);\n}\n\nexport { ctVoiRange, setCtTransferFunctionForVolumeActor };\n","/**\n * Similar to {@link addVolumesToViewports} it adds volumes to viewports; however,\n * this method will Set the volumes on the viewports which means that the previous\n * volumes will be removed.\n *\n * @param renderingEngine - The rendering engine to use to get viewports from\n * @param volumeInputs - Array of volume inputs including volumeId. Other properties\n * such as visibility, callback, blendMode, slabThickness are optional\n * @param viewportIds - Array of viewport IDs to add the volume to\n * @param immediateRender - If true, the volumes will be rendered immediately\n * @returns A promise that resolves when all volumes have been added\n */\nimport { StackViewport, Types } from '@cornerstonejs/core';\nimport { ctVoiRange } from './setCtTransferFunctionForVolumeActor';\n\nasync function setStacksForViewports(\n renderingEngine: Types.IRenderingEngine,\n viewportIds: Array<string>,\n imageIds: Array<string>,\n currentImageIdIndex?: number,\n): Promise<void> {\n // Check if all viewports are StackViewport\n viewportIds.forEach((viewportId) => {\n const viewport = renderingEngine.getViewport(viewportId);\n if (!viewport) {\n throw new Error(`Viewport with Id ${viewportId} does not exist`);\n }\n // if not instance of VolumeViewport, throw\n if (!(viewport instanceof StackViewport)) {\n throw new Error('setStacksForViewports only supports StackViewport');\n }\n });\n\n const setStackPromises = viewportIds.map(async (viewportId) => {\n const viewport = renderingEngine.getViewport(viewportId) as Types.IStackViewport;\n await viewport.setStack(imageIds, currentImageIdIndex);\n // Set the VOI of the stack\n viewport.setProperties({ voiRange: ctVoiRange });\n // Render the image\n viewport.render();\n });\n\n await Promise.all(setStackPromises);\n\n return;\n}\n\nexport { setStacksForViewports };\n","import { Enums as csCoreEnum, Types } from '@cornerstonejs/core';\nimport { ImageInfo } from './types';\n\nexport enum LayoutEnum {\n LAYOUT_1x1, // Single viewport layout\n LAYOUT_1x2, // 1 row 2 columns layout\n LAYOUT_1x3, // 1 row 3 columns layout\n LAYOUT_2x2, // 2 rows 2 columns layout\n}\n\nexport const STACK_VIEWPORT_INPUTS: Partial<Types.PublicViewportInput>[] = [\n {\n viewportId: 'viewport-stack',\n type: csCoreEnum.ViewportType.STACK,\n defaultOptions: {\n background: <Types.Point3>[0, 0, 0],\n },\n },\n];\n\nexport const SAGITTAL_VIEWPORT_INPUTS: Partial<Types.PublicViewportInput>[] = [\n {\n viewportId: 'viewport-mpr-sagittal',\n type: csCoreEnum.ViewportType.ORTHOGRAPHIC,\n defaultOptions: {\n background: <Types.Point3>[0, 0, 0],\n orientation: csCoreEnum.OrientationAxis.SAGITTAL,\n },\n },\n];\n\nexport const CORONAL_VIEWPORT_INPUTS: Partial<Types.PublicViewportInput>[] = [\n {\n viewportId: 'viewport-mpr-coronal',\n type: csCoreEnum.ViewportType.ORTHOGRAPHIC,\n defaultOptions: {\n background: <Types.Point3>[0, 0, 0],\n orientation: csCoreEnum.OrientationAxis.CORONAL,\n },\n },\n];\n\nexport const AXIAL_VIEWPORT_INPUTS: Partial<Types.PublicViewportInput>[] = [\n {\n viewportId: 'viewport-mpr-axial',\n type: csCoreEnum.ViewportType.ORTHOGRAPHIC,\n defaultOptions: {\n background: <Types.Point3>[0, 0, 0],\n orientation: csCoreEnum.OrientationAxis.AXIAL,\n },\n },\n];\n\nexport const VOLUME_3D_VIEWPORT_INPUTS: Partial<Types.PublicViewportInput>[] = [\n {\n viewportId: 'viewport-volume',\n type: csCoreEnum.ViewportType.VOLUME_3D,\n defaultOptions: {\n background: <Types.Point3>[0.2, 0, 0.2],\n },\n },\n];\n\nexport function generateViewportInputs(\n layout: LayoutEnum,\n suffix: string,\n imageInfo?: ImageInfo,\n): Partial<Types.PublicViewportInput>[] {\n const viewportType = imageInfo?.viewportType || csCoreEnum.ViewportType.STACK;\n const isStack = viewportType === csCoreEnum.ViewportType.STACK;\n const result: Partial<Types.PublicViewportInput>[] = [];\n\n switch (layout) {\n case LayoutEnum.LAYOUT_1x1:\n // 1x1 layout: set viewportType according to imageType\n if (viewportType === csCoreEnum.ViewportType.STACK) {\n const viewportInput = structuredClone(STACK_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-stack-${suffix}`;\n result.push(viewportInput);\n } else if (viewportType === csCoreEnum.ViewportType.VOLUME_3D) {\n const viewportInput = structuredClone(VOLUME_3D_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-volume-${suffix}`;\n result.push(viewportInput);\n } else {\n const viewportInput = structuredClone(SAGITTAL_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-sagittal-${suffix}`;\n result.push(viewportInput);\n }\n break;\n\n case LayoutEnum.LAYOUT_1x2:\n // 1x2 layout: all stack when imageType is stack, otherwise one volume3D one sagittal\n if (isStack) {\n for (let i = 0; i < 2; i++) {\n const viewportInput = structuredClone(STACK_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-stack-${i + 1}${suffix}`;\n result.push(viewportInput);\n }\n } else {\n const sagittalViewport = structuredClone(SAGITTAL_VIEWPORT_INPUTS[0]);\n sagittalViewport.viewportId = `viewport-sagittal-${suffix}`;\n result.push(sagittalViewport);\n\n const volumeViewport = structuredClone(VOLUME_3D_VIEWPORT_INPUTS[0]);\n volumeViewport.viewportId = `viewport-volume-${suffix}`;\n result.push(volumeViewport);\n }\n break;\n\n case LayoutEnum.LAYOUT_1x3:\n // 1x3 layout: all stack when imageType is stack, otherwise one volume3D one sagittal one axial\n if (isStack) {\n for (let i = 0; i < 3; i++) {\n const viewportInput = structuredClone(STACK_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-stack-${i + 1}${suffix}`;\n result.push(viewportInput);\n }\n } else {\n const sagittalViewport = structuredClone(SAGITTAL_VIEWPORT_INPUTS[0]);\n sagittalViewport.viewportId = `viewport-sagittal-${suffix}`;\n result.push(sagittalViewport);\n\n const axialViewport = structuredClone(AXIAL_VIEWPORT_INPUTS[0]);\n axialViewport.viewportId = `viewport-axial-${suffix}`;\n result.push(axialViewport);\n\n const volumeViewport = structuredClone(VOLUME_3D_VIEWPORT_INPUTS[0]);\n volumeViewport.viewportId = `viewport-volume-${suffix}`;\n result.push(volumeViewport);\n }\n break;\n\n case LayoutEnum.LAYOUT_2x2:\n // 2x2 layout: all stack when imageType is stack, otherwise one volume3D one sagittal one axial one coronal\n if (isStack) {\n for (let i = 0; i < 4; i++) {\n const viewportInput = structuredClone(STACK_VIEWPORT_INPUTS[0]);\n viewportInput.viewportId = `viewport-stack-${i + 1}${suffix}`;\n result.push(viewportInput);\n }\n } else {\n const sagittalViewport = structuredClone(SAGITTAL_VIEWPORT_INPUTS[0]);\n sagittalViewport.viewportId = `viewport-sagittal-${suffix}`;\n result.push(sagittalViewport);\n\n const axialViewport = structuredClone(AXIAL_VIEWPORT_INPUTS[0]);\n axialViewport.viewportId = `viewport-axial-${suffix}`;\n result.push(axialViewport);\n\n const coronalViewport = structuredClone(CORONAL_VIEWPORT_INPUTS[0]);\n coronalViewport.viewportId = `viewport-coronal-${suffix}`;\n result.push(coronalViewport);\n\n const volumeViewport = structuredClone(VOLUME_3D_VIEWPORT_INPUTS[0]);\n volumeViewport.viewportId = `viewport-volume-${suffix}`;\n result.push(volumeViewport);\n }\n break;\n }\n\n return result;\n}\n\nexport function generateRandomString(length = 6) {\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n let result = '';\n for (let i = 0; i < length; i++) {\n const randomIndex = Math.floor(Math.random() * characters.length);\n result += characters[randomIndex];\n }\n return '-' + result;\n}\n","import { ChangeDetectionStrategy, Component, Input } from '@angular/core';\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'i[nc-icon]',\n template: `\n <svg class=\"icon\" aria-hidden=\"true\">\n <use attr.xlink:href=\"#{{ iconfont }}\"></use>\n </svg>\n `,\n styles: [\n `\n :host {\n display: inline-block;\n line-height: 0;\n }\n\n .icon {\n fill: currentColor;\n height: 1em;\n overflow: hidden;\n vertical-align: -0.15em;\n width: 1em;\n }\n `,\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class IconComponent {\n @Input() iconfont: string | null = '';\n}\n","import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n HostBinding,\n HostListener,\n Input,\n OnDestroy,\n Output,\n} from '@angular/core';\nimport { InputBoolean } from '../../core';\nimport { Observable, Subject } from 'rxjs';\nimport { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion';\n\nexport type ButtonType = 'primary' | 'default';\nexport type ButtonContent = 'icon-text' | 'icon' | 'text' | 'default';\nexport type ButtonShape = 'circle' | 'round' | 'default';\nexport type ButtonSize = 'large' | 'default' | 'small';\nexport interface ButtonToggleChange {\n source: ButtonComponent;\n pressed: boolean;\n}\n\n@Component({\n // eslint-disable-next-line @angular-eslint/component-selector\n selector: 'button[nc-button], a[nc-button]',\n exportAs: 'ncButton',\n templateUrl: './button.component.html',\n styleUrls: ['./button.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\n host: {\n class: 'nc-btn',\n '[class.btn-type-primary]': `type === 'primary'`,\n '[class.btn-type-default]': `type === 'default'`,\n '[class.btn-content-icon-text]': `content === 'icon-text'`,\n '[class.btn-content-icon]': `content === 'icon'`,\n '[class.btn-content-text]': `content === 'text'`,\n '[class.btn-shape-round]': `shape === 'round'`,\n '[class.btn-shape-circle]': `shape === 'circle'`,\n '[class.btn-size-lg]': `size === 'large'`,\n '[class.btn-size-sm]': `size === 'small'`,\n '[attr.tabindex]': 'disabled ? -1 : (tabIndex === null ? null : tabIndex)',\n '[attr.disabled]': 'disabled || null',\n },\n})\nexport class ButtonComponent implements OnDestroy {\n @Input() @InputBoolean() disabled = false;\n @Input() tabIndex: number | string | null = null;\n @Input() type: ButtonType = 'default';\n @Input() shape: ButtonShape = 'default';\n @Input() content: ButtonContent = 'default';\n @Input() size: ButtonSize = 'default';\n @Input() @InputBoolean() isToggle = false;\n @Input() icon: string | null = null;\n @Input() text: string | null = null;\n @Input() value: any;\n\n @HostListener('click', ['$event'])\n onClick(event) {\n if (this.disabled) {\n event.preventDefault();\n event.stopImmediatePropagation();\n } else if (this.isToggle) {\n this.pressed = !this.pressed;\n }\n }\n /**\n * Emits whenever button pressed state change\n **/\n @Output() readonly pressedChange = new EventEmitter<boolean>();\n protected readonly _pressedChange$ = new Subject<ButtonToggleChange>();\n get pressedChange$(): Observable<ButtonToggleChange> {\n return this._pressedChange$.asObservable();\n }\n /**\n * Controls button pressed state\n **/\n @HostBinding('class.pressed')\n get pressed(): boolean {\n return this._pressed;\n }\n set pressed(value: boolean) {\n if (this.pressed !== coerceBooleanProperty(value)) {\n this._pressed = !this.pressed;\n this.pressedChange.emit(this.pressed);\n this._pressedChange$.next({ source: this, pressed: this.pressed });\n }\n }\n protected _pressed: boolean = false;\n static ngAcceptInputType_pressed: BooleanInput;\n\n private destroy$ = new Subject<void>();\n\n constructor(public _elementRef: ElementRef, private cd: ChangeDetectorRef) {}\n\n _getHostElement() {\n return this._elementRef.nativeElement;\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n /**\n * @docs-private\n */\n _updatePressed(value: boolean) {\n this.pressed = value;\n this.cd.markForCheck();\n }\n}\n","<ng-container [ngSwitch]=\"content\">\n <ng-container *ngSwitchCase=\"'icon-text'\">\n <i nc-icon [iconfont]=\"icon\"></i>\n <span class=\"text\">{{ text }}</span>\n </ng-container>\n <div *ngSwitchCase=\"'text'\">\n <span>{{ text }}</span>\n </div>\n <div *ngSwitchCase=\"'icon'\">\n <i nc-icon [iconfont]=\"icon\"></i>\n </div>\n <ng-content *ngSwitchDefault></ng-content>\n</ng-container>\n","import {\n AfterContentInit,\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ContentChildren,\n EventEmitter,\n HostBinding,\n Input,\n OnDestroy,\n Output,\n QueryList,\n} from '@angular/core';\nimport { debounceTime, filter, from, merge, Observable, startWith, Subject } from 'rxjs';\nimport { switchMap, takeUntil } from 'rxjs/operators';\n\nimport { ButtonComponent, ButtonToggleChange } from './button.component';\nimport { BooleanInput } from '@angular/cdk/coercion';\nimport { InputBoolean } from '../../core';\n\n@Component({\n selector: 'nc-button-group',\n template: ` <ng-content></ng-content> `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ButtonGroupComponent implements AfterContentInit, OnDestroy {\n protected lastEmittedValue: any[] = [];\n\n protected readonly destroy$: Subject<void> = new Subject<void>();\n protected readonly buttonsChange$ = new Subject<ButtonComponent[]>();\n\n @ContentChildren(ButtonComponent)\n readonly buttons!: QueryList<ButtonComponent>;\n\n @Input()\n @InputBoolean()\n protected disabled = false;\n static ngAcceptInputType_disabled: BooleanInput;\n\n /**\n * Allows to keep multiple button toggles pressed. Off by default.\n */\n @Input()\n @InputBoolean()\n protected multiple: boolean = false;\n static ngAcceptInputType_multiple: BooleanInput;\n\n /**\n * Emits when `nbButtonToggle` pressed state change. `$event` contains an array of the currently pressed button\n * toggles.\n */\n @Output() valueChange = new EventEmitter<any[]>();\n\n @HostBinding('attr.role') role = 'group';\n\n constructor(protected cd: ChangeDetectorRef) {}\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n ngAfterContentInit(): void {\n this.buttonsChange$.pipe(takeUntil(this.destroy$)).subscribe((buttons: ButtonComponent[]) => {\n this.listenButtonPressedState(buttons);\n });\n\n this.buttons.changes\n .pipe(\n // `buttons.changes` emit during change detection run after projected content already was initialized.\n // So at this time, it's too late to update projected buttons properties as updating bindings after\n // initialization doesn't make sense. Changes won't be picked up and should cause an \"expression changed\" error.\n // Instead, we wrap the new buttons list into a promise to defer update to the following microtask and also to\n // trigger change detection one more time.\n switchMap((buttons: QueryList<ButtonComponent>) => from(Promise.resolve(buttons.toArray()))),\n takeUntil(this.destroy$),\n )\n .subscribe((value) => this.buttonsChange$.next(value));\n\n this.buttonsChange$.next(this.buttons.toArray());\n }\n\n protected listenButtonPressedState(buttons: ButtonComponent[]): void {\n const toggleButtons: ButtonComponent[] = buttons.filter((button: ButtonComponent) => {\n return button.isToggle;\n });\n\n if (!toggleButtons.length) {\n return;\n }\n\n const buttonsPressedChange$: Observable<ButtonToggleChange>[] = toggleButtons.map(\n (button: ButtonComponent) => button.pressedChange$,\n );\n\n merge(...buttonsPressedChange$)\n .pipe(\n filter(({ pressed }: ButtonToggleChange) => !this.multiple && pressed),\n takeUntil(merge(this.buttonsChange$, this.destroy$)),\n )\n .subscribe(({ source }: ButtonToggleChange) => {\n toggleButtons\n .filter((button: ButtonComponent) => button !== source)\n .forEach((button: ButtonComponent) => button._updatePressed(false));\n });\n\n merge(...buttonsPressedChange$)\n .pipe(\n // Use startWith to emit if some buttons are initially pressed.\n startWith(''),\n // Use debounce to emit change once when pressed state change in multiple button toggles.\n debounceTime(0),\n takeUntil(merge(this.buttonsChange$, this.destroy$)),\n )\n .subscribe(() => this.emitCurrentValue(toggleButtons));\n }\n\n protected emitCurrentValue(toggleButtons: ButtonComponent[]): void {\n const pressedToggleValues = toggleButtons\n .filter((b: ButtonComponent) => b.pressed && typeof b.value !== 'undefined')\n .map((b: ButtonComponent) => b.value);\n\n // Prevent multiple emissions of empty value.\n if (pressedToggleValues.length === 0 && this.lastEmittedValue.length === 0) {\n return;\n }\n\n this.valueChange.emit(pressedToggleValues);\n this.lastEmittedValue = pressedToggleValues;\n }\n}\n","import { DOCUMENT } from '@angular/common';\r\nimport { Inject, Injectable, Optional, RendererFactory2 } from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class NcIconService {\r\n private initialized = false;\r\n\r\n constructor(\r\n private rendererFactory2: RendererFactory2,\r\n @Inject(DOCUMENT) private doc: any,\r\n @Optional() @Inject('ICON_URL') iconfontUrl: string,\r\n ) {\r\n console.debug('NcIconService init');\r\n this.init(iconfontUrl);\r\n }\r\n\r\n init(iconfontUrl?: string) {\r\n if (iconfontUrl && !this.initialized) {\r\n const renderer = this.rendererFactory2.createRenderer(null, null);\r\n const script = renderer.createElement('script');\r\n renderer.setAttribute(script, 'src', iconfontUrl);\r\n renderer.setAttribute(script, 'data-namespace', iconfontUrl);\r\n renderer.appendChild(this.doc.body, script);\r\n this.initialized = true;\r\n }\r\n }\r\n}\r\n","import { CommonModule } from '@angular/common';\r\nimport { ModuleWithProviders, NgModule } from '@angular/core';\r\nimport { IconComponent } from './icon.component';\r\nimport { NcIconService } from